【Android】Ripple使用总结及ClickableSpan的冲突解决
2016-12-28 16:50
851 查看
By Sodino 文章目录1. Ripple效果的设置
2. Ripple的生效
3. 不适用Ripple的场景
4. 无边界的Ripple (unbounded ripple)
5. 硬件加速开关对无边界Ripple的影响
6. 子层(Child Layer)
7. Mask层(Mask Layer)
8. 与ClickableSpan冲突
9. Ripple动画的自动播放
GitHub源码:Ripple Demo
RippleDrawable官方文档链接:RippleDrawable
效果图如下:
也可以在代码中动态设置.
因为当组件恢复为
当
如果父控件没有设置背景,则会进一步绘制在父控件的上一级父控件的背景之上。如在
但如果手贱关闭了,则无边界
见效果图中的第二行
所以
控制
细节显示可以通过
这个现象可以推断出
通过debug源码,发现当点击事件传递到
该方法在处理
那么解决思路也就简单了,重写
核心代码如下:
当然,在Demo中,为了进一步简化,直接把
原理见源码:
About Sodino
2. Ripple的生效
3. 不适用Ripple的场景
4. 无边界的Ripple (unbounded ripple)
5. 硬件加速开关对无边界Ripple的影响
6. 子层(Child Layer)
7. Mask层(Mask Layer)
8. 与ClickableSpan冲突
9. Ripple动画的自动播放
GitHub源码:Ripple Demo
RippleDrawable官方文档链接:RippleDrawable
效果图如下:
Ripple效果的设置
可以在XML布局文件中对View的
android:background属性进行赋值.
android:foreground的Ripple支持仅支持
FrameLayout或其子类如support-v7中的
CardView.
android:foreground的Ripple使用场景为当点击不透明的
Image时,见效果图中的
Ripple by 'foreground' Only FrameLayout Support
也可以在代码中动态设置.
Ripple的生效
当View有设置
OnClickListener的情况下被点击, 或者获得/失去焦点变化时,将出现Ripple效果.
不适用Ripple的场景
点击之后就立马消失的组件(setVisibility:gone invisible 或 remove).因为当组件恢复为
visiable后,未播放完的
Ripple动画会继续播放,会产生疑惑。
无边界的Ripple (unbounded ripple)
见效果图中第一行Ripple NO Child Layers or Mask(/drawable/ripple.xml)
12 | <!-- 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"删除,则会出现下图的效果:
硬件加速开关对无边界Ripple的影响
在Android 3.0 (API level 11)引入的硬件加速功能默认在application/Activity/View这三个层级上都是开启的。但如果手贱关闭了,则无边界
Ripple不会生效。
见效果图中的第二行
Ripple NO Child Layers or Mask but HARDWARE OFF
子层(Child Layer)
由于View在不同的交互下有不同的
state,常见的为
pressed和’focused’或
normal这三种状态.
所以
Ripple通过多个
item来表示不同
state下的显示,每个
item都是一个子层(Child Layer),能够直接显示
color、
shape、
drawable/image及
selector.当
Ripple存在一个或多个子层时,则
ripple效果则被限定在当前
View的边界内了.无边界效果(unbounded ripple)失效.
12345678910111213141516171819202122232425262728293031323334 | // ↓↓↓ Ripple With Child Layer(Color Red) and Mask<ripple android:color="@android:color/holo_green_light"> <item android:id="@android:id/mask" android:drawable="@android:color/holo_red_light" /></ripple >// ↓↓↓ Ripple With Shape and Mask<ripple android:color="@android:color/holo_green_light"> <item android:id="@android:id/mask"> <shape android:shape="rectangle"> <solid android:color="@android:color/holo_red_light" /> <corners android:radius="30dp" /> </shape> </item></ripple >// ↓↓↓ Ripple With Picture and Mask<ripple android:color="@android:color/holo_green_light"> <item android:id="@android:id/mask" android:drawable="@drawable/google" /></ripple >// ↓↓↓ Ripple With Selector// ↓↓↓ the drawing region will be drawn from RED gradient to GREEN.<ripple android:color="@android:color/holo_green_light"> <item> <selector> <item android:drawable="@android:color/holo_red_light" android:state_pressed="true"/> <item android:drawable="@android:color/transparent"/> </selector> </item></ripple > |
Mask层(Mask Layer)
可以设置指定子层item的
android:id="@android:id/mask"来设定当前
Ripple的
Mask.
Mask的内容并不会被绘制到屏幕上.它的作用是限定
Ripple效果的绘制区域.mask所在的的子层限制了
Ripple效果的最大范围只能是
View的边界,不会扩散到父组件.
控制
ripple效果区域的细节显示.
细节显示可以通过
Ripple With Picture and Mask来理解.本处中用于显示的是一张背景透明的彩色
Ripple的扩散过程中只在有颜色的区域中慢慢扩散,透明区域则仍是透明.
与ClickableSpan冲突
如果Layout有包含
ClickableSpan的
TextView,则发现该
Layout设置
Ripple的效果无法响应.
这个现象可以推断出
MotionEvent这个事件在
TextView这一层级被消耗了.下一步应该为找出该事件为什么被消耗?
通过debug源码,发现当点击事件传递到
TextView时,会进一步传递给
LinkMovementMethod::onTouchEvent(),如果点击位置处于
ClickableSpan以外,则返回
Touch.onTouchEvent(widget, buffer, event);
该方法在处理
MotionEvent::ACTION_DOWN时默认返回
true,导致
Ripple失效.见下图(android(level 23) source code ):
那么解决思路也就简单了,重写
LinkedMovementMethod::onTouchEvent()方法,当且仅当点击到
ClickableSpan时,才返回
true即可.
核心代码如下:
1234567891011121314151617181920212223242526272829303132333435363738 | 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; |
LinkedMovementMethod::onTouchEvent()写到了
RippleTextView::onTouchEvent()中去.具体见源码.
Ripple动画的自动播放
12345 | // 开始自动播放rippleDrawable.setState(new int[]{android.R.attr.state_pressed, android.R.attr.state_enabled});// 恢复初始状态rippleDrawable.setState(new int[]{android.R.attr.state_enabled}); |
About Sodino
相关文章推荐
- android使用GestureDetector实现手势下滑与ListView onTouchEvent 冲突问题 解决
- (2.2.8.8)Gradle依赖项学习总结,dependencies、transitive、force、exclude的使用与依赖冲突解决
- Android之滑动事件冲突解决 Touch事件处理机制总结
- Gradle依赖项学习总结,dependencies、transitive、force、exclude的使用与依赖冲突解决
- Gradle依赖项学习总结,dependencies、transitive、force、exclude的使用与依赖冲突解决
- Android关于libs,jniLibs库的基本使用说明及冲突解决
- Gradle依赖项学习总结,dependencies、transitive、force、exclude的使用与依赖冲突解决
- Android解决ListView中使用EditText所遇到的一些冲突
- Android-onMeasure使用解决Scrollview嵌套listview冲突
- Android ListView与ScrollView冲突的解决方法总结
- Gradle依赖项学习总结,dependencies、transitive、force、exclude的使用与依赖冲突解决
- Android基础控件ListView的使用与焦点冲突解决
- Android之SwipeRefreshLayout使用和冲突解决
- Android使用ScrollView+ListView时发生滑动冲突的解决办法
- Android解决ListView中使用EditText所遇到的一些冲突
- android 布局 使用 viewPager 时,如何解决 和 子页面 长按滑动 冲突问题
- SVN 的使用:用两次就会了,很简单.注意总结【①做完自己代码,首先右键项目>team >“与资源库同步”,把队友的代码更新下来(而不是直接提交),②整合完设置"冲突已解决",才能提交】===
- dwr与jquery同时使用出现的冲突解决
- subversion冲突解决和winmerge使用手册