小码哥-玩转【斗鱼直播APP】系列之首页布局分析实现

本帖最后由 王红元老师 于 2016-9-14 21:25 编辑

布局首页分析实现

完成最终效果

  • 这个效果还是挺常见的哈
  • 效果分析:
    • 顶部是一个工具类,并且为了该工具类可以复用。(其他界面也用到)最好是单独封装起来
    • 中间内容可以左右滚动,并且有分页效果。可以通过封装UIView,并且里面添加UICollectionView方式。

封装顶部的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
  1. private lazy var scrollView : UIScrollView = {
  2. let scrollView = UIScrollView(frame: self.bounds)
  3. scrollView.showsHorizontalScrollIndicator = false
  4. scrollView.scrollsToTop = false
  5. scrollView.bounces = false
  6. return scrollView
  7. }()
  8. private lazy var scrollLine : UIView = {
  9. let scrollLine = UIView()
  10. scrollLine.backgroundColor = kSelectTitleColor
  11. return scrollLine
  12. }()
  13. private func setupUI() {
  14. // 1.添加scrollView
  15. addSubview(scrollView)
  16. // 2.初始化labels
  17. setupTitleLabels()
  18. // 3.添加定义的线段和滑动的滑块
  19. setupBottomlineAndScrollline()
  20. }
  21. private func setupTitleLabels() {
  22. let titleY : CGFloat = 0
  23. let titleH : CGFloat = bounds.height - kScrollLineH
  24. let count = titles.count
  25. for (index, title) in titles.enumerate() {
  26. // 1.创建Label
  27. let label = UILabel()
  28. // 2.设置Label的属性
  29. label.text = title
  30. label.tag = index
  31. label.textAlignment = .Center
  32. label.textColor = kNormalTitleColor
  33. label.font = UIFont.systemFontOfSize(16.0)
  34. titleLabels.append(label)
  35. // 3.设置label的frame
  36. var titleW : CGFloat = 0
  37. var titleX : CGFloat = 0
  38. if !isScrollEnable {
  39. titleW = bounds.width / CGFloat(count)
  40. titleX = CGFloat(index) * titleW
  41. } else {
  42. let size = (title as NSString).boundingRectWithSize(CGSizeMake(CGFloat(MAXFLOAT), 0), options: .UsesLineFragmentOrigin, attributes: [NSFontAttributeName : label.font], context: nil)
  43. titleW = size.width
  44. if index != 0 {
  45. titleX = CGRectGetMaxX(titleLabels[index - 1].frame) + kTitleMargin
  46. }
  47. }
  48. label.frame = CGRect(x: titleX, y: titleY, width: titleW, height: titleH)
  49. // 4.将Label添加到父控件中
  50. scrollView.addSubview(label)
  51. // 5.监听label的点击
  52. label.userInteractionEnabled = true
  53. let tapGes = UITapGestureRecognizer(target: self, action: #selector(self.titleLabelClick(_:)))
  54. label.addGestureRecognizer(tapGes)
  55. }
  56. }
  57. private func setupBottomlineAndScrollline() {
  58. // 1.添加bottomline
  59. let bottomline = UIView()
  60. bottomline.frame = CGRect(x: 0, y: bounds.height - 0.5, width: bounds.width, height: 0.5)
  61. bottomline.backgroundColor = UIColor.lightGrayColor()
  62. addSubview(bottomline)
  63. // 2.设置滑块的view
  64. addSubview(scrollLine)
  65. guard let firstLabel = titleLabels.first else { return }
  66. let lineX = firstLabel.frame.origin.x
  67. let lineY = bounds.height - kScrollLineH
  68. let lineW = firstLabel.frame.width
  69. let lineH = kScrollLineH
  70. scrollLine.frame = CGRect(x: lineX, y: lineY, width: lineW, height: lineH)
  71. firstLabel.textColor = kSelectTitleColor
  72. }

封装顶部的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:- 懒加载属性
  2. private lazy var collectionView : UICollectionView = {
  3. // 1.创建布局
  4. let layout = UICollectionViewFlowLayout()
  5. layout.itemSize = self.bounds.size
  6. layout.minimumLineSpacing = 0
  7. layout.minimumInteritemSpacing = 0
  8. layout.scrollDirection = .Horizontal
  9. // 2.创建collectionView
  10. let collectionView = UICollectionView(frame: self.bounds, collectionViewLayout: layout)
  11. collectionView.showsHorizontalScrollIndicator = false
  12. collectionView.pagingEnabled = true
  13. collectionView.bounces = false
  14. collectionView.scrollsToTop = false
  15. collectionView.dataSource = self
  16. collectionView.delegate = self
  17. collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: kContentCellID)
  18. return collectionView
  19. }()
  20. private func setupUI() {
  21. // 1.添加所有的控制器
  22. for childVc in childVcs {
  23. parentViewController?.addChildViewController(childVc)
  24. }
  25. // 2.添加collectionView
  26. addSubview(collectionView)
  27. }

实现UICollectionView的数据源方法

  • 在返回Cell的方法中,先将cell的contentView中的子控件都移除,防止循环引用造成问题
  • 取出indexPath.item对应的控制器,将控制器的View添加到Cell的contentView中
  1. // MARK:- 遵守UICollectionView的数据源
  2. extension PageContentView : UICollectionViewDataSource {
  3. func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  4. return childVcs.count
  5. }
  6. func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
  7. let cell = collectionView.dequeueReusableCellWithReuseIdentifier(kContentCellID, forIndexPath: indexPath)
  8. // 移除之前的
  9. for subview in cell.contentView.subviews {
  10. subview.removeFromSuperview()
  11. }
  12. // 取出控制器
  13. let childVc = childVcs[indexPath.item]
  14. childVc.view.frame = cell.contentView.bounds
  15. cell.contentView.addSubview(childVc.view)
  16. return cell
  17. }
  18. }

PageTitleView点击改变PageContentView

  • 通过代理将PageTitleView的事件传递出去
  1. /// 定义协议
  2. protocol PageTitleViewDelegate : class {
  3. func pageTitleView(pageTitleView : PageTitleView, didSelectedIndex index : Int)
  4. }
  5. @objc private func titleLabelClick(tapGes : UITapGestureRecognizer) {
  6. // 1.获取点击的下标志
  7. guard let view = tapGes.view else { return }
  8. let index = view.tag
  9. // 2.滚到正确的位置
  10. scrollToIndex(index)
  11. // 3.通知代理
  12. delegate?.pageTitleView(self, didSelectedIndex: index)
  13. }
  • 内部调整
  1. // 内容滚动
  2. private func scrollToIndex(index : Int) {
  3. // 1.获取最新的label和之前的label
  4. let newLabel = titleLabels[index]
  5. let oldLabel = titleLabels[currentIndex]
  6. // 2.设置label的颜色
  7. newLabel.textColor = kSelectTitleColor
  8. oldLabel.textColor = kNormalTitleColor
  9. // 3.scrollLine滚到正确的位置
  10. let scrollLineEndX = scrollLine.frame.width * CGFloat(index)
  11. UIView.animateWithDuration(0.15) {
  12. self.scrollLine.frame.origin.x = scrollLineEndX
  13. }
  14. // 4.记录index
  15. currentIndex = index
  16. }
  • 在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

  • 首先在scrollViewDidScroll的代理方法中就可以监听滚动
  • 那么滚动时,我们有哪些内容是需要传递出去呢?
    • 1> 原来位置的Title颜色会逐渐变暗
    • 2> 目标位置的Title颜色会逐渐变亮
    • 3> 变化程度是和滚动的多少相关
  • 由此得出结论:
    • 我们一共需要获取三个值,并且将这三个值传递出去
    • 1> 起始位置下标值
    • 2> 目标位置下标值
    • 3> 当前滚动的进度
  • 图例分析(缺)
    • 左右滑动时,下标值、进度是不同的
    • 需要经过判断来获取,并且下标值需要防止越界问题
    • 注意:当左滑结束时,此时再+1会出错。因此必须加上targetIndex = sourceIndex,且进度为1
  • 实现代码
  1. extension PageContentView : UICollectionViewDelegate {
  2. func scrollViewWillBeginDragging(scrollView: UIScrollView) {
  3. startOffsetX = scrollView.contentOffset.x
  4. }
  5. func scrollViewDidScroll(scrollView: UIScrollView) {
  6. // 1.定义要获取的内容
  7. var sourceIndex = 0
  8. var targetIndex = 0
  9. var progress : CGFloat = 0
  10. // 2.获取进度
  11. let offsetX = scrollView.contentOffset.x
  12. let ratio = offsetX / scrollView.bounds.width
  13. progress = ratio - floor(ratio)
  14. // 3.判断滑动的方向
  15. if offsetX > startOffsetX { // 向左滑动
  16. sourceIndex = Int(offsetX / scrollView.bounds.width)
  17. targetIndex = sourceIndex + 1
  18. if targetIndex >= childVcs.count {
  19. targetIndex = childVcs.count - 1
  20. }
  21. if offsetX - startOffsetX == scrollView.bounds.width {
  22. progress = 1.0
  23. targetIndex = sourceIndex
  24. }
  25. } else { // 向右滑动
  26. targetIndex = Int(offsetX / scrollView.bounds.width)
  27. sourceIndex = targetIndex + 1
  28. if sourceIndex >= childVcs.count {
  29. sourceIndex = childVcs.count - 1
  30. }
  31. progress = 1 - progress
  32. }
  33. // 4.通知代理
  34. delegate?.pageContentView(self, sourceIndex: sourceIndex, targetIndex: targetIndex, progress: progress)
  35. }
  36. }
  • 根据滚动传入的值,调整PageTitleView
  • 两种颜色必须使用RGB值设置(方便通过RGB实现渐变效果)
  1. private let kNormalRGB : (CGFloat, CGFloat, CGFloat) = (85, 85, 85)
  2. private let kSelectRGB : (CGFloat, CGFloat, CGFloat) = (255, 128, 0)
  3. private let kDeltaRGB = (kSelectRGB.0 - kNormalRGB.0, kSelectRGB.1 - kNormalRGB.1, kSelectRGB.2 - kNormalRGB.2)
  4. private let kNormalTitleColor = UIColor(red: 85/255.0, green: 85/255.0, blue: 85/255.0, alpha: 1.0)
  5. private let kSelectTitleColor = UIColor(red: 255.0/255.0, green: 128/255.0, blue: 0/255.0, alpha: 1.0)
  • 调整scrollLine&两个Label颜色渐变
  1. // MARK:- 对外暴露方法
  2. extension PageTitleView {
  3. func setCurrentTitle(sourceIndex : Int, targetIndex : Int, progress : CGFloat) {
  4. // 1.取出两个Label
  5. let sourceLabel = titleLabels[sourceIndex]
  6. let targetLabel = titleLabels[targetIndex]
  7. // 2.移动scrollLine
  8. let moveMargin = targetLabel.frame.origin.x - sourceLabel.frame.origin.x
  9. scrollLine.frame.origin.x = sourceLabel.frame.origin.x + moveMargin * progress
  10. // 3.颜色渐变
  11. sourceLabel.textColor = UIColor(red: (kSelectRGB.0 - kDeltaRGB.0 * progress) / 255.0, green: (kSelectRGB.1 - kDeltaRGB.1 * progress) / 255.0, blue: (kSelectRGB.2 - kDeltaRGB.2 * progress) / 255.0, alpha: 1.0)
  12. targetLabel.textColor = UIColor(red: (kNormalRGB.0 + kDeltaRGB.0 * progress)/255.0, green: (kNormalRGB.1 + kDeltaRGB.1 * progress)/255.0, blue: (kNormalRGB.2 + kDeltaRGB.2 * progress)/255.0, alpha: 1.0)
  13. }
  14. }
来源:http://bbs.520it.com/forum.php?mod=viewthread&tid=2246
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章