天天看點

半糖iOS版首頁實作與基本原理揭秘實作上滑的的效果頭部三個View的坐标改變添加下拉重新整理的文字效果添加分類滑動為分類滑動添加上下滑動的互動最後

很久以前,一個學弟的曾問過我如何實作半糖iOS版本首頁效果,我當時一看覺得這個效果挺酷炫,然後去github上搜了一下,很多自稱是仿半糖首頁的,我下載下傳之後發現其實很多代碼都沒有實作主要的代碼。有些代碼也做了一些簡單的嘗試,但是最後都放棄了,是以說這個效果還是沒有很好的實作。我于是打算研究一下這個有趣的效果,經過工作之餘一段時間的研究。有時候路上也會想一想,做了很多的嘗試,一點一點的把遇到的問題解決了。于是寫下這篇文章,把自己的一些嘗試和想法與大家分享。

有的開發者可能會覺得這麼簡單的東西别拿來忽悠我,可以自己親自嘗試去做一個,并沒有想象的那麼簡單。

實作上滑的的效果

這一步是首頁效果的基礎,實作這一步後,才有繼續其它步的必要,這裡面難度不是很大,關鍵是要想到一個好方法不容易。下面就具體講講是如何實作的。

有一點可以确定的是,使用的肯定是KVO的做法。通過監聽

contentOffset

的變化來進行相應的處理。但是具體怎麼做,怎麼來劃分層次,真的是一個讓人腦殼痛的問題。

怎麼下手呢,開始真的毫無思緒,然後想到了一個利器,Reveal。不管别的,先用Reveal看看圖層結構再說。關于Reveal的使用,在我的另外一篇文章裡面有。使用Reveal檢視任意iOS App的圖層結構。通過圖層檢視後,下面是一個ScrollView,上面是幾個TableView。于是這個立刻把我帶入了坑,很多網上的Demo都是這樣嘗試,把TableView放到ScrollView上面,然後對它們的

contentOffset

都添加監聽。通過判斷偏移量來禁用TableView或是Scrollew的手勢。經過無數次的嘗試最後還是放棄了,手勢沖突這個問題不可能這樣很好的解決。

然後我搜了資料,有人說可以使用

contentInset

這個屬性,這是用來設定

ScrollView

及其子類的内容顯示區域,通過改變這個屬性的值,達到類似的滑動的效果。也就是在KVO的實作方法裡面不斷的改變

contentInset

的值,然後模拟上推的效果。沒有試過這個屬性的可以嘗試一下。我使用之後,出現的問題就是卡頓,特别卡,而且很難控制值,這就造成了完全沒有流暢性可言。間接說明了使用這個根本沒法達到這個效果。

然後我實在是想不到什麼好辦法,打算用

UISwipGesturer

,不過想想這就算了吧。太愚蠢了,而且也會很麻煩。像我這樣懶的,總想少寫幾行代碼。怎麼辦呢,再想想。有一天在地鐵上拿出半糖的APP來研究,突然靈光一閃,想到了。因為既然這麼流暢,那一定是使用了原生的

UITableView

,然後再使用

scrollIndicatorInsets

這個屬性就可以僞裝出

tableView

是從下面開始的效果,然後我的

tableView

從坐标(0,0)的位置開始。上面添加一個空白的View把内容往下面撐即可實作類似的效果。如圖(為了便于看清楚布局,我給每個視圖留了一個邊距)

半糖iOS版首頁實作與基本原理揭秘實作上滑的的效果頭部三個View的坐标改變添加下拉重新整理的文字效果添加分類滑動為分類滑動添加上下滑動的互動最後

能想到這一步其實完成了很大的工作了。接下來就是給上面的搜尋框和輪播頁面添加坐标變化的事件了。

頭部三個View的坐标改變

給TableView添加監聽,然後在如下方法裡面根據

contentOffset

的值改變輪播和分類選擇控價的坐标。

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
    UITableView *tableView = (UITableView *)object;

    if (![keyPath isEqualToString:@"contentOffset"]) {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
        return;
    }

    CGFloat tableViewoffsetY = tableView.contentOffset.y;
    self.lastTableViewOffsetY = tableViewoffsetY;

    if ( tableViewoffsetY>= && tableViewoffsetY<=) {
        self.segmentScrollView.frame = CGRectMake(, -tableViewoffsetY, SCREEN_WIDTH, );
        self.cycleScrollView.frame = CGRectMake(, -tableViewoffsetY, SCREEN_WIDTH, ); 
    }else if( tableViewoffsetY < ){
        self.segmentScrollView.frame = CGRectMake(, , SCREEN_WIDTH, );
        self.cycleScrollView.frame = CGRectMake(, , SCREEN_WIDTH, );

    }else if (tableViewoffsetY > ){
        self.segmentScrollView.frame = CGRectMake(, , SCREEN_WIDTH, );
        self.cycleScrollView.frame = CGRectMake(, -, SCREEN_WIDTH, );
    }
}
           

我們需要添加一個坐标的限制,因為偏移量有時候會無限大或者是無限小。而我們的輪播和分類選擇器的區間是固定不變的。是以需要找對坐标進行限制,一旦偏移量超過了這個坐标就不進行改變,而是保持固定的值不變。

為了子產品的劃厘清晰一些,我把上面的搜素框單獨的劃分到了

JQHeaderView

裡面。是以需要把外面的

tableView

傳到裡面去。然後在裡面同樣進行了監聽然後事件處理。

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{

    if (![keyPath isEqualToString:@"contentOffset"]) {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
        return;
    }
    UITableView *tableView = (UITableView *)object;
    CGFloat tableViewoffsetY = tableView.contentOffset.y;

    UIColor * color = [UIColor whiteColor];
    CGFloat alpha = MIN(, tableViewoffsetY/);

    self.backgroundColor = [color colorWithAlphaComponent:alpha];

    if (tableViewoffsetY < ){

        [UIView animateWithDuration: animations:^{
            self.searchButton.hidden = NO;
            [self.emailButton setBackgroundImage:[UIImage imageNamed:@"home_email_black"] forState:UIControlStateNormal];
            self.searchBar.frame = CGRectMake(-(self.width-), , self.width-, );
            self.emailButton.alpha = -alpha;
            self.searchButton.alpha = -alpha;


        }];
    } else if (tableViewoffsetY >= ){

        [UIView animateWithDuration: animations:^{
            self.searchBar.frame = CGRectMake(, , self.width-, );
            self.searchButton.hidden = YES;
            self.emailButton.alpha = ;
            [self.emailButton setBackgroundImage:[UIImage imageNamed:@"home_email_red"] forState:UIControlStateNormal];
        }];
    }

}
           

做完以上工作後,我們應該可以看到的是這樣的效果。

半糖iOS版首頁實作與基本原理揭秘實作上滑的的效果頭部三個View的坐标改變添加下拉重新整理的文字效果添加分類滑動為分類滑動添加上下滑動的互動最後

添加下拉重新整理的文字效果

下拉重新整理我單獨分離出來了

JQRefreshHeaader

檔案。實作的原理一樣是用了KVO。使用偏移量進行相應的圖檔替換,在某個偏移量開始出現圖檔,在另一個偏移量結束。這中間每兩個像素的偏移量替換為一張圖檔, 然後隐藏其它所有的圖檔,就顯示目前的圖檔,當偏移量的絕對值大于某個值時,顯示所有的圖檔,小于某個值時隐藏所有的圖檔。當然這裡面還值得推敲,感覺可以簡化一些步驟,

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{


    if (![keyPath isEqualToString:@"contentOffset"]) {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
        return;
    }

    UITableView *tableView = (UITableView *)object;
    CGFloat tableViewoffsetY = tableView.contentOffset.y;

    if ( tableViewoffsetY <=   &&tableViewoffsetY > -) {

        [self hideAllImageView];

    }else if(tableViewoffsetY < -){

        if (tableViewoffsetY < -) {

            [self showAllImageView];
        }else {
            CGFloat offset = fabs(tableViewoffsetY)-;
            NSInteger imageCount = offset/;//兩個偏移量切換一張圖檔
            [self hideImageViewExcept:imageCount];
        }

    }else if (tableViewoffsetY <){

    }

}
           

把這裡面使用的圖檔是我自己用PS做的,是以看起來很醜,實作後的效果如下

半糖iOS版首頁實作與基本原理揭秘實作上滑的的效果頭部三個View的坐标改變添加下拉重新整理的文字效果添加分類滑動為分類滑動添加上下滑動的互動最後

添加分類滑動

首先是單純的實作左右滑動的效果,這裡我使用了簡單的ScrollView來實作這個效果。上面的分類選擇是一個ScrollView,下面的也是ScrlloView,在切換時候修改對應的偏移量即可。當然實作方式很多,網上也有很多的架構,不過該項目的分類選擇控件需要實作上下滑動,是以我還是自己實作了。實作原理很簡單

首先是點選上面的分類控件實作下面ScrollView的滑動。我們隻需要在改變改變分類控件偏移量的同時改變下面内容ScrollView的偏移量

[UIView animateWithDuration: animations:^{
        if (index == ) {
            self.currentSelectedItemImageView.frame = CGRectMake(PADDING, self.segmentScrollView.frame.size.height - ,currentButton.frame.size.width, );

        }else{

            UIButton *preButton = self.titleButtons[index - ];

            float offsetX = CGRectGetMinX(preButton.frame)-PADDING*;

            [self.segmentScrollView scrollRectToVisible:CGRectMake(offsetX, , self.segmentScrollView.frame.size.width, self.segmentScrollView.frame.size.height) animated:YES];

            self.currentSelectedItemImageView.frame = CGRectMake(CGRectGetMinX(currentButton.frame), self.segmentScrollView.frame.size.height-, currentButton.frame.size.width, );
        }
        self.bottomScrollView.contentOffset = CGPointMake(SCREEN_WIDTH *index, );

    }];
           

然後們在滑動下面的ScrollView的時候滑動在代理方法裡面分類選擇控件也跟着進行滑動即可。

[UIView animateWithDuration: animations:^{
        if (index == ) {
            self.currentSelectedItemImageView.frame = CGRectMake(PADDING, self.segmentScrollView.frame.size.height - ,currentButton.frame.size.width, );

        }else{


            UIButton *preButton = self.titleButtons[index - ];

            float offsetX = CGRectGetMinX(preButton.frame)-PADDING*;

            [self.segmentScrollView scrollRectToVisible:CGRectMake(offsetX, , self.segmentScrollView.frame.size.width, self.segmentScrollView.frame.size.height) animated:YES];

            self.currentSelectedItemImageView.frame = CGRectMake(CGRectGetMinX(currentButton.frame), self.segmentScrollView.frame.size.height-, currentButton.frame.size.width, );
        }

    }];

           

這樣簡單實作的滑動控件肯定有很多值得優化的地方,最簡單優化就是把下面的

UIScrollView

換成

UICollectionView

,這樣就可以複用Cell進而優化記憶體。

實作後的基本效果如下

半糖iOS版首頁實作與基本原理揭秘實作上滑的的效果頭部三個View的坐标改變添加下拉重新整理的文字效果添加分類滑動為分類滑動添加上下滑動的互動最後

為分類滑動添加上下滑動的互動

到這一步感覺這也是一件非常費腦子的事情,每一個分類都要能滑動上面的分類控件和推薦控件,而且在滑動到任意位置之後,切換分類後還需要能夠繼續滑動視圖。

首先我們來解決第一個問題,如何讓所有的分類都可以有類似的效果呢。其實很簡單,弄一個數組,把所有的控制器裡面的TableView放到裡面去,然後給所有的TableView的contentOffset都添加KVO就好了。當然基本思路就是這樣,具體實踐的時候可能會遇到很多的問題,讀者可以自行嘗試研究。當然我們的下拉重新整理控件也需要添加到每一個tableView上面

for (int i = ; i<CATEGORY.count; i++) {

            JSDTableViewController *jsdTableViewController = [[JSDTableViewController alloc] init];
            jsdTableViewController.view.frame = CGRectMake(SCREEN_WIDTH * i, , SCREEN_WIDTH, SCREEN_HEIGHT);

            jsdTableViewController.view.backgroundColor = colors[i];
            [self.bottomScrollView addSubview:jsdTableViewController.view];

            [self.controlleres addObject:jsdTableViewController];
            [self.tableViews addObject:jsdTableViewController.tableView];

            //下拉重新整理動畫
           JQRefreshHeaader *jqRefreshHeader  = [[JQRefreshHeaader alloc] initWithFrame:CGRectMake(, , SCREEN_WIDTH, )];
            jqRefreshHeader.backgroundColor = [UIColor whiteColor];
            jqRefreshHeader.tableView = jsdTableViewController.tableView;
            [jsdTableViewController.tableView.tableHeaderView addSubview:jqRefreshHeader];


            NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
            [jsdTableViewController.tableView addObserver:self forKeyPath:@"contentOffset" options:options context:nil];

        }
           

這樣的話,我在切換之後其它的控制器也可以實作上滑的效果。不過如果我滑了一部分沒有到最上面或是最下面的話,就會出現錯亂的情況。要保持各個控制器能夠連續滑動。我們可以記錄下上一次的偏移量,然後在切換的時候對其它的tableView也設定同樣的偏移量,這樣就可以保證都保持統一的偏移量,當然我做了類似半糖的判斷,就是當偏移量大于最大值當時候設定為最大值,小于最小值的時候設定為0.

self.currentTableView  = self.tableViews[index];
    for (UITableView *tableView in self.tableViews) {

        if ( self.lastTableViewOffsetY>= &&  self.lastTableViewOffsetY<=) {
            tableView.contentOffset = CGPointMake(,  self.lastTableViewOffsetY);

        }else if(  self.lastTableViewOffsetY < ){
            tableView.contentOffset = CGPointMake(, );

        }else if ( self.lastTableViewOffsetY > ){
            tableView.contentOffset = CGPointMake(, );
        }

    }

           

最後實作的效果如下所示,感覺還可以吧。

半糖iOS版首頁實作與基本原理揭秘實作上滑的的效果頭部三個View的坐标改變添加下拉重新整理的文字效果添加分類滑動為分類滑動添加上下滑動的互動最後

最後

裡面的點選事件我都沒有添加,這些都很簡單。。不想浪費時間在這上面。

工程裡面的

Resource

目錄下是我抓包到的一些資料,為了防止半糖修改權限下載下傳不到圖檔,我把需要的圖檔都提前下載下傳一份,一旦下載下傳失敗就使用本地圖檔,還有一些自己的圖檔。為了友善大家學習使用,我沒有把圖檔檔案放入

Assets.xcassets

目錄下,因為放入這裡,對于需要圖檔學習複用的時候需要一張圖檔一隻圖檔拷貝出來,費時間費力氣。裡面關于半糖的資料僅用于交流和學習,若用于商業用途,後果自負。

如果你部落格給你了一些幫助,希望點一波CSDN的關注。最後附上Demo位址JSDBanTangHomeDemo,如果覺得滿意,請給個Satr。

繼續閱讀