Android的涟漪效应 – 触摸button反馈 – 使用XML

我正试图了解如何实现button和其他视图的“纹波效果 – 触摸反馈”。 我看了一下SO上Ripple touch效果的问题,并对其进行了一些了解。 我能够使用这个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:

  <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" /> 

我如何得到这个button的涟漪效应。 如果有人能指导我,我会感激的。

[编辑]添加ripple.xml和background.xml,如上面的链接之一所述。 我在res中创build了一个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> 

我添加了波纹作为我的button的背景,这里是我的button的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 

你可以做这样的事情:

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

只需在API 21+的button背景中添加?attr/selectableItemBackground ,如下所示:

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

对于棒棒糖(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> 

对于棒棒糖(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> 

上面答案略有补充:请注意,掩码颜色不能以任何方式使用。

你可以用波纹做更复杂的事情。 例如,如果你想在你的波纹button上有一个边框,你可以像层次列表一样使用它。

 <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的元素仅用于显示涟漪效应在哪里停止。 如果你想要覆盖整个button,可以将android:shape改为rectangle 。 你可以想象用这个做更多有趣的事情!

另外,请确保为尚未21的设备备份可拖动的应用程序,否则应用程序将在旧设备上崩溃。

android:foreground中使用这个最好的方法,因为它也允许你使用自己的背景。

机器人:前景= “机器人: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"/> 

我正在研究涟漪效应,因为它是我想要应用于我的应用程序中的几个button,并发生在您的post。 虽然你的问题是寻找一个答案,如何添加使用XML的涟漪效应,实际上是我试图避免,因为当试图添加该属性,你看到它需要v21。

如果你的目标是低于v21,那么扩展Button(或ImageButton等)的新类将避免编译器的投诉。

由于没有解释如何实现上面的自定义类,我想我会填写。所有你需要做的是创build新的类,然后在XML更改“button”到“the.package.name.MyButton”。

从:

  <Button android:id="@+id/Button" android:layout_width="wrap_content" android:layout_height="wrap_content" /> 

至:

  <the.package.name.MyButton android:id="@+id/Button" android:layout_width="wrap_content" android:layout_height="wrap_content" /> 

而已。 现在你的button按下时会有一个包含在其范围内的波纹。

我喜欢这种方法,我只是希望波纹会超越界限。 对于一个小button,这个涟漪效应确实突出显示了button的正方形或长方形。 从视觉上来说,如果波纹一直持续到达到完全的半径,它会更令人满意。

您可以添加clickable作为truebackgroundforeround作为?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"/> 

如果你的视图已经有了一些东西,你可以使用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"/>