GitHub源码: Ripple Demo
RippleDrawable官方文档链接: RippleDrawable
效果图如下:
可以在XML布局文件中对 View
的 android:background
属性进行赋值.
android:foreground
的Ripple支持仅支持 FrameLayout
或其子类如support-v7中的 CardView
.
android:foreground
的Ripple使用场景为当点击不透明的
Image
时,见效果图中的
Ripple by 'foreground' Only FrameLayout Support
也可以在代码中动态设置.
当 View
有设置 OnClickListener
的情况下被点击, 或者获得/失去焦点变化时,将出现Ripple效果.
点击之后就立马消失的组件(setVisibility:gone invisible 或 remove).
因为当组件恢复为 visiable
后,未播放完的 Ripple
动画会继续播放,会产生疑惑。
见效果图中第一行 Ripple NO Child Layers or Mask
(/drawable/ripple.xml)
<!-- An unbounded red ripple. --/> <ripple android:color="#ffff0000" />
当 ripple
标签内只指定一个 android:color
属性时,则该 ripple
效果的绘制会溢出其所在 View
的边界,直接绘制在父控件的背景之上。
如果父控件没有设置背景,则会进一步绘制在父控件的上一级父控件的背景之上。
如在 Demo
中 layout/layout_toolbar.xml
,把作为 rootView
的 LinearLayout
的属性 android:background="@android:color/background_dark"
删除,则会出现下图的效果:
在Android 3.0 (API level 11)引入的硬件加速功能默认在application/Activity/View这三个层级上都是开启的。
但如果手贱关闭了,则无边界 Ripple
不会生效。
见效果图中的第二行 Ripple NO Child Layers or Mask but HARDWARE OFF
由于 View
在不同的交互下有不同的 state
,常见的为 pressed
和’focused’或 normal
这三种状态.
所以 Ripple
通过多个 item
来表示不同 state
下的显示,每个 item
都是一个子层(Child Layer),能够直接显示 color
、 shape
、 drawable/image
及 selector
.
当 Ripple
存在一个或多个子层时,则 ripple
效果则被限定在当前 View
的边界内了.无边界效果(unbounded ripple)失效.
// ↓↓↓ Ripple With Child Layer(Color Red) and Mask <rippleandroid:color="@android:color/holo_green_light"> <itemandroid:id="@android:id/mask" android:drawable="@android:color/holo_red_light" /> </ripple> // ↓↓↓ Ripple With Shape and Mask <rippleandroid:color="@android:color/holo_green_light"> <itemandroid:id="@android:id/mask"> <shapeandroid:shape="rectangle"> <solidandroid:color="@android:color/holo_red_light"/> <cornersandroid:radius="30dp"/> </shape> </item> </ripple> // ↓↓↓ Ripple With Picture and Mask <rippleandroid:color="@android:color/holo_green_light"> <itemandroid:id="@android:id/mask" android:drawable="@drawable/google" /> </ripple> // ↓↓↓ Ripple With Selector // ↓↓↓ the drawing region will be drawn from RED gradient to GREEN. <rippleandroid:color="@android:color/holo_green_light"> <item> <selector> <itemandroid:drawable="@android:color/holo_red_light" android:state_pressed="true"/> <itemandroid:drawable="@android:color/transparent"/> </selector> </item> </ripple>
可以设置指定子层 item
的 android:id="@android:id/mask"
来设定当前 Ripple
的 Mask
.
Mask
的内容并不会被绘制到屏幕上.它的作用是限定 Ripple
效果的绘制区域.
Ripple
效果的最大范围只能是 View
的边界,不会扩散到父组件. ripple
效果区域的细节显示. Ripple With Picture and Mask
来理解.本处中用于显示的是一张背景透明的彩色 Google
图片,但 Ripple
的扩散过程中只在有颜色的区域中慢慢扩散,透明区域则仍是透明.
如果 Layout
有包含 ClickableSpan
的 TextView
,则发现该 Layout
设置 Ripple
的效果无法响应.
这个现象可以推断出 MotionEvent
这个事件在 TextView
这一层级被消耗了.下一步应该为找出该事件为什么被消耗?
TextView
时,会进一步传递给
LinkMovementMethod::onTouchEvent()
,如果点击位置处于
ClickableSpan
以外,则返回
Touch.onTouchEvent(widget, buffer, event);
该方法在处理 MotionEvent::ACTION_DOWN
时默认返回 true
,导致 Ripple
失效.见下图(android(level 23) source code ):
那么解决思路也就简单了,重写 LinkedMovementMethod::onTouchEvent()
方法,当且仅当点击到 ClickableSpan
时,才返回 true
即可.
核心代码如下:
int action = event.getAction(); if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) { int x = (int) event.getX(); int y = (int) event.getY(); x -= getTotalPaddingLeft(); y -= getTotalPaddingTop(); x += getScrollX(); y += getScrollY(); Layout layout = getLayout(); int line = layout.getLineForVertical(y); int off = layout.getOffsetForHorizontal(line, x); // get ClickableSpan whick were pressed ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class); if (link.length != 0) { // if find ClickableSpan if (action == MotionEvent.ACTION_UP) { link[0].onClick(this); } else if (action == MotionEvent.ACTION_DOWN) { Selection.setSelection(buffer, buffer.getSpanStart(link[0]), buffer.getSpanEnd(link[0])); } // consume DOWN or other action return true; } else { // if none Selection.removeSelection(buffer); } // deliver to parent view return false;
当然,在Demo中,为了进一步简化,直接把 LinkedMovementMethod::onTouchEvent()
写到了 RippleTextView::onTouchEvent()
中去.具体见 源码 .
About Sodino