定制触摸反馈:RippleDrawable

为了更好的在5.0及以上设备上使用波纹效果,简单跟大家聊下RippleDrawable的正确打开方式,如何方便快捷的定义不同的触摸反馈的范围以及波纹扩散的颜色,最后聊下5.0以下设备上的兼容方案

为了在5.0以上设备上实现波纹效果,我们可以通过给View设置background实现,而这个效果,官方已经有两个已经实现的效果供我们选择:?android:attr/selectableItemBackground?android:attr/selectableItemBackgroundBorderless,这两条属性不仅产生的效果不太一样,使用方式上也有些不同,第一条属性是可以随意用的,它是有向下兼容的能力,在5.0以下的设备上没有波纹效果,是普通的变色效果;而第二条属性只支持5.0及以上设备,RequiresApi = 21, 所以如果要使用它的话,一定要考虑兼容性问题。它们的效果也是不一样的:第一个的波纹效果会被限制在View的大小之内,而第二条属性的波纹扩散范围是圆形的,而且圆的大小是以View最远距离点距离View中心点距离为半径,就是说:波纹的扩散范围保证会覆盖整个View,而且会扩散出View。官方提供的解决方法有一点不好就是无法定制,我们不能很方便的自定义波纹效果的颜色以及大小,所以官方还是有另外一个类来让我们折腾,那就是:RippleDrawable,接下来我们将用自己的代码来一一实现我们上面刚刚讨论过的两种效果。

RippleDrawable

RippleDrawable 继承自 LayerDrawable, 可以实现多个Layer的层叠,今天只讨论用xml的形式来配置Ripple,就是普通的在drawable文件夹下新建一个drawable,它的最外层标签是ripple:

1
2
3
4
5
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/colorAccent"
>
</ripple>

上面这段简单的代码就帮我们实现了一个color自定义的?android:attr/selectableItemBackgroundBorderless 的效果,直接给目标view设置background属性,就能看到熟悉的波纹效果,如下图返回键的波纹效果:

我们继续在它上面修改来实现第一条属性的效果:

<ripple xmlns:android="http://schemas.android.com/apk/res/android"
        android:color="@color/colorAccent"
        >
    <item
            android:id="@android:id/mask"
            android:drawable=""@android:color/white"/>

</ripple>

只是在原来的代码中加上了一个item标签,还给这个标签设置了一个id="@android:id/mask",后面的那个drawable属性设置了波纹效果的扩散范围,这时候再运行我们的程序,发现波纹扩散的范围果然被限制住了,如下图中返回按钮的效果所示:

但是,等等,设置background之后,我的View背景岂不是一篇空白?放心,我们继续改代码:

<ripple xmlns:android="http://schemas.android.com/apk/res/android"
        android:color="@color/colorAccent"
        >
    <item
            android:id="@android:id/mask"
            android:drawable="@android:color/white"/>

    <item android:drawable="@color/colorPrimary"/>

</ripple>

修改之后再次执行我们的程序,运行效果如下图中Simple Button的点击效果所示:

总算大功告成!既有了我们定义的背景,还实现了点击时的波纹效果,唯一的不足可能就是不支持5.0以下的设备,这点因为系统的限制我们就不做考虑了,我们会在后面讨论怎么保证5.0以下设备不会崩溃。

我们来分析一下最后这段代码:定义在ripple节点的颜色定义的是最后我们想要的波纹的颜色,里面的两个item 标签分别定义了波纹扩散的范围以及普通状态目标View的颜色,它是怎么区分两个 item 的呢?没错,就是通过id,因为在第一个item设置了android:id="@android:id/mask",所以这个item不会出现被绘制在屏幕上,只会已蒙层的形式定义波纹的范围,如果没有这个定义这个id的item,那么ripple会根据综合所有的item最后得到波纹扩散的范围,再极端,如果一个item都没有呢?那就看到我们前面说到的第二种情况:波纹会扩散到parentView的范围内。

如果ripple中有多个普通的item标签,效果会和layer-list效果类似,多个item的drawable会叠加在一起,定义了maskid的item和普通item之间不会有影响。

因为在测试的时候一直用的Button测试,偶然的机会在ImageView上使用,发现竟然不生效,搜索了一下才发现只有当View的clickable属性为true的时候,波纹效果才会生效。

5.0以下的兼容方案

既然5.0以上的设备可以实现效果了,5.0以下的设备也不能让它直接崩溃,下面有两种兼容方案:

  1. layout-v21方案,相信大家都比较熟悉,写两份ripple_drawable文件,分别在layout-v21文件夹下和普通layout文件夹下,普通layout文件夹的ripple_drawable为空或者设置普通的点击效果,只要不使用ripple标签就好。

  2. 代码设置,相对省代码,但是比较费脑,不太跳进,具体大家可以看代码:

    1
    2
    3
    4
    5
    6
    7
    8
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    int[] attrs = new int[] { android.R.attr.selectableItemBackgroundBorderless};
    TypedArray ta = obtainStyledAttributes(attrs);
    button1.setBackground(ta.getDrawable(0));
    ta.recycle();
    } else {
    // do something on devices below 21
    }