日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

动手写一个快速集成网易新闻,腾讯视频,头条首页的ScrollPageView,显示滚动视图...

發(fā)布時(shí)間:2024/4/15 编程问答 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 动手写一个快速集成网易新闻,腾讯视频,头条首页的ScrollPageView,显示滚动视图... 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

最終效果


更新示例.gif
示例效果.gif
示例效果1.gif
示例效果2.gif
示例效果3.gif
示例效果4.gif
示例效果5.gif
示例效果6.gif

一.構(gòu)思部分:

打算分為三個(gè)部分, 滑塊部分View, 內(nèi)容顯示部分View, 包含滑塊View和顯示內(nèi)容View的View,以便于可以靈活的使用

1. 滑塊部分View

1.1 要實(shí)現(xiàn)滑塊可以滾動(dòng), 考慮可以直接使用collectionView, 但是這里還是直接使用scrollView方便里面的控件布局

1.2 要實(shí)現(xiàn)滑塊的點(diǎn)擊, 可以直接使用UIButton, 但是經(jīng)過(guò)嘗試, 要讓button的frame隨著文字的寬度來(lái)自適應(yīng)實(shí)現(xiàn)比較麻煩, 所以選擇了使用UILabel添加點(diǎn)擊手勢(shì)來(lái)實(shí)現(xiàn)點(diǎn)擊事件,這里使用了closures來(lái)實(shí)現(xiàn)(可以使用代理模式)

1.3 實(shí)現(xiàn)對(duì)應(yīng)的滾動(dòng)條和遮蓋同步移動(dòng)的效果,文字顏色漸變功能(在點(diǎn)擊的時(shí)候直接使用一個(gè)動(dòng)畫(huà)就可以簡(jiǎn)單的完成了)

2. 內(nèi)容顯示部分View

2.1 用來(lái)作為包含子控制器的view的容器, 并且實(shí)現(xiàn)可以分頁(yè)滾動(dòng)的效果

2.2 要實(shí)現(xiàn)分頁(yè)滾動(dòng), 可以使用UIScrollView來(lái)實(shí)現(xiàn), 但是這樣就要考慮UIScrollView上的各個(gè)view的復(fù)用的問(wèn)題, 其中細(xì)節(jié)還是很麻煩, 所以直接使用了UICollectionView(利用他的重用機(jī)制)來(lái)實(shí)現(xiàn)

2.3 將每一個(gè)子控制器的view直接添加到對(duì)應(yīng)的每一個(gè)cell的contentView中來(lái)展示, 所以這里需要注意cell重用可能帶來(lái)的內(nèi)容顯示不正常的問(wèn)題, 這里采用了每次添加contentView的內(nèi)容時(shí)移除所有的subviews(也可以直接給每個(gè)cell用不同的reuseIdentifier實(shí)現(xiàn))

2.4 實(shí)現(xiàn)實(shí)時(shí)監(jiān)控滾動(dòng)的進(jìn)度提供給滑塊部分來(lái)同步調(diào)整滾動(dòng)條和遮蓋,文字顏色的漸變, 并且在每次滾動(dòng)完成的時(shí)候可以通知給滑塊來(lái)調(diào)整他的內(nèi)容

3. 包含滑塊View和顯示內(nèi)容View的View

3.1 因?yàn)榛瑝K部分View和內(nèi)容顯示部分View是相對(duì)獨(dú)立的部分, 在這里只需要實(shí)現(xiàn)兩者的通信即可

3.2 可以自定義滑塊部分View和內(nèi)容顯示部分View的frame

實(shí)現(xiàn)部分

a. 滑塊部分

1. 基本屬性

/// 所有的title設(shè)置 -> 使用了一個(gè)結(jié)構(gòu)體, 將可以自定義的部分全部暴露了出來(lái), 使用的時(shí)候就可以比較方便的自定義很多屬性 -> 初始化時(shí)傳入的 var segmentStyle: SegmentStyle/// 點(diǎn)擊響應(yīng)的closures var titleBtnOnClick:((label: UILabel, index: Int) -> Void)? /// 用來(lái)緩存所有標(biāo)題的寬度, 達(dá)到根據(jù)文字的字?jǐn)?shù)和font自適應(yīng)控件的寬度 -> 為了只計(jì)算一次文字的寬度 private var titlesWidthArray: [CGFloat] = [] /// 所有的標(biāo)題 -> 初始化時(shí)傳入的 var titles:[String] /// 緩存標(biāo)題labels -> 以便于通過(guò)下標(biāo)直接取值 private var labelsArray: [UILabel] = [] /// 滾動(dòng)條 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) // 這里只有一個(gè)cover 需要設(shè)置圓角, 故不用考慮離屏渲染的消耗, 直接設(shè)置 masksToBounds 來(lái)設(shè)置圓角 cover.layer.masksToBounds = true return self.segmentStyle.showCover ? cover : nil }() /// 背景圖片 var backgroundImage: UIImage? = nil { didSet { // 在設(shè)置了背景圖片的時(shí)候才添加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) // 這個(gè)函數(shù)里面設(shè)置了基本屬性中的titles, labelsArray, titlesWidthArray,并且添加了label到scrollView上 setupTitles() // 這個(gè)函數(shù)里面設(shè)置了遮蓋, 滾動(dòng)條,和label的初始化位置 setupUI() } func titleLabelOnClick(tapGes: UITapGestureRecognizer) -> 處理點(diǎn)擊title的時(shí)候?qū)崿F(xiàn)標(biāo)題的切換,和遮蓋,滾動(dòng)條...的位置調(diào)整, 同時(shí)執(zhí)行了相應(yīng)點(diǎn)擊得兒blosure, 以便于外部相應(yīng)點(diǎn)擊方法 func adjustTitleOffSetToCurrentIndex(currentIndex: Int) -> 更改scrollview的contentOffSet來(lái)居中顯示title // 手動(dòng)滾動(dòng)時(shí)需要提供動(dòng)畫(huà)效果 func adjustUIWithProgress(progress: CGFloat, oldIndex: Int, currentIndex: Int) -> 提供給外部來(lái)執(zhí)行標(biāo)題切換之間的動(dòng)畫(huà)效果(注意這個(gè)方法里面進(jìn)行了一些簡(jiǎn)單的數(shù)學(xué)計(jì)算以便于"同步" 滾動(dòng)滾動(dòng)條和cell ) 這里以滑塊的位置x變化為例, 其他類(lèi)似 let xDistance = currentLabel.frame.origin.x - oldLabel.frame.origin.x 這個(gè)xDistance就是滑塊將要從一個(gè)label下面移動(dòng)到下一個(gè)label下面所需要移動(dòng)的路程, 這個(gè)progress是外界提供來(lái)的, 表示當(dāng)前已經(jīng)移動(dòng)的百分比(0 --- 1)是多少了,所以可以改變當(dāng)前滑塊的x為之前的x + 已經(jīng)完成滾動(dòng)的距離(xDistance * progress) scrollLine?.frame.origin.x = oldLabel.frame.origin.x + xDistance * progress 這樣就達(dá)到了滑塊的位置隨著提供的progress同步移動(dòng)

b 內(nèi)容顯示部分View

基本屬性

/// 所有的子控制器private var childVcs: [UIViewController] = []/// 用來(lái)禁止調(diào)用scrollview的代理來(lái)進(jìn)行相關(guān)的計(jì)算 var forbidTouchToAdjustPosition = false /// 用來(lái)記錄開(kāi)始滾動(dòng)的offSetX -> 用于判斷滾動(dòng)的方向是向左還是向右, 同時(shí)方便設(shè)置下面兩個(gè)Index private var oldOffSetX:CGFloat = 0.0 private var oldIndex = 0 private var currentIndex = 1 weak var delegate: ContentViewDelegate? // UICollectionView用來(lái)顯示子控制器的view的內(nèi)容 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 // 如果不設(shè)置代理, 將不會(huì)調(diào)用scrollView的delegate方法 collection.delegate = strongSelf collection.dataSource = strongSelf collection.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: ContentView.cellId) } return collection }()

邏輯處理

// 初始化設(shè)置frame和子控制器init(frame:CGRect, childVcs:[UIViewController]) {self.childVcs = childVcs super.init(frame: frame) // 設(shè)置collectionView的frame和添加collectionView同時(shí)做相關(guān)的數(shù)據(jù)錯(cuò)誤判斷 commonInit() } func setContentOffSet(offSet: CGPoint , animated: Bool) // 提供給外部來(lái)設(shè)置contentOffSet -> 比如說(shuō)點(diǎn)擊了滑塊切換title時(shí)同時(shí)切換內(nèi)容顯示 extension ContentView: UICollectionViewDelegate, UICollectionViewDataSource{ 其中的設(shè)置了cell的內(nèi)容和個(gè)數(shù) } extension ContentView: UIScrollViewDelegate { 這里面使用到了監(jiān)控滾動(dòng)的過(guò)程, 以便于計(jì)算滾動(dòng)的進(jìn)度和頁(yè)數(shù)的改變, 同時(shí)使用代理來(lái)完成相應(yīng)的工作 主要邏輯在func scrollViewWillBeginDragging(scrollView: UIScrollView) 方法里面 } 定義了一個(gè)protocol來(lái)完成相關(guān)的操作 protocol ContentViewDelegate: class { func contentViewMoveToIndex(fromIndex: Int, toIndex: Int, progress: CGFloat) func contentViewDidEndMoveToIndex(currentIndex: Int) var segmentView: ScrollSegmentView { get } } // 由于每個(gè)遵守這個(gè)協(xié)議的都需要執(zhí)行些相同的操作, 所以直接使用協(xié)議擴(kuò)展統(tǒng)一完成,協(xié)議遵守者只需要提供segmentView即可 extension ContentViewDelegate { // 內(nèi)容每次滾動(dòng)完成時(shí)調(diào)用, 確定title和其他的控件的位置 func contentViewDidEndMoveToIndex(currentIndex: Int) { segmentView.adjustTitleOffSetToCurrentIndex(currentIndex) segmentView.adjustUIWithProgress(1.0, oldIndex: currentIndex, currentIndex: currentIndex) } // 內(nèi)容正在滾動(dòng)的時(shí)候,同步滾動(dòng)滑塊的控件 func contentViewMoveToIndex(fromIndex: Int, toIndex: Int, progress: CGFloat) { segmentView.adjustUIWithProgress(progress, oldIndex: fromIndex, currentIndex: toIndex) } }

c. 包含滑塊View和顯示內(nèi)容View的View

這一部分比較簡(jiǎn)單直接看代碼就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, "標(biāo)題的個(gè)數(shù)必須和子控制器的個(gè)數(shù)相同") super.init(frame: frame) // 初始化設(shè)置了frame后可以在以后的任何地方直接獲取到frame了, 就不必重寫(xiě)layoutsubview()方法在里面設(shè)置各個(gè)控件的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) // 在這里調(diào)用了懶加載的collectionView, 那么之前設(shè)置的self.frame將會(huì)用于collectionView,如果在layoutsubviews()里面沒(méi)有相關(guān)的處理frame的操作, 那么將導(dǎo)致內(nèi)容顯示不正常 // 避免循環(huán)引用 segView.titleBtnOnClick = {[unowned self] (label: UILabel, index: Int) in // 不要執(zhí)行collectionView的scrollView的滾動(dòng)代理方法 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



文/ZeroJ(簡(jiǎn)書(shū)作者)
原文鏈接:http://www.jianshu.com/p/b84f4dd96d0c
著作權(quán)歸作者所有,轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),并標(biāo)注“簡(jiǎn)書(shū)作者”。

轉(zhuǎn)載于:https://www.cnblogs.com/fengmin/p/5460698.html

總結(jié)

以上是生活随笔為你收集整理的动手写一个快速集成网易新闻,腾讯视频,头条首页的ScrollPageView,显示滚动视图...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。