實現(xiàn)簡書個人中心UI效果

2018年6月25日更新:
群里有人提到點按header上的UIButton無法橫向滾動的問題

8月9日更新:
1、代碼重構(gòu),三個的tableView分別解耦到三個控制器中管理
2、添加MJRefresh,點擊右上角開關(guān)可動態(tài)添加/移除刷新控件,最新代碼可到GitHub 上下載

update.gif

這兩天比較閑,簡單實現(xiàn)了一下個人中心頁面scrollView嵌套的效果,也就是下邊這個頁面,大家感受一下先:

JSDemo2.gif

首先講下實現(xiàn)思路,很多人看到這個界面覺得是多個scrollView嵌套實現(xiàn)的,其實蘋果不推薦scrollView的嵌套,特別是有同方向滾動行為的scrollView嵌套,因為系統(tǒng)也不知道你當(dāng)前想要哪個scrollView滾動。那我就在想能不能想個辦法繞過這個問題,最終我采取了這樣的方案,底部一個橫向滑動的scrollView,上邊放著左中右三個tableView,這樣就避開了scrollView同方向滑動沖突問題,每個tableView上都有個空白的headerView占位使用(看了下邊的講解就明白了),然后再在控制器view上方一個view做真實的headerView,放控制器的view上是為了不讓它隨任何一個tableView滑動而滑動,而是在某些時候改變其y值來制造和三個tableView共同滾動的假象!接下來請看具體講解和代碼實現(xiàn):

控制器的view上是個橫向滑動的scrollView,上邊放著三個tableView,主要代碼如下


// 底部橫向滑動的scrollView
    UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:scrollView];
    scrollView.backgroundColor = [UIColor colorWithWhite:0.998 alpha:1];
    
    // 綁定代理
    scrollView.delegate = self;
    
    // 設(shè)置滑動區(qū)域
    scrollView.contentSize = CGSizeMake(3 * WZBScreenWidth, 0);
    scrollView.pagingEnabled = YES;
    self.scrollView = scrollView;

    // 創(chuàng)建三個tableView
    self.leftTableView = [self tableViewWithX:0];
    self.centerTableView = [self tableViewWithX:WZBScreenWidth];
    self.rightTableView = [self tableViewWithX:WZBScreenWidth * 2];

// 創(chuàng)建tableView
- (UITableView *)tableViewWithX:(CGFloat)x {
    
    UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectMake(x, 0, WZBScreenWidth, WZBScreenHeight - 0)];
    [self.scrollView addSubview:tableView];
    tableView.backgroundColor = [UIColor colorWithWhite:0.998 alpha:1];
    tableView.showsVerticalScrollIndicator = NO;
    
    // 代理&&數(shù)據(jù)源
    tableView.delegate = self;
    tableView.dataSource = self;
    
    // 創(chuàng)建一個假的headerView,高度等于控制器view上那個headerView的高度(這里暫且寫死150+44)
    UIView *headerView = [[UIView alloc] initWithFrame:(CGRect){0, 0, WZBScreenWidth, 194}];
    
    tableView.tableHeaderView = headerView;
    return tableView;
}

然后為每個tableView創(chuàng)建一個假的headerView,高度等于控制器view上那個headerView的高度(這里暫且寫死150+44),為什么這樣做?因為我要在控制器的view上放一個headerView,這樣就能方便的控制headerView的y值,使其達(dá)到預(yù)期的效果,這樣也創(chuàng)造出了一個假象,就是其實上邊的headerView跟三個tableView沒有什么關(guān)系,只是在滑動的時候動態(tài)的改變headerView的y值,當(dāng)tableView滑動到某個位置的時候,讓其能夠停留在這個位置。監(jiān)聽tableView滑動的代碼下邊會講到。如圖:

三個tableView

headerView分為兩部分,一部分是上邊的死數(shù)據(jù),其實就是一個自定義view,上邊幾個label。下邊部分是個類似segmeng的自定義控件,是用我之前寫的WZBSegmentedControl實現(xiàn)的,具體請看干貨!老司機(jī)工作中用到的自定義控件,總有一個適合你的(一),創(chuàng)建代碼為:


// 創(chuàng)建segmentedControl
    WZBSegmentedControl *sectionView = [WZBSegmentedControl segmentWithFrame:(CGRect){0, 150, WZBScreenWidth, 44} titles:@[@"動態(tài)", @"文章", @"更多"] tClick:^(NSInteger index) {
        
        // 改變scrollView的contentOffset
        self.scrollView.contentOffset = CGPointMake(index * WZBScreenWidth, 0);
        
        
        // 刷新最大OffsetY
        [self reloadMaxOffsetY];
    }];
    
    // 賦值給全局變量
    self.sectionView = sectionView;
    
    // 設(shè)置其他顏色
    [sectionView setNormalColor:[UIColor blackColor] selectColor:[UIColor redColor] sliderColor:[UIColor redColor] edgingColor:[UIColor clearColor] edgingWidth:0];
    
    // 去除圓角
    sectionView.layer.cornerRadius = sectionView.backgroundView.layer.cornerRadius = .0f;
    
    // 加兩條線
    for (NSInteger i = 0; i < 2; i++) {
        UIView *line = [UIView new];
        line.backgroundColor = WZBColor(228, 227, 230);
        line.frame = CGRectMake(0, 43.5 * i, WZBScreenWidth, 0.5);
        [sectionView addSubview:line];
    }

header

重點是下邊的一些代理方法的監(jiān)聽:

一、正在滾動的時候(本demo定死了headerView的高度為150+44)

如果發(fā)現(xiàn)是tableView在滑動,判斷滑動偏移量是否大于150,如果不大于150,則同步三個tableView的偏移量為當(dāng)前tableView的偏移量,這樣做是讓三個tableView看起來像是在同步移動,然后改變headerView的y值(讓headerView也往上走)。如果偏移量大于等于150,則讓headerView的y值等于-150,就相當(dāng)于停留在這個位置了。如果發(fā)現(xiàn)當(dāng)前滾動的是底部橫向那個scrollView,則需要同步segment的偏移量,代碼如下:


- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    
    // 如果當(dāng)前滑動的是tableView
    if ([scrollView isKindOfClass:[UITableView class]]) {
        
        CGFloat contentOffsetY = scrollView.contentOffset.y;
        
        // 如果滑動沒有超過150
        if (contentOffsetY < 150) {
            
            // 讓這三個tableView的便宜量相等
            self.leftTableView.contentOffset = self.centerTableView.contentOffset = self.rightTableView.contentOffset = scrollView.contentOffset;
            
            // 改變headerView的y值
            CGRect frame = self.headerView.frame;
            CGFloat y = -self.rightTableView.contentOffset.y;
            frame.origin.y = y;
            self.headerView.frame = frame;
            
            // 一旦大于等于150了,讓headerView的y值等于-150,就停留在上邊了
        } else if (contentOffsetY >= 150) {
            CGRect frame = self.headerView.frame;
            frame.origin.y = -150;
            self.headerView.frame = frame;
        }
    }
    
    if (scrollView == self.scrollView) {
        // 改變segmentdControl
        [self.sectionView setContentOffset:(CGPoint){scrollView.contentOffset.x / 3, 0}];
        return;
    }
}

這一步做完之后其實是不夠嚴(yán)謹(jǐn)?shù)?,事實證明scrollViewDidScroll:這個代理方法調(diào)用的并不足夠頻繁,導(dǎo)致一些bug,就是三個tableView并沒有同步,可能其中一個tableView滑了上去,而另外兩個還沒有跟上去。這時候如果左右滑動,就會暴露這個問題,如圖:

導(dǎo)致bug

紅色是tableView的那個假headerView

二、解決辦法就是在開始左右滑動的時候,同步一下三個tableView的偏移量



// 開始拖拽
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
    if (scrollView == self.scrollView) {
        
        // 刷新最大OffsetY
        [self reloadMaxOffsetY];
    }
}

// 刷新最大OffsetY,讓三個tableView同步
- (void)reloadMaxOffsetY {
    
    // 計算出最大偏移量
    CGFloat maxOffsetY = MAXValue(self.leftTableView.contentOffset.y, self.centerTableView.contentOffset.y, self.rightTableView.contentOffset.y);
    
    // 如果最大偏移量大于150,處理下每個tableView的偏移量
    if (maxOffsetY > 150) {
        if (self.leftTableView.contentOffset.y < 150) {
            self.leftTableView.contentOffset = CGPointMake(0, 150);
        }
        if (self.centerTableView.contentOffset.y < 150) {
            self.centerTableView.contentOffset = CGPointMake(0, 150);
        }
        if (self.rightTableView.contentOffset.y < 150) {
            self.rightTableView.contentOffset = CGPointMake(0, 150);
        }
    }
}

這時候,就解決了那個bug,基本功能也完成了,接下來解釋一下位于導(dǎo)航欄上的頭像處理:

為了能讓頭像自由的放大/縮小,并動態(tài)改變其y值,達(dá)到預(yù)期效果,我用了兩個view實現(xiàn),一個是底部普通的view做導(dǎo)航欄titleView,然后再在這個view上放imageView展示頭像,然后監(jiān)聽scrollViewDidScroll:方法,一邊改變imageView的大小,一邊改變y值,就實現(xiàn)了簡書的那種效果,注意最大和最小的邊界值,代碼如下:


// 加載頭部頭像
    UIView *avatarView = [[UIView alloc] initWithFrame:(CGRect){0, 0, 35, 35}];
    avatarView.backgroundColor = [UIColor clearColor];
    
    UIImageView *avatar = [[UIImageView alloc] initWithFrame:(CGRect){0, 26.5, 35, 35}];
    avatar.image = [UIImage imageNamed:@"demo1"];
    avatar.layer.masksToBounds = YES;
    avatar.layer.cornerRadius = 35 / 2;
    [avatarView addSubview:avatar];
    self.navigationItem.titleView = avatarView;
    avatar.transform = CGAffineTransformMakeScale(2, 2);
    
    self.avatar = avatar;

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    
    // 處理頂部頭像
    CGFloat scale = scrollView.contentOffset.y / 80;
    
    // 如果大于80,==1,小于0,==0
    if (scrollView.contentOffset.y > 80) {
        scale = 1;
    } else if (scrollView.contentOffset.y <= 0) {
        scale = 0;
    }
    
    // 縮放
    self.avatar.transform = CGAffineTransformMakeScale(2 - scale, 2 - scale);
    
    // 同步y(tǒng)值
    CGRect frame = self.avatar.frame;
    frame.origin.y = (1 - scale) * 8;
    self.avatar.frame = frame;
}

打完收工,最后附上源碼:JianShuMianPage 歡迎star

我的更多文章:老司機(jī)帶你飛

喜歡可以隨手點個喜歡或者關(guān)注一下哦!
您的支持是我最大的動力??!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容