¿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 efecto Ripple no aparece a veces
- Crear una ondulación dibujable sin transparencia
- Cómo crear efecto de rizo para pre-lollipop
- Cómo utilizar RippleDrawable programaticamente en código (no xml) con Android 5.0 Lollipop?
- Efecto de ondulación de Android anulado por el estado seleccionado
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:
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); }
- Botón de Toggle con estilo de botón sin bordes - ¿cómo obtener el fondo en estado marcado?
- Cómo crear efecto de rizo en un diseño sencillo
- Color de fondo ondulado de Android
- Android Ripple Effect + Elevación en las vistas que no son de botón
- El efecto Ripple no va por encima de ImageView
- RippleDrawable máscara de color, ¿para qué sirve?
- AppCompatButton backgroundTint API <21
- Android cómo hacer referencia a una nueva clase de la Ripple RippleDrawable
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