天天看點

OC版寫一個快速內建網易新聞,騰訊視訊,頭條首頁的ZJScrollPageView,實作視圖關聯

最終效果

OC版寫一個快速內建網易新聞,騰訊視訊,頭條首頁的ZJScrollPageView,實作視圖關聯
OC版寫一個快速內建網易新聞,騰訊視訊,頭條首頁的ZJScrollPageView,實作視圖關聯
OC版寫一個快速內建網易新聞,騰訊視訊,頭條首頁的ZJScrollPageView,實作視圖關聯
OC版寫一個快速內建網易新聞,騰訊視訊,頭條首頁的ZJScrollPageView,實作視圖關聯
OC版寫一個快速內建網易新聞,騰訊視訊,頭條首頁的ZJScrollPageView,實作視圖關聯
OC版寫一個快速內建網易新聞,騰訊視訊,頭條首頁的ZJScrollPageView,實作視圖關聯
OC版寫一個快速內建網易新聞,騰訊視訊,頭條首頁的ZJScrollPageView,實作視圖關聯
OC版寫一個快速內建網易新聞,騰訊視訊,頭條首頁的ZJScrollPageView,實作視圖關聯

一.構思部分:

打算分為三個部分, 滑塊部分View, 内容顯示部分View, 包含滑塊View和顯示内容View的View,以便于可以靈活的使用

1. 滑塊部分View

1.1 要實作滑塊可以滾動, 考慮可以直接使用collectionView, 但是這裡還是直接使用scrollView友善裡面的控件布局

1.2 要實作滑塊的點選, 可以直接使用UIButton, 但是經過嘗試, 要讓button的frame随着文字的寬度來自适應實作比較麻煩, 是以選擇了使用UILabel添加點選手勢來實作點選事件,這裡使用了closures來實作(可以使用代理模式)

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. 基本屬性

// 滾動條
@property (weak, nonatomic) UIView *scrollLine;
// 遮蓋
@property (weak, nonatomic) UIView *coverLayer;
// 滾動scrollView
@property (weak, nonatomic) UIScrollView *scrollView;
// 背景ImageView
@property (weak, nonatomic) UIImageView *backgroundImageView;
// 附加的按鈕
@property (weak, nonatomic) UIButton *extraBtn;
/// 所有的title設定 -> 使用了一個class ZJSegmentStyle, 将可以自定義的部分全部暴露了出來, 使用的時候就可以比較友善的自定義很多屬性  -> 初始化時傳入
@property (strong, nonatomic) ZJSegmentStyle *segmentStyle;
// 所有的标題
@property (strong, nonatomic) NSArray *titles;
// 用于懶加載計算文字的rgb內插補點, 用于顔色漸變的時候設定
@property (strong, nonatomic) NSArray *deltaRGB;
@property (strong, nonatomic) NSArray *selectedColorRgb;
@property (strong, nonatomic) NSArray *normalColorRgb;
/** 緩存所有标題label */
@property (nonatomic, strong) NSMutableArray *titleLabels;
// 緩存計算出來的每個标題的寬度
@property (nonatomic, strong) NSMutableArray *titleWidths;
// 響應标題點選
@property (copy, nonatomic) TitleBtnOnClickBlock titleBtnOnClick;

           

邏輯處理

#pragma mark - life cycle
- (instancetype)initWithFrame:(CGRect )frame segmentStyle:(ZJSegmentStyle *)segmentStyle titles:(NSArray *)titles titleDidClick:(TitleBtnOnClickBlock)titleDidClick {
    if (self = [super initWithFrame:frame]) {
        self.segmentStyle = segmentStyle;
        self.titles = titles;
        self.titleBtnOnClick = titleDidClick;
        _currentIndex = ;
        _oldIndex = ;
        _currentWidth = frame.size.width;

        if (!self.segmentStyle.isScrollTitle) { // 不能滾動的時候就不要把縮放和遮蓋或者滾動條同時使用, 否則顯示效果不好

            self.segmentStyle.scaleTitle = !(self.segmentStyle.isShowCover || self.segmentStyle.isShowLine);
        }

        // 這個函數裡面設定了基本屬性中的titles, labelsArray, titlesWidthArray,并且添加了label到scrollView上
        [self setupTitles];
        // 這個函數裡面設定了遮蓋, 滾動條,和label的初始化位置
        [self setupUI];

    }

    return self;
}

#pragma mark - button action
  -> 處理點選title的時候實作标題的切換,和遮蓋,滾動條...的位置調整, 同時執行了相應點選得兒blosure, 以便于外部相應點選方法
- (void)titleLabelOnClick:(UITapGestureRecognizer *)tapGes {

    ZJCustomLabel *currentLabel = (ZJCustomLabel *)tapGes.view;

    if (!currentLabel) {
        return;
    }

    _currentIndex = currentLabel.tag;
    // 同步更改UI
    [self adjustUIWhenBtnOnClickWithAnimate:true];
}

- (void)adjustTitleOffSetToCurrentIndex:(NSInteger)currentIndex  -> 更改scrollview的contentOffSet來居中顯示title


     // 手動滾動時需要提供動畫效果
- (void)adjustUIWithProgress:(CGFloat)progress oldIndex:(NSInteger)oldIndex currentIndex:(NSInteger)currentIndex -> 提供給外部來執行标題切換之間的動畫效果(注意這個方法裡面進行了一些簡單的數學計算以便于"同步" 滾動滾動條和cell )
    這裡以滑塊的位置x變化為例, 其他類似

    CGFloat xDistance = currentLabel.zj_x - oldLabel.zj_x;
    這個xDistance就是滑塊将要從一個label下面移動到下一個label下面所需要移動的路程,

    這個progress是外界提供來的, 表示目前已經移動的百分比( --- )是多少了,是以可以改變目前滑塊的x為之前的x + 已經完成滾動的距離(xDistance * progress)
    scrollLine?.frame.origin.x = oldLabel.frame.origin.x + xDistance * progress

    這樣就達到了滑塊的位置随着提供的progress同步移動

    其他的遮蓋 和顔色漸變的處理類似
           

b 内容顯示部分View

基本屬性

// 用于處理重用和内容的顯示
@property (weak, nonatomic) UICollectionView *collectionView;
// collectionView的布局
@property (strong, nonatomic) UICollectionViewFlowLayout *collectionViewLayout;
/** 避免循環引用*/
@property (weak, nonatomic) ZJScrollSegmentView *segmentView;

@property (weak, nonatomic) UIButton *extraBtn;
// 父類 用于處理添加子控制器  使用weak避免循環引用
@property (weak, nonatomic) UIViewController *parentViewController;
// 當這個屬性設定為YES的時候 就不用處理 scrollView滾動的計算
@property (assign, nonatomic) BOOL forbidTouchToAdjustPosition;
// 所有的子控制器
@property (strong, nonatomic) NSArray *childVcs;
           

邏輯處理

#pragma mark - life cycle 

- (instancetype)initWithFrame:(CGRect)frame childVcs:(NSArray *)childVcs segmentView:(ZJScrollSegmentView *)segmentView parentViewController:(UIViewController *)parentViewController {

    if (self = [super initWithFrame:frame]) {
        self.childVcs = childVcs;
        self.parentViewController = parentViewController;
        self.segmentView = segmentView;
        _oldIndex = ;
        _currentIndex = ;
        _oldOffSetX = ;
        self.forbidTouchToAdjustPosition = NO;
        // 觸發懶加載
        self.collectionView.backgroundColor = [UIColor whiteColor];
        [self commonInit];
    }
    return self;
}

/** 給外界可以設定ContentOffSet的方法 比如點選了标題的時候需要更改内容顯示調用*/
- (void)setContentOffSet:(CGPoint)offset animated:(BOOL)animated

/** 給外界重新整理視圖的方法  用于動态設定子控制器和标題*/
- (void)reloadAllViewsWithNewChildVcs:(NSArray *)newChileVcs {
  // 這種處理是結束子控制器和父控制器的關系
    for (UIViewController *childVc in self.childVcs) {
        [childVc willMoveToParentViewController:nil];
        [childVc.view removeFromSuperview];
        [childVc removeFromParentViewController];
    }

}


#pragma mark - UICollectionViewDelegate --- UICollectionViewDataSource

- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
    return ;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    return self.childVcs.count;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellID forIndexPath:indexPath];
    // 移除subviews 避免重用内容顯示錯誤
    [cell.contentView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
    // 這裡建立子控制器和父控制器的關系  -> 當然在這之前已經将對應的子控制器添加到了父控制器了, 隻不過還沒有建立完成
    UIViewController *vc = (UIViewController *)self.childVcs[indexPath.row];
    vc.view.frame = self.bounds;
    [cell.contentView addSubview:vc.view];
    [vc didMoveToParentViewController:self.parentViewController];

    return cell;
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView  -> 這個代理方法中處理了滾動的下标和進度  
           

3. “ZJScrollPageView.h”

這一部分是将segmentView和ContentView封裝在一起的, 便于使用者直接使用, 但是您也可以參照這裡面的實作, 任意組合segmentView和ContentView的位置, 在demo中也有示例
詳細請移步OC源碼, swift源碼,裡面都有詳細的Demo使用示例 如果您覺得有幫助,不妨給個star鼓勵一下, 歡迎關注