放松的时候看看知乎,生活不解的时候问问知乎,这貌似已经成为了生活中的一种习惯,它独特翻页方式也是本人喜欢的一个原因,通过上划与下滑进行页面的翻页,不必返回再进入下一个页面,显得非常的简介并且人性化,这里就模拟知乎进行一次模拟滚动换页,希望能给想做这种效果的人一种思路,实现的大体思路就是在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()

1万+

被折叠的 条评论
为什么被折叠?



