在很多APP中大家應(yīng)該都見過一些類似個人主頁的頁面,下邊是tableview列表答捕,上邊的頭部視圖可以拉伸放大.
在一番研究實現(xiàn)了這個拉伸效果后禀忆,順便把這個功能進行了封裝,使用時只需兩行代碼:
1.初始化調(diào)用
stretchableView = LPStretchableHeaderView(stretchableView: bgImageView)
2.代理方法實現(xiàn):
func scrollViewDidScroll(_ scrollView: UIScrollView) {
stretchableView.scrollViewDidScroll(scrollView)
}
github代碼demo
下邊是實現(xiàn)原理的解析营袜,大神請繞路~~~
實現(xiàn)原理解析
一:設(shè)置頭部視圖
創(chuàng)建imageView,并添加到控制器的view上
由于在往下拖動列表時丑罪,頭部視圖的y值是沒有跟著下移的荚板,所以肯定不能讓它作為tableview的tableHeaderView凤壁,只能把上邊的圖片視圖添加到Controller的view上
二:設(shè)置tableview
-
創(chuàng)建一個跟頭部視圖同樣大小的空視圖
headerView
作為tableview的tableHeaderView,來填充頭部圖片視圖區(qū)域 -
把tableview的背景顏色設(shè)置為clearColor跪另,這樣就可以看到下面的頭部圖片了
這樣一來拧抖,我們的列表視圖的實際大小就占據(jù)了整個屏幕,并且不影響看到下面的頭部圖片免绿,而且在頭部圖片區(qū)域拖動的時候(實際拖動的是列表的tableHeaderView)也可以觸發(fā)列表的滾動事件唧席,同時上滑的時候列表的頂部滾動區(qū)域也達到了導(dǎo)航欄位置,一石好幾鳥啊~??
三:頭部視圖添加子控件
一般在頭部視圖的圖片上方嘲驾,還會顯示昵稱淌哟、頭像等信息,我們需要把這些子控件添加到剛才創(chuàng)建的空視圖headerView
中
注意:不要添加到頭部視圖的imageView中辽故!
因為稍后我們在在拉伸列表時徒仓,會改變imageView的frame,但是并沒有改變其內(nèi)的子控件的frame誊垢,所以子控件位置會發(fā)生錯亂掉弛。
現(xiàn)在控件布局如下:
override func viewDidLoad() {
super.viewDidLoad()
// 頭部圖片視圖
bgImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: SCREEN_WIDTH * imageRatio))
bgImageView.image = UIImage(named: "123")
view.addSubview(bgImageView)
// 列表
tableView = UITableView(frame: CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: SCREEN_HEIGHT))
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.dataSource = self
tableView.delegate = self
tableView.showsVerticalScrollIndicator = false
tableView.backgroundColor = UIColor.clear // 注意要清除列表的背景顏色
view.addSubview(tableView)
// 創(chuàng)建一個空白view來進行填充tableHeaderView
let headerView = UIView(frame: bgImageView.bounds)
tableView.tableHeaderView = headerView
// 添加label子控件
let nameLabel = UILabel(frame: CGRect(x: 0, y: 150, width: bgImageView.width, height: 40))
nameLabel.text = "哈哈哈??"
nameLabel.textAlignment = .center
nameLabel.textColor = UIColor.white
headerView.addSubview(nameLabel) // 注意要把子控件添加到headerView中
// 導(dǎo)航欄
makeNavView()
}
三:實現(xiàn)滾動拉伸放大效果
- 下拉時通過tableview在y軸的偏移量來決定頭部圖片的高度拉伸多少
- 通過圖片拉伸的高度及圖片原來的寬高比例計算出要拉伸的寬度
- 通過圖片拉伸后的寬度計算出x值應(yīng)向左的偏移量
1)首先,我們會在頭部視圖初始化的時候記錄它的原始frame喂走,這個后邊會用到
// 圖片的原始frame
originFrame = bgImageView.frame
2)取出列表y值的偏移量
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let yOffset = scrollView.contentOffset.y
}
3)圖片拉伸后的高度
// 下拉時yOffset值是負數(shù)殃饿,所以需要減
frame.size.height = originFrame.size.height - yOffset
4)圖片拉伸后的寬度
// 通過圖片的寬高比imageRatio及拉伸后的高度等比計算新寬度
frame.size.width = frame.size.height / imageRatio
5)x值的位置重新計算
// 圖片寬高同時變大后,圖片會整體向右偏移芋肠,所以需要重新計算x值
frame.origin.x = originFrame.origin.x - (frame.size.width - originFrame.size.width) * 0.5
當(dāng)列表上滑時乎芳,移動頭部圖片跟著向上移動
var frame = originFrame
frame.origin.y = originFrame.origin.y - yOffset
bgImageView.frame = frame
最終,處理代碼為:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let yOffset = scrollView.contentOffset.y
// 頭部圖片拉伸設(shè)置
if yOffset > 0 { // 上滑
var frame = originFrame
frame.origin.y = originFrame.origin.y - yOffset
bgImageView.frame = frame
} else { // 下拉
var frame = originFrame
frame.size.height = originFrame.size.height - yOffset
frame.size.width = frame.size.height / imageRatio
frame.origin.x = originFrame.origin.x - (frame.size.width - originFrame.size.width) * 0.5
bgImageView.frame = frame
}
}
封裝
通過以上實現(xiàn)可以看出业栅,所有的操作都是通過拿到scrollViewDidScroll回調(diào)方法中列表y軸的偏移量秒咐,然后對頭部視圖bgImageView的frame進行更改實現(xiàn)的谬晕。
所以其實沒啥好封裝的碘裕,如果非得封裝的話,那就只需要把bgImageView控件和對它frame更改的操作拿出去就ok了攒钳。
于是我建了一個工具類LPStretchableHeaderView帮孔,實現(xiàn)如下:
LPStretchableHeaderView.swift文件
import UIKit
public class LPStretchableHeaderView: NSObject {
private var stretchView = UIView()
private var imageRatio: CGFloat
private var originFrame = CGRect()
public init(stretchableView: UIView) {
stretchView = stretchableView
originFrame = stretchableView.frame
imageRatio = stretchableView.bounds.height / stretchableView.bounds.width
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
let yOffset = scrollView.contentOffset.y
if yOffset > 0 { // 往上移動
var frame = originFrame
frame.origin.y = originFrame.origin.y - yOffset
stretchView.frame = frame
} else { // 往下移動
var frame = originFrame
frame.size.height = originFrame.size.height - yOffset
frame.size.width = frame.size.height / imageRatio
frame.origin.x = originFrame.origin.x - (frame.size.width - originFrame.size.width) * 0.5
stretchView.frame = frame
}
}
}
這樣,以后遇到有這種需求的頁面不撑,兩行代碼就搞定了~