小碼哥-玩轉【鬥魚直播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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章