Comment gérer l'effet D'ondulation sur 9-patch et CardView, et avoir le contrôle sur les États du sélecteur?
Contexte
Je souhaite ajouter un simple effet d'Ondulation pour les éléments listView D'Android Lollipop et au-dessus.
D'abord, je voudrais le définir pour des lignes simples, puis pour des lignes 9-patch et même CardView.
Le problème
J'étais sûr que celui-ci va être très facile, car il ne m'oblige même pas à définir le sélecteur normal. J'ai échoué à le faire même pour des lignes simples. Pour une raison quelconque, l'effet d'ondulation va au-delà de la ligne limites:
Non seulement cela, mais dans certains cas, l'arrière-plan de l'élément reste bloqué sur la couleur que je l'ai définie.
Le code
C'est ce que j'ai essayé:
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 (j'ai un sélecteur normal pour les autres versions Android)
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android" />
Ce que j'ai essayé
Mis à part le code simple ci-dessus, J'ai également essayé de définir la propriété de fond du sélecteur par élément, au lieu d'utiliser "listSelector" sur ListView, mais cela n'a pas aidé.
Une autre chose que j'ai essayé est de définir le premier plan des éléments, mais il a également eu le même résultat.
La question
Comment puis-je résoudre ce problème? Pourquoi faut-il se produire? Qu'ai-je fait de mal?
Comment puis-je aller plus loin, pour soutenir 9-patch et même CardView ?
Aussi, comment puis-je définir un état pour le nouvel arrière-plan, comme l' vérifié / sélectionné ?
Update: le dessin en dehors de la vue est corrigé en utilisant quelque chose comme ceci:
<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>
Pourtant, il y a le problème de l'arrière-plan bloqué, et je ne trouve pas comment gérer le reste des fonctionnalités manquantes (9-patch, cardView,...) .
Je pense que la couleur-être-coincé a quelque chose à voir avec l'utiliser comme premier plan des vues.
EDIT: je vois que certaines personnes ne comprennent pas de quoi parle la question ici.
Il s'agit de gérer le nouvel effet d'ondulation, tout en ayant l'ancien sélecteur / CardView.
Par exemple, voici un sélecteur-drawble:
<?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>
Cela peut être utilisé comme un sélecteur de liste ou un arrière-plan d'une vue unique.
Cependant, je ne trouve pas comment l'utiliser avec le drawable ripple.
Je sais que l'ondulation s'occupe déjà de certains États, mais pour certains, ce n'est pas le cas. de plus, Je ne peux pas savoir comment le faire gérer 9-patch et CardView.
J'espère maintenant il est plus facile de comprendre le problème que j'ai.
A propos de la question de la couleur de l'ondulation est "stucked", je pense que c'est à cause de la façon dont j'ai fait la mise en page. Je voulais une mise en page qui peut être vérifiée (quand je décide de le faire) et qui a également pour effet de cliquer, c'est donc ce que j'ai fait (basé sur ce site et un autre que je ne peux pas trouver):
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: le correctif consistait à ajouter les lignes suivantes pour la mise en page que j'ai faite:
@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);
}
5 réponses
RippleDrawable
s'étend LayerDrawable
. Touch feedback drawable peut contenir plusieurs calques enfants, y compris un calque de masque spécial qui n'est pas dessiné à l'écran. Une seule couche peut être définie comme masque en spécifiant sa valeur android:id
comme mask
. La deuxième couche peut être StateListDrawable
.
Par exemple, voici notre StateListDrawable
ressource avec le nom 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>
Pour obtenir un effet d'ondulation avec des sélecteurs, nous pouvons définir drawable ci-dessus comme une couche de RippleDrawable
avec le nom 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) Pour utiliser ce dessinés avec CardView
suffit de le définir comme android:foreground
, comme ceci:
<android.support.v7.widget.CardView
...
android:foreground="@drawable/list_selector_ripple"
/>
2) pour que l'effet d'ondulation fonctionne dans les limites du 9-patch, nous devrions définir ce 9-patch drawable comme masque de ripple drawable (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>
Ensuite, définissez l'arrière-plan de la vue:
<LinearLayout
...
android:background="@drawable/list_selector_ripple_nine_patch"
/>
Un moyen Simple de créer une ondulation créez un fichier xml dans le dossier drawable-v21 et utilisez ce code pour xml.
android:backgroung="@drawable/ripple_xyz"
Et si, via java / utiliser dynamiquement.
View.setBackgroundResource(R.drawable.ripple_xyz);
Voici le 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>
Vous devez définir un calque de masque dans l'ondulation.
Quelque Chose comme ceci:
<?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>
Consultez ce tutoriel. l'effet d'ondulation est implémenté et fonctionne bien. Effet D'ondulation sur RecyclerView
Vous pouvez gérer 9 images de patch par quelque chose comme le code ci-dessous:
<?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>
Où comment_background est une image 9 patch