经常需要用到横向滑动的控件,控件里面也有一个个Cell,就像横向的UITableView。发现网上也有很多横向UITableView的方案,甚至通过旋转UITableView来实现横向滑动。
我通常是通过UIScrollView来实现横向滑动,但是一旦加载内容过多就会出现卡顿。因此我通过添加界面缓存来避免卡顿的问题。还通过模仿UITableView的dataSource来实现面向协议编程。
实现步骤
自定义控件HMHorizontalScrollView。首先通过包含UIScrollView实现横向滑动。通过添加HMHorizontalScrollCell实现内容填充。设立一个Cell的缓存数组,显示复用缓存数组中的Cell。
缓存数组的内容需要根据滑动的位置进行位置更新。缓存的显示会根据ScrollView的滑动进行改变,保证在滑动到下一个Cell之前,已经有Cell在那个位置等待显示了。
最后实现协议编程,通过配置DataSource来传递界面显示配置,通过协议的返回值设置显示内容。通过Delegate首先点击事件。
缓存原理
定义一个数组,将显示界面放入数组中。每次滑动的时候重新计算缓存界面的显示位置,改变未显示出来的界面的位置。
假如一个屏幕可以显示3个Cell的话,可能最多会显示4个Cell,这个时候就需要在前后多加一个Cell,用来预加载界面,这样在快速滑动的时候就会更顺畅。通过不断更新这些显示的Cell的位置和信息,就可以完成缓存机制。
当向左滑时,当4号Cell滑出屏幕时,就把5号Cell移到第一个的位置。
当向右滑时,当1号Cell滑出屏幕时,就把0号Cell移到最后一个的位置。
当滑到最左边或最右边时,就保持数据原始的顺序,不再做改变。
extension HMHorizontalScrollView: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// 第一次次进来会可能向下滑动64
scrollView.contentOffset.y = 0
if numberOfCacheView < maxNumberOfCacheView {
return
}
if scrollView.contentOffset.x < 0 {
// 滑到第一个
resetItemViewOfStart()
} else if scrollView.contentOffset.x + scrollView.w > scrollView.contentSize.width {
// 滑到最后一个
resetItemViewOfEnd()
} else if cells[1].frame.origin.x + cells[0].w + separatorWidth < scrollView.contentOffset.x {
// 左滑
resetItemViewOfLeftPan()
// 更新最后一个显示
let lastCellIndex = Int((cells.last?.frame.origin.x ?? 0) / (cellSize.width + separatorWidth))
if lastCellIndex > 0 && lastCellIndex < numberOfCells {
_ = dataSource?.horizontalScrollView(in: self, cellAt: lastCellIndex)
}
} else if cells[numberOfCacheView - 2].frame.origin.x > scrollView.contentOffset.x + scrollView.frame.width {
// 右滑
resetItemViewOfRightPan()
// 更新第一个显示
let firstCellIndex = Int((cells.first?.frame.origin.x ?? 0) / (cellSize.width + separatorWidth))
if firstCellIndex > 0 && firstCellIndex < numberOfCells {
_ = dataSource?.horizontalScrollView(in: self, cellAt: firstCellIndex)
}
}
}
// 滑到第一个
func resetItemViewOfStart() {
hm_for(cells) { cell, index in
cell.x = separatorWidth + (cellSize.width + separatorWidth) * CGFloat(index)
if let index = indexOf(x: cell.x) {
_ = dataSource?.horizontalScrollView(in: self, cellAt: index)
}
}
}
// 滑到最后一个
func resetItemViewOfEnd() {
hm_for(cells) { cell, index in
cell.x = scrollView.contentSize.width - (cellSize.width + separatorWidth) * CGFloat(numberOfCacheView - index)
if let index = indexOf(x: cell.x) {
_ = dataSource?.horizontalScrollView(in: self, cellAt: index)
}
}
}
// 左滑
func resetItemViewOfLeftPan() {
let temp = cells.first!
temp.x = cells.last!.x + (cellSize.width + separatorWidth)
hm_for(cells) { (cell, index) in
if index < cells.count - 1 {
cells[index] = cells[index + 1]
} else {
cells[index] = temp
}
}
}
// 右滑
func resetItemViewOfRightPan() {
let temp = cells.last!
temp.x = cells.first!.x - (cellSize.width + separatorWidth)
hm_for(cells) { (cell, index) in
if index < cells.count - 1 {
cells[cells.count - index - 1] = cells[cells.count - index - 2]
} else {
cells[cells.count - index - 1] = temp
}
}
}
func indexOf(x: CGFloat) -> Index? {
if cellSize.width + separatorWidth != 0 {
return Int(x / (cellSize.width + separatorWidth))
}
return nil
}
}
面向协议编程
定义一个HMHorizontalScrollViewDataSource协议,这个协议主要是为了获取数据源信息。设置一个dataSource的委托,通过委托在实现界面配置数据。当需要某些数据的时候,dataSource实现协议获取返回值,返回值就是需要的数据。
-
协议
protocol HMHorizontalScrollViewDataSource: NSObjectProtocol { // cell数量 func numberOfCells(in horizontalScrollView: HMHorizontalScrollView) -> Int // 自身的宽度 func viewWidth(in horizontalScrollView: HMHorizontalScrollView) -> CGFloat // cell的宽度 func horizontalScrollView(_ horizontalScrollView: HMHorizontalScrollView, cellSizeAt index: Index) -> CGSize // 返回cell func horizontalScrollView(in horizontalScrollView: HMHorizontalScrollView, cellAt index: Index) -> HMHorizontalScrollCell }
-
协议使用
// cell的数量 fileprivate var numberOfCells: Int { return dataSource?.numberOfCells(in: self) ?? 0 } // cell尺寸 fileprivate var cellSize: CGSize { return dataSource?.horizontalScrollView(self, cellSizeAt: 0) ?? CGSize() } // 界面宽度 fileprivate var viewWidth: CGFloat { return dataSource?.viewWidth(in: self) ?? 0 }
坑
当显示界面的Cell很少时,就不需要缓存了,需要特殊处理。
当ScrollView滑动到边缘时,也需要重新计算Cell排布,不然隐藏的缓存Cell就会显示出来。
需要考虑边缘条件。
通过dataSource配置HMHorizontialView的数据源,HMHorizontialView内部通过复用显示Cell,可以在一次加载多个Cell而不卡顿。
UIScollView可以实现横向滑动的分页显示,但是一次加载过多的内容会出现卡顿,通过复用显示的界面来避免卡顿。并通过仿照UITableView的
dataSource实现面向协议的编程。
最后有木有人推荐个工作机会!
Demo地址:
https://github.com/hm306599934/HMHorizontalScrollView/tree/master