引言:今天這篇文章屬于分析型,我將一步步帶你走進(jìn)我的思路,跟我一起頭腦風(fēng)暴,歡迎加入。
內(nèi)容介紹:支付寶首頁(yè)結(jié)構(gòu)看起來(lái)很簡(jiǎn)單,無(wú)非就是TableView+Header
;但是當(dāng)我們仔細(xì)分析的時(shí)候,發(fā)現(xiàn)有一樣?xùn)|西用原生的TableView
是無(wú)法實(shí)現(xiàn)。那就是TableView
左側(cè)的閱讀進(jìn)度條。接下來(lái)我就為大家分析一下(我的做法只是推斷,或許并非原生的做法,如果內(nèi)部人員看到歡迎指正)
首先,我們先來(lái)看圖,分析一下。我們分析的重點(diǎn)就兩個(gè)地方,在圖中已經(jīng)標(biāo)出來(lái)了,位置1
和位置2
位置1:當(dāng)下拉支付寶首頁(yè)時(shí),頭部下拉刷新控件在位置1
處出現(xiàn)。
位置2:當(dāng)最開(kāi)始讓TableView
向下走一點(diǎn)點(diǎn)的時(shí)候,TableView
的進(jìn)度條在位置2處出現(xiàn)。
通過(guò)以上兩個(gè)位置,我最開(kāi)始是認(rèn)為,tableView只有下半屏幕的大小,如下圖這么大
我為什么這么以為呢,因?yàn)檎G闆r下,下拉刷新會(huì)出現(xiàn)在TableView的頂部,進(jìn)度條最開(kāi)始出現(xiàn)的地方也應(yīng)該是TableView的頂部。那我們就來(lái)以這個(gè)思路往下走走看,假設(shè)他就這么大。
通過(guò)以上假設(shè),我們可以提出一下幾個(gè)問(wèn)題:
- TableView大小是半個(gè)屏幕,它是如何顯示整個(gè)屏幕的東西的
- 在TableView向上滑動(dòng)的時(shí)候,為什么Header也會(huì)跟著往上走(此處Header指“掃一掃,付錢(qián),收錢(qián)...;余額,花唄,充值中心.....”那部分)
下面我們來(lái)試著解決這些問(wèn)題:
- 可能Header和tableview都是加在了
self.view
上了,當(dāng)TableView向上滑動(dòng)時(shí),根據(jù)滑動(dòng)的偏移量,讓Header向上移動(dòng) - 在第一步,我們不僅讓Header向上移動(dòng),同時(shí)改變TableView的frame,讓tableView慢慢變大,于是完美解決以上兩個(gè)問(wèn)題
但是,當(dāng)我們認(rèn)為問(wèn)題解決的時(shí)候,新問(wèn)題出現(xiàn)了,如果改變TableView的frame,讓tableView慢慢變成self.view
的大小,右側(cè)進(jìn)度條也勢(shì)必會(huì)向上移動(dòng),可是支付寶app并不是這樣。因此絞盡腦汁想的方法并不成立。
我新開(kāi)了個(gè)工程,寫(xiě)了一個(gè)TableView,當(dāng)我們下拉TableView的時(shí)候,右側(cè)進(jìn)度條并不會(huì)顯示,但是下拉支付寶的時(shí)候,進(jìn)度條依然存在;那么就說(shuō)明:支付寶的進(jìn)度條是假的,是自定義的
既然進(jìn)度條是自定義的,那我們之前的推論(TableView大小是屏幕的一半)可能就不成立了,我們把TableView的進(jìn)度條隱藏,換成我們自己封裝的進(jìn)度條。
此處我簡(jiǎn)單說(shuō)一下怎么封裝進(jìn)度條用起來(lái)最簡(jiǎn)單吧,文章后面我會(huì)上代碼的:1.自定義View,只需要把TableView傳給這個(gè)View;2.用View監(jiān)聽(tīng)TableView的contentOffSet屬性;3.根據(jù)監(jiān)聽(tīng)的結(jié)果顯示進(jìn)度條的長(zhǎng)度以及運(yùn)動(dòng);4.顯示和隱藏的邏輯請(qǐng)看代碼
進(jìn)度條的問(wèn)題解決完了,接下來(lái)我們解決頭部刷新的問(wèn)題
我們讓TableView和屏幕一樣大,那我們?cè)趺醋岊^部刷新控件出現(xiàn)在中間呢?答案很簡(jiǎn)單:
- MJRefresh控件有一個(gè)屬性
ignoredScrollViewContentInsetTop
,可以調(diào)整刷新控件的上下位置 - 先添加TableView到
self.view
上,設(shè)置TableView的頭部高度和Header一樣高,然后添加Header到TableView上,正好蓋在TableView的頭部 - 這一步我們解決tableView向上和向下滑動(dòng)時(shí),確定頭部的位置;當(dāng)TableView向下滑動(dòng)時(shí)(contentOffset.y < 0),我們需要把Header向上移動(dòng),讓Header相對(duì)于屏幕不動(dòng),就可以漏出擋住的頭部刷新控件。當(dāng)TableView向下滑動(dòng),因?yàn)镠eader在TableView上面,因此只需要讓Header的
y
等于0
就可以了。(在scrollVIew的滑動(dòng)就會(huì)觸發(fā)的代理方法里設(shè)置這些scrollViewDidScroll
)
// 上面的第二步設(shè)置頭的代碼
tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: HeaderViewHeight))
結(jié)束:至此,我們根據(jù)上面的思路就可以做出支付寶首頁(yè)的結(jié)構(gòu)了,剩下的無(wú)非就是Header的按鈕s,以及自定義的TableViewCell,架子就是這個(gè)樣子。
Demo下載鏈接
如果覺(jué)得寫(xiě)得還行,點(diǎn)個(gè)Star唄
同時(shí)也歡迎評(píng)論中指出本文存在的bug,或者疑問(wèn),互相促進(jìn)!
作者郵箱:pangshishan@aliyun.com
github地址:https://github.com/Pangshishan
qq/微信: 704158807
代碼:
import UIKit
import MJRefresh
func ScreenWidth() -> CGFloat {
return UIScreen.main.bounds.width
}
func ScreenHeight() -> CGFloat {
return UIScreen.main.bounds.height
}
let HeaderViewHeight: CGFloat = 150
let HeaderBGColor = UIColor.red
let TableViewCellID = "TableViewCellID"
let ProgressControlWidth: CGFloat = 6
class ViewController: UIViewController {
fileprivate lazy var headerView = UIView()
fileprivate var tableView: UITableView!
fileprivate var cellCount: Int = 30
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
}
// MARK:- 設(shè)置UI
extension ViewController {
fileprivate func setupUI() {
setupTableView()
setupHeaderView()
setupScrollProgressControl()
setupRefresh()
}
fileprivate func setupHeaderView() {
headerView.frame = CGRect(x: 0, y: 0, width: ScreenWidth(), height: HeaderViewHeight)
headerView.backgroundColor = HeaderBGColor
tableView.addSubview(headerView)
}
fileprivate func setupTableView() {
let tRect = CGRect(x: 0, y: 0, width: ScreenWidth(), height: ScreenHeight())
tableView = UITableView(frame: tRect, style: .grouped)
view.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
tableView.register(UITableViewCell.self, forCellReuseIdentifier: TableViewCellID)
tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: HeaderViewHeight))
tableView.showsVerticalScrollIndicator = false
}
fileprivate func setupScrollProgressControl() {
let pFrame = CGRect(x: ScreenWidth() - 0 - ProgressControlWidth, y: HeaderViewHeight, width: ProgressControlWidth, height: ScreenHeight() - HeaderViewHeight)
let progressC = ScrollProgressControl(frame: pFrame, scrollView: tableView)
view.addSubview(progressC)
}
fileprivate func adjustHeaderView() {
let offSetY = tableView.contentOffset.y
if offSetY <= 0 {
headerView.frame.origin.y = offSetY
} else {
headerView.frame.origin.y = 0
}
}
fileprivate func setupRefresh() {
tableView.mj_header = MJRefreshNormalHeader.init(refreshingBlock: { [weak self] in
self?.perform(#selector(self?.setupHeaderData), with: nil, afterDelay: 1.5)
})
tableView.mj_header.ignoredScrollViewContentInsetTop = -HeaderViewHeight
tableView.mj_footer = MJRefreshAutoNormalFooter(refreshingBlock: { [weak self] in
self?.perform(#selector(self?.setupFooterData), with: nil, afterDelay: 0.5)
})
}
}
// tableView代理方法
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellCount
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCellID, for: indexPath)
cell.textLabel?.text = "\(indexPath.row)"
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 0.1
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 0.1
}
}
// MARK:- 數(shù)據(jù)方法
extension ViewController {
@objc fileprivate func setupHeaderData() {
cellCount = 30
tableView.reloadData()
self.tableView.mj_header.endRefreshing()
}
@objc fileprivate func setupFooterData() {
cellCount += 30
tableView.reloadData()
self.tableView.mj_footer.endRefreshing()
}
}
// scrollView代理方法
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView == tableView {
adjustHeaderView()
}
}
}