效果展示
源码
实现上推缩放头部视图的基类PushUpScaleView.h与PushUpScaleView.m
#import <UIKit/UIKit.h>
#import "OnceLinearEquation.h"
#import "UIView+AnimationProperty.h"
@interface PushUpScaleView : UIView
/**
* 用于监听的scrollView
*/
@property (nonatomic, weak) UIScrollView *m_scrollView;
/**
* 缩放比例, 只读
*/
@property (nonatomic, readonly) float m_scale;
/**
* 偏移量, 只读
*/
@property (nonatomic, readonly) float m_offset;
/**
* 一次线性方程
*/
@property (nonatomic, strong) OnceLinearEquation *m_equation;
- (void)addScrollViewObserver;
- (void)removeScrollViewObserver;
/**
* 初始化
*
* @param frame frame
* @param scale 缩放比例
* @param offset 在多大偏移量内缩放
*
* @return 返回实例
*/
- (instancetype)initWithFrame:(CGRect)frame scale:(float)scale offset:(float)offset;
@end
#import "PushUpScaleView.h"
#define Width [UIScreen mainScreen].bounds.size.width
@interface PushUpScaleView ()
/**
* 缩放比例
*/
@property (nonatomic) float m_scale;
/**
* 偏移量
*/
@property (nonatomic) float m_offset;
@end
@implementation PushUpScaleView
- (instancetype)initWithFrame:(CGRect)frame {
self = [self initWithFrame:frame scale:0.5 offset:200];
return self;
}
- (instancetype)initWithFrame:(CGRect)frame scale:(float)scale offset:(float)offset {
float width = frame.size.width;
float height = frame.size.height;
/*
根据缩放比例,调整 x 和 width
由于调整锚点 (0.5,0.5) 为 (0.5,0) ,所以 y 为 -height/2 ,以实现根据此view最上面的线缩放悬停
*/
self = [super initWithFrame:CGRectMake(-(width/scale-width)/2, -height/2, width/scale, height)];
if (self) {
self.backgroundColor = [UIColor clearColor];
self.m_scale = scale;
self.m_offset = offset;
// 缩放方程
self.m_equation = [OnceLinearEquation onceLinearEquationWithPointA:MATHPointMake(1, 0)
PointB:MATHPointMake(scale, offset)];
// 调整锚点
self.layer.anchorPoint = CGPointMake(0.5, 0);
}
return self;
}
- (void)addScrollViewObserver {
if (self.m_scrollView) {
[self.m_scrollView addObserver:self
forKeyPath:@"contentOffset"
options:NSKeyValueObservingOptionNew
context:nil];
}
}
- (void)removeScrollViewObserver {
if (self.m_scrollView) {
[self.m_scrollView removeObserver:self forKeyPath:@"contentOffset"];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
CGPoint point = [change[@"new"] CGPointValue];
CGFloat y = point.y;
CGFloat x = [_m_equation xValueWhenYEqual:y];
if (y >= _m_offset) {
self.scale = _m_scale;
} else if (y >= 0 && y < _m_offset){
self.scale = x;
} else {
self.scale = 1;
}
}
@end
HeaderView负责在PushUpScaleView实现缩放功能的基础上,实现UI细节
#import <UIKit/UIKit.h>
#import "PushUpScaleView.h"
@interface HeaderView : UIView
@property (nonatomic, strong) PushUpScaleView *m_pushUpScaleView;
/**
* 初始化
*
* @param frame frame
* @param scale 缩放比例
* @param offset 在多大偏移量内缩放
* @param title 标题
*
* @return 返回实例
*/
- (instancetype)initWithFrame:(CGRect)frame scale:(float)scale offset:(float)offset title:(NSString*)title;
/**
* 更新描述
*
* @param description 新的描述
*/
- (void)updateDescription:(NSString*)description;
@end
#import "HeaderView.h"
#define Width [UIScreen mainScreen].bounds.size.width
@interface HeaderView ()
{
float _m_scale;
float _m_offset;
NSString *_m_titleStr;
}
@property (nonatomic, strong) UILabel *m_descriptionLabel;
@end
@implementation HeaderView
- (instancetype)initWithFrame:(CGRect)frame {
self = [self initWithFrame:frame scale:0.5 offset:200 title:@"这里需要一个标题"];
return self;
}
- (instancetype)initWithFrame:(CGRect)frame scale:(float)scale offset:(float)offset title:(NSString *)title {
self = [super initWithFrame:frame];
if (self) {
_m_scale = scale;
_m_offset = offset;
_m_titleStr = title;
// 非常重要,由于push up scale view 尺寸变大了,在导航栏POP时,会有很长的残影
// 这也是为什么要把push up scale view 再次放在一个view上,而不直接继承的原因
self.clipsToBounds = YES;
[self buildSubViews];
}
return self;
}
- (void)buildSubViews {
// 添加上推缩放试图
self.m_pushUpScaleView = [[PushUpScaleView alloc]initWithFrame:self.frame scale:_m_scale offset:_m_offset];
[self addSubview:_m_pushUpScaleView];
_m_pushUpScaleView.backgroundColor = [UIColor colorWithWhite:1 alpha:0.9];
[self buildPushUpView];
}
- (void)buildPushUpView {
// 标题
UILabel *titleLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 20, Width/_m_scale, 22)];
[_m_pushUpScaleView addSubview:titleLabel];
titleLabel.text = _m_titleStr;
titleLabel.textColor = [UIColor grayColor];
titleLabel.font = [UIFont systemFontOfSize:16];
titleLabel.textAlignment = NSTextAlignmentCenter;
// 描述
self.m_descriptionLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 20+22+17, Width/_m_scale, 40)];
[_m_pushUpScaleView addSubview:_m_descriptionLabel];
_m_descriptionLabel.text = @"描述";
_m_descriptionLabel.textColor = [UIColor redColor];
_m_descriptionLabel.font = [UIFont systemFontOfSize:45];
_m_descriptionLabel.textAlignment = NSTextAlignmentCenter;
// 分割线
UIView *segmentLineView = [[UIView alloc]initWithFrame:CGRectMake(0, 20+22+17+40+15-0.5, Width/_m_scale, 0.5)];
[_m_pushUpScaleView addSubview:segmentLineView];
segmentLineView.backgroundColor = [UIColor grayColor];
}
- (void)updateDescription:(NSString*)description {
_m_descriptionLabel.text = description;
}
@end
以下为一些工具辅助类
MathEquation.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
/**
* 定义数学坐标点 x, y
*/
struct MATHPoint {
CGFloat x;
CGFloat y;
};
typedef struct MATHPoint MATHPoint;
/**
* 生成数学坐标点 x, y
*
* @param x
* @param y
*
* @return 生成的坐标点
*/
static inline MATHPoint MATHPointMake(CGFloat x, CGFloat y) {
MATHPoint p; p.x = x; p.y = y; return p;
}
一元一次方程:OnceLinearEquation.h与OnceLinearEquation.m
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "MathEquation.h"
@interface OnceLinearEquation : NSObject
/*---- 计算一元一次方程 ----
y = kX + b
------------------------*/
@property (nonatomic) CGFloat k;
@property (nonatomic) CGFloat b;
/**
* 根据A点B点计算一元一次方程
*
* @param pointA
* @param pointB
*/
- (void)equationWithPointA:(MATHPoint)pointA PointB:(MATHPoint)pointB;
/**
* 根据Y的值获取X的值
*
* @param yValue Y值
*
* @return X值
*/
- (CGFloat)xValueWhenYEqual:(CGFloat)yValue;
/**
* 根据X的值获取Y的值
*
* @param xValue X值
*
* @return Y值
*/
- (CGFloat)yValueWhenXEqual:(CGFloat)xValue;
#pragma mark - 初始化方法
/**
* 根据A点B点计算一元一次方程
*
* @param pointA
* @param pointB
*
* @return 计算好的对象
*/
+ (instancetype)onceLinearEquationWithPointA:(MATHPoint)pointA PointB:(MATHPoint)pointB;
@end
#import "OnceLinearEquation.h"
@implementation OnceLinearEquation
+ (instancetype)onceLinearEquationWithPointA:(MATHPoint)pointA PointB:(MATHPoint)pointB {
OnceLinearEquation *equation = [[[self class] alloc] init];
CGFloat x1 = pointA.x; CGFloat y1 = pointA.y;
CGFloat x2 = pointB.x; CGFloat y2 = pointB.y;
equation.k = calculateSlope(x1, y1, x2, y2);
equation.b = calculateConstant(x1, y1, x2, y2);
return equation;
}
- (void)equationWithPointA:(MATHPoint)pointA PointB:(MATHPoint)pointB {
CGFloat x1 = pointA.x; CGFloat y1 = pointA.y;
CGFloat x2 = pointB.x; CGFloat y2 = pointB.y;
self.k = calculateSlope(x1, y1, x2, y2);
self.b = calculateConstant(x1, y1, x2, y2);
}
- (CGFloat)xValueWhenYEqual:(CGFloat)yValue {
return (yValue - _b) / _k;
}
- (CGFloat)yValueWhenXEqual:(CGFloat)xValue {
return _k * xValue + _b;
}
#pragma mark - 计算斜率 k
CGFloat calculateSlope(CGFloat x1, CGFloat y1, CGFloat x2, CGFloat y2)
{
return (y2 - y1) / (x2 - x1);
}
#pragma mark - 计算常数 b
CGFloat calculateConstant(CGFloat x1, CGFloat y1, CGFloat x2, CGFloat y2)
{
return (y1*(x2 - x1) - x1*(y2 - y1)) / (x2 - x1);
}
@end
UIView+AnimationProperty.h与UIView+AnimationProperty.m
#import <UIKit/UIKit.h>
@interface UIView (AnimationProperty)
@property (nonatomic) CGFloat scale;
@end
#import "UIView+AnimationProperty.h"
#import <objc/runtime.h>
@implementation UIView (AnimationProperty)
NSString * const _recognizerScale = @"_recognizerScale";
- (void)setScale:(CGFloat)scale {
objc_setAssociatedObject(self, (__bridge const void *)(_recognizerScale), @(scale), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
self.transform = CGAffineTransformMakeScale(scale, scale);
}
- (CGFloat)scale {
NSNumber *scaleValue = objc_getAssociatedObject(self, (__bridge const void *)(_recognizerScale));
return scaleValue.floatValue;
}
@end
ViewController.m
#import "ViewController.h"
#import "HeaderView.h"
@interface ViewController ()
@property (nonatomic, strong) UIScrollView *m_scrollView;
@property (nonatomic, strong) HeaderView *m_headerView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.m_scrollView = [[UIScrollView alloc]initWithFrame:self.view.frame];
[self.view addSubview:_m_scrollView];
_m_scrollView.contentSize = CGSizeMake(self.view.frame.size.width, self.view.frame.size.height*2);
_m_scrollView.backgroundColor = [UIColor brownColor];
_m_scrollView.showsVerticalScrollIndicator = NO;
self.m_headerView = [[HeaderView alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 20+20+17+40+15)
scale:0.6
offset:200
title:@"刘大帅"];
[self.view addSubview:_m_headerView];
_m_headerView.m_pushUpScaleView.m_scrollView = _m_scrollView;
[_m_headerView.m_pushUpScaleView addScrollViewObserver];
[self performSelector:@selector(netWork) withObject:nil afterDelay:1];
}
- (void)netWork {
[_m_headerView updateDescription:@"到此一游"];
}
- (void)dealloc
{
[self.m_headerView.m_pushUpScaleView removeScrollViewObserver];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end