InstaMaterial 概念设计(4):新闻item的上下文菜单

这篇文章是实现Material 风格的INSTAGRAM 系列文章第第四部分。今天,我们将为新闻的item创建一个从“motre”按钮打开的上下文菜单。大约是视频18 到 20 秒之间的内容。

下面是今天所要达到的效果(分别是Lollipop以及Lollipop之前的效果):

视频暂略

先热身

在我们开始菜单的实现之前,我们先重构以下代码。

Riyaz在评论中指出MainActivityCommentsActivity中的Toolbar是完全一样的,因此可以单独新建一个布局,使用<include />标签来导入以达到重用的效果。

重构之后大概长这样。这个新建的布局文件在res/layout/view_feed_toolbar.xml中:

view_feed_toolbar.xml

现在我们只需在activity_main.xmlactivity_comments.xml中如下使用它就可以了:

activity_main_include.xml

值得一提的是我们可以在<include />标签中重写一些属性(重写将会覆盖被包含文件中根视图的相关属性),就如上面已经重写了的android:id属性。(顺便说下这里Toolbar需要放在RelativeLayout中,ps:真啰嗦啊,说了几次了。)

这里是 提交 的重构代码。

上下文菜单

准备工作

在开始代码之前我们先做一些准备工作,下面是期望的效果截图:

首先,在item_feed.xml中添加一个按钮,并处理其onClick()事件。这不难,只要将以前的一些代码拷贝过来,再添加一个三个圆点的图片(我用的是从官方Material Design图标包中弄来的 这个图标 )。这里是 提交 的这些更改的代码。

上下文菜单(Context menu)的布局

让我们从view的布局开始吧,我们再一次使用<merge /> 标签,然后在xml和java代码中构建

res/layout/view_context_menu.xml:

其实就是四个共用如下样式的按钮:

很简单吧,java代码也同样简单:

有没有注意到从这里开始作者使用了注解-译者注。

第20行我们使用了模拟的方式来绑定数据模型(直接和一个int类型关联),在实际项目中肯定会更复杂。第30行的dismiss()方法帮助我们将菜单从父视图中移除。其余代码都很简单。

其实这里作者又用到了观察者模式, OnFeedContextMenuItemClickListener在这里就是观察者,将里面的各种点击事件,暴露给外部调用-译者注。

.9图片背景

你应该注意到了我们的菜单是有阴影效果的。在Lollipop中我们可以直接使用原生的elevation值来达到这种效果,但是这只适合最新的安卓设备,即使你使用ViewCompat.setElevation();也无济于事。(如果你分析ViewCompat中的实现,你会发现只是判断了下是否是在Lollipop平台下,否则什么也不做)。

这就是为什么我们要使用.9图片作为背景。为了照顾没有接触过.9图片的同学,简要的说明下:.9图片是一个按一定规则定义了拉伸区域与内容区域的图片,它帮助我们创建一个可以缩放而不失真的图片。

下面是我们的背景图片:

左边和上边的黑线定义可以被拉伸的区域(其实就是为了防止四个圆角被拉伸),右边和下边的黑线定义了被融区域(从覆盖的范围可以看出,阴影部分未被包括,那么它会自动的给view加上一个和阴影部分相同的padding)

这里你可以找到关于NinePatch的更多解释。

Selector

菜单布局中,最后一件事是点击的按下效果(称作selector)。我们还是直接拷贝之前的现成代码(包括Lollipop和Lollipop之前两个版本):

res/drawable/btn_context_menu.xml/:

res/drawable-v21/btn_context_menu.xml/:

Context menu manager

有了上下文菜单的整个布局,现在可以开始逻辑部分了。为了方便使用,我们用FeedContextMenuManager来管理这些逻辑。下面是FeedContextMenuManager需要满足的需求:

1.一次只能有一个菜单显示在屏幕上

2.点击按钮“more”之后显示菜单,再次点击则菜单消失。

3.菜单需要显示在被点击按钮的正上方。

4.在滚动新闻列表的时候菜单消失。

实现第一点最简单的方式是FeedContextMenuManager作为单例模式来实现。

manager需要处理菜单视图,其中最重要的就是在恰当的时候将其移除。我们使用OnAttachStateChangeListener以及它的onViewDetachedFromWindow(View v)方法来达到此目的,利用它们,就可以实现在菜单dismiss或者activity destroyed的时候菜单视图被移除。

下面的方法实现显示和隐藏view

我们直接在“more”按钮的onClick回调中触发这个方法。

现在我们来想想显示和隐藏的具体实现。首先需要考虑的是“isAnimating” 状态(在显示和隐藏两种情况下都需考虑)。当动画执行的过程中,很有必要阻断manager以防止执行两次(同时也避免因此而导致的不可预测的bug)。

下面是剩余的代码(以及代码下面的注释):

关于上面代码的三点解释:

  • Context menu 初始化– 创建菜单对象,设置一些事件,添加菜单到根view。最后一条我们使用始终指向activity根view的android.R.id.content。在本例中添加view很简单,因为我们知道根view是RelativeLayoutFrameLayout也同样适用),但是在其他情况下比如根view是LinearLayout,就会复杂一点。
  • Menu 的位置– 我们知道menu的初始位置其实是在activity的左上角,我们也知道打开菜单的按钮在屏幕的什么位置,因此这就是个数学问题。这里最重要的是选择一个计算位置的恰当时机。我们需要在onPreDraw()回调中来做这件事情,确保view以及布局完成。否则getWidth()getHeight()返回的是0。
  • Menu 动画– 不过是ViewPropertyAnimator的运用罢了。

隐藏menu更简单,都不需要解释:

最后一件事情是在滚动列表的时候隐藏菜单,为此我们继承RecyclerView.OnScrollListener 类:

你可以看到这里的实现方式有点技巧。在菜单隐藏的同时使用setTranslationY(),这样就达到了滚动时候的这种效果:

 你可能觉得隐藏菜单这种效果理所当然,其实不是,为了区分出不使用setTranslationY()会发生什么,你可以将其注释掉看看效果-译者注。

现在我们所要做的就是使用好这个FeedContextMenuManager了,这里是  最后一次提交 的代码。

1 收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部