Swift UI 交互 动效Swiftswift-问题栈

swift/制作一个简单的tableHeaderview+_na

2015-11-05  本文已影响2473人  疯狂的剁椒鱼头

接着上一篇swift/制作一个简单的tableheaderview+_navigationbar渐变效果(一)继续完成没有做完的任务

编码

上一篇已经完成了对于UINavigationBar的扩展,仅仅只用了24行代码变达成想要的效果。接下来编写tableheaderview的效果部分。


ParallaxHeaderView模糊.gif

2:ParallaxHeaderView(1在上篇%>_<%)

虽然只是类似于一个图片下拉放大的效果,但请允许我使用Parallax来命名。

示例图给出两种效果,一种有模糊效果一种无模糊效果。先来实现第一种无模糊效果的。

目标分析

要求:

1:tableHeaderView需要有张图片(废话)
2:图片能够根据滚动的不同而展示不一样的样式(大小、显示范围)

在分析方法之前先弄清楚两点

UIImageView的contentMode属性的作用效果
        /**
         UIViewContentModeScaleToFill : 图片拉伸至填充整个UIImageView(图片可能会变形)
         
         UIViewContentModeScaleAspectFit : 图片拉伸至完全显示在UIImageView里面为止(图片不会变形)
         
         UIViewContentModeScaleAspectFill : 
         图片拉伸至 图片的宽度等于UIImageView的宽度 或者 图片的高度等于UIImageView的高度 为止
         
         UIViewContentModeRedraw : 调用了setNeedsDisplay方法时,就会将图片重新渲染
         
         UIViewContentModeCenter : 居中显示
         UIViewContentModeTop,
         UIViewContentModeBottom,
         UIViewContentModeLeft,
         UIViewContentModeRight,
         UIViewContentModeTopLeft,
         UIViewContentModeTopRight,
         UIViewContentModeBottomLeft,
         UIViewContentModeBottomRight,
         
         经验规律:
         1.凡是带有Scale单词的,图片都会拉伸
         2.凡是带有Aspect单词的,图片都会保持原来的宽高比,图片不会变形
         */

上面是以前用oc的时候对于UIImageView的contentMode的一些解释,在这利用UIViewContentModeScaleAspectFill这一效果进行演示说明(因为项目中使用的就是这个模式)。

红色的框框是UIImageView的大小320X100,而它的image大小是320X320,如果未设置clipsToBounds属性的话,还是能看到整张图片即使UIImageView的大小不够。(注意:使用UIImageVIew的init?(named name: String)方法创建出的imageView大小默认是和image一样大的)

tableView的contentOffset以及contentInset属性(其实是UIScrollView的属性)

网上有很多解释的文章,这里就不详细讲解,做个简单解释.

正如上图所示,UITableViewController有导航栏的情况。默认是不会被导航栏遮挡的,这并不是因为tableView的y值是64的原因,而是因为automaticallyAdjustsScrollViewInsets属性的存在,会将tableViewcontentInset。Top设置为64,从而导致未"被遮挡"(其实TableView还是被遮挡了一部分,只是显示UI的contentView位置偏移了)。那么现在我想问tableViewcontentOffset值是多少?答案是-64。contentOffset 是scrollview当前显示区域顶点相对于frame顶点的偏移量,contentInset 是scrollview中contentView.frame.origin与scrollview.frame.origin的关系

编码实现:

任然一步一步的来

1:设计构造方法

创建一个ParallaxHeaderView.swift并添加

class ParallaxHeaderView: UIView {
    
    var subView: UIView
    var contentView: UIView = UIView()
    
    
    init(subView: UIView, headerViewSize: CGSize) {
        
        self.subView = subView
        super.init(frame: CGRectMake(0, 0, headerViewSize.width, headerViewSize.height))
        //这里是自动布局的设置,大概意思就是subView与它的superView拥有一样的frame
        subView.autoresizingMask = [.FlexibleLeftMargin, .FlexibleRightMargin, .FlexibleTopMargin, .FlexibleBottomMargin, .FlexibleWidth, .FlexibleHeight]
        self.clipsToBounds = false;  //必须得设置成false
        
        self.contentView.frame = self.bounds
        self.contentView.addSubview(subView)
        self.contentView.clipsToBounds = true
        self.addSubview(contentView)
        
    }
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

然后创建一个ParallaxHeaderView并设置为tableHeaderView

             override func viewDidLoad() {
        super.viewDidLoad()

         self.navigationController?.navigationBar.setMyBackgroundColor(UIColor(red: 0/255.0, green: 130/255.0, blue: 210/255.0, alpha: 0))
        
        let imageView = UIImageView(frame: CGRectMake(0, 0, self.tableView.bounds.width, 100))
        imageView.image = UIImage(named: "ba1ec0437cc8d5367a516ff69b01ea89")
        imageView.contentMode = .ScaleAspectFill
        
        let heardView = ParallaxHeaderView(subView: imageView, headerViewSize: CGSizeMake(self.tableView.frame.width, 100))
        self.tableView.tableHeaderView = heardView
        
    }

结果效果是这样的

发现图片压根就没从(0,0)点显示,因为我们仅仅值是创建了,啥都没有处理的。这里我们目标很明确,要让图片从(0,0)开始显示并重新设置大小。所以我们更改ParallaxHeaderViewcontentViewframe就好了。还记得前面说的automaticallyAdjustsScrollViewInsets属性让tableViewontentInset。Top自动变成64的吗?这里自动设置的过程系统会自动调用scrollViewDidScroll,于是我顺水推舟,顺便让它也帮我重新设置contentViewframe

2:滚动计算contentView的frame

先贴代码再解释,在ParallaxHeaderView.swift中添加如下方法

    func layoutHeaderViewWhenScroll(let offset: CGPoint) {

        var delta:CGFloat = 0.0
        var rect = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height)
        
        delta = offset.y
        rect.origin.y += delta ;
        rect.size.height -= delta;
        
        self.contentView.frame = rect;
 
    }
    

在控制器中继续添加

    override func scrollViewDidScroll(scrollView: UIScrollView) {
        let heardView = self.tableView.tableHeaderView as! ParallaxHeaderView
        heardView.layoutHeaderViewWhenScroll(scrollView.contentOffset)
    }

Bingo!在ParallaxHeaderView.swift里面添加的是滑动设置contentViewframe的方法。在scrollViewDidScroll中便是调用。那为什么只修改这两个地方,一开始图片大小也变了呢?没错,在automaticallyAdjustsScrollViewInsets属性为true的情况下,系统在加载的时候便会自动调用scrollViewDidScroll方法。至于layoutHeaderViewWhenScroll里面的计算方式,自己体会体会,很简单。

3:锁定最大滑动位置。

按照上面那样做是不完善的,因为我不断的下拉,图片会不断的变大。而实际需求往往是,下拉到一定的位置便不能继续下拉了。接下来便继续完善。

    /// 最大的下拉限度(因为是下拉所以总是为负数),超过(小于)这个值,下拉将不会有效果
    var maxOffsetY: CGFloat
   init(subView: UIView, headerViewSize: CGSize, maxOffsetY: CGFloat) {
        
        ...
        self.maxOffsetY = maxOffsetY < 0 ? maxOffsetY : -maxOffsetY
        ....
   }
   

添加了一个最大下拉的y值,其他未修改的地方没有贴出

protocol ParallaxHeaderViewDelegate: class {
    func LockScorllView(maxOffsetY: CGFloat)
}
//这个的意思是,对协议进行扩展,任何遵守此协议的UITableViewController都由默认的实现方法
extension ParallaxHeaderViewDelegate where Self : UITableViewController {
    func LockScorllView(maxOffsetY: CGFloat) {
        self.tableView.contentOffset.y = maxOffsetY
    }
}

这里用到swift2.0的新特性了。因为只要是使用ParallaxHeaderView这个的类的,我总是希望他能锁定最大的位置,所以必定都会去实现LockScorllView这个协议方法,又因为这个方法的实现是固定的,所以我直接给了它一个默认的实现,这样就不要总是去写重复的协议了。

然后修改一下layoutHeaderViewWhenScroll方法

    func layoutHeaderViewWhenScroll(let offset: CGPoint) {

        if offset.y < maxOffsetY {
            self.delegate.LockScorllView(maxOffsetY)
            
        }else {
            var delta:CGFloat = 0.0
            var rect = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height)
            
            delta = offset.y
            rect.origin.y += delta ;
            rect.size.height -= delta;
            
            self.contentView.frame = rect;
            
        }
    }

再次修改构造方法,添加一个参数。

    weak var delegate: ParallaxHeaderViewDelegate!
    
    init(subView: UIView, headerViewSize: CGSize, maxOffsetY: CGFloat, delegate: ParallaxHeaderViewDelegate) {
    ...
        self.delegate = delegate
    ...
    
    }

这时候再在控制器里完善初始化,并添在scrollViewDidScroll加上上篇博客写的扩展,就ok了

    override func scrollViewDidScroll(scrollView: UIScrollView) {
        let heardView = self.tableView.tableHeaderView as! ParallaxHeaderView
        heardView.layoutHeaderViewWhenScroll(scrollView.contentOffset)
        
        let color = UIColor(red: 0/255.0, green: 130/255.0, blue: 210/255.0, alpha: 1)
        let offsetY = scrollView.contentOffset.y
        let prelude: CGFloat = 50
        
        if offsetY >= -64 {
            let alpha = min(1, (64 + offsetY) / (64 + prelude))
            //NavBar透明度渐变
            self.navigationController?.navigationBar.setMyBackgroundColor(color.colorWithAlphaComponent(alpha))
            
        } else {
            self.navigationController?.navigationBar.setMyBackgroundColor(color.colorWithAlphaComponent(0))
        }
    }

运行效果图就不贴了。

模糊效果

因为原理相似,而且时间比较紧就不写了。最终版代码已经上传到GitHub[ParallaxHeaderView][id]
[id]: https://github.com/SmallLang/ParallaxHeaderView "ParallaxHeaderView"

进一步优化

代码还有很多不足,比如我还得在scrollViewDidScroll中添加大量计算导航栏透明度的代码。这种计算是通用的,因为总是希望在第一个cell滑动到导航栏下,导航栏就不透明了。完整代码在GitHub[ParallaxHeaderView][id]

总结

虽然重复造轮子不是一种好的习惯,在正式项目开发中也会严重拖缓项目开发进度。但是对于初学者,可以说是一种不错的学习方式。文章中如果出现什么错误或者好的建议欢迎大家提出,我们一起进行学习。要上课了就不啰嗦了。

最后还是附上GitHub[ParallaxHeaderView][id]

上一篇下一篇

猜你喜欢

热点阅读