iOS 超Easy实现 渐变导航栏
2016年06月27日 17:05:04 上天眷顾我 阅读数:3338
接着上周的项目, 在上周我别出心裁的在自定义TabbarController中加入了自定义转场动画, 受到了大家广泛的喜爱, 再次表示感激, 今天我们继续实现LifestyleViewController的第二个功能渐变导航栏!!
渐变导航栏, 现在很多项目里都有这个功能, 我们就把这个功能集成到我们的项目中来; 根据设计图纸需求, 我们需要在轮播图下面有一个搜索栏, 搜索栏根据滑动偏移到导航栏之上.
具体怎么实现呢, Easy啦~ 不急,我们一步一步来.
创建搜索栏Cell
首先打开我们的项目先在轮播图Cell下创建一个搜索栏Cell 并添加子控件.
-
- (UIImageView *)hotSpotsImageView {
-
-
if (!_hotSpotsImageView) {
-
_hotSpotsImageView = [UIImageView new];
-
_hotSpotsImageView.image = [UIImage imageNamed:@"Hot Spots"];
-
}
-
return _hotSpotsImageView;
-
}
-
-
- (void)layoutSubviews {
-
[super layoutSubviews];
-
-
CGFloat padding = kSpace;
-
-
CGFloat hotSpotsImageViewX = padding;
-
CGFloat hotSpotsImageViewW = 90;
-
CGFloat hotSpotsImageViewH = 20;
-
CGFloat hotSpotsImageViewY = self.height - hotSpotsImageViewH - 5;
-
self.hotSpotsImageView.frame = CGRectMake(hotSpotsImageViewX, hotSpotsImageViewY, hotSpotsImageViewW, hotSpotsImageViewH);
-
}
这时你可能会问 为什么不在Cell中添加TextField之类的, 由于我们的效果,之后再向您揭晓;
控制器多Cell设计
这时回到控制器, 但是控制器中不止一种Cell, 我们怎么来设计呢?? 我的实现方法是KeysArr, 那什么是KeysArr呢,我们来看代码.
首先我们需要创建一个全局类 (这个写法和上周的 投机流 自定义转场有异曲同工之妙)
创建全局类
全局类中的每一个Key对应着你的一个Cell
.h
-
extern NSString * const kSQLifestyleBannerKey;
-
extern NSString * const kSQLifestyleSearchKey;
.m
-
NSString * const kSQLifestyleBannerKey = @"轮播图";
-
NSString * const kSQLifestyleSearchKey = @"热点";
keys数组
接着我们创建一个数组来持有这些key;
@property (nonatomic,strong) NSArray * keysArr;
-
- (NSArray *)keysArr {
-
-
if (!_keysArr) {
-
_keysArr = @[kSQLifestyleBannerKey,
-
kSQLifestyleSearchKey];
-
}
-
return _keysArr;
-
}
Tableview delegate mothod
最后在代理方法中进行判断
-
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
-
return self.keysArr.count;
-
}
-
-
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
-
-
NSString * const key = self.keysArr[indexPath.row];
-
if (key == kSQLifestyleBannerKey) {
-
SQLifestyleBannerCell * cell = [SQLifestyleBannerCell cellWithTableView:tableView];
-
return cell;
-
}
-
if (key == kSQLifestyleSearchKey) {
-
SQLifestyleSearchCell * cell = [SQLifestyleSearchCell cellWithTableView:tableView];
-
return cell;
-
}
-
return nil;
-
}
-
-
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
-
-
NSString * const key = self.keysArr[indexPath.row];
-
if (key == kSQLifestyleBannerKey) {
-
return [SQLifestyleBannerCell cellHeight];
-
}
-
if (key == kSQLifestyleSearchKey) {
-
return [SQLifestyleSearchCell cellHeight];
-
}
-
return 0;
-
}
这样当不需要某个cell显示的时候,只需要将keys从KeysArr中移除即可, 是不是很方便!!
创建搜索栏View
这里需要提醒大家的一点, 当你在自定义View的时候最好要实现initWithFrame和initWithCoder两个方法, 这样无论是用纯代码还是Xib,Storyboard使用自定义View时都能够直接加载!!
-
- (instancetype)initWithFrame:(CGRect)frame {
-
-
self = [super initWithFrame:frame];
-
if (self) {
-
[self setupSubviews];
-
}
-
return self;
-
}
-
-
- (instancetype)initWithCoder:(NSCoder *)coder {
-
-
self = [super initWithCoder:coder];
-
if (self) {
-
[self setupSubviews];
-
}
-
return self;
-
}
-
-
- (UITextField *)searchBarTextField {
-
-
if (!_searchBarTextField) {
-
_searchBarTextField = [UITextField new];
-
_searchBarTextField.backgroundColor = [UIColor whiteColor];
-
_searchBarTextField.layer.borderColor = KC05_dddddd.CGColor;
-
_searchBarTextField.layer.borderWidth = 0.5f;
-
_searchBarTextField.layer.cornerRadius = 5;
-
_searchBarTextField.layer.masksToBounds= YES;
-
_searchBarTextField.placeholder = @"Searching for something new";
-
_searchBarTextField.leftView = self.magnifierImageView;
-
_searchBarTextField.leftViewMode = UITextFieldViewModeAlways;
-
[_searchBarTextField setValue:KC04_999999 forKeyPath:@"placeholderLabel.textColor"];
-
}
-
return _searchBarTextField;
-
}
-
-
- (UIImageView *)magnifierImageView {
-
-
if (!_magnifierImageView) {
-
_magnifierImageView = [UIImageView new];
-
_magnifierImageView.image = [UIImage imageNamed:@"fa-search"];
-
_magnifierImageView.frame = CGRectMake(0, 0, 34, 34);
-
_magnifierImageView.contentMode = UIViewContentModeCenter;
-
}
-
return _magnifierImageView;
-
}
-
-
- (void)setupSubviews {
-
[self addSubview:self.searchBarTextField];
-
}
-
-
- (void)layoutSubviews {
-
[super layoutSubviews];
-
-
CGFloat searchBarTextFieldX = 0;
-
CGFloat searchBarTextFieldY = 0;
-
CGFloat searchBarTextFieldW = self.width;
-
CGFloat searchBarTextFieldH = 34;
-
self.searchBarTextField.frame = CGRectMake(searchBarTextFieldX, searchBarTextFieldY, searchBarTextFieldW, searchBarTextFieldH);
-
}
实现位移渐变
重于到今天的重头戏了, 我们来实现位移渐变!! 上面的自定义View 我们并不是将其添加到Cell之中, 而是将他添加到navigationController.view之上!!
创建两个自定义View
-
@property (nonatomic,strong) SQLifestyleSearchBarView * titleView;
-
@property (nonatomic,strong) SQLifestyleSearchBarView * searchBarView;
-
- (SQLifestyleSearchBarView *)titleView {
-
-
if (!_titleView) {
-
_titleView = [SQLifestyleSearchBarView new];
-
_titleView.frame = self.navigationController.navigationBar.frame;
-
}
-
return _titleView;
-
}
-
-
- (SQLifestyleSearchBarView *)searchBarView {
-
-
if (!_searchBarView) {
-
_searchBarView = [SQLifestyleSearchBarView new];
-
}
-
return _searchBarView;
-
}
其中一个的frame = self.navigationController.navigationBar.frame
并将其添加到titleView中!! 另一个将其加载navigationController.view中!! 并在viewWillLayoutSubviews中设置布局!!
-
- (void)loadView {
-
[super loadView];
-
[self.navigationItem setTitleView:self.titleView];
-
[self.navigationController.navigationBar setBackgroundImage:[UIImage imageWithColor:[UIColor clearColor]] forBarMetrics:UIBarMetricsDefault];
-
[self.navigationController.navigationBar setShadowImage:[UIImage imageWithColor:[UIColor clearColor]]];
-
}
-
-
- (void)viewDidLoad {
-
[super viewDidLoad];
-
[self.view addSubview:self.tableView];
-
[self.navigationController.view addSubview:self.searchBarView];
-
}
-
-
- (void)viewWillLayoutSubviews {
-
[super viewWillLayoutSubviews];
-
CGFloat searchBarViewX = kSpace;
-
CGFloat searchBarViewW = self.titleView.width;
-
CGFloat searchBarViewH = self.titleView.height;
-
CGFloat searchBarViewY = kScaleLength(210) + searchBarViewH - self.tableView.contentOffset.y - searchBarViewH;
-
self.searchBarView.frame = CGRectMake(searchBarViewX, searchBarViewY, searchBarViewW, searchBarViewH);
-
}
监听偏移
我们将设置导航栏背景颜色的方法从LoadView移到scrollViewDidScroll中来
这里需要和大家细说一下监听偏移渐变,其实很简单,就是一个公式而已 float alpha = 1 - (offset) - scrollView.contentOffset.y) / offset); 其中的offset 指的是y轴方向从初始值viewWillLayoutSubviews中的初始设定 到导航栏的位移!!
每滑动下重新调用viewWillLayoutSubviews方法重新布局, 当其到达位移点的时候, 两个View进行交换就达到了 预期的效果!!
-
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
-
[self viewWillLayoutSubviews];
-
[[[UIApplication sharedApplication] keyWindow] endEditing:YES];
-
float alpha = 1 - ((kScaleLength(190.5) - scrollView.contentOffset.y) / kScaleLength(190.5));
-
[self.navigationController.navigationBar setBackgroundImage:[UIImage imageWithColor:[KC01_57c2de colorWithAlphaComponent:alpha > 0.95f ? 0.95f : alpha]] forBarMetrics:UIBarMetricsDefault];
-
self.titleView.hidden = scrollView.contentOffset.y > kScaleLength(190.5) ? NO : YES;
-
self.searchBarView = !titleView.hidden;
-
}
对位移渐变的封装
上面的代码看到晕晕乎乎,讲的不清不楚!! 我们来讲这个功能封装一下, 先创建一个UIViewController的Catagory 实现方法!!
- scrollView: 传入需要位移的scrollView;
- titleView: 传入导航栏上的titleView;
- movableView: 传入需要移动的自定义View;
- offset: 传入y轴方向从初始值到导航栏的位移;
- color: 传入导航栏颜色
-
- (void)navigationBarGradualChangeWithScrollView:(UIScrollView *)scrollView titleView:(UIView *)titleView movableView:(UIView *)movableView offset:(CGFloat)offset color:(UIColor *)color {
-
-
float alpha = 1 - ((offset - scrollView.contentOffset.y) / offset);
-
[self.navigationController.navigationBar setBackgroundImage:[UIImage imageWithColor:[color colorWithAlphaComponent:alpha > 0.95f ? 0.95f : alpha]] forBarMetrics:UIBarMetricsDefault];
-
titleView .hidden = scrollView.contentOffset.y > offset ? NO : YES;
-
movableView.hidden = !titleView .hidden;
-
}
这样我们在调用的时候就简单明了多了!! 以后做到这个功能的时候可以直接拿来用了!!
-
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
-
[self viewWillLayoutSubviews];
-
[[[UIApplication sharedApplication] keyWindow] endEditing:YES];
-
[self navigationBarGradualChangeWithScrollView:scrollView titleView:self.titleView movableView:self.searchBarView offset:kScaleLength(190.5) color:KC01_57c2de];
-
}
模拟效果
为了能看到效果 我们在控制器中多加几个cell 方法很简单只要在Key数组中多加几个对象即可! 并在key == @"" 时加载如下Cell
-
- (NSArray *)keysArr {
-
-
if (!_keysArr) {
-
_keysArr = @[kSQLifestyleBannerKey,
-
kSQLifestyleSearchKey,
-
@"",@"",@"",@"",@"",
-
@"",@"",@"",@"",@"",
-
@"",@"",@"",@"",@"",
-
@"",@"",@"",@"",@""];
-
}
-
return _keysArr;
-
}
-
-
static NSString * identifier = @"cell";
-
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:identifier];
-
if (!cell) {
-
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
-
} cell.textLabel.text = @"https://coderZsq.github.io";
-
return cell;
模拟效果
github 下载地址!!!
https://github.com/sundayios/coderZsq.project.ios-master.git
原文链接:http://www.jianshu.com/p/bba27212de69
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
iOS导航栏背景渐变过渡
2017.04.27 19:09*
前言
最近新做的一个项目,里面出现了页面 Push
之后的新页面导航栏颜色改变的情况.但是仅仅用系统提供的 api, Push
时候的动画特别难看.最终用 透明的导航栏背景
+假背景视图
的方式稍微做出了一些改善.效果如图:
实际就是把
NavigationBar
的背景颜色设置为透明,然后在控制器的主视图上添加一个跟导航栏一样大小的 view
来当做背景.
设置 navigationBar 背景为透明
// 设置 navigationBar 背景为透明
[self.navigationBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
// 隐藏 navigationBar 底部分割线
self.navigationBar.shadowImage = [UIImage new];
添加背景视图,来营造一个错觉,来让用户认为 navigationBar 就是背景视图的颜色.
CGSize screenSize = [UIScreen mainScreen].bounds.size;
self.navBackView_zhk = [[UIView alloc] initWithFrame:CGRectMake(0, 0, screenSize.width, 64)];
[self.view addSubview:_navBackView_zhk];
本来感觉已经挺满意了,但是后来发现支付宝里面的页面过渡,导航栏背景颜色的渐变过渡更加的流畅.
于是决定研究一下.
全屏滑动返回手势
既然决定做导航栏背景过渡渐变效果,为了方便测试和突显这个流畅的效果.就顺便把全屏滑动返回手势
也顺便做了吧.
先放最终效果图:
1.添加需要用到的属性
1.添加需要用到的属性
@interface BaseViewController () <UINavigationControllerDelegate>
// 导航栏背景视图
@property (nonatomic, strong) UIView *navBackView_zhk;
// 交互动画控制对象(主要用于全屏手势返回过程动画的控制)
@property (nonatomic, strong) UIPercentDrivenInteractiveTransition *interactiveTransition_zhk;
@end
2.添加拖动手势
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(_zhk_popGestureAction:)];
[self.view addGestureRecognizer:pan];
3.手势响应方法
当手势发生时候调用[self.navigationController popViewControllerAnimated:YES]
进行Pop
.
手势拖动过程中不断调用[_interactiveTransition_zhk updateInteractiveTransition:progress]
来更新过渡动画的进度.
当手势取消或者结束的时候,判断progress
,如果>0.5
则直接完成过渡动画,否则取消过渡动画.
- (void)_zhk_popGestureAction:(UIPanGestureRecognizer *)pan {
// 计算动画进度百分比
CGFloat offset = [pan translationInView:self.view].x;
CGFloat progress = offset / [UIScreen mainScreen].bounds.size.width;
if (pan.state == UIGestureRecognizerStateBegan) {
self.interactiveTransition_zhk = [[UIPercentDrivenInteractiveTransition alloc] init];
// pop
[self.navigationController popViewControllerAnimated:YES];
}else if (pan.state == UIGestureRecognizerStateChanged) {
// 更新动画进度
[_interactiveTransition_zhk updateInteractiveTransition:progress];
}else if (pan.state == UIGestureRecognizerStateEnded || pan.state == UIGestureRecognizerStateCancelled) {
// 大于 50% 完成过渡动画, 否则取消
if (progress > 0.5) {
[_interactiveTransition_zhk finishInteractiveTransition];
}else {
[_interactiveTransition_zhk cancelInteractiveTransition];
}
self.interactiveTransition_zhk = nil;
}
}
4.实现UINavigationControllerDelegate
代理方法
传递交互动画控制对象_interactiveTransition_zhk
和动画对象NavBackAnimate
#pragma mark - UINavigationController delegate
// 返回交互过渡动画控制对象
- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController {
return _interactiveTransition_zhk;
}
// 返回过渡动画对象
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC {
return [NavBackAnimate animationWithOperation:operation];
}
动画对象实现
动画对象需要接受UIViewControllerAnimatedTransitioning
协议
@interface NavBackAnimate : NSObject <UIViewControllerAnimatedTransitioning>
+ (instancetype)animationWithOperation:(UINavigationControllerOperation)operation;
@end
UIViewControllerAnimatedTransitioning
协议:
// 返回动画完成需要的时间
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;
// 过渡动画的定义都在这里
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
// 如果过渡动画是可以被中断的,则可以实现这个方法( iOS 10 之后)
- (id <UIViewImplicitlyAnimating>) interruptibleAnimatorForTransition:(id <UIViewControllerContextTransitioning>)transitionContext NS_AVAILABLE_IOS(10_0);
// 过渡动画结束时候调用
- (void)animationEnded:(BOOL) transitionCompleted;
最主要的是- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
方法的动画实现部分
// This method can only be a nop if the transition is interactive and not a percentDriven interactive transition.
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
BaseViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
BaseViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
// nav 背景是否需要过渡动画(颜色不同则需要,否则不需要)
BOOL navBackNeedTransition = ![fromVC.navBackColor_zhk isEqual:toVC.navBackColor_zhk];
// 获取呈现过渡动画的视图(容器)
UIView *containerView = [transitionContext containerView];
CGSize screenSize = [UIScreen mainScreen].bounds.size;
//
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, screenSize.width, screenSize.height)];
// Push 时候的动画设置(动画开始状态设置)
if (_operation == UINavigationControllerOperationPush) {
[containerView addSubview:imageView];
[containerView addSubview:toVC.view];
imageView.image = [self snapshot:fromVC.view];
imageView.frame = CGRectMake(0, 0, screenSize.width, screenSize.height);
toVC.view.frame = CGRectMake(screenSize.width, 0, screenSize.width, screenSize.height);
// 设置阴影
toVC.view.layer.shadowColor = [UIColor grayColor].CGColor;
toVC.view.layer.shadowOffset = CGSizeMake(-3, 0);
toVC.view.layer.shadowOpacity = .5;
// Pop 时候过渡动画的设置(动画开始状态设置)
}else if (_operation == UINavigationControllerOperationPop) {
[containerView addSubview:toVC.view];
[containerView addSubview:imageView];
imageView.image = [self snapshot:fromVC.view];
imageView.frame = fromVC.view.bounds;
toVC.view.frame = CGRectMake(-screenSize.width / 3, 0, screenSize.width, screenSize.height);
imageView.layer.shadowColor = [UIColor grayColor].CGColor;
imageView.layer.shadowOffset = CGSizeMake(-3, 0);
imageView.layer.shadowOpacity = .5;
}
// navigationBar 底部假背景视图
// 背景的渐变过渡将会通过 backView 背景的渐变来呈现出 navigationBar 背景渐变的错觉
UIView *backView = nil;
if (navBackNeedTransition) {
backView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, screenSize.width, 64)];
backView.backgroundColor = fromVC.navBackColor_zhk;
[containerView addSubview:backView];
}
[UIView animateWithDuration:Duration animations:^{
if (_operation == UINavigationControllerOperationPush) {
// Push 过渡动画结束时候状态设置
imageView.frame = CGRectMake(-screenSize.width / 3, 0, screenSize.width, screenSize.height);
toVC.view.frame = CGRectMake(0, 0, screenSize.width, screenSize.height);
}else if (_operation == UINavigationControllerOperationPop) {
// Pop 过渡动画结束时候状态设置
imageView.frame = CGRectMake(screenSize.width, 0, screenSize.width, screenSize.height);
toVC.view.frame = CGRectMake(0, 0, screenSize.width, screenSize.height);
}
// backView 目标背景色
backView.backgroundColor = toVC.navBackColor_zhk;
} completion:^(BOOL finished) {
// 动画结束时候调用
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
// imageView 如果不移除, 后面页面的交互将被遮挡
[imageView removeFromSuperview];
[backView removeFromSuperview];
}];
}
Push
和 Pop
的过渡动画实际上都是通过 toVC.view
和 从 fromVC.view
获取到的 image
也就是 imageView
这个容器进行一些列改变或者动作来呈现出页面切换这个动态.
最后放上 GitHub地址,希望能帮到有需求的小伙伴.
作者:ZHK1024
链接:https://www.jianshu.com/p/3abb9869619c
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。