¿Cómo lograr animación de rizado usando la biblioteca de soporte?

Estoy tratando de agregar una animación de onda al hacer clic en el botón. Me gustó más abajo, pero requiere minSdKVersion a 21.

ripple.xml

       

Botón

  

Quiero que sea compatible con la biblioteca de diseño.

¿Cómo se puede hacer esto?

Configuración básica de ondulación

  • Ondulaciones contenidas dentro de la vista.
    android:background="?selectableItemBackground"

  • Ondulaciones que se extienden más allá de los límites de la vista:
    android:background="?selectableItemBackgroundBorderless"

    Eche un vistazo aquí para resolver las referencias de xml ?(attr) en código Java.

Biblioteca de soporte

  • Usando ?attr: (o la ? Taquigrafía) en lugar de ?android:attr referencia a la biblioteca de soporte , por lo que está disponible de nuevo en API 7.

Ondulaciones con imágenes / fondos

  • Para tener una imagen o fondo y superponer ondas, la solución más fácil es envolver la View en un FrameLayout con el conjunto de setForeground() con setForeground() o setBackground() .

Honestamente, no hay una manera clara de hacer esto de otra manera, aunque Nick Butcher sí publicó esto sobre el tema de ImageView con ondas.

Anteriormente voté para cerrar esta pregunta como fuera de tema, pero en realidad cambié de opinión, ya que este es un efecto visual bastante agradable que, desafortunadamente, aún no forma parte de la biblioteca de soporte. Lo más probable es que aparezca en futuras actualizaciones, pero no hay un marco de tiempo anunciado.

Afortunadamente, hay pocas implementaciones personalizadas disponibles:

incluidos los conjuntos de widgets temáticos Materlial compatibles con versiones anteriores de Android:

para que pueda probar uno de estos o google para otros “widgets materiales” o eso …

Hice una clase simple que hace botones de onda, nunca la necesité al final, así que no es la mejor, pero aquí está:

 import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.os.Handler; import android.support.annotation.NonNull; import android.util.AttributeSet; import android.view.MotionEvent; import android.widget.Button; public class RippleView extends Button { private float duration = 250; private float speed = 1; private float radius = 0; private Paint paint = new Paint(); private float endRadius = 0; private float rippleX = 0; private float rippleY = 0; private int width = 0; private int height = 0; private OnClickListener clickListener = null; private Handler handler; private int touchAction; private RippleView thisRippleView = this; public RippleView(Context context) { this(context, null, 0); } public RippleView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RippleView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { if (isInEditMode()) return; handler = new Handler(); paint.setStyle(Paint.Style.FILL); paint.setColor(Color.WHITE); paint.setAntiAlias(true); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); width = w; height = h; } @Override protected void onDraw(@NonNull Canvas canvas) { super.onDraw(canvas); if(radius > 0 && radius < endRadius) { canvas.drawCircle(rippleX, rippleY, radius, paint); if(touchAction == MotionEvent.ACTION_UP) invalidate(); } } @Override public boolean onTouchEvent(@NonNull MotionEvent event) { rippleX = event.getX(); rippleY = event.getY(); switch(event.getAction()) { case MotionEvent.ACTION_UP: { getParent().requestDisallowInterceptTouchEvent(false); touchAction = MotionEvent.ACTION_UP; radius = 1; endRadius = Math.max(Math.max(Math.max(width - rippleX, rippleX), rippleY), height - rippleY); speed = endRadius / duration * 10; handler.postDelayed(new Runnable() { @Override public void run() { if(radius < endRadius) { radius += speed; paint.setAlpha(90 - (int) (radius / endRadius * 90)); handler.postDelayed(this, 1); } else { clickListener.onClick(thisRippleView); } } }, 10); invalidate(); break; } case MotionEvent.ACTION_CANCEL: { getParent().requestDisallowInterceptTouchEvent(false); touchAction = MotionEvent.ACTION_CANCEL; radius = 0; invalidate(); break; } case MotionEvent.ACTION_DOWN: { getParent().requestDisallowInterceptTouchEvent(true); touchAction = MotionEvent.ACTION_UP; endRadius = Math.max(Math.max(Math.max(width - rippleX, rippleX), rippleY), height - rippleY); paint.setAlpha(90); radius = endRadius/4; invalidate(); return true; } case MotionEvent.ACTION_MOVE: { if(rippleX < 0 || rippleX > width || rippleY < 0 || rippleY > height) { getParent().requestDisallowInterceptTouchEvent(false); touchAction = MotionEvent.ACTION_CANCEL; radius = 0; invalidate(); break; } else { touchAction = MotionEvent.ACTION_MOVE; invalidate(); return true; } } } return false; } @Override public void setOnClickListener(OnClickListener l) { clickListener = l; } } 

EDITAR

Como muchas personas están buscando algo como esto, hice una clase que puede hacer que otras vistas tengan el efecto dominó:

 import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.os.Handler; import android.support.annotation.NonNull; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; public class RippleViewCreator extends FrameLayout { private float duration = 150; private int frameRate = 15; private float speed = 1; private float radius = 0; private Paint paint = new Paint(); private float endRadius = 0; private float rippleX = 0; private float rippleY = 0; private int width = 0; private int height = 0; private Handler handler = new Handler(); private int touchAction; public RippleViewCreator(Context context) { this(context, null, 0); } public RippleViewCreator(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RippleViewCreator(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { if (isInEditMode()) return; paint.setStyle(Paint.Style.FILL); paint.setColor(getResources().getColor(R.color.control_highlight_color)); paint.setAntiAlias(true); setWillNotDraw(true); setDrawingCacheEnabled(true); setClickable(true); } public static void addRippleToView(View v) { ViewGroup parent = (ViewGroup)v.getParent(); int index = -1; if(parent != null) { index = parent.indexOfChild(v); parent.removeView(v); } RippleViewCreator rippleViewCreator = new RippleViewCreator(v.getContext()); rippleViewCreator.setLayoutParams(v.getLayoutParams()); if(index == -1) parent.addView(rippleViewCreator, index); else parent.addView(rippleViewCreator); rippleViewCreator.addView(v); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); width = w; height = h; } @Override protected void dispatchDraw(@NonNull Canvas canvas) { super.dispatchDraw(canvas); if(radius > 0 && radius < endRadius) { canvas.drawCircle(rippleX, rippleY, radius, paint); if(touchAction == MotionEvent.ACTION_UP) invalidate(); } } @Override public boolean onInterceptTouchEvent(MotionEvent event) { return true; } @Override public boolean onTouchEvent(@NonNull MotionEvent event) { rippleX = event.getX(); rippleY = event.getY(); touchAction = event.getAction(); switch(event.getAction()) { case MotionEvent.ACTION_UP: { getParent().requestDisallowInterceptTouchEvent(false); radius = 1; endRadius = Math.max(Math.max(Math.max(width - rippleX, rippleX), rippleY), height - rippleY); speed = endRadius / duration * frameRate; handler.postDelayed(new Runnable() { @Override public void run() { if(radius < endRadius) { radius += speed; paint.setAlpha(90 - (int) (radius / endRadius * 90)); handler.postDelayed(this, frameRate); } else if(getChildAt(0) != null) { getChildAt(0).performClick(); } } }, frameRate); break; } case MotionEvent.ACTION_CANCEL: { getParent().requestDisallowInterceptTouchEvent(false); break; } case MotionEvent.ACTION_DOWN: { getParent().requestDisallowInterceptTouchEvent(true); endRadius = Math.max(Math.max(Math.max(width - rippleX, rippleX), rippleY), height - rippleY); paint.setAlpha(90); radius = endRadius/3; invalidate(); return true; } case MotionEvent.ACTION_MOVE: { if(rippleX < 0 || rippleX > width || rippleY < 0 || rippleY > height) { getParent().requestDisallowInterceptTouchEvent(false); touchAction = MotionEvent.ACTION_CANCEL; break; } else { invalidate(); return true; } } } invalidate(); return false; } @Override public final void addView(@NonNull View child, int index, ViewGroup.LayoutParams params) { //limit one view if (getChildCount() > 0) { throw new IllegalStateException(this.getClass().toString()+" can only have one child."); } super.addView(child, index, params); } } 

A veces tienes un fondo personalizado, en esos casos una mejor solución es usar android:foreground="?selectableItemBackground"

Es muy sencillo 😉

Primero debe crear dos archivos dibujables uno para la versión anterior de la API y otro para la versión más reciente, ¡por supuesto! si creas el archivo dibujable para la versión api más nueva, el estudio de Android te sugiere que crees uno viejo automáticamente. y finalmente establece este dibujable a tu vista de fondo.

Muestra dibujable para la nueva versión de la API (res / drawable-v21 / ripple.xml):

          

Muestra dibujable para la versión anterior de la API (res / drawable / ripple.xml)

      

Para obtener más información acerca de la ondulación dibujable, visite esto: https://developer.android.com/reference/android/graphics/drawable/RippleDrawable.html

a veces será utilizable esta línea en cualquier diseño o componentes.

  android:background="?attr/selectableItemBackground" 

Como.

   
 android:foreground="?selectableItemBackground" 

Esto es suficiente para la onda.