动手写一个快速集成网易新闻,腾讯视频,头条首页的ScrollPageView,显示滚动视图...
最終效果
更新示例.gif
示例效果.gif
示例效果1.gif
示例效果2.gif
示例效果3.gif
示例效果4.gif
示例效果5.gif
示例效果6.gif
一.構思部分:
打算分為三個部分, 滑塊部分View, 內容顯示部分View, 包含滑塊View和顯示內容View的View,以便于可以靈活的使用
1. 滑塊部分View
1.1 要實現滑塊可以滾動, 考慮可以直接使用collectionView, 但是這里還是直接使用scrollView方便里面的控件布局
1.2 要實現滑塊的點擊, 可以直接使用UIButton, 但是經過嘗試, 要讓button的frame隨著文字的寬度來自適應實現比較麻煩, 所以選擇了使用UILabel添加點擊手勢來實現點擊事件,這里使用了closures來實現(可以使用代理模式)
1.3 實現對應的滾動條和遮蓋同步移動的效果,文字顏色漸變功能(在點擊的時候直接使用一個動畫就可以簡單的完成了)
2. 內容顯示部分View
2.1 用來作為包含子控制器的view的容器, 并且實現可以分頁滾動的效果
2.2 要實現分頁滾動, 可以使用UIScrollView來實現, 但是這樣就要考慮UIScrollView上的各個view的復用的問題, 其中細節還是很麻煩, 所以直接使用了UICollectionView(利用他的重用機制)來實現
2.3 將每一個子控制器的view直接添加到對應的每一個cell的contentView中來展示, 所以這里需要注意cell重用可能帶來的內容顯示不正常的問題, 這里采用了每次添加contentView的內容時移除所有的subviews(也可以直接給每個cell用不同的reuseIdentifier實現)
2.4 實現實時監控滾動的進度提供給滑塊部分來同步調整滾動條和遮蓋,文字顏色的漸變, 并且在每次滾動完成的時候可以通知給滑塊來調整他的內容
3. 包含滑塊View和顯示內容View的View
3.1 因為滑塊部分View和內容顯示部分View是相對獨立的部分, 在這里只需要實現兩者的通信即可
3.2 可以自定義滑塊部分View和內容顯示部分View的frame
實現部分
a. 滑塊部分
1. 基本屬性
/// 所有的title設置 -> 使用了一個結構體, 將可以自定義的部分全部暴露了出來, 使用的時候就可以比較方便的自定義很多屬性 -> 初始化時傳入的 var segmentStyle: SegmentStyle/// 點擊響應的closures var titleBtnOnClick:((label: UILabel, index: Int) -> Void)? /// 用來緩存所有標題的寬度, 達到根據文字的字數和font自適應控件的寬度 -> 為了只計算一次文字的寬度 private var titlesWidthArray: [CGFloat] = [] /// 所有的標題 -> 初始化時傳入的 var titles:[String] /// 緩存標題labels -> 以便于通過下標直接取值 private var labelsArray: [UILabel] = [] /// 滾動條 private lazy var scrollLine: UIView? = {[unowned self] in let line = UIView() return self.segmentStyle.showLine ? line : nil }() /// 遮蓋 -> 懶加載 private lazy var coverLayer: UIView? = {[unowned self] in let cover = UIView() cover.layer.cornerRadius = CGFloat(self.segmentStyle.coverCornerRadius) // 這里只有一個cover 需要設置圓角, 故不用考慮離屏渲染的消耗, 直接設置 masksToBounds 來設置圓角 cover.layer.masksToBounds = true return self.segmentStyle.showCover ? cover : nil }() /// 背景圖片 var backgroundImage: UIImage? = nil { didSet { // 在設置了背景圖片的時候才添加imageView if let image = backgroundImage { backgroundImageView.image = image insertSubview(backgroundImageView, atIndex: 0) } } } private lazy var backgroundImageView: UIImageView = {[unowned self] in let imageView = UIImageView(frame: self.bounds) return imageView }() ```邏輯處理
init(frame: CGRect, segmentStyle: SegmentStyle, titles: [String]) {self.segmentStyle = segmentStyle self.titles = titles super.init(frame: frame) // 這個函數里面設置了基本屬性中的titles, labelsArray, titlesWidthArray,并且添加了label到scrollView上 setupTitles() // 這個函數里面設置了遮蓋, 滾動條,和label的初始化位置 setupUI() } func titleLabelOnClick(tapGes: UITapGestureRecognizer) -> 處理點擊title的時候實現標題的切換,和遮蓋,滾動條...的位置調整, 同時執行了相應點擊得兒blosure, 以便于外部相應點擊方法 func adjustTitleOffSetToCurrentIndex(currentIndex: Int) -> 更改scrollview的contentOffSet來居中顯示title // 手動滾動時需要提供動畫效果 func adjustUIWithProgress(progress: CGFloat, oldIndex: Int, currentIndex: Int) -> 提供給外部來執行標題切換之間的動畫效果(注意這個方法里面進行了一些簡單的數學計算以便于"同步" 滾動滾動條和cell ) 這里以滑塊的位置x變化為例, 其他類似 let xDistance = currentLabel.frame.origin.x - oldLabel.frame.origin.x 這個xDistance就是滑塊將要從一個label下面移動到下一個label下面所需要移動的路程, 這個progress是外界提供來的, 表示當前已經移動的百分比(0 --- 1)是多少了,所以可以改變當前滑塊的x為之前的x + 已經完成滾動的距離(xDistance * progress) scrollLine?.frame.origin.x = oldLabel.frame.origin.x + xDistance * progress 這樣就達到了滑塊的位置隨著提供的progress同步移動b 內容顯示部分View
基本屬性
/// 所有的子控制器private var childVcs: [UIViewController] = []/// 用來禁止調用scrollview的代理來進行相關的計算 var forbidTouchToAdjustPosition = false /// 用來記錄開始滾動的offSetX -> 用于判斷滾動的方向是向左還是向右, 同時方便設置下面兩個Index private var oldOffSetX:CGFloat = 0.0 private var oldIndex = 0 private var currentIndex = 1 weak var delegate: ContentViewDelegate? // UICollectionView用來顯示子控制器的view的內容 private lazy var collectionView: UICollectionView = {[weak self] in let flowLayout = UICollectionViewFlowLayout() let collection = UICollectionView(frame: CGRectZero, collectionViewLayout: flowLayout) if let strongSelf = self { flowLayout.itemSize = strongSelf.bounds.size flowLayout.scrollDirection = .Horizontal flowLayout.minimumLineSpacing = 0 flowLayout.minimumInteritemSpacing = 0 collection.bounces = false collection.showsHorizontalScrollIndicator = false collection.frame = strongSelf.bounds collection.collectionViewLayout = flowLayout collection.pagingEnabled = true // 如果不設置代理, 將不會調用scrollView的delegate方法 collection.delegate = strongSelf collection.dataSource = strongSelf collection.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: ContentView.cellId) } return collection }()邏輯處理
// 初始化設置frame和子控制器init(frame:CGRect, childVcs:[UIViewController]) {self.childVcs = childVcs super.init(frame: frame) // 設置collectionView的frame和添加collectionView同時做相關的數據錯誤判斷 commonInit() } func setContentOffSet(offSet: CGPoint , animated: Bool) // 提供給外部來設置contentOffSet -> 比如說點擊了滑塊切換title時同時切換內容顯示 extension ContentView: UICollectionViewDelegate, UICollectionViewDataSource{ 其中的設置了cell的內容和個數 } extension ContentView: UIScrollViewDelegate { 這里面使用到了監控滾動的過程, 以便于計算滾動的進度和頁數的改變, 同時使用代理來完成相應的工作 主要邏輯在func scrollViewWillBeginDragging(scrollView: UIScrollView) 方法里面 } 定義了一個protocol來完成相關的操作 protocol ContentViewDelegate: class { func contentViewMoveToIndex(fromIndex: Int, toIndex: Int, progress: CGFloat) func contentViewDidEndMoveToIndex(currentIndex: Int) var segmentView: ScrollSegmentView { get } } // 由于每個遵守這個協議的都需要執行些相同的操作, 所以直接使用協議擴展統一完成,協議遵守者只需要提供segmentView即可 extension ContentViewDelegate { // 內容每次滾動完成時調用, 確定title和其他的控件的位置 func contentViewDidEndMoveToIndex(currentIndex: Int) { segmentView.adjustTitleOffSetToCurrentIndex(currentIndex) segmentView.adjustUIWithProgress(1.0, oldIndex: currentIndex, currentIndex: currentIndex) } // 內容正在滾動的時候,同步滾動滑塊的控件 func contentViewMoveToIndex(fromIndex: Int, toIndex: Int, progress: CGFloat) { segmentView.adjustUIWithProgress(progress, oldIndex: fromIndex, currentIndex: toIndex) } }c. 包含滑塊View和顯示內容View的View
這一部分比較簡單直接看代碼就ok了
// // ScrollPageView.swift // ScrollViewController // // Created by jasnig on 16/4/6. // Copyright ? 2016年 ZeroJ. All rights reserved. // import UIKit class ScrollPageView: UIView { static let cellId = "cellId" var segmentStyle = SegmentStyle() var segView: ScrollSegmentView! var contentView: ContentView! var titlesArray: [String] = [] /// 所有的子控制器 var childVcs: [UIViewController] = [] init(frame:CGRect, segmentStyle: SegmentStyle, titles: [String], childVcs:[UIViewController]) { self.childVcs = childVcs self.titlesArray = titles self.segmentStyle = segmentStyle assert(childVcs.count == titles.count, "標題的個數必須和子控制器的個數相同") super.init(frame: frame) // 初始化設置了frame后可以在以后的任何地方直接獲取到frame了, 就不必重寫layoutsubview()方法在里面設置各個控件的frame commonInit() } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func commonInit() { segView = ScrollSegmentView(frame: CGRect(x: 0, y: 0, width: bounds.size.width, height: 44), segmentStyle: segmentStyle, titles: titlesArray) contentView = ContentView(frame: CGRect(x: 0, y: CGRectGetMaxY(segView.frame), width: bounds.size.width, height: bounds.size.height - 44), childVcs: childVcs) contentView.delegate = self addSubview(contentView) addSubview(segView) // 在這里調用了懶加載的collectionView, 那么之前設置的self.frame將會用于collectionView,如果在layoutsubviews()里面沒有相關的處理frame的操作, 那么將導致內容顯示不正常 // 避免循環引用 segView.titleBtnOnClick = {[unowned self] (label: UILabel, index: Int) in // 不要執行collectionView的scrollView的滾動代理方法 self.contentView.setContentOffSet(CGPoint(x: self.contentView.bounds.size.width * CGFloat(index), y: 0), animated: false) } } } extension ScrollPageView: ContentViewDelegate { var segmentView: ScrollSegmentView { return segView } }使用方法
使用方式一
Snip20160414_1.png
使用方式二
Snip20160414_3.png
原文鏈接:http://www.jianshu.com/p/b84f4dd96d0c
著作權歸作者所有,轉載請聯系作者獲得授權,并標注“簡書作者”。
轉載于:https://www.cnblogs.com/fengmin/p/5460698.html
總結
以上是生活随笔為你收集整理的动手写一个快速集成网易新闻,腾讯视频,头条首页的ScrollPageView,显示滚动视图...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SVN 服务端、客户端安装及配置、导入导
- 下一篇: KNN算法的简单实现