仿写知乎日报 - 侧边栏菜单 (Part 3)

转自我自己的 blog

侧边栏菜单

这一篇 blog 是专门介绍如何仿写知乎日报的侧边栏菜单,主要介绍如何构建 UI 以及一个渐隐的图层效果,和如何控制侧边栏的显示和隐藏。

#界面

首先是画 UI。侧边栏的 UI 比较简单,由上到下分别是:

  1. 头像和登录按钮
  2. 收藏、消息和设置按钮
  3. 专栏的列表
  4. 离线下载和切换主题的按钮

用 xib 做出来就是这样子:

xib

值得一提的是专栏列表的底部,在滑动的时候会有一个渐隐的效果,如图所示:

列表底部的渐隐

实现这个效果的第一个想法是在 UITableView 的底部改一个带渐变效果的透明 UIView,但是原版中处于渐隐效果的底部 Cell 也是可以点击的,这样子遮罩的 UIView 还要处理一下点击事件,麻烦,弃用!第二个想法就和 Part 2 中一样,给 UITableView 设置一个 CAGradientLayer,专门在底部加一个渐变的效果,但是试验了几次,发现渐变的效果十分不理想,所以也放弃了。

最后的解决方案,也是我无意中发现的。UIViewlayer 有个属性是 mask, 根据文档里面写的,这也是个 CALayer,用来给 layer 的 alpha 通道设置遮罩。但是,这个属性必须在滚动的时候不断重新设置,不然遮罩会固定在一个地方。所以更新的代码就在 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;
}

这段代码实现的就是每次滚动的时候,都新建一个 CAGradientLayer,设置好大小、渐变的颜色和位置,然后将其赋给 UITableViewlayermask。但是这样肯定会有性能问题,可我也没想到其他的方法,如果有更好的解决方案还请赐教!

#View 层次

弄好了 UI,接下来就是交互操作了。显示侧边栏有两个方式:一是点击主页面左上角的菜单按钮,二是向右滑动。同样隐藏侧边栏也有两个方式:一是点击主页面的任意区域,二是向左滑动。两个的根本区别就是,一个是 tap,一个是 slide。

这里先说一个 Part 1 中遗留的东西,就是主页面的 view (以下简称 main view)是 HomeViewController 的 view(下面简称 root view) 的一个 subview。为什么要多套这一层呢?我最开始做的时候是没有这一层的,也就是 root view 就是主页面的 View,侧边栏的 view (以下简称 side view)是作为 root view 的 subview 放在 root view 的左侧。移动两个 view 的动画代码就是这个样子的(半伪代码,示意而已):

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

就是 root view 和 side view 同时向右移动一段距离。但是这样做的话,root view 向右移动后,side view 也无法显示出来,只是一个同样大小的黑色区域。我用 Reveal 看了一下,移动后 root view 的 frame 也右移了,也就是 origin 不在屏幕的左上角,而 side view 仍然是在 root view 的 bounds 之外(我猜想这是无法显示的原因)。

尝试之后,我的解决方法就是,main view 和 side view 都作为 root view 的 subview,移动的只是 main view 和 side view, 而 root view 是固定不动的,这就是多了一层的原因。

#Tap 操作

首先说如何通过点击来显示和隐藏侧边栏。因为我所有的 UI 都是靠 AutoLayout 来控制的,所以侧边栏和主页面的位置变换的动画也是用 AutoLayout。

HomeViewController 在加载的时候把 side view 加入到 root view 中,设置好位置和大小,这里的代码就不贴了。下面是点击菜单按钮显示侧边栏的代码:

- (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 中拖过来的约束,通过改变这两个约束的值来移动 main view。

原版中侧边栏滑出后,移到右侧的 main view 就不可以上下滑动内容了,只可以点击或滑动隐藏侧边栏菜单。所以在上面的代码中,动画结束后,新建一个 tapView 盖在 main view 上面,这样就无法直接操作 main view 了,然后给这个 tapView 添加一个 tap 的手势,这个手势就是用来隐藏侧边栏的。下面是隐藏的代码:

- (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;
                     }];
}

这个动画里没有使用 AutoLayout,还是用 frame 来操作位置。在隐藏动画结束后,顺便把 tapView 干掉。

#Slide 操作

另外一种交互就是向左向右滑动,这里就用到了 UIPanGestureRecognizer

滑动的细节有两点:

  1. side view 和 main view 随着滑动手势同步移动。
  2. 如果向右滑动到某一阈值,side view 就自动完全显示出来。反之,side view 就完全隐藏。

具体实现就是给 main view 添加一个 'UIPanGestureRecognizer',手势的 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];
        }
    }
}

这段代码中,第一个 if 是用 AutoLayout 和 frame 根据滑动距离同时移动两个 view;第二个 if 就是手势结束时判断是要隐藏还是显示,如果超过侧边栏宽度的一半,就进行显示动画,反之就进行隐藏动画。

代码请猛戳 github

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 197,597评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,053评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,583评论 0 326
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,888评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,772评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,536评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,922评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,554评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,838评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,861评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,677评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,483评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,928评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,104评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,403评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,945评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,156评论 2 339

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,188评论 25 707
  • 内容抽屉菜单ListViewWebViewSwitchButton按钮点赞按钮进度条TabLayout图标下拉刷新...
    皇小弟阅读 46,670评论 22 664
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,965评论 4 60
  • 1 夏天的黄昏,依然暑气逼人,蜗在鸽子笼的出租房里,电风扇吹来的微风夹带着滚烫的热浪。被闷热空气逼得无处躲藏的我只...
    和风轻和阅读 290评论 0 0
  • 最近超爱一部剧,那就是《宫》泰版。当我推荐给我舍友的时候,她说《宫》韩版好看。我并没有看过韩版,也不打算看。可能觉...
    听你的声音阅读 3,180评论 0 1