话不多说,直接上图,要实现类似如下效果。

  • 这个效果非常常见,这里着重讲讲核心代码

封装顶部的PageTitleView

封装构造函数

  • 封装构造函数,让别人在创建对象时,就传入其实需要显示的内容

    • frame:创建对象时确定了frame就可以直接设置子控件的位置和尺寸
    • isScrollEnable:是否可以滚动。某些地方该控件是可以滚动的。
    • titles:显示的所有标题
  1. // MARK:- 构造函数
  2. init(frame: CGRect, isScrollEnable : Bool, titles : [String]) {
  3. self.isScrollEnable = isScrollEnable
  4. self.titles = titles
  5. super.init(frame: frame)
  6. }

设置UI界面

  • 设置UI界面

    • 添加UIScrollView,如果标题过多,则可以滚动
    • 初始化所有的Label,用于显示标题。并且给label添加监听手势
    • 添加顶部线和滑块的View

实现相对来说比较简单,这里代码从略

封装底部的PageCotentView

封装构造函数

  • 封装构造函数,让别人在创建对象时,就传入其实需要显示的内容

    • 所有用于显示在UICollectionView的Cell的所有控制器
    • 控制器的父控制器
  1. // MARK:- 构造函数
  2. init(frame: CGRect, childVcs : [UIViewController], parentViewController : UIViewController) {
  3. self.childVcs = childVcs
  4. self.parentViewController = parentViewController
  5. super.init(frame: frame)
  6. }

设置UI界面内容

  • 设置UI界面

    • 将所有的子控制器添加到父控制器中
    • 添加UICollectionView,用于展示内容
  1. // MARK:- 懒加载属性
    private lazy var collectionView : UICollectionView = {
    // 1.创建布局
    let layout = UICollectionViewFlowLayout()
    layout.itemSize = self.bounds.size
    layout.minimumLineSpacing =
    layout.minimumInteritemSpacing =
    layout.scrollDirection = .Horizontal
    // 2.创建collectionView
    let collectionView = UICollectionView(frame: self.bounds, collectionViewLayout: layout)
    collectionView.showsHorizontalScrollIndicator = false
    collectionView.pagingEnabled = true
    collectionView.bounces = false
    collectionView.scrollsToTop = false
    collectionView.dataSource = self
    collectionView.delegate = self
    collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: kContentCellID)
    return collectionView
    }()
    private func setupUI() {
    // 1.添加所有的控制器
    for childVc in childVcs {
    parentViewController?.addChildViewController(childVc)
    }
    // 2.添加collectionView
    addSubview(collectionView)
    }

实现UICollectionView的数据源方法

  • 在返回Cell的方法中,先将cell的contentView中的子控件都移除,防止循环引用
  • 取出indexPath.item对应的控制器,将控制器的View添加到Cell的contentView中
    // MARK:- 遵守UICollectionView的数据源
    extension PageContentView : UICollectionViewDataSource {
    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return childVcs.count
    }
    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(kContentCellID, forIndexPath: indexPath)
    // 移除之前的
    for subview in cell.contentView.subviews {
    subview.removeFromSuperview()
    }
    // 取出控制器
    let childVc = childVcs[indexPath.item]
    childVc.view.frame = cell.contentView.bounds
    cell.contentView.addSubview(childVc.view)
    return cell
    }
    }

    PageTitleView点击改变PageContentView

  • 通过代理将PageTitleView的事件传递出去

    /// 定义协议
    protocol PageTitleViewDelegate : class {
    func pageTitleView(pageTitleView : PageTitleView, didSelectedIndex index : Int)
    }
    @objc private func titleLabelClick(tapGes : UITapGestureRecognizer) {
    // 1.获取点击的下标志
    guard let view = tapGes.view else { return }
    let index = view.tag
    // 2.滚到正确的位置
    scrollToIndex(index)
    // 3.通知代理
    delegate?.pageTitleView(self, didSelectedIndex: index)
    }

    内部调整

    // 内容滚动
    private func scrollToIndex(index : Int) {
    // 1.获取最新的label和之前的label
    let newLabel = titleLabels[index]
    let oldLabel = titleLabels[currentIndex]
    // 2.设置label的颜色
    newLabel.textColor = kSelectTitleColor
    oldLabel.textColor = kNormalTitleColor
    // 3.scrollLine滚到正确的位置
    let scrollLineEndX = scrollLine.frame.width * CGFloat(index)
    UIView.animateWithDuration(0.15) {
    self.scrollLine.frame.origin.x = scrollLineEndX
    }
    // 4.记录index
    currentIndex = index
    }
  • 在PageContentView中设置当前应该滚动的位置
  1. // MARK:- 对外暴露方法
  2. extension PageContentView {
  3. func scrollToIndex(index : Int) {
  4. let offset = CGPoint(x: CGFloat(index) * collectionView.bounds.width, y: 0)
  5. collectionView.setContentOffset(offset, animated: false)
  6. }
  7. }

PageContentView滚动调整PageTitleView

通过观察,我们发现:

1> 原来位置的Title颜色会逐渐变暗

2> 目标位置的Title颜色会逐渐变亮

3> 变化程度是和滚动的多少相关

  • 由此得出结论:
  • 我们一共需要获取三个值

    • 1> 起始位置下标值
    • 2> 目标位置下标值
    • 3> 当前滚动的进度

其实前2点可以由第3点计算而来,可以只需要将进度传递出去。

  • 根据进度值处理标题颜色渐变及滑块逻辑

。当前进度值唯一确定了标题的状态,计算出需要发生颜色变化的两相邻标题索引

。注意:下标值需要防止越界问题,临界点的处理

实现代码

extension PageContentView : UICollectionViewDelegate {

func scrollViewWillBeginDragging(scrollView: UIScrollView) {

startOffsetX = scrollView.contentOffset.x

}

func scrollViewDidScroll(scrollView: UIScrollView) {

// 0.判断是否是点击事件

       if isForbidScrollDelegate { return }

// 1.定义获取需要的数据

        var progress : CGFloat = 

        let currentOffsetX = scrollView.contentOffset.x

        let scrollViewW = scrollView.bounds.width

             // 1.计算progress

            progress = currentOffsetX / scrollViewW

             // 3.将progress传递给titleView

        delegate?.pageContentView(self, progress: progress)

   }

}

根据滚动传入的值,调整PageTitleView

两种颜色必须使用RGB值设置(方便通过RGB实现渐变效果)

private let kNormalRGB : (CGFloat, CGFloat, CGFloat) = (85, 85, 85)

private let kSelectRGB : (CGFloat, CGFloat, CGFloat) = (255, 128, 0)

private let kDeltaRGB = (kSelectRGB.0 - kNormalRGB.0, kSelectRGB.1 - kNormalRGB.1, kSelectRGB.2 - kNormalRGB.2)

private let kNormalTitleColor = UIColor(red: 85/255.0, green: 85/255.0, blue: 85/255.0, alpha: 1.0)

private let kSelectTitleColor = UIColor(red: 255.0/255.0, green: 128/255.0, blue: 0/255.0, alpha: 1.0)

调整scrollLine及两个Label颜色渐变

// MARK:- 对外暴露方法

extension PageTitleView

    func changeLabel(progress: CGFloat) {

//        开启弹簧效果时的过滤处理
var progress = progress > ? progress : progress = progress <= CGFloat(titleLabels.count - ) ? progress : CGFloat(titleLabels.count - ) var leftLabelIndex = Int(floor(progress)) let ratio = progress - CGFloat(leftLabelIndex) //获取leftLabel和rightLabel let leftLabel = titleLabels[leftLabelIndex] if leftLabelIndex >= { leftLabelIndex = } print("leftLabelIndex = \(leftLabelIndex)") var rightIndex = leftLabelIndex + if rightIndex >= { rightIndex = } print("rightIndex = \(rightIndex)") let rightLabel = titleLabels[rightIndex] //滑块的逻辑 let moveTotalX = leftLabel.frame.width let moveX = moveTotalX * ratio scrollLine.frame.origin.x = leftLabel.frame.origin.x + moveX //3.Label颜色的渐变 // 3.1.取出变化的范围 let colorDelta = (kSelectedColor. - kNormalColor., kSelectedColor. - kNormalColor., kSelectedColor. - kNormalColor.) if leftLabelIndex != rightIndex { // 3.2.变化leftLabel leftLabel.textColor = UIColor(r: kSelectedColor. - colorDelta. * ratio, g: kSelectedColor. - colorDelta. * ratio, b: kSelectedColor. - colorDelta. * ratio) // 3.2.变化rightLabel rightLabel.textColor = UIColor(r: kNormalColor. + colorDelta. * ratio, g: kNormalColor. + colorDelta. * ratio, b: kNormalColor. + colorDelta. * ratio) } // 4.记录最新的index currentIndex = leftLabelIndex
}
}

iOS 滑动页面标题切换颜色渐变效果的更多相关文章

  1. Selenium WebDriver-通过页面标题切换窗口

    selenium webdriver可以通过获取页面标题,再跟据标题去切换浏览器窗口,代码如下: #encoding=utf-8 import unittest import time import ...

  2. 解决IOS滑动页面fixed浮动问题

    <div style="position: fixed"></div> <div style="height: 100%; overflow ...

  3. 在uwp仿IOS的页面切换效果

    有时候我们需要编写一些迎合IOS用户使用习惯的uwp应用,我在这里整理一下仿IOS页面切换效果的代码. 先分析IOS的页面切换.用户使用左右滑动方式进行前进和后退,播放类似于FlipView的切换动画 ...

  4. Android之怎样实现滑动页面切换【Fragment】

    Fragment 页面切换不能滑动 所以对于listview 能够加入的左右滑动事件 .不会有冲突比如(QQ的好友列表的删除)  Fragment 和viewpager 的差别  Viewpager ...

  5. bootsrtap h5 移动版页面 在苹果手机ios滑动上下拉动滚动卡顿问题解决方法

    bootsrtap h5 移动版页面 在苹果手机ios滑动上下拉动滚动卡顿问题解决方法 bootsrtap框架做的h5页面,在android手机下没有卡顿问题,在苹果手机就一直存在这问题,开始毫无头绪 ...

  6. ViewPager+PagerTabStrip实现页面的切换

    页面切换效果图 首先创建布局: 代码: <?xml version="1.0" encoding="utf-8"?><LinearLayout ...

  7. iOS开发系列--视图切换

    概述 在iOS开发中视图的切换是很频繁的,独立的视图应用在实际开发过程中并不常见,除非你的应用足够简单.在iOS开发中常用的视图切换有三种,今天我们将一一介绍: UITabBarController ...

  8. Visual Studio跨平台开发实战(3) - Xamarin iOS多页面应用程式开发

    原文 Visual Studio跨平台开发实战(3) - Xamarin iOS多页面应用程式开发 前言 在前一篇教学中, 我们学会如何使用Visual Studio 搭配Xcode 进行iOS基本控 ...

  9. ViewPager和View组合 实现页面的切换

    //--------------主页面------------------------------- package com.bw.test; import java.util.ArrayList;i ...

随机推荐

  1. libviso中的姿态解算【转载】

    这篇关于libviso的文章,本人已投稿在泡泡机器人微信公众号中,放到这里,作学习笔记用. libviso一直以来被称为在视觉里程计(VO)中的老牌开源算法.它通过corner,chessboard两 ...

  2. php投票练习

    一.题目要求:建立如下两个数据库,实现投票和%的统计结果: 二.具体编写方法: (1).建立数据库: 里面的蓝色背景的表格名称就是我们所需的表格! 表格内容如下: 表名:diaoyantimu 表名: ...

  3. Linux SVN安装部署

    系统:centos6.3 svn: subversion-1.6.1 apache: httpd-2.2.29 //创建svn路径 [root@localhost /]# mkdir svn [roo ...

  4. iOS 推送问题全解答《十万个为啥吖?》

    Q 1:为啥收不到推送(1)? 如果收到推送时,App 在前台运行,那么: iOS 10 before 顶部横幅不会弹出.没有任何展示,你以为「没有收到推送」. iOS 10 after 如果没有实现 ...

  5. 【DOORS】如何基于DOORS实施需求管理

    引言 IBM Rational DOORS,简称DOORS,是被业界广泛认可的需求管理工具,在国内外需求管理领域具有较高的市场占有率.需求管理作为传统的工程领域,理论发展相对成熟和健全.随着越来越多的 ...

  6. 4001: [TJOI2015]概率论

    4001: [TJOI2015]概率论 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 262  Solved: 108[Submit][Status] ...

  7. SEO-长尾词与分词技术

        长尾关键词与分词技术 长尾关键词:网站非目标关键词,能给网站带来流量的关键词. 例如:主关键词是成都网站建设 那么,跟成都网站建设相关的词,就叫做长尾关键词. 比如:成都网站建设哪里好?成都网 ...

  8. i++和++i 的学习

    在<<C++ Primer>> 第四版 5.5中讲解到: ++i:返回操作之后的结果,且是个左值. i++:返回操作之前的结果,且是有右值. // 2_3.cpp : 定义控制 ...

  9. multiSelect 下拉多选插件

    multiSelect是一款很好用的下拉多选插件,可以在下拉框中实现多选框,全选及取消全选等方法.使用方法:1.引用 multiSelect.css及 multiSelect.js.下载地址 http ...

  10. 【NFS项目实战二】NFS共享数据的时时同步推送备份

    [NFS项目实战二]NFS共享数据的时时同步推送备份 标签(空格分隔): Linux服务搭建-陈思齐 ---本教学笔记是本人学习和工作生涯中的摘记整理而成,此为初稿(尚有诸多不完善之处),为原创作品, ...