美文网首页
模仿香奈儿首页效果(Swift版)

模仿香奈儿首页效果(Swift版)

作者: 黑化肥发灰 | 来源:发表于2018-01-31 13:38 被阅读20次

    最近项目中要实现一个动画效果,跟香奈儿首页很相似,多张图上下滑动,屏幕上面的图最大,点击下面的cell,点中的cell滚动到上面,并且放大显示。最终效果如下所示


    1. 实现思路

    这种能够上下滚动,显然是一个scrollview。我们可以通过自定义UICollectionViewFlowLayout,来实现我们想要的效果。

    2. 自定义UICollectionViewFlowLayout

    首先定义几个常量,方便后面代码中使用

    // cell在下面的时候高度
    let kNormalCellHeight: CGFloat = kScreenWidth * 0.3
    
    // cell在屏幕上面第一个时候高度
    let kBigCellHeight: CGFloat = kScreenWidth * 1.0
    
    let kScreenWidth = UIScreen.main.bounds.width
    let kScreenHeight = UIScreen.main.bounds.height
    let kRectRange: CGFloat = UIScreen.main.bounds.size.height + kNormalCellHeight * 2
    

    设置collectionViewContentSize,注意凡是滑到屏幕上方的cell高度都是kBigCellHeight,所以为了最后一个cell显示的下,contentSize进行如下设置

        override var collectionViewContentSize: CGSize {
            return CGSize(width: kScreenWidth, height: kBigCellHeight * CGFloat(count) + (kScreenHeight - kBigCellHeight))
        }
    
    

    初始化一些变量,默认的cell高度就是kNormalCellHeight

        override init() {
            super.init()
            itemSize = CGSize(width: kScreenWidth, height: kNormalCellHeight)
            scrollDirection = .vertical
            minimumInteritemSpacing = 0
            minimumLineSpacing = 0
        }
        
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
    

    重写prepare和shouldInvalidateLayout

    // 在布局开始的时候,layout对象会先调用prepareLayout方法,这个方法里面你可以计算一会儿你要用到的信息。 prepareLayout方法并不是必须实现的,但是它给你一个机会去做一些必要地初始化计算。
        override func prepare() {
            super.prepare()
        }
    
    // 当前layout的布局发生变动时,是否重写加载该layout。默认返回NO,若返回YES,则重新执行prepare和layoutAttributesForElements(in rect: CGRect)
        override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
            return true
        }
    

    重写layoutAttributesForElements(in rect: CGRect),这个方法是核心,返回在collectionView的可见范围内(bounds)所有item对应的layoutAttrure对象装成的数组。collectionView的每个item都对应一个专门的UICollectionViewLayoutAttributes类型的对象来表示该item的一些属性,比如bounds, size, transform, alpha等。

        override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
            let screen_y = collectionView?.contentOffset.y
            let current_floor = floorf(Float((screen_y!) / kBigCellHeight)) + 1
            let current_mod = fmodf(Float(screen_y!), Float(kBigCellHeight))
            let percent = current_mod / Float(kBigCellHeight)
            
            var correctRect: CGRect = .zero
            if current_floor == 0 || current_floor == 1 {
                correctRect = CGRect(x: 0, y: 0, width: kScreenWidth, height: kRectRange)
            } else {
                correctRect = CGRect(x: 0, y: kNormalCellHeight * CGFloat(current_floor - 2), width: kScreenWidth, height: kRectRange)
            }
            let original = super.layoutAttributesForElements(in: correctRect)
            let array = original
            
            let incrementalHeightOfCurrentItem = kBigCellHeight - kNormalCellHeight
            
            if screen_y! >= 0 {
                for attributes in array! {
                    let row = attributes.indexPath.row
                    if row < Int(current_floor) {
                   // zIndex 表示层级,数字越大,层级越高(最上面)
                        attributes.zIndex = 7
                        attributes.frame = CGRect(x: 0, y: kBigCellHeight * CGFloat(row - 1), width: kScreenWidth, height: kBigCellHeight)
                    } else if (row == Int(current_floor)) {
                        attributes.zIndex = 8
                        attributes.frame = CGRect(x: 0, y: kBigCellHeight * CGFloat(row - 1), width: kScreenWidth, height: kBigCellHeight)
                    } else if (row == Int(current_floor) + 1) {
                        attributes.zIndex = 9
                        let part = (CGFloat(current_floor) - 1) * incrementalHeightOfCurrentItem
                        let partOne = attributes.frame.origin.y + part
                        let partTwo = kNormalCellHeight + (kBigCellHeight - kNormalCellHeight) * CGFloat(percent)
                        attributes.frame = CGRect(x: 0, y: partOne, width: kScreenWidth, height: partTwo)
                        
                    } else {
                        if row == Int(current_floor) + 2 {
                            attributes.zIndex = 6
                        } else if (row == Int(current_floor) + 3) {
                            attributes.zIndex = 5
                        } else if (row == Int(current_floor) + 4) {
                            attributes.zIndex = 4
                        } else if (row == Int(current_floor) + 5) {
                            attributes.zIndex = 3
                        } else if (row == Int(current_floor) + 6) {
                            attributes.zIndex = 2
                        } else if (row == Int(current_floor) + 7) {
                            attributes.zIndex = 1
                        } else {
                            attributes.zIndex = 0
                        }
                        let partOne = (current_floor - 1) * Float(incrementalHeightOfCurrentItem)
                        let originY = Float(attributes.frame.origin.y) + partOne + Float(incrementalHeightOfCurrentItem) * percent
                        attributes.frame = CGRect(x: 0, y: CGFloat(originY), width: kScreenWidth, height: kNormalCellHeight)
                        
                    }
    
                }
            }
            
            return array
            
        }
    
    

    重写targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint),返回layout“最终”的偏移量,何谓“最终”,手指离开屏幕时layout的偏移量不是最终的,因为它有惯性,当它停止时才是“最终”偏移量。
    注意:

    • 整个方法都是在求手指离开屏幕后滑动到的最终点,因为是上下滑,所以主要是找到Y值
    • 如果手指滑动后停下来再离开,那么velocity.y == 0,如果手指向上滑动,contentOffset.y变大,那么velocity.y > 0,如果手指向下滑动,contentOffset.y变小,那么velocity.y < 0
        override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint {
    // 整个方法所有的核心都是在求这个destinationPointY
            var destinationPointY: CGFloat
            var destinationPoint: CGPoint = .zero
            let contentOffsetY: CGFloat = (self.collectionView?.contentOffset.y)!
            var realVelocityY: CGFloat
            var cellLocation: CGFloat
            
            if contentOffsetY < 0 {
                return proposedContentOffset
            }
            
            if velocity.y == 0 {
                cellLocation = CGFloat(roundf(Float((proposedContentOffset.y)/kBigCellHeight))) + 1
                self.currentCount = Int(cellLocation)
                if cellLocation == 0 {
                    destinationPointY = 0
                } else {
                    destinationPointY = (cellLocation - 1) * kBigCellHeight
                }
            } else {
                if velocity.y > 1 {
                    realVelocityY = 1
                } else if (velocity.y < -1) {
                    realVelocityY = -1
                } else {
                    realVelocityY = velocity.y
                }
                
                if velocity.y > 0 {
                    cellLocation = CGFloat(ceilf(Float((contentOffsetY + realVelocityY * kBigCellHeight) / kBigCellHeight)) + 1)
                } else {
                    cellLocation = CGFloat(floorf(Float((contentOffsetY + realVelocityY * kBigCellHeight) / kBigCellHeight)) + 1)
                }
                
                if cellLocation == 0 {
                    destinationPointY = 0
                    currentCount = 1
                } else {
                    if velocity.y > 0 {
                        cellLocation = CGFloat(self.currentCount + 1)
                        self.currentCount = self.currentCount + 1
                    } else {
                        cellLocation = CGFloat(self.currentCount - 1)
                        self.currentCount = self.currentCount - 1
                    }
                    destinationPointY = (cellLocation - 1) * kBigCellHeight
                }
            }
            if destinationPointY < 0 {
                destinationPointY = 0
            }
            if destinationPointY > ((self.collectionView?.contentSize.height)! - kScreenHeight) {
                destinationPointY = ((self.collectionView?.contentSize.height)! - kScreenHeight)
                self.currentCount = self.currentCount - 1
                cellLocation = CGFloat(self.currentCount)
            }
            
            self.collectionView?.decelerationRate = 0.1
            destinationPoint = CGPoint(x: 0, y: destinationPointY)
            return destinationPoint
        }
    

    3. 点击cell时候的处理

    如果是顶上的大图则不处理,如果是下面的图则滚动到指定位置即可

        func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
            
            let offset = ceilf(Float(kBigCellHeight) * Float(indexPath.row  - 1))
            if ceilf(Float(collectionView.contentOffset.y)) != offset {
                self.layout?.currentCount = indexPath.row
                collectionView.setContentOffset(CGPoint.init(x: 0, y: Int(offset)), animated: true)
            } else {
                print("点击了大图哦")
            }
            print("点击了第\(indexPath.row)个")
        }
    
    

    最后给出demo ChanelDemo

    相关文章

      网友评论

          本文标题:模仿香奈儿首页效果(Swift版)

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