仿寫知乎日?qǐng)?bào) - 側(cè)邊欄菜單 (Part 3)

轉(zhuǎn)自我自己的 blog

側(cè)邊欄菜單

這一篇 blog 是專門介紹如何仿寫知乎日?qǐng)?bào)的側(cè)邊欄菜單,主要介紹如何構(gòu)建 UI 以及一個(gè)漸隱的圖層效果,和如何控制側(cè)邊欄的顯示和隱藏。

#界面

首先是畫 UI。側(cè)邊欄的 UI 比較簡(jiǎn)單,由上到下分別是:

  1. 頭像和登錄按鈕
  2. 收藏、消息和設(shè)置按鈕
  3. 專欄的列表
  4. 離線下載和切換主題的按鈕

用 xib 做出來(lái)就是這樣子:

xib

值得一提的是專欄列表的底部,在滑動(dòng)的時(shí)候會(huì)有一個(gè)漸隱的效果,如圖所示:

列表底部的漸隱

實(shí)現(xiàn)這個(gè)效果的第一個(gè)想法是在 UITableView 的底部改一個(gè)帶漸變效果的透明 UIView,但是原版中處于漸隱效果的底部 Cell 也是可以點(diǎn)擊的,這樣子遮罩的 UIView 還要處理一下點(diǎn)擊事件,麻煩,棄用!第二個(gè)想法就和 Part 2 中一樣,給 UITableView 設(shè)置一個(gè) CAGradientLayer,專門在底部加一個(gè)漸變的效果,但是試驗(yàn)了幾次,發(fā)現(xiàn)漸變的效果十分不理想,所以也放棄了。

最后的解決方案,也是我無(wú)意中發(fā)現(xiàn)的。UIViewlayer 有個(gè)屬性是 mask, 根據(jù)文檔里面寫的,這也是個(gè) CALayer,用來(lái)給 layer 的 alpha 通道設(shè)置遮罩。但是,這個(gè)屬性必須在滾動(dòng)的時(shí)候不斷重新設(shè)置,不然遮罩會(huì)固定在一個(gè)地方。所以更新的代碼就在 scrollViewDidScroll: 里:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    UIColor *backgroundColor = [UIColor colorWithRed:0.106 green:0.125 blue:0.141 alpha:1];
    CAGradientLayer *gradientLayer = [CAGradientLayer layer];
    gradientLayer.frame = self.menuTableView.bounds;
    gradientLayer.colors =
        @[ (id)[UIColor clearColor].CGColor, (id)backgroundColor.CGColor];
    gradientLayer.endPoint = CGPointMake(0.5, 0.8);
    gradientLayer.startPoint = CGPointMake(0.5, 1);
    self.menuTableView.layer.mask = gradientLayer;
}

這段代碼實(shí)現(xiàn)的就是每次滾動(dòng)的時(shí)候,都新建一個(gè) CAGradientLayer,設(shè)置好大小、漸變的顏色和位置,然后將其賦給 UITableViewlayermask。但是這樣肯定會(huì)有性能問題,可我也沒想到其他的方法,如果有更好的解決方案還請(qǐng)賜教!

#View 層次

弄好了 UI,接下來(lái)就是交互操作了。顯示側(cè)邊欄有兩個(gè)方式:一是點(diǎn)擊主頁(yè)面左上角的菜單按鈕,二是向右滑動(dòng)。同樣隱藏側(cè)邊欄也有兩個(gè)方式:一是點(diǎn)擊主頁(yè)面的任意區(qū)域,二是向左滑動(dòng)。兩個(gè)的根本區(qū)別就是,一個(gè)是 tap,一個(gè)是 slide。

這里先說(shuō)一個(gè) Part 1 中遺留的東西,就是主頁(yè)面的 view (以下簡(jiǎn)稱 main view)是 HomeViewController 的 view(下面簡(jiǎn)稱 root view) 的一個(gè) subview。為什么要多套這一層呢?我最開始做的時(shí)候是沒有這一層的,也就是 root view 就是主頁(yè)面的 View,側(cè)邊欄的 view (以下簡(jiǎn)稱 side view)是作為 root view 的 subview 放在 root view 的左側(cè)。移動(dòng)兩個(gè) view 的動(dòng)畫代碼就是這個(gè)樣子的(半偽代碼,示意而已):

[UIView animateWithDuration:kSideMenuAnimationDuration
                     animations:^{
                        self.view.left = 225;
                        self.sideMenuVC.view.left = 0;
                     }];

就是 root view 和 side view 同時(shí)向右移動(dòng)一段距離。但是這樣做的話,root view 向右移動(dòng)后,side view 也無(wú)法顯示出來(lái),只是一個(gè)同樣大小的黑色區(qū)域。我用 Reveal 看了一下,移動(dòng)后 root view 的 frame 也右移了,也就是 origin 不在屏幕的左上角,而 side view 仍然是在 root view 的 bounds 之外(我猜想這是無(wú)法顯示的原因)。

嘗試之后,我的解決方法就是,main view 和 side view 都作為 root view 的 subview,移動(dòng)的只是 main view 和 side view, 而 root view 是固定不動(dòng)的,這就是多了一層的原因。

#Tap 操作

首先說(shuō)如何通過點(diǎn)擊來(lái)顯示和隱藏側(cè)邊欄。因?yàn)槲宜械?UI 都是靠 AutoLayout 來(lái)控制的,所以側(cè)邊欄和主頁(yè)面的位置變換的動(dòng)畫也是用 AutoLayout。

HomeViewController 在加載的時(shí)候把 side view 加入到 root view 中,設(shè)置好位置和大小,這里的代碼就不貼了。下面是點(diǎn)擊菜單按鈕顯示側(cè)邊欄的代碼:

- (IBAction)showSideMenu:(UIButton *)sender {
    self.homeViewLeft.constant = 225;
    self.homeViewRight.constant = -225;
    [self.homeView setNeedsUpdateConstraints];
    [UIView animateWithDuration:kSideMenuAnimationDuration
                     animations:^{
                         self.sideMenuVC.view.left = 0;
                         [self.view layoutIfNeeded];
                     }
                     completion:^(BOOL finished) {
                         // setup transparent view for tapping to hide the side menu
                         self.tapView = [[UIView alloc] initWithFrame:self.homeView.bounds];
                         self.tapView.backgroundColor = [UIColor clearColor];
                         [self.tapView addGestureRecognizer:self.tapToHideSideMenu];
                         [self.homeView addSubview:self.tapView];
                         self.isShowSideMenu = YES;
                     }];
}

代碼中的 homeViewLefthomeViewRight 是從 StoryBoard 中拖過來(lái)的約束,通過改變這兩個(gè)約束的值來(lái)移動(dòng) main view。

原版中側(cè)邊欄滑出后,移到右側(cè)的 main view 就不可以上下滑動(dòng)內(nèi)容了,只可以點(diǎn)擊或滑動(dòng)隱藏側(cè)邊欄菜單。所以在上面的代碼中,動(dòng)畫結(jié)束后,新建一個(gè) tapView 蓋在 main view 上面,這樣就無(wú)法直接操作 main view 了,然后給這個(gè) tapView 添加一個(gè) tap 的手勢(shì),這個(gè)手勢(shì)就是用來(lái)隱藏側(cè)邊欄的。下面是隱藏的代碼:

- (void)hideSideMenu {
    [UIView animateWithDuration:kSideMenuAnimationDuration
                     animations:^{
                         self.homeView.left = 0;
                         self.sideMenuVC.view.left = -255;
                     }
                     completion:^(BOOL finished) {
                         [self.tapView removeGestureRecognizer:self.tapToHideSideMenu];
                         [self.tapView removeFromSuperview];
                         self.isShowSideMenu = NO;
                     }];
}

這個(gè)動(dòng)畫里沒有使用 AutoLayout,還是用 frame 來(lái)操作位置。在隱藏動(dòng)畫結(jié)束后,順便把 tapView 干掉。

#Slide 操作

另外一種交互就是向左向右滑動(dòng),這里就用到了 UIPanGestureRecognizer。

滑動(dòng)的細(xì)節(jié)有兩點(diǎn):

  1. side view 和 main view 隨著滑動(dòng)手勢(shì)同步移動(dòng)。
  2. 如果向右滑動(dòng)到某一閾值,side view 就自動(dòng)完全顯示出來(lái)。反之,side view 就完全隱藏。

具體實(shí)現(xiàn)就是給 main view 添加一個(gè) 'UIPanGestureRecognizer',手勢(shì)的 action 代碼如下:

- (void)handlePanGesture:(UIPanGestureRecognizer *)recognizer {
    CGFloat offsetX = [recognizer translationInView:self.homeView].x;
    if (offsetX > 0 && offsetX < kSideMenuWidth) {
        self.sideMenuVC.view.right = offsetX;
        self.homeViewLeft.constant = offsetX;
        self.homeViewRight.constant = -offsetX;
        [self.homeView layoutIfNeeded];
    }
    if (recognizer.state == UIGestureRecognizerStateEnded) {
        
        if (offsetX >= kSideMenuWidth / 2) {
            [self showSideMenu:nil];
        } else {
            [self hideSideMenu];
        }
    }
}

這段代碼中,第一個(gè) if 是用 AutoLayout 和 frame 根據(jù)滑動(dòng)距離同時(shí)移動(dòng)兩個(gè) view;第二個(gè) if 就是手勢(shì)結(jié)束時(shí)判斷是要隱藏還是顯示,如果超過側(cè)邊欄寬度的一半,就進(jìn)行顯示動(dòng)畫,反之就進(jìn)行隱藏動(dòng)畫。

代碼請(qǐng)猛戳 github

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

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

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,063評(píng)論 25 709
  • 內(nèi)容抽屜菜單ListViewWebViewSwitchButton按鈕點(diǎn)贊按鈕進(jìn)度條TabLayout圖標(biāo)下拉刷新...
    皇小弟閱讀 47,162評(píng)論 22 665
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,383評(píng)論 4 61
  • 1 夏天的黃昏,依然暑氣逼人,蝸在鴿子籠的出租房里,電風(fēng)扇吹來(lái)的微風(fēng)夾帶著滾燙的熱浪。被悶熱空氣逼得無(wú)處躲藏的我只...
    和風(fēng)輕和閱讀 380評(píng)論 0 0
  • 最近超愛一部劇,那就是《宮》泰版。當(dāng)我推薦給我舍友的時(shí)候,她說(shuō)《宮》韓版好看。我并沒有看過韓版,也不打算看??赡苡X...
    聽你的聲音閱讀 3,686評(píng)論 0 1

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