經常需要用到橫向滑動的控件薄霜,控件里面也有一個個Cell茎芭,就像橫向的UITableView牧牢。發(fā)現網上也有很多橫向UITableView的方案看锉,甚至通過旋轉UITableView來實現橫向滑動。
我通常是通過UIScrollView來實現橫向滑動塔鳍,但是一旦加載內容過多就會出現卡頓伯铣。因此我通過添加界面緩存來避免卡頓的問題。還通過模仿UITableView的dataSource來實現面向協(xié)議編程轮纫。
實現步驟
自定義控件HMHorizontalScrollView腔寡。首先通過包含UIScrollView實現橫向滑動。通過添加HMHorizontalScrollCell實現內容填充掌唾。設立一個Cell的緩存數組放前,顯示復用緩存數組中的Cell。
緩存數組的內容需要根據滑動的位置進行位置更新糯彬。緩存的顯示會根據ScrollView的滑動進行改變凭语,保證在滑動到下一個Cell之前,已經有Cell在那個位置等待顯示了撩扒。
最后實現協(xié)議編程似扔,通過配置DataSource來傳遞界面顯示配置,通過協(xié)議的返回值設置顯示內容。通過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
}
}
面向協(xié)議編程
定義一個HMHorizontalScrollViewDataSource協(xié)議获茬,這個協(xié)議主要是為了獲取數據源信息。設置一個dataSource的委托倔既,通過委托在實現界面配置數據恕曲。當需要某些數據的時候,dataSource實現協(xié)議獲取返回值渤涌,返回值就是需要的數據佩谣。
-
協(xié)議
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 }
-
協(xié)議使用
// 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實現面向協(xié)議的編程。
最后有木有人推薦個工作機會归形!
Demo地址:
https://github.com/hm306599934/HMHorizontalScrollView/tree/master