Efecto Ripple de Android – Comentarios táctiles para botones – Uso de XML

Estoy tratando de comprender cómo implementar el “Efecto Ripple – Touch Feedback” para botones y otras vistas. Miré las preguntas relacionadas con el efecto Ripple touch en SO y obtuve una idea de ello. Pude obtener el efecto de onda con este código de 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); } } 

Pero, quiero usar el enfoque XML. ¿Cómo logro esto? He visto esto y esto , pero todavía no me siento cómodo con los estilos, así que me resulta difícil lograr el efecto dominó.

Tengo un botón con el siguiente código XML:

  

¿Cómo obtengo efecto dominó para este botón? Si alguien puede guiarme, estaré agradecido.

[EDITAR] Agregar ripple.xml y background.xml, como se menciona en uno de los enlaces de arriba. Creé una carpeta drawable-v21 en res y agregué los archivos a continuación allí.

ripple.xml

      

background.xml

     

Agregué la ondulación como fondo para mi botón, aquí está el xml para mi botón ahora …

 

Cuando ejecuto la aplicación, obtengo una ResourceNotFoundException. Aquí está la traza del 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  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 

Puedes hacer algo como esto:

  

Donde el ripple.xml es:

        

Simplemente ponga ?attr/selectableItemBackground en el fondo del botón para la API 21+, como a continuación:

  

Para lollipop (API> 21) haga que el archivo como btn_ripple_effect.xml sea dibujable y coloque debajo del código

           

Para pre lollipop (API <21) crea un archivo como btn_ripple_effect.xml en la carpeta drawable-v21 y pon el código siguiente

              

Leve adición a la respuesta anterior: tenga en cuenta que el color de la máscara no se utiliza de ninguna manera.

También puedes hacer cosas más complicadas con ondas. Por ejemplo, si desea un borde en su botón de ondulación, puede usarlo como una lista de capas.

                  

Tenga en cuenta que el elemento con id @android:id/mask solo se usa para mostrar dónde se detendrá el efecto @android:id/mask . Si quieres que cubra todo el botón, puedes cambiar el android:shape para que sea rectangle . ¡Puedes imaginar hacer muchas más cosas interesantes con esto también!

También asegúrese de tener una copia de seguridad dibujable para dispositivos que aún no son 21 o la aplicación se bloqueará en dispositivos viejos.

La mejor manera de usar esto en Android: primer plano , porque también te permite usar tu propio fondo.

android: foreground = “? android: attr / seleccionableItemBackground”

Ejemplo:

  

Estaba investigando el efecto dominó, ya que era algo que quería aplicar a unos pocos botones en mi aplicación y que pasó por su publicación. Si bien su pregunta es buscar una respuesta sobre cómo agregar el efecto dominó usando XML que en realidad era algo que estaba tratando de evitar, cuando intente agregar ese atributo verá que requiere v21.

Si su objective es inferior a v21, ese nuevo botón de ampliación de clase (o ImageButton, etc.) evitará las quejas del comstackdor.

Como no había ninguna explicación sobre cómo implementar la clase personalizada anterior, pensé en completarla. Todo lo que necesitas hacer es crear la nueva clase y luego en el XML cambiar “Botón” a “the.package.name.MyButton”.

De:

   

A:

   

Eso es. Ahora su botón cuando se presiona tendrá una onda contenida dentro de sus límites.

Me gusta este enfoque. Solo deseo que la onda se extienda más allá de los límites. Para un botón pequeño, este efecto dominó realmente resalta cuán cuadrado o rectangular es realmente el botón. Visualmente sería más satisfactorio si la onda continuara hasta que alcanzara su radio completo.

Puede agregar los datos clickable como true y de background o de background como atributos de atributo de atributo ?attr/selectableItemBackground atributo ?attr/selectableItemBackground a la vista:

  

Si en el caso de que su vista ya tenga un background lleno de algo, podría llenar su foreground con selectableItemBackground