美文网首页iOS资料
头部视图拉伸放大效果实现原理解析

头部视图拉伸放大效果实现原理解析

作者: Tony_Yang | 来源:发表于2017-08-24 19:27 被阅读352次
    stretchableView.gif

    在很多APP中大家应该都见过一些类似个人主页的页面,下边是tableview列表,上边的头部视图可以拉伸放大.

    在一番研究实现了这个拉伸效果后,顺便把这个功能进行了封装,使用时只需两行代码

    1.初始化调用

    stretchableView = LPStretchableHeaderView(stretchableView: bgImageView)
    

    2.代理方法实现:

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        stretchableView.scrollViewDidScroll(scrollView)
    }
    

    github代码demo

    下边是实现原理的解析,大神请绕路~~~

    实现原理解析

    一:设置头部视图
    创建imageView,并添加到控制器的view上

    由于在往下拖动列表时,头部视图的y值是没有跟着下移的,所以肯定不能让它作为tableview的tableHeaderView,只能把上边的图片视图添加到Controller的view上

    二:设置tableview
    • 创建一个跟头部视图同样大小的空视图headerView作为tableview的tableHeaderView,来填充头部图片视图区域
    • 把tableview的背景颜色设置为clearColor,这样就可以看到下面的头部图片了

    这样一来,我们的列表视图的实际大小就占据了整个屏幕,并且不影响看到下面的头部图片,而且在头部图片区域拖动的时候(实际拖动的是列表的tableHeaderView)也可以触发列表的滚动事件,同时上滑的时候列表的顶部滚动区域也达到了导航栏位置,一石好几鸟啊~😎

    三:头部视图添加子控件

    一般在头部视图的图片上方,还会显示昵称、头像等信息,我们需要把这些子控件添加到刚才创建的空视图headerView

    注意:不要添加到头部视图的imageView中!

    因为稍后我们在在拉伸列表时,会改变imageView的frame,但是并没有改变其内的子控件的frame,所以子控件位置会发生错乱。

    现在控件布局如下:

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 头部图片视图
        bgImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: SCREEN_WIDTH * imageRatio))
        bgImageView.image = UIImage(named: "123")
        view.addSubview(bgImageView)
        
        // 列表
        tableView = UITableView(frame: CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: SCREEN_HEIGHT))
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
        tableView.dataSource = self
        tableView.delegate = self
        tableView.showsVerticalScrollIndicator = false
        tableView.backgroundColor = UIColor.clear // 注意要清除列表的背景颜色
        view.addSubview(tableView)
        
        // 创建一个空白view来进行填充tableHeaderView
        let headerView = UIView(frame: bgImageView.bounds)
        tableView.tableHeaderView = headerView
        
        // 添加label子控件
        let nameLabel = UILabel(frame: CGRect(x: 0, y: 150, width: bgImageView.width, height: 40))
        nameLabel.text = "哈哈哈😆"
        nameLabel.textAlignment = .center
        nameLabel.textColor = UIColor.white
        headerView.addSubview(nameLabel) // 注意要把子控件添加到headerView中
        
        // 导航栏
        makeNavView()
    }
    
    三:实现滚动拉伸放大效果
    • 下拉时通过tableview在y轴的偏移量来决定头部图片的高度拉伸多少
    • 通过图片拉伸的高度及图片原来的宽高比例计算出要拉伸的宽度
    • 通过图片拉伸后的宽度计算出x值应向左的偏移量

    1)首先,我们会在头部视图初始化的时候记录它的原始frame,这个后边会用到

    // 图片的原始frame
    originFrame = bgImageView.frame
    

    2)取出列表y值的偏移量

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let yOffset = scrollView.contentOffset.y
    }
    

    3)图片拉伸后的高度

    // 下拉时yOffset值是负数,所以需要减
    frame.size.height = originFrame.size.height - yOffset
    

    4)图片拉伸后的宽度

    // 通过图片的宽高比imageRatio及拉伸后的高度等比计算新宽度
    frame.size.width = frame.size.height / imageRatio
    

    5)x值的位置重新计算

    // 图片宽高同时变大后,图片会整体向右偏移,所以需要重新计算x值
    frame.origin.x = originFrame.origin.x - (frame.size.width - originFrame.size.width) * 0.5
    

    当列表上滑时,移动头部图片跟着向上移动

    var frame = originFrame
    frame.origin.y = originFrame.origin.y - yOffset
    bgImageView.frame = frame
    

    最终,处理代码为:

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        
        let yOffset = scrollView.contentOffset.y
        
        // 头部图片拉伸设置
        if yOffset > 0 { // 上滑
            var frame = originFrame
            frame.origin.y = originFrame.origin.y - yOffset
            bgImageView.frame = frame
            
        } else { // 下拉
            var frame = originFrame
            frame.size.height = originFrame.size.height - yOffset
            frame.size.width = frame.size.height / imageRatio
            frame.origin.x = originFrame.origin.x - (frame.size.width - originFrame.size.width) * 0.5
            bgImageView.frame = frame
        }
    }
    

    封装

    通过以上实现可以看出,所有的操作都是通过拿到scrollViewDidScroll回调方法中列表y轴的偏移量,然后对头部视图bgImageView的frame进行更改实现的。

    所以其实没啥好封装的,如果非得封装的话,那就只需要把bgImageView控件和对它frame更改的操作拿出去就ok了。

    于是我建了一个工具类LPStretchableHeaderView,实现如下:

    LPStretchableHeaderView.swift文件

    import UIKit
    
    public class LPStretchableHeaderView: NSObject {
    
        private var stretchView = UIView()
        private var imageRatio: CGFloat
        private var originFrame = CGRect()
        
        public init(stretchableView: UIView) {
            
            stretchView = stretchableView
            originFrame = stretchableView.frame
            imageRatio = stretchableView.bounds.height / stretchableView.bounds.width
        }
        
        public func scrollViewDidScroll(_ scrollView: UIScrollView) {
            
            let yOffset = scrollView.contentOffset.y
            if yOffset > 0 { // 往上移动
                var frame = originFrame
                frame.origin.y = originFrame.origin.y - yOffset
                stretchView.frame = frame
            } else { // 往下移动
                var frame = originFrame
                frame.size.height = originFrame.size.height - yOffset
                frame.size.width = frame.size.height / imageRatio
                frame.origin.x = originFrame.origin.x - (frame.size.width - originFrame.size.width) * 0.5
                stretchView.frame = frame
            }
        }
    }
    

    这样,以后遇到有这种需求的页面,两行代码就搞定了~


    如果感觉有帮助就点个❤️鼓励下吧😄

    相关文章

      网友评论

      • FANTASIED:有个问题,如果tableView是UITableViewStyleGrouped的怎么办,背景色改clearColor就看不到HeaderFooter了
        Tony_Yang:可以把grouped样式改为plain,grouped的分组样式可以通过自定义Section的headerView、footerView来实现
      • 我无法忘却的美丶:OMG 没有OC版吗
        我无法忘却的美丶:@Tony_Yang 好的 谢谢~
        Tony_Yang:呃,,语法其实都差不多,不过OC的话你可以看下这个https://github.com/jixiang0903/MineHeadDemo

      本文标题:头部视图拉伸放大效果实现原理解析

      本文链接:https://www.haomeiwen.com/subject/kmhedxtx.html