iOS 实现NavigationController的titleView动态缩放效果

早就心水简书的个人中心界面的NavigationController动态缩放titleView效果,也就是如下图的效果:

screenShot.png

自己动手用Object-C和Swift两种语言各写了一个简单的小demo,下面先说一下用Object-C实现的简单原理.

知识补充=====>

因为在这个效果实现的过程中我遇到一些关于tableView的contentInset和contentOffset的困扰,所以在这里我想先解释明白关于这两个属性,然后再谈怎样实现我们需要的效果。

1:概念:

contentSize:The size of the content view.其实就是scrollview可以滚动的区域。比如frame = (0, 0, 320, 480) contentSize = (320, 960),代表scrollview可以上下滚动,滚动区域为frame大小的两倍。
contentOffset:The point at which the origin of the content view is offset from the origin of the scroll view.是scrollview当前显示区域定点相对于frame定点的偏移量,(向屏幕内拉,偏移量是负值。向屏幕外推,偏移量是正数),比如上个例子,从初始状态向下拉50像素,contentoffset就是(0 ,-50),从初始状态向上推tableview100像素,contentOffset就是(0 ,100)。
contentInset:The distance that the content view is inset from the enclosing scroll view。是scrollview的contentview的顶点相对于scrollview的位置,例如你的contentInset = (0 ,100),那么你的contentview就是从scrollview的(0 ,100)开始显示。
举个简单的例子:

截图1.png

这是一个带有导航栏的的控制器,控制器上有一个tableView。我将tableView初始化后并打印各个属性的值:如下图:

截图2.png

打印属性值的时候我是在scrollview的这个代理方法中打印的:


- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    
    NSLog(@"contentInset:{%f,%f,%f,%f}", self.tableView.contentInset.top,self.tableView.contentInset.right,self.tableView.contentInset.bottom,self.tableView.contentInset.left);
    
    NSLog(@"contentOffset:{%f,%f}", self.tableView.contentOffset.x, self.tableView.contentOffset.y);
    
    NSLog(@"contentSize:{%f,%f}", self.tableView.contentSize.height, self.tableView.contentSize.width);
}

*contentInset: top=64,即是navBar的高度,说明是从这儿开始显示tableview而不是从(0,0,0,0)开始显示的。

*contentOffset: y = -64。当前显示区域顶点相对于frame的偏移量。即是-64,可以理解成从(0,0)的位置向下拉了64像素,上面我们说到过,(向屏幕内拉,偏移量是负值。向屏幕外推,偏移量是正数)。

*contentSize 是tableView的滑动区域。宽度就是屏幕的宽度,高度是cell.Height乘以cell.Count

解释完这个概念接下来就相对比较好理解了,接下来说一下效果实现的原理。

Object-C实现NavigationController的titleView的动态缩放

demo运行图片如下图:

PageBlurTestGif.gif
1 ===>

首先创建一个topBkView用来替代系统的titleView.然后声明一个全局的_topImageView放在topBkView上.在这里我是这样做的:

//MARK:-createScaleHeaderView
- (void)createScaleHeaderView {
    
    UIView *topBkView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 60, 30)];
    topBkView.backgroundColor = [UIColor clearColor];
    _topImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 60, 60)];
    _topImageView.backgroundColor = [UIColor whiteColor];
    _topImageView.layer.cornerRadius = _topImageView.bounds.size.width/2;
    _topImageView.layer.masksToBounds = YES;
    _topImageView.image = [UIImage imageNamed:@"head"];
    [topBkView addSubview:_topImageView];
    self.navigationItem.titleView = topBkView;
}

topImageViewheight设为topBkView的2倍,为的是显示出头像在导航栏上山下各一半的效果.

2 ===>

实现动态缩放的思路主要体现在监听ScrollView滑动的代理事件中:

//MARK:-滑动代理
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    
    CGFloat contentSet = scrollView.contentOffset.y + _tableView.contentInset.top;

    if (contentSet >= 0 && contentSet <= 30) {
        _topImageView.transform = CGAffineTransformMakeScale(1 - contentSet/60, 1-contentSet/60);
        _topImageView.y = 0;
    } else if (contentSet > 30) {
        _topImageView.transform = CGAffineTransformMakeScale(0.5, 0.5);
        _topImageView.y = 0;
    } else if (contentSet < 0 ) {
        _topImageView.transform = CGAffineTransformMakeScale(1, 1);
        _topImageView.y = 0;
    }
    
}

在这里声明了一个变量contentSet这是scrollView.contentOffset.y_tableView.contentInset.top的和,初始值是0,当tableView向上滑动的时候contentSet为正值并增大,这里我们判断,当contentSet >= 0 && contentSet <= 30时,我们控制_topImageView的缩放量,另外还有两个情况的判断已经在代码中写出来了,还要说明的一点是在tableView滚动监听并且改变_topImageView大小的过程中,要始终保持_topImageView.y = 0;要不然_topImageView会随着大小的变化乱动。

Swift实现NavigationController的titleView的动态缩放

效果如图:


PageBlurTestGif.gif

这个比较好玩,是我之前看的一个大牛做的一个效果,后来闲着没事儿用swift写了一遍。这几个功能也是咱们平时比较常见的。这一块我比较懒,没有进行tableView的复用,每一个功能直接copy的tableview。哈哈。

1 titleView动态缩放===>

先看第一个界面,就是和简书那个titleView动态缩放比较相似的效果,只是多了一个下拉放大功能,原理是相同的。


//滑动代理方法
    func scrollViewDidScroll(scrollView: UIScrollView) {
        
        let offsetY:CGFloat = scrollView.contentOffset.y + (tableView?.contentInset.top)!
        print("offsetY = %f contentOffset.y = %f contentInset.top = %f", offsetY, scrollView.contentOffset.y, tableView?.contentInset.top)
        if offsetY < 0 && offsetY >= -150 {
            topImageView?.transform = CGAffineTransformMakeScale(1 - offsetY/300, 1 - offsetY/300)
        } else if (offsetY >= 0 && offsetY <= 150) {
            topImageView?.transform = CGAffineTransformMakeScale(1 - offsetY/300, 1 - offsetY/300)
        } else if (offsetY > 150) {
            topImageView?.transform = CGAffineTransformMakeScale(0.45, 0.45)
        } else if(offsetY < -150) {
            topImageView?.transform = CGAffineTransformMakeScale(1.5, 1.5)
        }
        var frame:CGRect = (topImageView?.frame)!
        frame.origin.y = 5;
        topImageView?.frame = frame
        
    }
2 滑动隐藏navBar===>

panTranslationY这个变量是监听tableView在scrollView中滑动状态的。

// translationInView :translation in the coordinate system of the specified view

意思就是你用手指触动tableView上下滑动时所反映出来的一个数值,向下拉动为正值,向上拖动为负值。通过这个值的变动进行navBar的隐藏或显示。

func scrollViewDidScroll(scrollView: UIScrollView) {
        
        let offsetY:CGFloat = scrollView.contentOffset.y + (tableView?.contentInset.top)!
        let panTranslationY = scrollView.panGestureRecognizer.translationInView(tableView).y
        
        if offsetY > 0 {
            if panTranslationY > 0 {
                //下滑趋势 显示
                [self.navigationController?.setNavigationBarHidden(false, animated: true)]
            } else {
                //上滑趋势 隐藏
                [self.navigationController?.setNavigationBarHidden(true, animated: true)]
            }
        } else {
            [self.navigationController?.setNavigationBarHidden(false, animated: true)]
        }
    }
3 view动态缩放===>

先放一张图大致理解这个效果实现的套路:

截图3.png

首先在创建tableview的时候需要设置tableview的contentInset,目的是给上部的view留出空间。而且不影响tableview的正常使用。在这里设置topContentInset = 100

func createTableView() -> () {
        
        if (tableView == nil) {
            tableView = UITableView(frame: UIScreen .mainScreen().bounds, style: .Plain)
            tableView?.contentInset = UIEdgeInsetsMake(topContentInset, 0, 0, 0)
            tableView?.delegate = self
            tableView?.dataSource = self
            tableView?.backgroundColor = UIColor.clearColor()
            tableView?.separatorStyle = .SingleLine
            self.view.addSubview(tableView!)
        }
    }

然后创建头部背景视图。我们将这个topImageView 插到tableView下方。这样的话topImageViewtableview是不在同一层级的不会相互影响,并且我们在初始化tableView的时候已经设置好了tableViewcontentInset了,给topImageView的显示留出了空间。

//MARK:-创建顶部背景视图
    func createScaleImageView() -> Void {
        
        topImageView = UIImageView(frame: CGRectMake(0, 0, UIScreen .mainScreen().bounds.width, UIScreen .mainScreen().bounds.width*435.5/313.0))
        topImageView?.backgroundColor = UIColor.whiteColor()
        topImageView?.image = UIImage(named: "backImage")
        self.view.insertSubview(topImageView!, belowSubview: tableView!)
    }

接下来创建头像视图,headBkView:UIView的高度也是topContentInset = 100背景颜色设置为透明。将tableView?.tableHeaderView = headBkView,因为我们需要头像跟随tableview的滑动而移动。这样我们也给topImageView留出了充足的显示空间。

//MARK:-创建头像视图
    func createHeadView() -> Void {
        
//        topContentInset = 136;//136+64 = 200
        let headBkView:UIView = UIView(frame: CGRectMake(0, 0, UIScreen .mainScreen().bounds.width, topContentInset))
        headBkView.backgroundColor = UIColor.clearColor()
        tableView?.tableHeaderView = headBkView
        
        let headImageView = UIImageView()
        headImageView.bounds = CGRectMake(0, 0, 64, 64)
        headImageView.center = CGPointMake(UIScreen .mainScreen().bounds.width/2.0, (topContentInset - 64)/2.0)
        headImageView.backgroundColor = UIColor.whiteColor()
        headImageView.layer.cornerRadius = headImageView.bounds.size.width / 2.0
        headImageView.layer.masksToBounds = true
        headImageView.image = UIImage(named: "head")
        headBkView.addSubview(headImageView)
    }

最后就是实现滑动时topImageView的缩放了,在这里面我有对navBar的透明度的设置,真正起到改变topImageView缩放效果的就是这一句

else if offsetY < 0 { topImageView?.transform = CGAffineTransformMakeScale(1 + offsetY/(-500), 1 + offsetY/(-500)) }
//MARK:-滑动代理
    func scrollViewDidScroll(scrollView: UIScrollView) {
        
        let offsetY = scrollView.contentOffset.y + (tableView?.contentInset.top)!
        
        print("\(offsetY)")
        
        if offsetY > topContentInset && offsetY <= topContentInset*2 {
            
            statusBarStyleControl = true
            self.navigationController?.navigationBar.setBackgroundImage(UIImage(), forBarMetrics: UIBarMetrics.Default)
            self.navigationController?.navigationBar.shadowImage = UIImage()
            self.navigationController?.navigationBar.translucent = true
            
        }
        else if (offsetY <= topContentInset && offsetY >= 0) {
            
            statusBarStyleControl = false
            if (self.respondsToSelector(#selector(UIViewController.setNeedsStatusBarAppearanceUpdate))) {
                self.setNeedsStatusBarAppearanceUpdate()
            }
            self.navigationController?.navigationBar.setBackgroundImage(UIImage(), forBarMetrics: UIBarMetrics.Default)
            self.navigationController?.navigationBar.shadowImage = UIImage()
            self.navigationController?.navigationBar.translucent = true
        }
        else if offsetY > topContentInset * 2 {

            self.navigationController?.navigationBar.setBackgroundImage(UIImage(), forBarMetrics: UIBarMetrics.Default)
            self.navigationController?.navigationBar.shadowImage = UIImage()
            self.navigationController?.navigationBar.translucent = true
            
        }
        else if offsetY < 0 {
            topImageView?.transform = CGAffineTransformMakeScale(1 + offsetY/(-500), 1 + offsetY/(-500))
        }
        var frame:CGRect = (topImageView?.frame)!
        frame.origin.y = 0
        topImageView?.frame = frame
        
    }

END

这几个小功能的大体思路就大概是这样的,如果有不正确的地方欢迎批评指正。最后放上代码链接:
https://github.com/irembeu/NavBarTitleViewScaleDemo.git

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容