安卓L的涟漪效果 - 按钮触摸反馈 - 使用XML

50

我正在尝试理解如何为按钮和其他视图实现“涟漪效果-触摸反馈”。我查看了与SO上的涟漪触摸效果相关的问题,并对此有了一些见解。我能够成功地使用以下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方法。我该怎么做?我已经查看了这个这个,但是我对样式还不太熟悉,所以很难实现水波纹效果。
我有一个带有以下XML代码的按钮:
 <Button
            android:id="@+id/button_email"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            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>

background.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_width="0dip"
    android:layout_height="wrap_content"
    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): TypedValue{t=0x1/d=0x7f020075 a=-1 r=0x

是的,我不想使用Java代码。如果我使用MyButton,我会得到涟漪效果。但我想使用形状和背景来实现它。我希望你明白我的意思。 - Vamsi Challa
2
根据 https://developer.android.com/preview/material/animations.html#touch 的说明,你应该在xml代码中将按钮的背景设置为 ?android:attr/selectableItemBackground 或者 ?android:attr/selectableItemBackgroundBorderless - Michał Z.
@MichałZ. 当我按照网站上的说明设置背景时,我根本看不到背景,也没有任何涟漪效果。我之前尝试过这样做。 - Vamsi Challa
@blackbelt,对于我更新的问题有什么想法吗? - Vamsi Challa
请帮忙为Android Lollipop之前的设备创建涟漪效果。 - Tushar Pandey
显示剩余5条评论
7个回答

72

更新 Material 组件:

使用 Material 组件库 很容易应用涟漪效果。
只需使用 MaterialButtonapp: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_width="wrap_content"
      android:layout_height="wrap_content"
      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时遇到了资源未找到的问题,该如何解决? - Vamsi Challa
1
当然可以使用你喜欢的颜色。你正在使用以android:Theme.Material.Light为父级的主题吗? - Gabriele Mariotti
1
刚刚意识到...ripple需要最低API级别为21。但是,在此之前,Eclipse没有给我报错..现在我该如何在Nexus上测试呢? - Vamsi Challa
2
@BenitoBertoli和Gabriele,我们如何在Android 5.0以下的设备上使用水波纹效果? - Tushar Pandey
1
@TusharPandey 这不是官方支持的。 - Gabriele Mariotti
显示剩余4条评论

30

只需在API 21+的按钮背景中放置?attr/selectableItemBackground,如下所示:

<Button
        android:layout_width="match_parent"
        android:layout_height="70dp"
        android:background="?attr/selectableItemBackground"
        android:text="Button" />

8
这个答案提供了一个矩形内的涟漪效果。如果您想在圆形中完成涟漪效果,请使用:android:background="?attr/selectableItemBackgroundBorderless" - David Velasquez
这只适用于按钮吗,还是适用于任何视图? - SMBiggs
7
如果我在使用这个属性,该如何设置按钮的背景? - Arpit Patel
2
在我的情况下,android:foreground="?attr/selectableItemBackground" 适用于 Button/ImageButton。 - e-zuka

20

对于棒棒糖(API>21),请在drawable中创建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>

对于 Android 5.0 以下版本(API<21),请在 drawable-v21 文件夹中创建 btn_ripple_effect.xml 文件,并添加以下代码:

<?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>

可能需要补充一下,我认为pre-lollipop应该包含在<selector xmlns:android="http://schemas.android.com/apk/res/android">标签中。 - AllDayAmazing
API>21不等于将Lollipop设置为targetApi。我的建议是在这种情况下不要设置targetApi。 - Michiel
2
这是相反的文件夹结构,drawable-v21用于Lollipop及以上版本的资源。 - Mahdi Javaheri

12

对上面的回答稍作补充:请注意,掩码颜色不会以任何方式使用。

您还可以用涟漪做更复杂的事情。例如,如果您想在涟漪按钮上添加边框,则可以将其像图层列表一样使用。

<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:width="1dp" android:color="#f00"/>
        </shape>
    </item>
 </ripple>

请注意,具有id @android:id/mask的元素仅用于显示涟漪效果停止的位置。如果您希望它覆盖整个按钮,可以将android:shape更改为rectangle。您还可以想象做更多有趣的事情!

此外,请确保为尚未升级至21的设备准备备用可绘制对象,否则应用程序将在旧设备上崩溃。


能否将遮罩层扩展到边界之外,以模拟无边框按钮? - Grzegorz Adam Hankiewicz
@William 是否可以在ImageButton中使用涟漪效果? - Vivek Kumar

7

在Android中,最好的使用方法是android:foreground,因为它允许你使用自己的背景。

android:foreground="?android:attr/selectableItemBackground"

例如:

<android.support.v7.widget.AppCompatButton
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:foreground="?android:attr/selectableItemBackground"
    android:background="@color/button.normal"
    android:textColor="@color/white"/>

1
你还需要在视图中添加 android:clickable="true" - backslashN
你是对的,但这取决于你的XML设计。 - Md Imran Choudhury

3
我正在研究涟漪效应,因为我想在我的应用程序中将它应用于一些按钮,并偶然发现了您的帖子。虽然您的问题是寻找如何使用XML添加涟漪效应的答案,但实际上我正试图避免这样做,因为尝试添加该属性时,您会看到它需要v21。
如果您的目标低于v21,则可以使用新的扩展Button(或ImageButton等)的类来避免编译器的投诉。
由于没有关于如何实现上述自定义类的说明,所以我想填写一下。您只需要创建新类,然后在XML中将“Button”更改为“the.package.name.MyButton”。
来源:
 <Button
    android:id="@+id/Button"     

    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

To:

 <the.package.name.MyButton
   android:id="@+id/Button"     

    android:layout_width="wrap_content"
    android:layout_height="wrap_content" />

就是这样。现在,当你按下按钮时,它将在其边界内产生涟漪效果。

我喜欢这种方法,但希望涟漪效果可以超出边界。对于小按钮,这种涟漪效果确实突出了按钮的正方形或矩形外观。如果涟漪一直持续到达到它的完整半径,视觉上会更加令人满意。


1
你可以将视图添加 clickable 属性设置为 true,并将 backgroundforeround 属性设置为 ?attr/selectableItemBackground 以使其可点击:
<Button
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:text="Button"
     android:clickable="true"
     android:background="?attr/selectableItemBackground"
     android:textColor="@android:color/white"/>

如果您的视图已经有填充了某些内容的background,则可以使用selectableItemBackground来填充foreground
<Button
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:text="Button"
     android:clickable="true"
     android:foreground="?attr/selectableItemBackground"
     android:background="@color/colorPrimary"
     android:textColor="@android:color/white"/>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接