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



文/Doubles_Z(简书作者)
原文链接:http://www.jianshu.com/p/bba27212de69
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
 
 
 
 

iOS导航栏背景渐变过渡

2017.04.27 19:09*

前言

最近新做的一个项目,里面出现了页面 Push之后的新页面导航栏颜色改变的情况.但是仅仅用系统提供的 api, Push时候的动画特别难看.最终用 透明的导航栏背景+假背景视图的方式稍微做出了一些改善.效果如图:

 

 
效果1.gif

实际就是把 NavigationBar的背景颜色设置为透明,然后在控制器的主视图上添加一个跟导航栏一样大小的 view来当做背景.

 

 
导航栏背景.png

设置 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];

本来感觉已经挺满意了,但是后来发现支付宝里面的页面过渡,导航栏背景颜色的渐变过渡更加的流畅.
于是决定研究一下.

全屏滑动返回手势

既然决定做导航栏背景过渡渐变效果,为了方便测试和突显这个流畅的效果.就顺便把全屏滑动返回手势也顺便做了吧.
先放最终效果图:

 
最终效果.gif
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地址,希望能帮到有需求的小伙伴.

https://github.com/sundayios/NavBackTransition-master.git

作者:ZHK1024
链接:https://www.jianshu.com/p/3abb9869619c
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

 

 

posted @ 2019-04-12 18:14  sundaysios  阅读(285)  评论(0编辑  收藏  举报