最终效果

更新示例.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
文/ZeroJ(简书作者)
原文链接:http://www.jianshu.com/p/b84f4dd96d0c
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

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

  1. 动手写一个简单版的谷歌TPU-矩阵乘法和卷积

    谷歌TPU是一个设计良好的矩阵计算加速单元,可以很好的加速神经网络的计算.本系列文章将利用公开的TPU V1相关资料,对其进行一定的简化.推测和修改,来实际编写一个简单版本的谷歌TPU.计划实现到行为 ...

  2. 死磕 java同步系列之自己动手写一个锁Lock

    问题 (1)自己动手写一个锁需要哪些知识? (2)自己动手写一个锁到底有多简单? (3)自己能不能写出来一个完美的锁? 简介 本篇文章的目标一是自己动手写一个锁,这个锁的功能很简单,能进行正常的加锁. ...

  3. 动手写一个简单版的谷歌TPU-指令集

    系列目录 谷歌TPU概述和简化 基本单元-矩阵乘法阵列 基本单元-归一化和池化(待发布) TPU中的指令集 SimpleTPU实例: (计划中) 拓展 TPU的边界(规划中) 重新审视深度神经网络中的 ...

  4. 死磕 java线程系列之自己动手写一个线程池

    欢迎关注我的公众号"彤哥读源码",查看更多源码系列文章, 与彤哥一起畅游源码的海洋. (手机横屏看源码更方便) 问题 (1)自己动手写一个线程池需要考虑哪些因素? (2)自己动手写 ...

  5. 自己动手写一个服务网关-java

    自己动手写一个服务网关 原文链接:https://www.cnblogs.com/bigben0123/p/9252444.html 引言 什么是网关?为什么需要使用网关? 如图所示,在不使用网关的情 ...

  6. 动手写一个简单的Web框架(模板渲染)

    动手写一个简单的Web框架(模板渲染) 在百度上搜索jinja2,显示的大部分内容都是jinja2的渲染语法,这个不是Web框架需要做的事,最终,居然在Werkzeug的官方文档里找到模板渲染的代码. ...

  7. 动手写一个简单的Web框架(Werkzeug路由问题)

    动手写一个简单的Web框架(Werkzeug路由问题) 继承上一篇博客,实现了HelloWorld,但是这并不是一个Web框架,只是自己手写的一个程序,别人是无法通过自己定义路由和返回文本,来使用的, ...

  8. 动手写一个简单的Web框架(HelloWorld的实现)

    动手写一个简单的Web框架(HelloWorld的实现) 关于python的wsgi问题可以看这篇博客 我就不具体阐述了,简单来说,wsgi标准需要我们提供一个可以被调用的python程序,可以实函数 ...

  9. 动手写一个Remoting接口测试工具(附源码下载)

    基于.NET开发分布式系统,经常用到Remoting技术.在测试驱动开发流行的今天,如果针对分布式系统中的每个Remoting接口的每个方法都要写详细的测试脚本,无疑非常浪费时间.所以,我想写一个能自 ...

随机推荐

  1. 在C++中子类继承和调用父类的构造函数方法

    构造方法用来初始化类的对象,与父类的其它成员不同,它不能被子类继承(子类可以继承父类所有的成员变量和成员方法,但不继承父类的构造方法).因此,在创建子类对象时,为了初始化从父类继承来的数据成员,系统需 ...

  2. 令人头疼的clientTop、scrollTop、offsetTop

    1.网络上流传的图片 2.稍微容易理解点的示意图 参考链接:http://blog.csdn.net/lidiansheng/article/details/7950751 3.言简意赅的示意图 4. ...

  3. MongoDB实战开发 【零基础学习,附完整Asp.net示例】

    MongoDB实战开发 [零基础学习,附完整Asp.net示例] 阅读目录 开始 下载MongoDB,并启动它 在C#使用MongoDB 重构(简化)代码 使用MongoDB的客户端查看数据 使用Mo ...

  4. 【原】模式之-适配器Adapter模式

    适配器Adapter模式 适配器模式(Adapter Pattern)把一个类的接口变换成客户端所期待的的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作. 模式所涉及的角色有 ...

  5. bzoj 1951 [Sdoi2010]古代猪文(数论知识)

    [题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1951 [思路] 一道优(e)秀(xin)的数论题. 首先我们要求的是(G^sigma{ ...

  6. POJ 1661 Help Jimmy DP

    思路:Jimmy 跳到一块板上后,可以有两种选择,向左走或向右走.走到左端和走到右端所需的时间,容易算出. n如果我们能知道,以左端为起点到达地面的最短时间,和以右端为起点到达地面的最短时间,那么向左 ...

  7. Windows Azure的故障检测和重试逻辑

    高度可用的应用程序设计的一个关键点,是利用代码中的重试逻辑正常处理临时中断的服务.Microsoft 模式和实践团队开发的暂时性故障处理应用程序块可协助应用程序开发人员完成此过程.“暂时性”一词表示仅 ...

  8. VellCar(barracuda buggy)

    这是我的新目标,不过是外国人设计的,youtube上好多相关视频,只是在中国被墙了,FQ能看到一个不一样的世界,附:FQ教程.国外好多玩车的都是选择这辆车,因为它结构紧凑,制作相对简单,但越野性能强悍 ...

  9. org.apache.hadoop.hbase.TableNotDisabledException 解决方法

    Exception in thread "main" org.apache.hadoop.hbase.TableNotDisabledException: org.apache.h ...

  10. HDOJ-ACM1004(JAVA)

    转载声明:原文转自http://www.cnblogs.com/xiezie/p/5502876.html JAVA语言实现: 拿到题目第一反应是用HashMap实现,但是感觉用HashMap实在太浪 ...