Android L 的涟漪效应 - 按钮的触摸反馈 - 使用 XML
Posted
技术标签:
【中文标题】Android L 的涟漪效应 - 按钮的触摸反馈 - 使用 XML【英文标题】:Android L's Ripple Effect - Touch Feedback for Buttons - Using XML 【发布时间】:2014-09-11 20:38:21 【问题描述】:我正在尝试了解如何为按钮和其他视图实现“涟漪效应 - 触摸反馈”。我查看了与 SO 上的 Ripple touch effect 相关的问题,并对此有所了解。我能够使用这个 java 代码成功地获得涟漪效应。
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RadialGradient;
import android.graphics.Region;
import android.graphics.Shader;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.animation.AccelerateInterpolator;
import android.widget.Button;
public class MyButton extends Button
private float mDownX;
private float mDownY;
private float mRadius;
private Paint mPaint;
public MyButton(final Context context)
super(context);
init();
public MyButton(final Context context, final AttributeSet attrs)
super(context, attrs);
init();
public MyButton(final Context context, final AttributeSet attrs,
final int defStyle)
super(context, attrs, defStyle);
init();
private void init()
mPaint = new Paint();
mPaint.setAlpha(100);
@Override
public boolean onTouchEvent(@NonNull final MotionEvent event)
if (event.getActionMasked() == MotionEvent.ACTION_UP)
mDownX = event.getX();
mDownY = event.getY();
ObjectAnimator animator = ObjectAnimator.ofFloat(this, "radius", 0,
getWidth() * 3.0f);
animator.setInterpolator(new AccelerateInterpolator());
animator.setDuration(400);
animator.start();
return super.onTouchEvent(event);
public void setRadius(final float radius)
mRadius = radius;
if (mRadius > 0)
RadialGradient radialGradient = new RadialGradient(mDownX, mDownY,
mRadius * 3, Color.TRANSPARENT, Color.BLACK,
Shader.TileMode.MIRROR);
mPaint.setShader(radialGradient);
invalidate();
private Path mPath = new Path();
private Path mPath2 = new Path();
@Override
protected void onDraw(@NonNull final Canvas canvas)
super.onDraw(canvas);
mPath2.reset();
mPath2.addCircle(mDownX, mDownY, mRadius, Path.Direction.CW);
canvas.clipPath(mPath2);
mPath.reset();
mPath.addCircle(mDownX, mDownY, mRadius / 3, Path.Direction.CW);
canvas.clipPath(mPath, Region.Op.DIFFERENCE);
canvas.drawCircle(mDownX, mDownY, mRadius, mPaint);
但是,我想使用 XML 方法。我如何实现这一目标?我看过this 和this,但我对样式还不是很满意,所以我发现很难实现涟漪效果。
我有一个带有以下 XML 代码的按钮:
<Button
android:id="@+id/button_email"
android:layout_
android:layout_
android:layout_weight="0.50"
android:gravity="center"
android:text="@string/email" />
如何获得此按钮的涟漪效果。如果有人可以指导我,我将不胜感激。
[编辑] 添加ripple.xml 和background.xml,如上述链接之一所述。我在 res 中创建了一个 drawable-v21 文件夹,并在那里添加了以下文件。
ripple.xml
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@android:color/black" >
<item android:drawable="@drawable/background">
</item>
</ripple>
背景.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<solid android:color="@android:color/darker_gray" />
</shape>
我添加了波纹作为按钮的背景,现在这是我的按钮的 xml..
<Button
android:id="@+id/button_email"
android:layout_
android:layout_
android:layout_weight="0.50"
android:gravity="center"
android:background="@drawable/ripple"
android:text="@string/email" />
当我运行应用程序时,我得到了 ResourceNotFoundException。这是 logcat 跟踪..
07-21 17:03:39.043: E/AndroidRuntime(15710): FATAL EXCEPTION: main
07-21 17:03:39.043: E/AndroidRuntime(15710): Process: com.xx.xxx, PID: 15710
07-21 17:03:39.043: E/AndroidRuntime(15710): android.view.InflateException: Binary XML file line #60: Error inflating class <unknown>
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.view.LayoutInflater.createView(LayoutInflater.java:620)
07-21 17:03:39.043: E/AndroidRuntime(15710): at com.android.internal.policy.impl.PhoneLayoutInflater.onCreateView(PhoneLayoutInflater.java:56)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.view.LayoutInflater.onCreateView(LayoutInflater.java:669)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:694)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.view.LayoutInflater.rInflate(LayoutInflater.java:755)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.view.LayoutInflater.rInflate(LayoutInflater.java:758)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.view.LayoutInflater.inflate(LayoutInflater.java:492)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.view.LayoutInflater.inflate(LayoutInflater.java:397)
07-21 17:03:39.043: E/AndroidRuntime(15710): at com.xx.xxx.BusinessAdapter.onCreateViewHolder(BusinessAdapter.java:106)
07-21 17:03:39.043: E/AndroidRuntime(15710): at com.xx.xxx.BusinessAdapter.onCreateViewHolder(BusinessAdapter.java:1)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.support.v7.widget.RecyclerView$Adapter.createViewHolder(RecyclerView.java:2915)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.support.v7.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:2511)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.support.v7.widget.LinearLayoutManager$RenderState.next(LinearLayoutManager.java:1425)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:999)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:524)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:1461)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:1600)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.view.View.layout(View.java:14817)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.view.ViewGroup.layout(ViewGroup.java:4631)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.view.View.layout(View.java:14817)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.view.ViewGroup.layout(ViewGroup.java:4631)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.view.View.layout(View.java:14817)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.view.ViewGroup.layout(ViewGroup.java:4631)
07-21 17:03:39.043: E/AndroidRuntime(15710): at com.android.internal.widget.ActionBarOverlayLayout.onLayout(ActionBarOverlayLayout.java:374)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.view.View.layout(View.java:14817)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.view.ViewGroup.layout(ViewGroup.java:4631)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.view.View.layout(View.java:14817)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.view.ViewGroup.layout(ViewGroup.java:4631)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:1983)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1740)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:996)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5600)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.view.Choreographer$CallbackRecord.run(Choreographer.java:761)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.view.Choreographer.doCallbacks(Choreographer.java:574)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.view.Choreographer.doFrame(Choreographer.java:544)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:747)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.os.Handler.handleCallback(Handler.java:733)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.os.Handler.dispatchMessage(Handler.java:95)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.os.Looper.loop(Looper.java:136)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.app.ActivityThread.main(ActivityThread.java:5001)
07-21 17:03:39.043: E/AndroidRuntime(15710): at java.lang.reflect.Method.invokeNative(Native Method)
07-21 17:03:39.043: E/AndroidRuntime(15710): at java.lang.reflect.Method.invoke(Method.java:515)
07-21 17:03:39.043: E/AndroidRuntime(15710): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
07-21 17:03:39.043: E/AndroidRuntime(15710): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
07-21 17:03:39.043: E/AndroidRuntime(15710): at dalvik.system.NativeStart.main(Native Method)
07-21 17:03:39.043: E/AndroidRuntime(15710): Caused by: java.lang.reflect.InvocationTargetException
07-21 17:03:39.043: E/AndroidRuntime(15710): at java.lang.reflect.Constructor.constructNative(Native Method)
07-21 17:03:39.043: E/AndroidRuntime(15710): at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
07-21 17:03:39.043: E/AndroidRuntime(15710): at android.view.LayoutInflater.createView(LayoutInflater.java:594)
07-21 17:03:39.043: E/AndroidRuntime(15710): ... 50 more
07-21 17:03:39.043: E/AndroidRuntime(15710): Caused by: android.content.res.Resources$NotFoundException: Resource is not a Drawable (color or path): TypedValuet=0x1/d=0x7f020075 a=-1 r=0x
【问题讨论】:
你应该使用MyButton
,而不是Button
是的,我不想使用 java 代码。如果我使用 MyButton,我会得到涟漪效果。但我想使用形状和背景来实现它。我希望你明白我的意思跨度>
我查看了其中一个链接。它看起来很简单,你的问题是什么?你的ripple
xml 在哪里?
根据:developer.android.com/preview/material/animations.html#touch,您应该在您的 xml 代码中将按钮的背景设置为?android:attr/selectableItemBackground
或?android:attr/selectableItemBackgroundBorderless
。
@blackbelt 我用波纹和背景 xml 更新了我的问题,请看一下。
【参考方案1】:
更新材质组件:
使用Material Components Library 很容易应用波纹。
只需使用 MaterialButton
和 app:rippleColor
属性:
<com.google.android.material.button.MaterialButton
app:rippleColor="@color/my_selector"
../>
使用这样的选择器:
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:alpha="..." android:color="?attr/colorOnPrimary" android:state_pressed="true"/>
<item android:alpha="..." android:color="?attr/colorOnPrimary" android:state_focused="true" android:state_hovered="true"/>
<item android:alpha="..." android:color="?attr/colorOnPrimary" android:state_focused="true"/>
<item android:alpha="..." android:color="?attr/colorOnPrimary" android:state_hovered="true"/>
<item android:alpha="..." android:color="?attr/colorOnPrimary"/>
</selector>
旧答案 你可以这样做:
<Button
android:layout_
android:layout_
android:background="@drawable/ripple"
/>
ripple.xml 在哪里:
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:colorControlHighlight">
<item android:id="@android:id/mask">
<shape android:shape="oval">
<solid android:color="?android:colorAccent" />
</shape>
</item>
</ripple>
【讨论】:
我找不到 colorControlHighlight 和 colorAccent 的资源,我该如何摆脱这个? 当然可以使用自己喜欢的颜色。您是否使用 android:Theme.Material.Light 作为父主题? 刚刚意识到..ripple 需要 21 的最低 api 级别。但是,在此之前 eclipse 并没有给我带来错误..现在我如何在 Nexus 上测试这个? @BenitoBertoli & gabriele,我们如何在棒棒糖设备中使用涟漪涟漪效应。 @TusharPandey 它不受官方支持。【参考方案2】:只需将?attr/selectableItemBackground
放在 API 21+ 按钮的背景中,如下所示:
<Button
android:layout_
android:layout_
android:background="?attr/selectableItemBackground"
android:text="Button" />
【讨论】:
这个答案给出了矩形内的涟漪效应。如果您想在圆圈中完成波纹效果,请使用:android:background="?attr/selectableItemBackgroundBorderless"
这仅适用于按钮,还是适用于任何视图?
如果我使用这个属性,如何设置按钮背景??
就我而言,android:foreground="?attr/selectableItemBackground"
为 Button/ImageButton 工作【参考方案3】:
对于棒棒糖(API>21),将文件制作为可绘制的 btn_ripple_effect.xml 并放在下面的代码中
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:color="?android:colorAccent"
tools:targetApi="lollipop">
<item android:drawable="@color/cancel_btn_clr" /> <!-- default -->
<item android:id="@android:id/mask">
<shape android:shape="rectangle">
<solid android:color="?android:colorAccent" />
</shape>
</item>
</ripple>
对于 pre lollipop (API
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape>
<solid android:color="@color/colorAccent"></solid>
</shape>
</item>
<item>
<shape>
<solid android:color="@color/cancel_btn_clr"></solid>
</shape>
</item>
</selector>
【讨论】:
可能只想补充一点,我相信棒棒糖前应该用<selector xmlns:android="http://schemas.android.com/apk/res/android">
标签包装
API>21 不等于在 targetApi 中设置棒棒糖。在这种情况下,我的建议是不要设置 targetApi。
是相反的文件夹结构,drawable-v21是用于棒棒糖及以上资源的【参考方案4】:
对上述答案的轻微补充:请注意,不以任何方式使用遮罩颜色。
你也可以用涟漪做更复杂的事情。例如,如果您想要波纹按钮上的边框,您可以像使用图层列表一样使用它。
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
android:color="?android:colorControlHighlight">
<!-- Note: <ripple> acts like a layer-list -->
<item android:id="@android:id/mask">
<shape android:shape="oval">
<!-- This color is not displayed in any way -->
<solid android:color="@android:color/black" />
</shape>
</item>
<!-- This is the border -->
<item>
<shape android:shape="rectangle">
<corners android:radius="3dp"/>
<!-- Use your border color in place of #f00 -->
<stroke android: android:color="#f00"/>
</shape>
</item>
</ripple>
请注意,id 为@android:id/mask
的元素仅用于显示波纹效果将停止的位置。如果您希望它覆盖整个按钮,您可以将android:shape
更改为rectangle
。你可以想象用它做更多有趣的事情!
还要确保为 21 岁以下的设备备份可绘制对象,否则应用会在旧设备上崩溃。
【讨论】:
是否可以将遮罩扩展到边界之外以伪造无边框按钮? @William ImageButton 可以使用波纹效果吗?【参考方案5】:在 android:foreground 中使用它的最佳方式,因为它也允许您使用自己的背景。
android:foreground="?android:attr/selectableItemBackground"
例子:
<android.support.v7.widget.AppCompatButton
android:layout_
android:layout_
android:foreground="?android:attr/selectableItemBackground"
android:background="@color/button.normal"
android:textColor="@color/white"/>
【讨论】:
您还必须将android:clickable="true"
添加到视图中
@backslashN 你是对的,但这取决于你的 XML 设计。【参考方案6】:
我正在研究涟漪效应,因为我想将其应用于我的应用程序中的几个按钮,并在您的帖子中发生。虽然您的问题是寻找关于如何使用 XML 添加涟漪效应的答案,但实际上这是我试图避免的,因为当您尝试添加该属性时,您会看到它需要 v21。
如果您的目标低于 v21,则扩展 Button(或 ImageButton 等)的新类将避免编译器的投诉。
由于没有说明如何实现上面的自定义类,我想我会填写。您需要做的就是创建新类,然后在 XML 中将“Button”更改为“the.package.name”。我的按钮”。
发件人:
<Button
android:id="@+id/Button"
android:layout_
android:layout_ />
收件人:
<the.package.name.MyButton
android:id="@+id/Button"
android:layout_
android:layout_ />
就是这样。现在您的按钮在按下时将在其范围内包含一个波纹。
我喜欢这种方法,我只是希望涟漪能够越界。对于一个小按钮,这种涟漪效应确实突出了按钮的实际方形或矩形。从视觉上看,如果波纹一直持续到它的全部半径会更令人满意。
【讨论】:
【参考方案7】:您可以将clickable
作为true
和background
或foreround
作为?attr/selectableItemBackground
属性添加到视图:
<Button
android:layout_
android:layout_
android:text="Button"
android:clickable="true"
android:background="?attr/selectableItemBackground"
android:textColor="@android:color/white"/>
如果你的视图已经有一个 background
填充了一些东西,你可以用 selectableItemBackground
填充你的 foreground
<Button
android:layout_
android:layout_
android:text="Button"
android:clickable="true"
android:foreground="?attr/selectableItemBackground"
android:background="@color/colorPrimary"
android:textColor="@android:color/white"/>
【讨论】:
以上是关于Android L 的涟漪效应 - 按钮的触摸反馈 - 使用 XML的主要内容,如果未能解决你的问题,请参考以下文章