当前位置:网站首页>Create cool collectionviewcell conversion animation

Create cool collectionviewcell conversion animation

2022-06-11 05:26:00 the Summer Palace

newly build iOS App project , open Main.storyboad, Drag into one CollectionView, Create layout constraints for it as follows :

by CollectionView Create a IBOutlet Connect :

@IBOutlet weak var collectionView: UICollectionView!

newly build swift file , Act as our model , This is what we're going to render in cell The data on the :

public struct SalonEntity {
    
    
    // MARK: - Variables
    
    /// Name
    public internal(set) var name: String?
    /// Address
    public internal(set) var address: String?

    // MARK: - Init
    
    /// Convenience init
    public init(name: String, address: String) {
    
        self.name = name
        self.address = address
    }
}

newly build UICollectionViewCell Subclass SalonSelectorCollectionViewCell. open SalonSelectorCollectionViewCell.xib, Create as follows UI :

SalonSelectorCollectionViewCell It is still very simple :

class SalonSelectorCollectionViewCell: UICollectionViewCell {
    
    @IBOutlet weak var containerView: UIView!
    @IBOutlet weak var salonNameLabel: UILabel!
    @IBOutlet weak var salonAddressLabel: UILabel!
    @IBOutlet weak var separatorLine: UIView!

    func configure(with salon: SalonEntity) {
    
        salonNameLabel.text = salon.name
        salonAddressLabel.text = salon.address
    }

    override func prepareForReuse() {
    
        super.prepareForReuse()
        salonNameLabel.text = nil
        salonAddressLabel.text = nil
    }
}
extension UICollectionViewCell {
    
    class var reuseIdentifier: String {
     return NSStringFromClass(self).components(separatedBy: ".").last! }
}

open ViewController.swift, stay viewDidLoad in :

        collectionView.register(UINib(nibName: "SalonSelectorCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: SalonSelectorCollectionViewCell.reuseIdentifier)
        collectionView.dataSource = self
				collectionView.delegate = self

Then implement UICollectionViewDataSource:

extension ViewController: UICollectionViewDataSource {
    
    public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    
        salons.count
    }

    public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    
        guard let selectorCell = collectionView.dequeueReusableCell(withReuseIdentifier: SalonSelectorCollectionViewCell.reuseIdentifier, for: indexPath) as? SalonSelectorCollectionViewCell else {
     return UICollectionViewCell() }
        let salon = salons[indexPath.item]
        selectorCell.configure(with: salon)
        return selectorCell
    }
}
extension ViewController: UICollectionViewDelegate {
    
    public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    
    }
    func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
    
    }
}

function App,collect view It is shown in 5 individual cell:

Next , We're going to use UICollectionViewDelegate Agreement let collection view Display a little different style when it is selected :

    public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    
        collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
        guard let cell = collectionView.cellForItem(at: indexPath) as? SalonSelectorCollectionViewCell else {
     return }
        cell.containerView.backgroundColor = .lightGray
    }
    func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) {
    
        guard let cell = collectionView.cellForItem(at: indexPath) as? SalonSelectorCollectionViewCell else {
     return }
        cell.containerView.backgroundColor = .white
    }

In this way, one of the cell when ,cell The background color turns grey . But this is obviously not cool enough . We need to add a little animation to it .

First , We are SalonSelectorCollectionViewCell Add a state :

    enum State {
    
        case collapsed
        case expanded
				var backgroundColor: UIColor {
    
            switch self {
    
            case .collapsed:
                return UIColor.lightGray
            case .expanded:
                return .white
            }
        }
    }

State There are two states :collapsed and expanded, The difference between the two is backgroundColor - collapse This value is gray in the state , and expanded It is white in the state , It is similar to what we have just done , When cell When selected, it is a color , The reverse selection is another color .

Except for the background color, of course , We also need to let cell Do something in two different states UI Change on , For example expanded Let... In a state cell Get a little bigger . This requires us to create some for some layout constraints IBOutlet:

    @IBOutlet weak var interLabelsPaddingConstraint: NSLayoutConstraint!   //  Two  label  Between the  padding
    @IBOutlet weak var separatorLineWidthConstraint: NSLayoutConstraint!   //  The width of the middle thin line 
    @IBOutlet weak var separatorLineHeightConstraint: NSLayoutConstraint!  //  The height of the middle thin line 
    @IBOutlet weak var containerViewHeightConstraint: NSLayoutConstraint!  //  Whole  cell  The height of 
    @IBOutlet weak var containerViewWidthConstraint: NSLayoutConstraint!   //  Whole  cell  The width of 
    @IBOutlet weak var salonNameLeadingConstraint: NSLayoutConstraint!     //  Salon name ( above  label) Of  leading
    @IBOutlet weak var salonAddressLeadingConstraint: NSLayoutConstraint!  //  Salon address ( Below  label) Of  leading 

At the same time enm State The definition of , Specified in different states ( collapase State and expanded state ) Under the corresponding constraint constant value , In general, except for the different background colors , Will make cell stay expanded It is slightly larger in the state , meanwhile collapsed In this state, the split line in the middle is invisible :

enum State {
    
  			...
        var interLabelPadding: CGFloat {
    
            switch self {
    
            case .collapsed:
                return 6
            case .expanded:
                return 56
            }
        }

        var separatorWidth: CGFloat {
    
            switch self {
    
            case .collapsed:
                return 0
            case .expanded:
                return 240
            }
        }

        var separatorHeight: CGFloat {
    
            switch self {
    
            case .collapsed:
                return 0
            case .expanded:
                return 2
            }
        }

        var salonNameLeadingConstant: CGFloat {
    
            switch self {
    
            case .collapsed:
                return 20
            case .expanded:
                return 40
            }
        }

        var salonAddressLeadingConstant: CGFloat {
    
            switch self {
    
            case .collapsed:
                return 60
            case .expanded:
                return 80
            }
        }

        var containerWidth: CGFloat {
    
            switch self {
    
            case .collapsed:
                return 250
            case .expanded:
                return 320
            }
        }

        var containerHeight: CGFloat {
    
            switch self {
    
            case .collapsed:
                return 150
            case .expanded:
                return 200
            }
        }
}

And then to SalonSelecotrCollectionViewCell Add a property :

    var state: State = .collapsed {
    
        didSet {
    
            guard oldValue != state else {
     return }
            updateViewConstraints()
        }
    }

And then in updateViewConstraints In the method , Modify constraint constants according to different states :

    private func updateViewConstraints() {
    
        containerView.backgroundColor = state.backgroundColor
        containerViewWidthConstraint.constant = state.containerWidth
        containerViewHeightConstraint.constant = state.containerHeight
        salonNameLeadingConstraint.constant = state.salonNameLeadingConstant
        salonAddressLeadingConstraint.constant = state.salonAddressLeadingConstant
        interLabelsPaddingConstraint.constant = state.interLabelPadding
        separatorLineWidthConstraint.constant = state.separatorWidth
        separatorLineHeightConstraint.constant = state.separatorHeight
        layoutIfNeeded()
    }

Of course , By default cell yes collapsed state ( Reverse election ):

    override func prepareForReuse() {
    
        ...
        state = .collapsed
    }

go back to view controller modify didSelectItemAt Method :

    public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    
        collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
        guard let cell = collectionView.cellForItem(at: indexPath) as? SalonSelectorCollectionViewCell else {
     return }
        cell.containerView.backgroundColor = .lightGray
        UIView.animate(withDuration: 0.3) {
    
            cell.state = .expanded
        }
    }

actually ,didDeselectItemAt Methods are unnecessary , We can delete it .

function App, Now we choose cell when ,cell The background color changes from light grey to white , meanwhile cell Zoom in :

Usually select one cell You need to click on it , But we often have some app see , occasionally cell You don't need to click , Just scroll it to the center of the view and it will be automatically selected , How does this work ?

This actually takes advantage of UIScrollView The relevant agent of the company and not UICollectionView. go back to ViewController.swift, Implement the following method :

    func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    
        var offsetAdjustment = CGFloat.greatestFiniteMagnitude
        let horizontalCenter = targetContentOffset.pointee.x + collectionView.bounds.width / 2

        let targetRect = CGRect(origin: targetContentOffset.pointee, size: collectionView.bounds.size)
        guard let layoutAttributes = collectionView.collectionViewLayout.layoutAttributesForElements(in: targetRect) else {
     return }
        for layoutAttribute in layoutAttributes {
    
            let itemHorizontalCenter = layoutAttribute.center.x
            if abs(itemHorizontalCenter - horizontalCenter) < abs(offsetAdjustment) {
    
                offsetAdjustment = itemHorizontalCenter - horizontalCenter
            }
        }
        targetContentOffset.pointee.x += offsetAdjustment
    }

such , Rolling scroll view when , When you release your finger , This method automatically returns scroll view The scrolling position is adjusted to cell Center alignment , Of course , Premise is contentView There is enough space ( Exceptions : first cell And the last cell). You can run App Look at the effect .

Then define a new enumeration , Used to record ScrollView The scrolling state of :

enum SelectionCollectionViewScrollingState {
    
    case idle
    case scrolling(animateSelectionFrame: Bool)
}

idle Express scroll view Scrolling has stopped ,scrolling It means you are still scrolling . stay ViewController Define a SelectorCollectionViewScrollingState attribute :

    private var scrollingState: SelectionCollectionViewScrollingState = .idle {
    
        didSet {
    
            if scrollingState != oldValue {
    
                updateSelection()
            }
        }
    }

Here to SelectionCollectionViewScrollingState the != Compare , Need make SelectionCollectionViewScrollingState Realization Equatable agreement :

extension SelectionCollectionViewScrollingState: Equatable {
    
    public static func ==(lhs: SelectionCollectionViewScrollingState, rhs: SelectionCollectionViewScrollingState) -> Bool {
    
        switch (lhs, rhs) {
    
        case (.idle, .idle):
            return true
        case (.scrolling(_), .scrolling(_)):
            return true
        default:
            return false
        }
    }
}

When scrollingState When there is a change , call updateSelection To modify cell The state of :

    	func updateSelection() {
    
func updateSelection() {
    
        UIView.animate(withDuration: 0.15) {
     () -> Void in
            guard let indexPath = self.getSelectedIndexPath(),
                  let cell = self.collectionView.cellForItem(at: indexPath) as? SalonSelectorCollectionViewCell else {
    
                      return
                  }
                switch self.scrollingState {
    
                case .idle:
                    cell.state = .expanded
                case .scrolling(_):
                    cell.state = .collapsed
                }
        }
    }
   		}
 		func getSelectedIndexPath() -> IndexPath? {
    
        let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size)
        let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
        if let visibleIndexPath = collectionView.indexPathForItem(at: visiblePoint) {
    
            return visibleIndexPath
        }
        return nil
    }

getSelectedIndex() First of all get collection view Of the current viewable area frame, And then get its center point , call collectionView.indexPathForItem() Method and pass in this central point , You can know the cell Of indexPath.

Then implement scrollView Two methods of agency :

    public func scrollViewDidScroll(_ scrollView: UIScrollView) {
    
        scrollingState = .scrolling(animateSelectionFrame: true)
    }

    public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    
        scrollingState = .idle
    }

such , When you scroll collection view when , Scroll to the center of the screen cell Will automatically select and render expanded state :

video link: https://gitee.com/kmyhy/CollectionViewCoolAnimation/raw/master/5.mov

If the video doesn't play , Download here :https://gitee.com/kmyhy/CollectionViewCoolAnimation/raw/master/5.mov

It's not cool enough , We are going to select the cell Add another type of viewfinder outside :

First , stay Main.storyboard, Drag into one view, And create a IBOutlet:

    @IBOutlet weak var selectionFrameView: UIView!

stay selectionFrameView Put two on top image view, Add corresponding constraints , Wide and high 340*220 And let it and collection view Center alignment , Like this :

Note that the picture in the lower left corner can be rotated 180 degree :layer.transform.rotation.z = 3.14

Similar to State Enumerate what has been done , We will SelectionCollectionViewScrollingState The two states of are bound to the other two properties :

enum SelectionCollectionViewScrollingState {
    
    ...
    var alpha: CGFloat {
    
        switch self {
    
        case .idle:
            return 1
        case .scrolling(let animateSelectionFrame):
            return animateSelectionFrame ? 0 : 1
        }
    }

    var transform: CGAffineTransform {
    
        switch self {
    
        case .idle:
            return .identity
        case .scrolling(let animateSelectionFrame):
            return animateSelectionFrame ? CGAffineTransform(scaleX: 1.5, y: 1.5) : CGAffineTransform(scaleX: 1.15, y: 1.15)
        }
    }
}

When idle In the state of ,selectionFrameView Of alpha Will be set to 1, Switch to .scrolling Post state ,alpha according to animatedSelectionFrame And decide , by true when = 0, by false when = 1, meanwhile transform Will also make corresponding changes . such , Just switch idle/scrolling state , Can change “ finder frame ” Show / Hidden state and frame size .

Whenever... Is selected cell Will be called updateSelection Method , We just need to updateSelection Method to add this 2 sentence :

 UIView.animate(withDuration: 0.15) {
     () -> Void in
 		self.selectionFrameView.transform = self.scrollingState.transform
 		self.selectionFrameView.alpha = self.scrollingState.alpha
 		...
 }

You can make the viewfinder display automatically , And execute a slightly enlarged animation .

And then in UICollectionViewDelegate Agreed didSelectItem In the method , increase

scrollingState = .scrolling(animateSelectionFrame: false)

In this way, when the user selects a by clicking rather than dragging cell when ,“ Viewfinder animation ” Still play .

Then, when we load view 1, the first one is selected by default cell. stay viewDidLoad() in :

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
     [weak self] in
 	self?.updateSelection()
}

because collection view stay viewDidLoad It is very likely that there is no rendering , here collection view There may be no time to instantiate any cell , Lead to update cell Status failed , So we delay 0.5 Seconds before calling updateSelection Method , To solve this problem . This is an imperfect solution .

video link: 7.mov

If the video doesn't play , Please download here :https://gitee.com/kmyhy/CollectionViewCoolAnimation/raw/master/7.mov

You can find , As mentioned earlier , first cell And the last cell Not scrolling to the center of the screen . This can be done by making ViewController Realization UICollectionViewDelegateFlowLayout Agreement to solve :


extension ViewController: UICollectionViewDelegateFlowLayout {
    
    public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
    
        let padding = (collectionView.bounds.width - SalonSelectorCollectionViewCell.State.expanded.containerWidth) / 2
        return UIEdgeInsets(top: 10, left: padding, bottom: 10, right: padding)
    }
}

Through adjustment cell About padding , Give Way cell Auto Center . The end result is as follows :

video link: 8.mov

If the video doesn't play , Please download here :https://gitee.com/kmyhy/CollectionViewCoolAnimation/raw/master/8.mov

原网站

版权声明
本文为[the Summer Palace]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/162/202206110522399940.html