居中放大 參考:https://blog.csdn.net/u013282507/article/details/54136812
自白
以前弄過無限循環(huán)滾動(dòng)的捆姜,好長(zhǎng)時(shí)間沒弄了性雄,想從網(wǎng)上直接找一個(gè)來用, 找到的確實(shí)把數(shù)據(jù)條目重復(fù)發(fā)大做到的懂衩,cell數(shù)據(jù)多的時(shí)候會(huì)在內(nèi)存中占著一大塊數(shù)據(jù)华坦,而且我是在首頁用愿吹,內(nèi)存根本釋放不掉,感覺不好惜姐。 還是花時(shí)間實(shí)現(xiàn)了自己以前的做法犁跪。(一直沒管理自己的代碼庫, 管理自己的代碼庫是多么重要)
無限滾動(dòng)原理
假設(shè)有 a歹袁、b坷衍、c 三條數(shù)據(jù),在collectionview中的數(shù)據(jù)是 c条舔、a枫耳、b、c孟抗、a 共5條數(shù)據(jù)迁杨,當(dāng)向左滾動(dòng),滾動(dòng)到第4條數(shù)據(jù)c凄硼,滾動(dòng)結(jié)束后铅协,無動(dòng)畫滾動(dòng)到第1條數(shù)據(jù)c,就可以繼續(xù)向左滾動(dòng)了摊沉;當(dāng)向右滾動(dòng)狐史,滾動(dòng)到第2條數(shù)據(jù)a,滾動(dòng)結(jié)束后说墨,無動(dòng)畫滾動(dòng)到第5條數(shù)據(jù)a骏全,就可以繼續(xù)向右滾動(dòng)了
本來代碼也是基于前輩的這個(gè)XLCardSwitch改的, 修改后的代碼貼出來,以后用??
懶到一定程度了
import UIKit
//滾動(dòng)切換回調(diào)方法
typealias XLCardScollIndexChangeBlock = (Int) -> Void
//布局類
class XLCardSwitchFlowLayout: UICollectionViewFlowLayout {
//卡片和父視圖寬度比例
let cardWidthScale: CGFloat = 1.0 // 0.7
//卡片和父視圖高度比例
let cardHeightScale: CGFloat = 1.0 // 0.8
//滾動(dòng)到中間的調(diào)方法
var indexChangeBlock: XLCardScollIndexChangeBlock?
override func prepare() {
self.scrollDirection = UICollectionView.ScrollDirection.horizontal
self.sectionInset = UIEdgeInsets(top: self.insetY(), left: self.insetX(), bottom: self.insetY(), right: self.insetX())
self.itemSize = CGSize(width: self.itemWidth(), height: self.itemHeight())
self.minimumLineSpacing = 5
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
//獲取cell的布局
let originalAttributesArr = super.layoutAttributesForElements(in: rect)
//復(fù)制布局,以下操作尼斧,在復(fù)制布局中處理
var attributesArr: Array<UICollectionViewLayoutAttributes> = Array.init()
for attr: UICollectionViewLayoutAttributes in originalAttributesArr! {
attributesArr.append(attr.copy() as! UICollectionViewLayoutAttributes)
}
//屏幕中線
let centerX: CGFloat = (self.collectionView?.contentOffset.x)! + (self.collectionView?.bounds.size.width)!/2.0
//最大移動(dòng)距離姜贡,計(jì)算范圍是移動(dòng)出屏幕前的距離
let maxApart: CGFloat = ((self.collectionView?.bounds.size.width)! + self.itemWidth())/2.0
//刷新cell縮放 現(xiàn)在不縮放了 只判斷是否滾動(dòng)到中間
for attributes: UICollectionViewLayoutAttributes in attributesArr {
//獲取cell中心和屏幕中心的距離
let apart: CGFloat = abs(attributes.center.x - centerX)
// 放到的注釋了, 不用了棺棵, 只用居中的效果
// //移動(dòng)進(jìn)度 -1~0~1
// let progress: CGFloat = apart/maxApart
// //在屏幕外的cell不處理
// if (abs(progress) > 1) {continue}
// //根據(jù)余弦函數(shù)鲁豪,弧度在 -π/4 到 π/4,即 scale在 √2/2~1~√2/2 間變化
// let scale: CGFloat = abs(cos(progress * CGFloat(Double.pi/4)))
// //縮放大小
// attributes.transform = CGAffineTransform.init(scaleX: scale, y: scale)
//更新中間位
if (apart <= self.itemWidth()/2.0) {
self.indexChangeBlock?(attributes.indexPath.row)
}
}
return attributesArr
}
//MARK -
//MARK 配置方法
//卡片寬度
func itemWidth() -> CGFloat {
// return (self.collectionView?.bounds.size.width)! * cardWidthScale
return (self.collectionView?.bounds.size.width)! - 50
}
//卡片高度
func itemHeight() -> CGFloat {
return (self.collectionView?.bounds.size.height)! * cardHeightScale
}
//設(shè)置左右縮進(jìn)
func insetX() -> CGFloat {
let insetX: CGFloat = ((self.collectionView?.bounds.size.width)! - self.itemWidth())/2.0
return insetX
}
//上下縮進(jìn)
func insetY() -> CGFloat {
let insetY: CGFloat = ((self.collectionView?.bounds.size.height)! - self.itemHeight())/2.0
return insetY
}
//是否實(shí)時(shí)刷新布局
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
}
//代理
@objc protocol XLCardSwitchDelegate: NSObjectProtocol {
//滑動(dòng)切換到新的位置回調(diào)
@objc optional func cardSwitchDidScrollToIndex(index: Int) -> ()
//手動(dòng)點(diǎn)擊了
@objc optional func cardSwitchDidSelectedAtIndex(index: Int) -> ()
}
//數(shù)據(jù)源
@objc protocol XLCardSwitchDataSource: NSObjectProtocol {
//卡片的個(gè)數(shù)
func cardSwitchNumberOfCard() -> (Int)
//卡片cell
func cardSwitchCellForItemAtIndex(index: Int) -> (UICollectionViewCell)
}
//展示類
class XLCardSwitch: UIView ,UICollectionViewDelegate,UICollectionViewDataSource {
//公有屬性
weak var delegate: XLCardSwitchDelegate?
weak var dataSource: XLCardSwitchDataSource?
var selectedIndex: Int = 0
var pagingEnabled: Bool = false
//私有屬性
private var _dragStartX: CGFloat = 0
private var _dragEndX: CGFloat = 0
private var _dragAtIndex: Int = 0
private let flowlayout = XLCardSwitchFlowLayout()
private var isInfinite = false
private var isAutoScroll = false
private var scrollTimeInterval: TimeInterval = 3.0
private var timer: Timer?
private lazy var _collectionView: UICollectionView = {
let view = UICollectionView(frame: .zero, collectionViewLayout: flowlayout)
view.delegate = self
view.dataSource = self
view.backgroundColor = UIColor.clear
view.showsHorizontalScrollIndicator = false
return view
}()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
self.buildUI()
}
func buildUI() {
self.addSubview(_collectionView)
//添加回調(diào)方法
flowlayout.indexChangeBlock = { (index) -> () in
if self.selectedIndex != index {
self.selectedIndex = index
// 3 0 1 2 3 0
if self.isInfinite {
let num = self.dataSource?.cardSwitchNumberOfCard() ?? 0 // 4
if index == 0 {
self.delegateUpdateScrollIndex(index: num - 1)
}else if index == num + 1 {
self.delegateUpdateScrollIndex(index: 0)
} else {
self.delegateUpdateScrollIndex(index: index-1)
}
} else {
self.delegateUpdateScrollIndex(index: index)
}
// self.delegateUpdateScrollIndex(index: index)
}
}
}
func reloadData() {
_collectionView.reloadData()
if isInfinite {
self.autoScrollFixToPosition(index: 1)
}
if isAutoScroll {
removeTimer()
addTimer()
}
}
func setScrollViewTag(tag: Int) {
_collectionView.tag = tag
}
//MARK:自動(dòng)滾動(dòng)
func isAutoScroll(autoScroll: Bool) {
isAutoScroll = autoScroll
if isAutoScroll {
isInfinite(infinite: true)
}
}
func isInfinite(infinite: Bool) {
isInfinite = infinite
}
func scrollTimeIntrval(timeInterval: TimeInterval) {
scrollTimeInterval = timeInterval
}
func addTimer() {
let timer = Timer.scheduledTimer(timeInterval: scrollTimeInterval, target: self, selector: #selector(nextpage), userInfo: nil, repeats: true)
RunLoop.main.add(timer, forMode: .commonModes)
self.timer = timer
}
func removeTimer() {
self.timer?.invalidate()
self.timer = nil
}
@objc private func nextpage() {
switchToIndex(index: self.selectedIndex + 1)
}
//MARK:自動(dòng)布局
override func layoutSubviews() {
super.layoutSubviews()
_collectionView.frame = self.bounds
}
//MARK:-
//MARK:CollectionView方法
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if let num = self.dataSource?.cardSwitchNumberOfCard(), num > 0 {
if num == 1 {
isInfinite = false
isAutoScroll = false
return num
}
if isInfinite {
return num + 2 // 3 0 1 2 3 0 布局cell順序
} else {
return num
}
} else {
return 0
}
// return (self.dataSource?.cardSwitchNumberOfCard()) ?? 0
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if isInfinite {
let num = self.dataSource?.cardSwitchNumberOfCard() ?? 0
if indexPath.row == 0 {
return (self.dataSource?.cardSwitchCellForItemAtIndex(index: num - 1))!
}else if indexPath.row == num + 1 {
return (self.dataSource?.cardSwitchCellForItemAtIndex(index: 0))!
} else {
return (self.dataSource?.cardSwitchCellForItemAtIndex(index: indexPath.row - 1))!
}
} else {
return (self.dataSource?.cardSwitchCellForItemAtIndex(index: indexPath.row))!
}
// return (self.dataSource?.cardSwitchCellForItemAtIndex(index: indexPath.row))!
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
//執(zhí)行代理方法
selectedIndex = indexPath.row
self.scrollToCenterAnimated(animated: true)
if isInfinite {
let num = self.dataSource?.cardSwitchNumberOfCard() ?? 0
if indexPath.row == 0 {
self.delegateSelectedAtIndex(index: num-1)
}else if indexPath.row == num + 1 {
self.delegateSelectedAtIndex(index: 0)
} else {
self.delegateSelectedAtIndex(index: indexPath.row - 1)
}
} else {
self.delegateSelectedAtIndex(index: indexPath.row)
}
// self.delegateSelectedAtIndex(index: indexPath.row)
}
//MARK:-
//MARK:ScrollViewDelegate
@objc func fixCellToCenter() -> () {
if self.selectedIndex != _dragAtIndex {
self.scrollToCenterAnimated(animated: true)
return
}
//最小滾動(dòng)距離
let dragMiniDistance: CGFloat = self.bounds.size.width/20.0
if _dragStartX - _dragEndX >= dragMiniDistance {
self.selectedIndex -= 1//向右
}else if _dragEndX - _dragStartX >= dragMiniDistance {
self.selectedIndex += 1 //向左
}
let maxIndex: Int = (_collectionView.numberOfItems(inSection: 0)) - 1
self.selectedIndex = max(self.selectedIndex, 0)
self.selectedIndex = min(self.selectedIndex, maxIndex)
self.scrollToCenterAnimated(animated: true)
}
//滾動(dòng)到中間
func scrollToCenterAnimated(animated: Bool) -> () {
// _collectionView.scrollToItem(at: IndexPath.init(row:self.selectedIndex, section: 0), at: UICollectionView.ScrollPosition.centeredHorizontally, animated: true)
_collectionView.scrollToItem(at: IndexPath.init(row: self.selectedIndex, section: 0), at: UICollectionViewScrollPosition.centeredHorizontally, animated: animated)
}
//手指拖動(dòng)開始
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
if isAutoScroll {
removeTimer()
}
if (!self.pagingEnabled) { return }
_dragStartX = scrollView.contentOffset.x
_dragAtIndex = self.selectedIndex
}
//手指拖動(dòng)停止
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if isAutoScroll {
addTimer()
}
if (!self.pagingEnabled) { return }
_dragEndX = scrollView.contentOffset.x
//在主線程執(zhí)行居中方法
DispatchQueue.main.async {
self.fixCellToCenter()
}
}
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
// 3 0 1 2 3 0
if self.isInfinite {
let num = self.dataSource?.cardSwitchNumberOfCard() ?? 0 // 4
let index = self.selectedIndex
if index == 0 {
self.autoScrollFixToPosition(index: num)
}else if index == num + 1 {
self.autoScrollFixToPosition(index: 1)
} else {
}
}
}
//MARK:-
//MARK:執(zhí)行代理方法
//回調(diào)滾動(dòng)方法
func delegateUpdateScrollIndex(index: Int) -> () {
guard let delegate = self.delegate else { return }
if (delegate.responds(to: #selector(delegate.cardSwitchDidScrollToIndex(index:)))) {
delegate.cardSwitchDidScrollToIndex?(index: index)
}
}
//回調(diào)點(diǎn)擊方法
func delegateSelectedAtIndex(index: Int) -> () {
guard let delegate = self.delegate else { return }
if (delegate.responds(to: #selector(delegate.cardSwitchDidSelectedAtIndex(index:)))) {
delegate.cardSwitchDidSelectedAtIndex?(index: index)
}
}
//MARK:-
//MARK:切換位置方法
func switchToIndex(index: Int) -> () {
DispatchQueue.main.async {
self.selectedIndex = index
self.scrollToCenterAnimated(animated: true)
}
}
func autoScrollFixToPosition(index: Int) -> () {
// DispatchQueue.main.asyncAfter(deadline: .now()+1) {
// self.selectedIndex = index
// self.scrollToCenterAnimated(animated: false)
// }
DispatchQueue.main.async {
self.selectedIndex = index
self.scrollToCenterAnimated(animated: false)
}
}
//向前切換
func switchPrevious() -> () {
guard let index = currentIndex() else { return }
var targetIndex = index - 1
if !isInfinite {
targetIndex = max(0, targetIndex)
}
self.switchToIndex(index: targetIndex)
}
//向后切換
func switchNext() -> () {
guard let index = currentIndex() else { return }
var targetIndex = index + 1
if !isInfinite {
let maxIndex = (self.dataSource?.cardSwitchNumberOfCard())! - 1
targetIndex = min(maxIndex, targetIndex)
}
// var maxIndex = (self.dataSource?.cardSwitchNumberOfCard())! - 1
// targetIndex = min(maxIndex, targetIndex)
self.switchToIndex(index: targetIndex)
}
func currentIndex() -> Int? {
let x = _collectionView.contentOffset.x + _collectionView.bounds.width/2
let index = _collectionView.indexPathForItem(at: CGPoint(x: x, y: _collectionView.bounds.height/2))?.item
if isInfinite {
let num = self.dataSource?.cardSwitchNumberOfCard() ?? 0
if index == 0 {
return num - 1
}else if index == num + 1 {
return 0
} else {
return ((index ?? 1) - 1)
}
} else {
return index
}
}
//MARK:-
//MARK:數(shù)據(jù)源相關(guān)方法
open func register(cellClass: AnyClass?, forCellWithReuseIdentifier identifier: String) {
_collectionView.register(cellClass, forCellWithReuseIdentifier: identifier)
}
open func register(nib: UINib?, forCellWithReuseIdentifier identifier: String) {
_collectionView.register(nib, forCellWithReuseIdentifier: identifier)
}
open func dequeueReusableCell(withReuseIdentifier identifier: String, for index: Int) -> UICollectionViewCell {
return _collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: IndexPath(row: index, section: 0))
}
deinit {
if self.timer != nil {
self.timer?.invalidate()
self.timer = nil
}
}
}
使用
和人家的使用一樣潘悼,只是多了是否無限滾動(dòng),是否自動(dòng)滾動(dòng)爬橡,滾動(dòng)時(shí)間的方法
//滾動(dòng)卡片
lazy var cardSwitch: XLCardSwitch = {
let temp = XLCardSwitch.init()
temp.frame = CGRect(x: 0, y: 55, width: ScreenWidth, height: 139)
temp.pagingEnabled = true
temp.dataSource = self
temp.delegate = self
temp.setScrollViewTag(tag: 9090) // 這個(gè)是避免父視圖滾動(dòng)沖突的??,和本章知識(shí)無關(guān)
temp.isAutoScroll(autoScroll: true)
//注冊(cè)cell
temp.register(nib: UINib(nibName: "ItemCell", bundle: nil), forCellWithReuseIdentifier: "ItemCell")
return temp
}()