logo头像

学如逆水行舟

iOS头部渐变的表格视图设计

iOS头部渐变的表格视图设计

今天再来给大家带来一个开发中常用到的视图控制器,在很多应用中,可能都会遇到这样的一个需求:表视图控制器最上方有一个头图控件,当表格视图滑动在顶部时,导航栏透明,当表格视图逐渐向下滑动时,导航栏渐渐出现,并且在滑动期间,头图会展示相关的渐变动画效果。以前经常会遇到这样的需求,但从没有整理与封装完善,这次将其封装成完整的控件,无论有无导航,都可以很好的支持,方便以后使用也提供给需要的朋友。

在设计控件之前,我们应该先编写控件的头文件,头文件中将控件需要的属性和方法列举,之后再按定义好的接口一步步的来实现控件的编写设计,这样可以结构清晰,并且不会显得无从下手,控件的头文件设计如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
//
//  YHBaseHeaderAnimatedViewController.h
//  YHBaseFoundationTest
//
//  Created by vip on 16/4/26.
//  Copyright © 2016年 jaki.zhang. All rights reserved.
/*
 *  这个视图控制器创建出带缩放头图效果的视图控制器
 *  tip:
 *      1.这个视图控制器如果是被导航push出来的 则内部会使用假导航进行渐隐模拟
 *      2.这个视图控制器如果是被present出来的 则不会出现假导航栏
 *      3.这个视图控制器中自带一个TableView 设置TableView的头图不会影响原动画效果
 *
 */

#import <UIKit/UIKit.h>
/**
 *  这个枚举设置头图动画滑动的速度等级
 */
typedef enum {
    YHBaseHeaderAnimatedLevelSlow,
    YHBaseHeaderAnimatedLevelNormal,
    YHBaseHeaderAnimatedLevelFast
}YHBaseHeaderAnimatedLevel;
@interface YHBaseHeaderAnimatedViewController : UIViewController<UITableViewDataSource,UITableViewDelegate>
@property(nonatomic,strong,readonly)UITableView * tableView;
/**
 *  设置动画头图图片
 */
@property(nonatomic,strong)UIView * animatedHeaderView;
/**
 *  设置TableView的头视图 
 *
 *  注意:设置tableView的头视图不能够在使用tableHeatherView方法 要使用这个属性设置
 *
 */
@property(nonatomic,strong)UIView * tableHeaderView;
/**
 *  设置动画头图高度
 *
 *  这个属性如果不设置或者设置为0 则默认会使用设置的image图片比例
 *
 */
@property(nonatomic,assign)CGFloat headerHeight;
/**
 *
 *  设置动画滑动速率
 */
@property(nonatomic,assign)YHBaseHeaderAnimatedLevel animatedlevel;
/**
 *
 *  设置头图可方法的最大scrollView偏移量 默认为40
 *
 */
@property(nonatomic,assign)CGFloat maxScrollOffset;
/**
 *  设置是否带渐隐效果
 *
 */
@property(nonatomic,assign)BOOL alphaAnimated;
/**
 *  设置最小渐变到的alpha渐隐值 <0 >1之间 默认为0.5
 *
 */
@property(nonatomic,assign)CGFloat minAlpha;
/**
 *
 *  是否显示毛玻璃模糊效果
 *
 */
@property(nonatomic,assign)BOOL bluerAnimated;
/**
 *
 *  设置最大小模糊度 默认为1 
 *
 */
@property(nonatomic,assign)CGFloat maxBluer;
/**
 *  设置导航栏颜色 默认白色
 *
 */
@property(nonatomic,strong)UIColor * naviColor;
/**
 *  设置导航左侧按钮数组 如果不设置 会自动带一个返回按钮
 *
 */
@property(nonatomic,strong)NSArray * leftBarButtons;
/**
 *  设置导航左侧按钮数组 如果不设置 会自动带一个返回按钮
 *
 */
@property(nonatomic,strong)NSArray * rightBarButtons;
/**
 *  这个方法在修改了头图相关属性后 需要调用刷新
 *
 *  注意:如果重新设置了TableView的tableheaderView属性 也需要调用这个方法刷新
 *
 */
-(void)reloadAnimatedView;
@end
  在设计这个控件时,我主要考虑两个需要优化的地方,第一是这个控制器在不同的场景下可能会有不同的结构,例如在导航结构中被push出来或者通过模态跳转被present出来,我在这个控件的实现时做了兼容,实际上无论有无导航,控件内部都没有使用系统的导航栏,而是模拟实现了一个自定义的导航栏来与系统的导航无缝衔接。

.m文件中实现的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
#import "YHBaseHeaderAnimatedViewController.h"

@interface YHBaseHeaderAnimatedViewController ()
{
    //承载视图
    UIView * _privteBGHeaderView;
    //宽高比例
    CGFloat _privteRate;
    //原始宽高
    CGFloat _privteOriWidth;
    CGFloat _privteOriHeught;
    UIVisualEffectView * _blurView;
    UINavigationBar * _naviBar;
}
@end

@implementation YHBaseHeaderAnimatedViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    self.edgesForExtendedLayout = UIRectEdgeNone;
    _tableView = [[UITableView alloc]initWithFrame:self.view.frame style:UITableViewStylePlain];
    [self.view addSubview:_tableView];
    _tableView.delegate=self;
    _tableView.dataSource=self;
     [self YHCreatView];
    // Do any additional setup after loading the view.
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
//对导航进行处理
-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    if (self.navigationController) {
        [self.navigationController setNavigationBarHidden:YES animated:YES];
        [self showNavigationBar];
    }
}
-(void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    if (self.navigationController) {
        [self.navigationController setNavigationBarHidden:NO animated:YES];
    }
}
-(void)showNavigationBar{
    if (_naviBar==nil) {
        _naviBar = [[UINavigationBar alloc]init];
        [self.view addSubview:_naviBar];
        _naviBar.frame = CGRectMake(0, 0, self.view.frame.size.width, 64);
        [_naviBar setBackgroundImage:[UIImage imageNamed:@"clear"] forBarMetrics:UIBarMetricsDefault];
        [_naviBar setShadowImage:[UIImage imageNamed:@"clear"]];
        _naviBar.backgroundColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:0];
        UINavigationItem * item = [[UINavigationItem alloc]initWithTitle:self.title];
        _naviBar.items = @[item];
        item.leftBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:@"返回" style:UIBarButtonItemStyleDone target:self action:@selector(popToLast)];
    }
    if (self.animatedHeaderView==nil) {
        _naviBar.hidden=YES;
    }else{
        _naviBar.hidden=NO;
    }
}
-(void)popToLast{
    [self.navigationController popViewControllerAnimated:YES];
}
-(void)YHCreatView{
    if (self.maxScrollOffset==0) {
        self.maxScrollOffset=40;
    }
    if (self.minAlpha==0) {
        self.minAlpha = 0.5;
    }
    if (self.maxBluer==0) {
        self.maxBluer = 1;
    }
    _privteBGHeaderView = [[UIView alloc]init];
    _privteBGHeaderView.backgroundColor = [UIColor clearColor];
    self.tableView.backgroundColor = [UIColor clearColor];
    _blurView = [[UIVisualEffectView alloc]initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight]];
    [self reloadAnimatedView];
}

-(void)reloadAnimatedView{
    CGFloat viewH=0;
    CGFloat aniH=0;
    for (UIView * view in _privteBGHeaderView.subviews) {
        [view removeFromSuperview];
    }
    if (self.tableHeaderView) {
        viewH = viewH+self.tableHeaderView.bounds.size.height;
        [_privteBGHeaderView addSubview:self.tableHeaderView];
    }
    if (self.animatedHeaderView) {
        _privteRate = self.animatedHeaderView.frame.size.width/self.animatedHeaderView.frame.size.height;
        [self.animatedHeaderView addSubview:_blurView];
        _blurView.frame= self.animatedHeaderView.bounds;
        _blurView.alpha=0;
        _privteOriWidth = self.animatedHeaderView.frame.size.width;
        _privteOriHeught = self.animatedHeaderView.frame.size.height;
        if (self.headerHeight!=0) {
            viewH+=self.headerHeight;
            aniH = self.headerHeight;
        }else{
            viewH+=self.view.frame.size.width/self.animatedHeaderView.bounds.size.width*self.animatedHeaderView.frame.size.height;
            aniH = self.animatedHeaderView.bounds.size.height;
        }
        [self.view insertSubview:self.animatedHeaderView atIndex:0];
    }
    self.tableHeaderView.frame=CGRectMake(0, aniH, self.tableHeaderView.frame.size.width, self.tableHeaderView.frame.size.height);
    _privteBGHeaderView.frame=CGRectMake(0, 0, self.view.frame.size.width, viewH);
    self.tableView.tableHeaderView = _privteBGHeaderView;
}
-(void)setAnimatedHeaderView:(UIView *)animatedHeaderView{
    if (animatedHeaderView==nil) {
        if (_animatedHeaderView) {
            [_animatedHeaderView removeFromSuperview];
        }
    }
    _animatedHeaderView = animatedHeaderView;
    
}
-(void)setMinAlpha:(CGFloat)minAlpha{
    if (minAlpha<=0) {
        minAlpha=0.001;
    }
    if (minAlpha>=1) {
        minAlpha=0.999;
    }
    _minAlpha=minAlpha;
}
-(void)setMaxBluer:(CGFloat)maxBluer{
    if (maxBluer<=0) {
        maxBluer = 0.001;
    }
    if (maxBluer>=1) {
        maxBluer = 0.999;
    }
    _maxBluer = maxBluer;
}
-(void)setNaviColor:(UIColor *)naviColor{
    _naviColor = naviColor;
    _naviBar.backgroundColor = naviColor;
}
-(void)setLeftBarButtons:(NSArray *)leftBarButtons{
    _naviBar.topItem.leftBarButtonItems = leftBarButtons;
    _leftBarButtons = leftBarButtons;
}
-(void)setRightBarButtons:(NSArray *)rightBarButtons{
    _naviBar.topItem.rightBarButtonItems = rightBarButtons;
    _rightBarButtons = rightBarButtons;
}
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
    if (self.animatedHeaderView==nil) {
        return;
    }
    if (self.tableView==scrollView) {
        //获取到偏移量 默认两倍
        CGFloat offset = scrollView.contentOffset.y*2;
        //如果小于0 则进行形变拉伸操作
        if (offset<=0) {
            
            if (offset<-self.maxScrollOffset*2) {
               
            }else{
                CGFloat height = _privteOriHeught+abs((int)offset);
                CGFloat width = height*_privteRate;
                self.animatedHeaderView.frame = CGRectMake(self.view.frame.size.width/2-width/2, offset/2, width, height);
            }
            if (_naviBar) {
                if (_naviBar.shadowImage==nil) {
                     [_naviBar setShadowImage:[UIImage imageNamed:@"clear"]];
                }
            }
        }else{
            //如果大于零 进行推出操作
            if (self.animatedlevel==YHBaseHeaderAnimatedLevelSlow) {
                //慢速
                self.animatedHeaderView.frame = CGRectMake(0, -offset/8, self.animatedHeaderView.frame.size.width, self.animatedHeaderView.frame.size.height);
            }else if(self.animatedlevel==YHBaseHeaderAnimatedLevelFast){
                //快速
                self.animatedHeaderView.frame = CGRectMake(0, -offset/2, self.animatedHeaderView.frame.size.width, self.animatedHeaderView.frame.size.height);
            }else{
                //正常
                self.animatedHeaderView.frame = CGRectMake(0, -offset/4, self.animatedHeaderView.frame.size.width, self.animatedHeaderView.frame.size.height);
            }
            //进行渐隐动画处理
            CGFloat tmp = offset/2/_privteOriHeught;
            if (self.alphaAnimated) {
                self.animatedHeaderView.alpha = 1-(1-self.minAlpha)*tmp;
            }
            //进行模糊动画
            if (self.bluerAnimated) {
                _blurView.alpha = self.maxBluer*tmp;
            }
            //进行导航显示
            if (_naviBar) {
                _naviBar.backgroundColor = [_naviBar.backgroundColor colorWithAlphaComponent:tmp];
                if (_naviBar.shadowImage) {
                    [_naviBar setShadowImage:nil];
                }
                
            }
        }
        
       
    }
}
@end
在使用时,创建一个视图控制器继承于它,在其viewDidLoad方法中进行一些简单的设置即可,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    //设置头图
    UIImageView * imageView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 200)];
    imageView.image = [UIImage imageNamed:@"image"];
    self.animatedHeaderView = imageView;
    //设置tableView的头视图
    UILabel * label = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 30)];
    label.backgroundColor = [UIColor purpleColor];
    label.textColor = [UIColor whiteColor];
    label.textAlignment =NSTextAlignmentCenter;
    label.text = @"下面进行功能演示";
    self.tableHeaderView = label;
    [self reloadAnimatedView];
}
我写了一个完整的演示Demo,Demo中有完整的功能演示,需要的朋友可以自行下载:http://pan.baidu.com/s/1c1VKT00。

专注技术,热爱生活,交流技术,也做朋友。

——珲少 QQ群:203317592