Efecto de ondulación en la parte superior de la imagen – Android

He estado experimentando con la animación de ripple en mi último proyecto paralelo. Tengo problemas para encontrar una solución "elegante" para usarla en ciertas situaciones para eventos de toque. A saber, con imágenes, especialmente en listas, cuadrículas y vistas de reciclaje. La animación casi siempre parece animar detrás de la vista, no la parte superior de la misma. Este es un problema ninguno en los botones y TextViews pero si tiene un GridView de imágenes, la ondulación aparece detrás o debajo de la imagen real. Obviamente esto no es lo que quiero, y aunque hay soluciones que considero que son un trabajo alrededor, espero que haya algo más simple que simplemente desconozco.

Utilizo el código siguiente para lograr una vista de cuadrícula personalizada con imágenes. Le daré el código completo HAGA CLIC AQUÍ para que pueda seguirlo si lo desea.

Ahora sólo las cosas importantes. Para que mi imagen se anime al tocar necesito esto

Button_ripple.xml

<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/cream_background"> <item> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <!-- Pressed --> <item android:drawable="@color/button_selected" android:state_pressed="true"/> <!-- Selected --> <item android:drawable="@color/button_selected" android:state_selected="true"/> <!-- Focus --> <item android:drawable="@color/button_selected" android:state_focused="true"/> <!-- Default --> <item android:drawable="@color/transparent"/> </selector> </item> </ripple> 

Custom_grid.xml

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center_horizontal" android:orientation="horizontal"> <ImageView android:id="@+id/sceneGridItem" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/button_ripple" android:gravity="center_horizontal"/> </LinearLayout> 

Activity_main.xml

 <GridView android:id="@+id/sceneGrid" android:layout_marginTop="15dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:verticalSpacing="15dp" android:numColumns="5" /> 

La línea en la que ocurren todas las magias y problemas ocurre cuando configuro el fondo. Si bien esto de hecho me da una animación de ripple en mi imagen, se anima detrás de la imagen. Quiero que la animación aparezca en la parte superior de la imagen. Así que probé algunas cosas diferentes como

Establecer todo el fondo de la cuadrícula a button_ripple.

 <GridView android:id="@+id/sceneGrid" android:layout_marginTop="15dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:verticalSpacing="15dp" android:background="@drawable/button_ripple" android:numColumns="5" /> 

Hace exactamente lo que usted piensa, ahora la rejilla entera tiene un fondo semitransparente y no importa qué imagen presione la cuadrícula entera anima desde el centro de la rejilla. Aunque esto es un poco genial, no es lo que quiero.

Establecer el fondo raíz / padre a button_ripple.

 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center_horizontal" android:background="@drawable/button_ripple" android:orientation="horizontal"> 

El área ahora es más grande y llena la célula entera de la rejilla (no apenas la imagen), sin embargo no la trae al frente.

Cambiando custom_grid.xml a RelativeLayout y poniendo dos ImageViews uno encima del otro

Custom_grid.xml

 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal"> <ImageView android:id="@+id/gridItem" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_alignParentLeft="true" /> <ImageView android:id="@+id/gridItemOverlay" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_alignParentLeft="true" android:background="@drawable/button_ripple" /> </RelativeLayout> 

CustomGridAdapter.java

 .... gridItemOverLay = (ImageView) findViewById(R.id.gridItemOverlay); gridItemOverlay.bringToFront(); 

Esto funciona. Ahora el ImageView inferior contiene mi imagen, y la parte superior se anima, dando la ilusión de una animación de rizo en la parte superior de mi imagen. Sinceramente, aunque esto es un trabajo alrededor. Siento que esto no es lo que se pretendía. Así que les pregunto gente fina, ¿hay una mejor manera o incluso una manera diferente?

Me gustó la respuesta del desarrollador android, así que decidí investigar cómo hacer el paso 2 de su solución en código.

Usted necesita conseguir este pedazo de código de Jake Wharton aquí: https://gist.github.com/JakeWharton/0a251d67649305d84e8a

 import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.widget.ImageView; public class ForegroundImageView extends ImageView { private Drawable foreground; public ForegroundImageView(Context context) { this(context, null); } public ForegroundImageView(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ForegroundImageView); Drawable foreground = a.getDrawable(R.styleable.ForegroundImageView_android_foreground); if (foreground != null) { setForeground(foreground); } a.recycle(); } /** * Supply a drawable resource that is to be rendered on top of all of the child * views in the frame layout. * * @param drawableResId The drawable resource to be drawn on top of the children. */ public void setForegroundResource(int drawableResId) { setForeground(getContext().getResources().getDrawable(drawableResId)); } /** * Supply a Drawable that is to be rendered on top of all of the child * views in the frame layout. * * @param drawable The Drawable to be drawn on top of the children. */ public void setForeground(Drawable drawable) { if (foreground == drawable) { return; } if (foreground != null) { foreground.setCallback(null); unscheduleDrawable(foreground); } foreground = drawable; if (drawable != null) { drawable.setCallback(this); if (drawable.isStateful()) { drawable.setState(getDrawableState()); } } requestLayout(); invalidate(); } @Override protected boolean verifyDrawable(Drawable who) { return super.verifyDrawable(who) || who == foreground; } @Override public void jumpDrawablesToCurrentState() { super.jumpDrawablesToCurrentState(); if (foreground != null) foreground.jumpToCurrentState(); } @Override protected void drawableStateChanged() { super.drawableStateChanged(); if (foreground != null && foreground.isStateful()) { foreground.setState(getDrawableState()); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (foreground != null) { foreground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight()); invalidate(); } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (foreground != null) { foreground.setBounds(0, 0, w, h); invalidate(); } } @Override public void draw(Canvas canvas) { super.draw(canvas); if (foreground != null) { foreground.draw(canvas); } } } 

Este es el attrs.xml:

 <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="ForegroundImageView"> <attr name="android:foreground"/> </declare-styleable> </resources> 

Ahora, cree su ForegroundImageView como en su layout.xml:

 <com.example.ripples.ForegroundImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:scaleType="centerCrop" android:foreground="?android:selectableItemBackground" android:src="@drawable/apples" android:id="@+id/image" /> 

La imagen se ondula ahora.

En lugar de intentar agregar la ondulación a cada vista individual del adaptador, sólo puede agregarla al nivel GridView de la siguiente manera:

 <GridView android:id="@+id/gridview" ... android:drawSelectorOnTop="true" android:listSelector="@drawable/your_ripple_drawable"/> 

Tal vez pruebe una de estas soluciones (tiene el consejo de aquí ):

  1. Envuelva el RippleDrawable en un RippleDrawable ² antes de configurarlo en el ImageView :

     Drawable image = … RippleDrawable rippledImage = new RippleDrawable( ColorStateList.valueOf(rippleColor), image, null); imageView.setImageDrawable(rippledImage); 
  2. Amplíe ImageView y añada un atributo de primer plano (como FrameLayout has³). Vea este ejemplo³ de + Chris Banes de agregarlo a LinearLayout . Si lo hace, asegúrese de pasar a través de las coordenadas táctiles para que la ondulación empiece desde el punto correcto:

     @Override public void drawableHotspotChanged(float x, float y) { super.drawableHotspotChanged(x, y); if (foreground != null) { foreground.setHotspot(x, y); } } 

Tengo una galería de imágenes (construido con un RecyclerView y un GridLayoutManager ). La imagen se ajusta con Picasso. Para agregar un ripple a la imagen he envuelto el ImageView con un FrameLayout . El diseño del elemento es:

 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="120dp" android:foreground="@drawable/ripple" android:clickable="true" android:focusable="true" > <ImageView android:id="@+id/grid_item_imageView" android:layout_width="match_parent" android:layout_height="120dp" android:layout_gravity="center" android:scaleType="centerInside" /> </FrameLayout> 

Tenga en cuenta el android:foreground . No es lo mismo que android:background . He intentado sin android:clickable="true" y android:focusable="true" y también funciona , pero no duele.

A continuación, agregue un ripple.xml en res/drawable :

 <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true"> <shape android:shape="rectangle"> <solid android:color="#7FF5AC8E" /> </shape> </item> <item> <shape android:shape="rectangle"> <solid android:color="@android:color/transparent" /> </shape> </item> </selector> 

Tenga en cuenta que esto muestra un color semi-transparente en la parte superior de la imagen cuando se selecciona el elemento (para dispositivos <5.0). Puede quitarlo si no lo desea.

A continuación, agregue el ripple.xml con ripple en res/drawable-v21 :

 <ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/coral"> </ripple> 

Puede utilizar el efecto de rizado predeterminado en lugar del ripple.xml personalizado, pero es realmente difícil verlo encima de una imagen porque es gris:

 android:foreground="?android:attr/selectableItemBackground" 
  • Android: El mejor analizador para analizar datos XML
  • Programación Android de fondo de cultivo
  • Fallo Android Studio
  • "Unhandled Event Loop Exception" al hacer algo en Android XML
  • Android - Botón de radio - el botón dibujable no se muestra en Lollipop
  • Entendiendo & lt & gt
  • Cambiar el radio de la esquina de programación programable
  • Utilice símbolo especial (<,>) en diseño de diseño en Android
  • Cambiar el tamaño del mapa de bits dentro de una lista de capas
  • Android Drawable: ¿Especificar el ancho de la forma en porcentaje en el archivo XML?
  • fragment_display_message: Mi primer error en la aplicación para Android
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.