放鬆的時候看看知乎,生活不解的時候問問知乎,這貌似已經成爲了生活中的一種習慣,它獨特翻頁方式也是本人喜歡的一個原因,通過上劃與下滑進行頁面的翻頁,不必返回再進入下一個頁面,顯得非常的簡介並且人性化,這裏就模擬知乎進行一次模擬滾動換頁,希望能給想做這種效果的人一種思路,實現的大體思路就是在ScrollView中嵌套ScrollView來感應達到切換效果。利用工作之餘就做了一個類似功能的小Demo,當然距離知乎還差得遠,但是基本原理應該如此,如果有更好的思路,也請告知一下,在這裏先初始化一個Thanks()
O(∩_∩)O
如果有解析不詳細的地方,也歡迎去Demo的GitHub下載一下本人的Demo:https://github.com/YRunIntoLove/YSimilarZHPullDownDemo
看一下效果圖(因爲圖片太大,所以草草的滑動了幾下,Sorry - -):
梳理層次(順序:子視圖 -> 父視圖):
- 響應滑動的ScrollView,搭載自定義的customView,能夠響應滑動反饋進行上下翻頁的回調,並通過Delegate告知superView進行怎麼樣的翻頁操作,Demo中叫做SimilarZHPullDownView
- 進行整體翻頁的ScrollView,搭載SimilarZHPullDownView,通過感知上下翻頁的回調,設置自身的ContentOffset更改偏移量達到翻頁的效果,Demo中叫做YSimilarZHPullDownMainView
SimilarZHPullDownView
需要一個滾動視圖(ScrollView)作爲底層視圖,響應滑動操作,加載的標籤以及自定義的視圖都貼到這個滾動視圖上,通過代理中的相關回調獲取偏移量,判斷進行什麼樣子的滾動。
這裏選擇通過懶加載的方式加載這個屬性,與Objective-C不太一樣,正好熟悉一下Swift中的懶加載
/// 懶加載底層的滾動視圖
lazy var bottomScrollView : UIScrollView =
{
//當前視圖的寬度
let width = self.bounds.size.width
//初始化滾動視圖
var scrollView:UIScrollView = UIScrollView(frame:CGRectMake(0, 0, width, self.bounds.size.height))
//添加自定義視圖
scrollView.addSubview(self.customView!)
//自定義視圖的的高度
var height = self.customView?.bounds.size.height
//頭頁
scrollView.addSubview(self.createLable(CGRectMake(0,-1 * self.responseHeight,width,30), title: self.headerTitle))
//尾頁
scrollView.addSubview(self.createLable(CGRectMake(0,height!,width,self.responseHeight), title:self.footerTitle))
//設置ContentSize的高度,保證不小於視圖的高度
height = (height > self.bounds.size.height ? height : self.bounds.size.height)
scrollView.contentSize = CGSize(width: self.bounds.size.width, height: height!)
scrollView.delegate = self;
return scrollView
}()
一個描述最小響應幅度大小的變量,上拉或者下拉達到切換狀態的最小幅度,或者說上拉、下拉達到換頁響應的最小幅度
/// 響應的高度,就是滑動響應的最小幅度
final let responseHeight = CGFloat(60)
一個自定義視圖,將需要展示的視圖賦值給這個屬性即可,但是必須設置好frame
/// 中間位置的自定義視圖
var customView:UIView?
其他的相關屬性:
/// 代理
weak var delegate:SimilarZHPullDownViewDelegate?
/// 標籤上寫的Title
var headerTitle = "上一篇"
var footerTitle = "下一篇"
var title = "Title"
/// 類型
var type:SimilarZHPullDownType = .Default
相關的類型屬性通過枚舉來完成,枚舉格式如下:
//滑動響應的方式
enum PullType
{
case PullTypeUp //上翻頁
case PullTypeDown //下翻頁
}
//視圖的類型
enum SimilarZHPullDownType
{
case Default //默認中間頁
case Header //第一頁
case Footer //尾頁
}
Swift是可以實現init方法的多態模式的,只不過需要用convenience
關鍵字來修飾一下init方法,並且這個關鍵詞只能修飾init方法,只不過參數不一樣而已,因此下面的就是便利構造方法:
//MARK: - FUNCTION
override init(frame: CGRect) {
super.init(frame: frame)
}
/**
* 便利構造方法
*/
@available(iOS 8.0,*)
convenience init(frame: CGRect ,custom:UIView)
{
self.init(frame:frame)
self.customView = custom
}
/**
* 便利構造方法
*/
@available(iOS 8.0,*)
convenience init(custom:UIView)
{
self.init(frame:CGRectNull,custom:custom)
}
/**
* 便利構造方法
*/
@available(iOS 8.0,*)
convenience init(custom:UIView,title:String)
{
self.init(custom:custom)
self.title = title
}
在加載視圖的時候添加上底層的scrollView,用self.scrollView實現懶加載的實現
override func layoutSubviews(){
self.addSubview(self.bottomScrollView)
}
最後這個視圖的最重要的,通過底層ScrollView的代理方法,獲得偏移量的垂直偏移量,通過偏移量的大小判斷是否進行滾動以及滾動的方式
//MARK: -UIScrollView Delegate
func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
//獲得偏移量
let contentOffsetY = scrollView.contentOffset.y
//當前響應的高度,因爲下拉,所以響應距離爲負數
let beforeHeight = self.responseHeight * (-1)
if(contentOffsetY < beforeHeight)
{
print("我是DownView,上一頁!")
//表示上一頁
self.delegate?.similarZHPullDownView(self, pullType: .PullTypeUp)
}
//偏移量是視圖左上角,所以垂直座標需要-滾動視圖的高度
else if(contentOffsetY > (scrollView.contentSize.height - scrollView.bounds.size.height + self.responseHeight))
{
print("我是DownView,下一頁!")
//表示下一頁
self.delegate?.similarZHPullDownView(self, pullType: .PullTypeDown)
}
}
對外的傳值和之前一樣,用的是Delegate(委託) 傳值,但是這裏依舊可以使用閉包傳值,以後可以嘗試,定義的協議如下
protocol SimilarZHPullDownViewDelegate : class
{
/**
* 需要翻頁進行的回調
*/
@available(iOS 8.0,*)
func similarZHPullDownView(similarZHPullDownView : SimilarZHPullDownView , pullType:PullType)
}
這樣視圖就基本完成了
YSimilarZHPullDownMainView
這個視圖是一個過渡作用的視圖,是負責存儲並且展示SimilarZHPullDownView的一個容器
定義兩個對外的屬性,提供默認值,第一個是第一個視圖下拉出現的標籤顯示的String字段,第二個是最後一篇上拉出現標籤顯示的String字段,在外也可以通過設置來更改這兩個存儲屬性
/*** 第一篇上劃以及最後一篇下滑顯示的默認字樣,可修改 ***/
var headerTitle:String = "已是第一篇"
var footerTitle:String = "最後一篇"
創建底層進行切換的ScrollView,這個ScrollView不能響應手動滾動,必須通過SimilarZHPullDownView的Delegate回調方法來更新contentOffset,達到切換的效果
/// 存放滾動頁的主滾動頁
lazy var scrollView:UIScrollView = {
var scrollView:UIScrollView = UIScrollView(frame: CGRectMake(0,self.titleLabel.bounds.size.height,self.bounds.size.width,self.bounds.size.height - 50 - 64))
scrollView.pagingEnabled = true//分頁顯示
scrollView.showsVerticalScrollIndicator = false //不顯示垂直滾條
scrollView.scrollEnabled = false //不能滾動
return scrollView
}()
在SimilarZHPullDownView
上面顯示title屬性的標籤,就是Demo中顯示測試I
的視圖
/// 顯示標題的標題
lazy var titleLabel:UILabel = {
var label:UILabel = UILabel(frame: CGRectMake(0,0,self.bounds.size.width,50))
label.textAlignment = .Center
return label
}()
存儲SimilarZHPullDownView
對象的數據源,並在設置新值得時候進行數據的處理,是存儲屬性,willSet
相當於Objcetive-C中的KVO,監聽屬性的新值
/// 存放預覽視圖的數組,默認爲空數組,爲存儲屬性,設置KVO
var pullViews:[SimilarZHPullDownView] = []
{
willSet
{
self.pullViews = newValue
//開始做處理
self.pullViews.first?.headerTitle = self.headerTitle
self.pullViews.first?.type = .Header
self.pullViews.last?.footerTitle = self.footerTitle
self.pullViews.last?.type = .Footer
}
}
自定義的便利構造方法,require
表示繼承與該類的類必須實現這個init方法,當然這裏可以不用加require
,只是爲了瞭解一下這個關鍵字而已(調皮0.0)
//MARK: - 構造方法
override init(frame: CGRect)
{
super.init(frame: frame)
self.addSubview(self.titleLabel)
self.addSubview(self.scrollView)
}
/**
* 便利構造方法
*/
@available(iOS 8.0,*)
required convenience init(frame: CGRect,pullViews:[SimilarZHPullDownView])
{
self.init(frame:frame)
self.pullViews = pullViews
}
在加載該視圖的時候,設置底層滾動視圖容納域的大小以及添加數據源中所有的SimilarZHPullDownView
對象
override func layoutSubviews() {
//設置自身的contentSize
self.scrollView.contentSize = CGSize(width: self.bounds.size.width, height: CGFloat(self.pullViews.count) * self.bounds.size.height)
self.addChildView()
}
添加所有SimilarZHPullDownView
視圖的方法如下
//MARK: - 功能方法
/**
* 添加子視圖
*/
@available(iOS 8.0,*)
func addChildView()
{
//獲取當前視圖的高度和寬度
let height = self.bounds.size.height
let width = self.bounds.size.width
for(var i:Int = 0 ; i < self.pullViews.count; i++)
{
//獲取存儲的SimilarZHPullDownView對象
let pullDownView = self.pullViews[i]
pullDownView.frame = CGRectMake(0, CGFloat(i) * height, width, height - 64 - 50)
//設置代理
pullDownView.delegate = self
//添加視圖
self.scrollView.addSubview(pullDownView)
}
self.titleLabel.text = self.pullViews.first?.title
}
實現SimilarZHPullDownView Delegate的協議方法
// MARK: - SimilarZHPullDownView Delegate
func similarZHPullDownView(similarZHPullDownView: SimilarZHPullDownView, pullType: PullType)
{
//獲得當前的偏移量
let contentOffset = self.scrollView.contentOffset
//獲得索引數
let index = self.pullViews.indexOf(similarZHPullDownView)
var paramNumber = CGFloat(1)
switch pullType
{
case .PullTypeUp: //上翻頁
guard similarZHPullDownView.type == .Header else{
paramNumber = CGFloat(-1)
self.pullDone(paramNumber, contentOffset: contentOffset, index: index!)
break
}
case .PullTypeDown://下翻頁
guard similarZHPullDownView.type == .Footer else{
paramNumber = CGFloat(1)
self.pullDone(paramNumber, contentOffset: contentOffset, index: index!)
break
}
}
}
封裝之後的滾動操作
/**
* 滾動操作
*/
@available(iOS 8.0,*)
func pullDone(paramNumber:CGFloat,var contentOffset:CGPoint,index:Int)
{
contentOffset.y += (paramNumber * self.bounds.size.height)
self.scrollView.setContentOffset(contentOffset, animated: true)
//顯示即將出現的similarZHPullDownView對象的title
self.titleLabel.text = self.pullViews[index + Int(paramNumber)].title
}
在ViewController
中進行加載過渡視圖YSimilarZHPullDownMainView
並作相關設置,Demo中的設置如下:
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = "Yue"
//創建DemoMainView對象
//賦值
/*** 設置表頭與表位文字需要設置數據源之前 ***/
demoMainView.headerTitle = "啦啦啦,我已經是第一篇了"
demoMainView.footerTitle = "哈哈哈,我是最後一篇啦"
demoMainView.pullViews = self.createPullDownViews()
//添加視圖
self.view.addSubview(demoMainView)
}
/**
* 創建測試PullDown視圖
*/
@available(iOS 8.0,*)
func createPullDownViews() -> [SimilarZHPullDownView]
{
var views :[SimilarZHPullDownView] = []
for(var i:Int = 0 ; i < 3; i++)
{
let similarPullDownView = SimilarZHPullDownView(custom:self.createCustomView(),title:"測試\(i)")
views.append(similarPullDownView)
}
return views
}
//隨機創建UIView的對象
func createCustomView() -> UIView
{
//初始化視圖
let view = UIImageView()
//獲得隨機數
//let count:UInt32 = arc4random_uniform(3) + UInt32(1)
let count = 1//爲了測試
view.frame = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height * CGFloat(count))
view.image = UIImage(named: "testImage.jpg")
view.contentMode = .ScaleToFill
return view
}
這樣功能基本就會像Demp中那樣實現響應上下拉切換頁面,理解不深,如果有錯誤,也請指點一下,Thanks()