当collectionView滚动时,如何使iOS collectionView的特定单元格淡出?

Posted

技术标签:

【中文标题】当collectionView滚动时,如何使iOS collectionView的特定单元格淡出?【英文标题】:How can I make a particular cell of an iOS collectionView fade out as the collectionView scrolls? 【发布时间】:2016-10-22 19:39:00 【问题描述】:

我想让 UICollectionView 的所有右侧单元格在滚动时淡出,类似于 Apple 的消息应用程序,但不影响 collectionView 中其他单元格的颜色或透明度。有没有办法根据 UICollectionViewCell 的滚动位置调整其透明度以实现该效果?

【问题讨论】:

尝试搜索渐变效果这可能会有所帮助***.com/questions/22726103/… 我喜欢使用渐变蒙版的想法,但我相信这会影响 scrollView 的所有内容,而不仅仅是正确的单元格。 你是从上往下掉还是两者兼而有之? 【参考方案1】:

如果你继承 UICollectionViewFlowLayout,这很简单。您需要做的第一件事是确保在发生边界更改/滚动时重新计算可见属性,方法是在

中返回 true

shouldInvalidateLayout(forBoundsChange newBounds: CGRect)

然后在layoutAttributesForElements(in rect: CGRect)委托调用中,获取超类计算出来的属性,根据item在可见范围内的偏移量修改alpha值,就这样。 左侧/右侧项目之间的区别可以在控制器中使用您拥有的任何逻辑进行处理,并传达给布局类以避免将此效果应用于左侧项目。 (我使用 'CustomLayoutDelegate' 来实现在控制器中实现的功能,它只是将带有奇数 indexPath.row 的项目识别为左侧单元格)

这是一个演示,它将此效果应用于具有偶数 indexPath.row 跳过奇数行的项目

import UIKit

class ViewController: UIViewController 

    /// Custom flow layout
    lazy var layout: CustomFlowLayout = 
        let l: CustomFlowLayout = CustomFlowLayout()
        l.itemSize = CGSize(width: self.view.bounds.width / 1.5, height: 100)
        l.delegate = self

        return l
    ()

    /// The collectionView if you're not using UICollectionViewController
    lazy var collectionView: UICollectionView = 
        let cv: UICollectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: self.layout)
        cv.backgroundColor = UIColor.lightGray

        cv.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")
        cv.dataSource = self

        return cv
    ()

    override func viewDidLoad() 
        super.viewDidLoad()

        view.addSubview(collectionView)
    



extension ViewController: UICollectionViewDataSource, CustomLayoutDelegate 

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 
        return 30
    

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
        cell.backgroundColor = UIColor.black

        return cell
    

    // MARK: CustomLayoutDelegate

    func cellSide(forIndexPath indexPath: IndexPath) -> CellSide 

        // TODO: Your implementation to distinguish left/right indexPath

        // Even rows are .right and Odds .left
        if indexPath.row % 2 == 0 
            return .right
         else 
            return .left
        
    


public enum CellSide 
    case right
    case left


protocol CustomLayoutDelegate: class 

    func cellSide(forIndexPath indexPath: IndexPath) -> CellSide


class CustomFlowLayout: UICollectionViewFlowLayout 

    /// Delegates distinguishing between left and right items
    weak var delegate: CustomLayoutDelegate!

    /// Maximum alpha value
    let kMaxAlpha: CGFloat = 1

    /// Minimum alpha value. The alpha value you want the first visible item to have
    let kMinAlpha: CGFloat = 0.3

    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? 
        guard let cv = collectionView, let rectAtts = super.layoutAttributesForElements(in: rect) else  return nil 

        for atts in rectAtts 

            // Skip left sides
            if delegate.cellSide(forIndexPath: atts.indexPath) == .left 
                continue
            

            // Offset Y on visible bounds. you can use
            //      ´cv.bounds.height - (atts.frame.origin.y - cv.contentOffset.y)´
            // To reverse the effect
            let offset_y = (atts.frame.origin.y - cv.contentOffset.y)

            let alpha = offset_y * kMaxAlpha / cv.bounds.height

            atts.alpha = alpha + kMinAlpha
        

        return rectAtts
    

    // Invalidate layout when scroll happens. Otherwise atts won't be recalculated
    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool 
        return true
    


【讨论】:

谢谢!我缺少的关键位是为 shouldInvalidateLayout 返回 true【参考方案2】:

您可以对集合视图做很多有趣的事情。我喜欢继承 UICollectionViewFlowLayout。这是一个示例,它根据与中心的距离淡化集合视图的顶部和底部。我可以将其修改为仅淡化边缘,但您应该在查看代码后弄清楚。

import UIKit

class FadingLayout: UICollectionViewFlowLayout,UICollectionViewDelegateFlowLayout 

    //should be 0<fade<1
    private let fadeFactor: CGFloat = 0.5
    private let cellHeight : CGFloat = 60.0

    required init?(coder aDecoder: NSCoder) 
        super.init(coder: aDecoder)
    

    init(scrollDirection:UICollectionViewScrollDirection) 
        super.init()
        self.scrollDirection = scrollDirection

    

    override func prepare() 
        setupLayout()
        super.prepare()
    

    func setupLayout() 
        self.itemSize = CGSize(width: self.collectionView!.bounds.size.width,height:cellHeight)
        self.minimumLineSpacing = 0
    

    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool 
        return true
    

    func scrollDirectionOver() -> UICollectionViewScrollDirection 
        return UICollectionViewScrollDirection.vertical
    
    //this will fade both top and bottom but can be adjusted
    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? 
        let attributesSuper: [UICollectionViewLayoutAttributes] = super.layoutAttributesForElements(in: rect) as [UICollectionViewLayoutAttributes]!
        if let attributes = NSArray(array: attributesSuper, copyItems: true) as? [UICollectionViewLayoutAttributes]
            var visibleRect = CGRect()
            visibleRect.origin = collectionView!.contentOffset
            visibleRect.size = collectionView!.bounds.size
            for attrs in attributes 
                if attrs.frame.intersects(rect) 
                    let distance = visibleRect.midY - attrs.center.y
                    let normalizedDistance = abs(distance) / (visibleRect.height * fadeFactor)
                    let fade = 1 - normalizedDistance
                    attrs.alpha = fade
                
            
            return attributes
        else
            return nil
        
    
    //appear and disappear at 0
    override func initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? 
        let attributes = super.layoutAttributesForItem(at: itemIndexPath)! as UICollectionViewLayoutAttributes
        attributes.alpha = 0
        return attributes
    

    override func finalLayoutAttributesForDisappearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? 
        let attributes = super.layoutAttributesForItem(at: itemIndexPath)! as UICollectionViewLayoutAttributes
        attributes.alpha = 0
        return attributes
    

在你的控制器中的集合视图设置中,它看起来像这样。

let layout = FadingLayout(scrollDirection: .vertical)
collectionView.delegate = self
collectionView.dataSource = self
self.collectionView.setCollectionViewLayout(layout, animated: false)

如果我更了解用例,我可以告诉你如何修改它。

【讨论】:

只有当用户开始滚动时才能淡出?另外,当用户到达滚动视图的末尾时,底部单元格不会褪色?【参考方案3】:

当然!请注意,UICollectionView 是 UIScrollView 的子类,并且您的 UICollectionViewController 已经是集合视图的delegate。这意味着它也符合 UIScrollViewDelegate 协议,该协议包含一组通知您滚动位置变化的方法。

对我来说最值得注意的是scrollViewDidScroll(_:),当集合视图中的contentOffset 发生变化时会调用它。您可以实现该方法来迭代集合视图的visibleCells,或者自己调整单元格的alpha,或者向单元格发送一些消息以通知它根据其框架和偏移量调整自己的alpha。

我能想出的最简单的实现方式(尊重您的仅右侧要求)如下所示。请注意,这可能会在视图顶部或底部附近出现一些故障,因为单元格的 alpha 仅在滚动时调整,而不是在初始出列或重用时调整。

class FadingCollectionViewController: UICollectionViewController 

    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int 
        return 500
    

    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell 
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
        return cell
    

    override func scrollViewDidScroll(_ scrollView: UIScrollView) 
        guard let collectionView = collectionView else 
            return
        

        let offset = collectionView.contentOffset.y
        let height = collectionView.frame.size.height
        let width = collectionView.frame.size.width
        for cell in collectionView.visibleCells 
            let left = cell.frame.origin.x
            if left >= width / 2 
                let top = cell.frame.origin.y
                let alpha = (top - offset) / height
                cell.alpha = alpha
             else 
                cell.alpha = 1
            
        
    


【讨论】:

以上是关于当collectionView滚动时,如何使iOS collectionView的特定单元格淡出?的主要内容,如果未能解决你的问题,请参考以下文章

如何在ios objective-c 应用程序中使屏幕的全部内容滚动

CollectionViewCell 顶部的按钮使 collectionView 停止滚动

当scrollview快速滚动时如何滚动collectionView?

ios collectionview 怎么使cell滚动到指定的位置

当用户点击 InputAccessoryView 时如何滚动到 CollectionView 的底部

使用一个collectionview IOS滚动多个UICollectionView