¿Cómo manejar el efecto Ripple en 9-patch y CardView, y tener control sobre los estados del selector?

Fondo

Deseo agregar un efecto de rizo simple para los elementos de listView de Android Lollipop y superiores.

En primer lugar me gustaría configurarlo para filas simples, y luego a 9-parche de filas e incluso CardView.

El problema

Estaba seguro de que este va a ser muy fácil, ya que ni siquiera me obliga a definir el selector normal. No lo hice incluso para filas simples. Por alguna razón, el efecto de rizado va más allá de los límites de la fila:

Introduzca aquí la descripción de la imagen

No sólo eso, sino que en algunos casos, el fondo del artículo consigue pegado en el color que lo he fijado para ser.

El código

Esto es lo que he intentado:

MainActivity.java

public class MainActivity extends ActionBarActivity { @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final ListView listView = (ListView) findViewById(android.R.id.list); final LayoutInflater inflater = LayoutInflater.from(this); listView.setAdapter(new BaseAdapter() { @Override public View getView(final int position, final View convertView, final ViewGroup parent) { View rootView = convertView; if (rootView == null) { rootView = inflater.inflate(android.R.layout.simple_list_item_1, parent, false); ((TextView) rootView.findViewById(android.R.id.text1)).setText("Test"); } return rootView; } @Override public long getItemId(final int position) { return 0; } @Override public Object getItem(final int position) { return null; } @Override public int getCount() { return 2; } }); } } 

Activity_main.xml

 <?xml version="1.0" encoding="utf-8"?> <ListView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@android:id/list" android:layout_width="match_parent" android:layout_height="match_parent" android:cacheColorHint="@android:color/transparent" android:divider="@null" android:dividerHeight="0px" android:fadeScrollbars="false" android:fastScrollEnabled="true" android:listSelector="@drawable/listview_selector" android:scrollingCache="false" android:verticalScrollbarPosition="right" /> 

Res / drawable-v21 / listview_selector.xml (Tengo un selector normal para otras versiones de Android)

 <?xml version="1.0" encoding="utf-8"?> <ripple xmlns:android="http://schemas.android.com/apk/res/android" /> 

Lo que he intentado

Aparte del código simple anterior, también he intentado establecer el selector por propiedad de fondo del elemento, en lugar de usar "listSelector" en el ListView, pero no ayudó.

Otra cosa que he intentado es establecer el primer plano de los elementos, pero también tuvo el mismo resultado.

La pregunta

¿Cómo soluciono este problema? ¿Por qué ocurre? ¿Qué hice mal?

¿Cómo puedo ir más allá, para soportar 9 parches e incluso CardView?

Además, ¿cómo puedo establecer un estado para el nuevo fondo, como estar marcado / seleccionado?


Actualización: el dibujo fuera de la vista se fija utilizando algo como esto:

 <ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?attr/colorControlHighlight" > <item android:id="@android:id/mask"> <color android:color="@color/listview_pressed" /> </item> </ripple> 

Sin embargo, tiene la cuestión de fondo de estar atrapado, y no puedo encontrar la forma de manejar el resto de las características que faltan (9-patch, cardView, …).

Creo que el color-estar-atascado tiene algo que ver con usarlo como el primer plano de las vistas.


EDIT: Veo que algunas personas no entienden de qué trata la pregunta aquí.

Se trata de manejar el nuevo efecto de rizado, mientras que todavía tiene el selector más antiguo / CardView.

Por ejemplo, aquí hay un selector:

 <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="..." android:state_selected="true"/> <item android:drawable="..." android:state_activated="true"/> <item android:drawable="..." android:state_focused="true" android:state_pressed="true"/> <item android:drawable="..." android:state_pressed="true"/> <item android:drawable="..."/> </selector> 

Se puede utilizar como selector de listas o como fondo de una única vista.

Sin embargo, no puedo encontrar cómo utilizarlo junto con la ondulación dibujable.

Sé que la ondulación ya se ocupa de algunos de los estados, pero para algunos, no lo hace. Además, no puedo averiguar cómo hacer que maneje 9 parches y CardView.

Espero que ahora sea más fácil entender el problema que tengo.


Acerca de la cuestión del color de la onda se "atascado", creo que es debido a cómo he hecho el diseño. Quería un diseño que se pueda comprobar (cuando decida) y también tener el efecto de hacer clic, así que esto es lo que hice (basado en este sitio web y otro que no puedo encontrar):

 public class CheckableRelativeLayout extends RelativeLayout implements Checkable { private boolean mChecked; private static final String TAG = CheckableRelativeLayout.class.getCanonicalName(); private static final int[] CHECKED_STATE_SET = { android.R.attr.state_checked }; private Drawable mForegroundDrawable; public CheckableRelativeLayout(final Context context) { this(context, null, 0); } public CheckableRelativeLayout(final Context context, final AttributeSet attrs) { this(context, attrs, 0); } public CheckableRelativeLayout(final Context context, final AttributeSet attrs, final int defStyle) { super(context, attrs, defStyle); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CheckableRelativeLayout, defStyle, 0); setForeground(a.getDrawable(R.styleable.CheckableRelativeLayout_foreground)); a.recycle(); } public void setForeground(final Drawable drawable) { this.mForegroundDrawable = drawable; } public Drawable getForeground() { return this.mForegroundDrawable; } @Override protected int[] onCreateDrawableState(final int extraSpace) { final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); if (isChecked()) { mergeDrawableStates(drawableState, CHECKED_STATE_SET); } return drawableState; } @Override protected void drawableStateChanged() { super.drawableStateChanged(); final Drawable drawable = getBackground(); boolean needRedraw = false; final int[] myDrawableState = getDrawableState(); if (drawable != null) { drawable.setState(myDrawableState); needRedraw = true; } if (mForegroundDrawable != null) { mForegroundDrawable.setState(myDrawableState); needRedraw = true; } if (needRedraw) invalidate(); } @Override protected void onSizeChanged(final int width, final int height, final int oldwidth, final int oldheight) { super.onSizeChanged(width, height, oldwidth, oldheight); if (mForegroundDrawable != null) mForegroundDrawable.setBounds(0, 0, width, height); } @Override protected void dispatchDraw(final Canvas canvas) { super.dispatchDraw(canvas); if (mForegroundDrawable != null) mForegroundDrawable.draw(canvas); } @Override public boolean isChecked() { return mChecked; } @Override public void setChecked(final boolean checked) { setChecked(checked, true); } public void setChecked(final boolean checked, final boolean alsoRecursively) { mChecked = checked; refreshDrawableState(); if (alsoRecursively) ViewUtil.setCheckedRecursively(this, checked); } @Override public void toggle() { setChecked(!mChecked); } @Override public Parcelable onSaveInstanceState() { // Force our ancestor class to save its state final Parcelable superState = super.onSaveInstanceState(); final SavedState savedState = new SavedState(superState); savedState.checked = isChecked(); return savedState; } @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public void drawableHotspotChanged(final float x, final float y) { super.drawableHotspotChanged(x, y); if (mForegroundDrawable != null) { mForegroundDrawable.setHotspot(x, y); } } @Override public void onRestoreInstanceState(final Parcelable state) { final SavedState savedState = (SavedState) state; super.onRestoreInstanceState(savedState.getSuperState()); setChecked(savedState.checked); requestLayout(); } // ///////////// // SavedState // // ///////////// private static class SavedState extends BaseSavedState { boolean checked; SavedState(final Parcelable superState) { super(superState); } private SavedState(final Parcel in) { super(in); checked = (Boolean) in.readValue(null); } @Override public void writeToParcel(final Parcel out, final int flags) { super.writeToParcel(out, flags); out.writeValue(checked); } @Override public String toString() { return TAG + ".SavedState{" + Integer.toHexString(System.identityHashCode(this)) + " checked=" + checked + "}"; } @SuppressWarnings("unused") public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { @Override public SavedState createFromParcel(final Parcel in) { return new SavedState(in); } @Override public SavedState[] newArray(final int size) { return new SavedState[size]; } }; } } 

EDIT: la solución era añadir las siguientes líneas para el diseño que he hecho:

 @SuppressLint("ClickableViewAccessibility") @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public boolean onTouchEvent(final MotionEvent e) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && // e.getActionMasked() == MotionEvent.ACTION_DOWN && // mForeground != null) mForeground.setHotspot(e.getX(), e.getY()); return super.onTouchEvent(e); } 

RippleDrawable amplía LayerDrawable . La retroalimentación táctil puede contener varias capas secundarias, incluida una capa de máscara especial que no se dibuja en la pantalla. Una sola capa se puede establecer como la máscara especificando su valor de android:id como mask . La segunda capa puede ser StateListDrawable .

Por ejemplo, aquí está nuestro recurso StateListDrawable con el nombre item_selectable.xml :

 <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="..." android:state_selected="true"/> <item android:drawable="..." android:state_activated="true"/> <item android:drawable="..." android:state_focused="true" android:state_pressed="true"/> <item android:drawable="..." android:state_pressed="true"/> <item android:drawable="..."/> </selector> 

Para lograr el efecto de ondulación junto con los selectores podemos establecer dibujados arriba como una capa de RippleDrawable con el nombre list_selector_ripple.xml :

 <?xml version="1.0" encoding="utf-8"?> <ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/colorControlHighlight"> <item android:id="@android:id/mask"> <color android:color="@android:color/white"/> </item> <item android:drawable="@drawable/item_selectable"/> </ripple> 

UPD:

1) Para utilizar este CardView con CardView simplemente establecerlo como android:foreground , como este:

 <android.support.v7.widget.CardView ... android:foreground="@drawable/list_selector_ripple" /> 

2) Para que el efecto ripple funcione dentro de los límites del 9-parche deberíamos establecer este 9-parcheable como una máscara de ripple list_selector_ripple_nine_patch.xml ( list_selector_ripple_nine_patch.xml ):

 <?xml version="1.0" encoding="utf-8"?> <ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="@color/colorControlHighlight"> <item android:id="@android:id/mask" android:drawable="@drawable/your_nine_patch" /> <item android:drawable="@drawable/your_nine_patch" /> </ripple> 

A continuación, establezca el fondo de la vista:

 <LinearLayout ... android:background="@drawable/list_selector_ripple_nine_patch" /> 

Manera simple de crear una ondulación hace un xml en la carpeta drawable-v21 y utiliza este código para xml.

 android:backgroung="@drawable/ripple_xyz" 

Y si, a través de java / dinámicamente uso.

 View.setBackgroundResource(R.drawable.ripple_xyz); 

Aquí está el ripple_xyz.xml.

 <?xml version="1.0" encoding="utf-8"?> <ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="#228B22" > // ^ THIS IS THE COLOR FOR RIPPLE <item> <shape android:shape="rectangle" android:useLevel="false" > <solid android:color="#CCFFFFFF" /> // ^ THIS IS THE COLOR FOR BACK GROUND </shape> </item> 

Tienes que configurar una capa de máscara en la ondulación.

Algo como esto:

 <?xml version="1.0" encoding="utf-8"?> <ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?attr/colorControlHighlight"> <item android:id="@id/mask"> <color android:color="@color/myColor" /> </item> </ripple> 

Compruebe este tutorial. El efecto de la ondulación es instrumento y su funcionamiento muy bien. Efecto de ondulación en RecyclerView

Puedes manejar 9 imágenes de parche por algo como el siguiente código:

  <?xml version="1.0" encoding="utf-8"?> <ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="?attr/colorControlHighlight" > <item android:id="@android:id/mask" android:drawable="@drawable/comment_background"> </item> </ripple> 

Donde comment_background es una imagen de 9 parches

  • Android Button Ripple en Lollipop y resaltar en pre lollipop
  • Android L's Ripple Effect - Touch Feedback para los botones - Uso de XML
  • El efecto Ripple no funciona en RelativeLayout
  • Cambiar el color FAB con efecto de ondulación
  • Efecto de ondulación de material oculto por otra vista en el diseño
  • No se pudo encontrar la clase 'android.graphics.drawable.RippleDrawable' Unicode issue?
  • ¿Cómo implementar el efecto ripple en Android KeyboardView?
  • ¿Cómo hacer una ondulación circular en un botón cuando se está haciendo clic?
  • Lollipop RippleDrawable vs Selector para Pre-Lollipop
  • Agregar los efectos de un RippleDrawable y un StateListDrawable a un RecyclerView
  • ¿Cómo activar el efecto de rizo en Android Lollipop, en una ubicación específica dentro de la vista, sin desencadenar eventos de touch?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.