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

ali*_*ego 5 transparency fade collectionview ios swift

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

agi*_*007 6

你可以对集合视图做很多有趣的事情。我喜欢继承 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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

let layout = FadingLayout(scrollDirection: .vertical)
collectionView.delegate = self
collectionView.dataSource = self
self.collectionView.setCollectionViewLayout(layout, animated: false)
Run Code Online (Sandbox Code Playgroud)

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


Luk*_*kas 5

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

\n\n
\n

shouldInvalidateLayout(forBoundsChange newBounds: CGRect)

\n
\n\n

然后在layoutAttributesForElements(in rect: CGRect)委托调用中,获取超类计算出的属性,并根据项目在可见边界中的偏移量修改alpha值,就是这样。\n区分左侧/右侧项目可以在控制器中使用您拥有的任何逻辑进行处理,并与布局类通信,以避免在左侧项目上应用此效果。(我使用 \xc2\xb4CustomLayoutDelegate\xc2\xb4 来实现在控制器中实现的功能,该控制器简单地将奇数 indexPath.row 的项目标识为左侧单元格)

\n\n

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

\n\n
import UIKit\n\nclass ViewController: UIViewController {\n\n    /// Custom flow layout\n    lazy var layout: CustomFlowLayout = {\n        let l: CustomFlowLayout = CustomFlowLayout()\n        l.itemSize = CGSize(width: self.view.bounds.width / 1.5, height: 100)\n        l.delegate = self\n\n        return l\n    }()\n\n    /// The collectionView if you\'re not using UICollectionViewController\n    lazy var collectionView: UICollectionView = {\n        let cv: UICollectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: self.layout)\n        cv.backgroundColor = UIColor.lightGray\n\n        cv.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "Cell")\n        cv.dataSource = self\n\n        return cv\n    }()\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n\n        view.addSubview(collectionView)\n    }\n\n}\n\nextension ViewController: UICollectionViewDataSource, CustomLayoutDelegate {\n\n    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {\n        return 30\n    }\n\n    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {\n        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)\n        cell.backgroundColor = UIColor.black\n\n        return cell\n    }\n\n    // MARK: CustomLayoutDelegate\n\n    func cellSide(forIndexPath indexPath: IndexPath) -> CellSide {\n\n        // TODO: Your implementation to distinguish left/right indexPath\n\n        // Even rows are .right and Odds .left\n        if indexPath.row % 2 == 0 {\n            return .right\n        } else {\n            return .left\n        }\n    }\n}\n\npublic enum CellSide {\n    case right\n    case left\n}\n\nprotocol CustomLayoutDelegate: class {\n\n    func cellSide(forIndexPath indexPath: IndexPath) -> CellSide\n}\n\nclass CustomFlowLayout: UICollectionViewFlowLayout {\n\n    /// Delegates distinguishing between left and right items\n    weak var delegate: CustomLayoutDelegate!\n\n    /// Maximum alpha value\n    let kMaxAlpha: CGFloat = 1\n\n    /// Minimum alpha value. The alpha value you want the first visible item to have\n    let kMinAlpha: CGFloat = 0.3\n\n    override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {\n        guard let cv = collectionView, let rectAtts = super.layoutAttributesForElements(in: rect) else { return nil }\n\n        for atts in rectAtts {\n\n            // Skip left sides\n            if delegate.cellSide(forIndexPath: atts.indexPath) == .left {\n                continue\n            }\n\n            // Offset Y on visible bounds. you can use\n            //      \xc2\xb4cv.bounds.height - (atts.frame.origin.y - cv.contentOffset.y)\xc2\xb4\n            // To reverse the effect\n            let offset_y = (atts.frame.origin.y - cv.contentOffset.y)\n\n            let alpha = offset_y * kMaxAlpha / cv.bounds.height\n\n            atts.alpha = alpha + kMinAlpha\n        }\n\n        return rectAtts\n    }\n\n    // Invalidate layout when scroll happens. Otherwise atts won\'t be recalculated\n    override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {\n        return true\n    }\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n


Tim*_*Tim 1

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

\n\n

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

\n\n

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

\n\n
class FadingCollectionViewController: UICollectionViewController {\n\n    override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {\n        return 500\n    }\n\n    override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {\n        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)\n        return cell\n    }\n\n    override func scrollViewDidScroll(_ scrollView: UIScrollView) {\n        guard let collectionView = collectionView else {\n            return\n        }\n\n        let offset = collectionView.contentOffset.y\n        let height = collectionView.frame.size.height\n        let width = collectionView.frame.size.width\n        for cell in collectionView.visibleCells {\n            let left = cell.frame.origin.x\n            if left >= width / 2 {\n                let top = cell.frame.origin.y\n                let alpha = (top - offset) / height\n                cell.alpha = alpha\n            } else {\n                cell.alpha = 1\n            }\n        }\n    }\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n