登录 立即注册
金钱:

Code4App-iOS开发-iOS 开源代码库-iOS代码实例搜索-iOS特效示例-iOS代码例子下载-Code4App.com

快速灵活的集成网易新闻, 头条, 腾讯视频的滚动切换页面

[复制链接]
来自: zerojswift 分类: iOS精品源码 上传时间: 2016-4-14 16:58:41

项目介绍:

快速灵活的集成网易新闻, 头条, 腾讯视频的滚动切换页面

可以简单快速灵活的实现上图中的效果

一.构思部分:

打算分为三个部分, 滑块部分View, 内容显示部分View, 包含滑块View和显示内容View的View,以便于可以灵活的使用

1. 滑块部分View
1.1 要实现滑块可以滚动, 考虑可以直接使用collectionView, 但是这里还是直接使用scrollView方便里面的控件布局
1.2 要实现滑块的点击, 可以直接使用UIButton, 但是经过尝试, 要让button的frame随着文字的宽度来自适应实现比较麻烦, 所以选择了使用UILabel添加点击手势来实现点击事件,这里使用了blosure来实现(可以使用代理模式)
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. 基本属性
[Objective-C] 查看源文件 复制代码
/// 所有的title设置 -> 使用了一个结构体, 将可以自定义的部分全部暴露了出来, 使用的时候就可以比较方便的自定义很多属性  -> 初始化时传入的
var segmentStyle: SegmentStyle
/// 点击响应的blosure
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
}()


2.逻辑处理
[Objective-C] 查看源文件 复制代码
    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
1.基本属性
[Objective-C] 查看源文件 复制代码
/// 所有的子控制器
    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
    }()


2.逻辑处理
[Objective-C] 查看源文件 复制代码
// 初始化设置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)
    }
}
[font=Tahoma, Helvetica, SimSun, sans-serif]


c. 包含滑块View和显示内容View的View
这一部分比较简单直接看代码就ok了
[Objective-C] 查看源文件 复制代码
//
//  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
    }

}


三、使用方法
一. 使用ScrollPageView , 提供了各种效果的组合,但是不能修改segmentView和ContentView的相对位置,两者是结合在一起的
[Objective-C] 查看源文件 复制代码
   //1. 添加子控制器,类似
    //let vc2 = UIViewController()
    //vc2.view.backgroundColor = UIColor.greenColor()
    //addChildViewController(vc2)

     addChildVcs()

    // 2.这个是必要的设置
    automaticallyAdjustsScrollViewInsets = false

    //3. 自定义效果组合
    var style = SegmentStyle()
    // 缩放文字
    style.scaleTitle = true
    // 颜色渐变
    style.gradualChangeTitleColor = true
    // segment可以滚动
    style.scrollTitle = true
    // 4. 注意: 标题个数和子控制器的个数要相同
    let titles = ["国内头条", "国际要闻", "趣事", "囧图", "明星八卦", "爱车", "国防要事", "科技频道", "手机专页", "风景图", "段子"]
    // 5.
    let scroll = ScrollPageView(frame: CGRect(x: 0, y: 64, width: view.bounds.size.width, height: view.bounds.size.height - 64), segmentStyle: style, titles: titles, childVcs: childViewControllers)
    // 6.
    view.addSubview(scroll) 


二 使用 ScrollSegmentView 和 ContentView, 提供相同的效果组合, 但是同时可以分离开segmentView和contentView,可以单独设置他们的frame, 使用更灵活
[Objective-C] 查看源文件 复制代码
 // 1. 添加子控制器
    addChildVcs()

    //2. 这个是必要的设置
    automaticallyAdjustsScrollViewInsets = false

    // 3. 自定义效果组合
    var style = SegmentStyle()

    // 标题不滚动, 则每个label会平分宽度
    style.scrollTitle = false
    // 颜色渐变
    style.gradualChangeTitleColor = true
    // 遮盖
    style.showCover = true
    // 遮盖颜色
    style.coverBackgroundColor = UIColor.whiteColor()

    // title正常状态颜色 使用RGB空间值
    style.normalTitleColor = UIColor(red: 255.0/255.0, green: 255.0/255.0, blue: 255.0/255.0, alpha: 1.0)
    // title选中状态颜色 使用RGB空间值
    style.selectedTitleColor = UIColor(red: 235.0/255.0, green: 0.0/255.0, blue: 0.0/255.0, alpha: 1.0)

    // 4. 标题个数和子控制器的个数要相同
    let titles = ["国内头条", "国际要闻"]

    // 5. 初始化segmentView
    topView = ScrollSegmentView(frame: CGRect(x: 0, y: 0, width: 150, height: 28), segmentStyle: style, titles: titles)
    topView.backgroundColor = UIColor.redColor()
    topView.layer.cornerRadius = 14.0
    // 可以直接设置背景色
    //        topView.backgroundImage = UIImage(named: "test")

    // 6. 初始化 contentView 
    contentView = ContentView(frame: view.bounds, childVcs: childViewControllers)
    // 7. 设置代理 必要的设置
    contentView.delegate = self // 必须实现代理方法

    // 8. 处理点击title时切换contentView的内容, 建议您可以直接使用和下面一样的代码
    topView.titleBtnOnClick = {[unowned self] (label: UILabel, index: Int) in
        self.contentView.setContentOffSet(CGPoint(x: self.contentView.bounds.size.width * CGFloat(index), y: 0), animated: false)

    }
    // 9. 添加segmentVIew 和ContentView
    navigationItem.titleView = topView
    view.addSubview(contentView)

    // 10. 实现代理, 只需要提供当前的segmentVIew即可

    extension Vc6Controller: ContentViewDelegate {
        var segmentView: ScrollSegmentView {
         return topView
        }
    }


文/ZeroJ(简书作者)




相关源码推荐:

我来说两句
*滑动验证:
所有评论(344)
nBonneNuit 2016-4-15 09:38:38
精华内容,楼主V5!
回复
李世铿 2016-4-15 09:40:15
作者您好,您的100金钱已经充值到账,请注意查收哦,祝您玩得愉快~
回复
dachuzi 2016-4-15 09:51:12
楼主你好,请问预览动态图怎么做的
回复
tianfantasy 2016-4-15 10:20:59
很给力,Code4App有你更精彩!
回复
zerojswift 2016-4-15 10:57:26
dachuzi 发表于 2016-4-15 09:51
楼主你好,请问预览动态图怎么做的

你好 使用mac自带的QuickTime Player 可以录制屏幕, 然后使用GIFBrewery软件可以将录制的视频转换为gif格式的图片
回复
zerojswift 2016-4-15 10:58:17
李世铿 发表于 2016-4-15 09:40
作者您好,您的100金钱已经充值到账,请注意查收哦,祝您玩得愉快~

, 多谢
回复
zerojswift 2016-4-15 10:58:50
李世铿 发表于 2016-4-15 09:40
作者您好,您的100金钱已经充值到账,请注意查收哦,祝您玩得愉快~

, 多谢
回复
夏天_mzOox 2016-4-15 11:17:45
正需要啊,感谢楼主无私分享!
回复
青书展码 2016-4-15 12:28:47
楼主威武啊,Code4App有你更给力!
回复
提取码:  下载次数:341 状态:已购或VIP 售价:0(原价:5)金钱 下载权限:初级码农 
4535 0 341
联系我们
首页/微信公众账号投稿

帖子代码编辑/版权问题

QQ:435399051,742864542

如何获得代码达人称号?

代码贡献英雄榜
用户名 下载数
通过邮件订阅最新 Code4App 信息
上一条 /4 下一条
联系我们
关闭
合作电话:
13802416937
Email:
435399051@qq.com
商务市场合作/投稿
问题反馈及帮助
联系我们

广告投放| Github|申请友链|手机版|Code4App ( 粤ICP备15117877号-1 )

快速回复 返回顶部 返回列表