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.
- Org.w3c.dom.Document a String sin javax.xml.transform
- Android Hacer un XML dibujable compuesto de varios mapas de bits
- Android R.java no generará en Eclipse
- Trunca automáticamente texto TextView para no superponer otro TextView
- Android: DOM vs SAX vs análisis XMLPullParser?
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?
- Imagen de estiramiento en la imagen para ajustar la pantalla en xml
- Xml getText devolver null - Android
- ¿Qué sucede si la minSdkVersion es menor que la targetSdkVersion?
- El desplazamiento de texto no funciona
- Android.view.InflateException: Archivo XML binario línea # 10: Error al inflar fragmento de clase
- No existe una etiqueta de meta-datos necesaria en AndroidManifest.xml de la aplicación.
- Android Color - ¿Cuánto más oscuro es primaryColorDark que primaryColor?
- Las etiquetas HTML no funcionan dentro de strings.xml
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í ):
-
Envuelva el
RippleDrawable
en unRippleDrawable
² antes de configurarlo en elImageView
:Drawable image = … RippleDrawable rippledImage = new RippleDrawable( ColorStateList.valueOf(rippleColor), image, null); imageView.setImageDrawable(rippledImage);
-
Amplíe
ImageView
y añada un atributo de primer plano (comoFrameLayout
has³). Vea este ejemplo³ de + Chris Banes de agregarlo aLinearLayout
. 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"
- Haciendo clic en un botón para cambiar el idioma
- Mostrar mi diseño mientras SurfaceView está cargando Vista previa de cámara