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:

entrez la description de l'image ici

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);
}
25
demandé sur android developer 2014-11-27 15:32:52

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"
    />
20
répondu erakitin 2014-12-05 16:00:57

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>

5
répondu Hemant Shori 2014-12-18 07:08:05

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>
2
répondu Gabriele Mariotti 2014-11-27 17:42:45

Consultez ce tutoriel. l'effet d'ondulation est implémenté et fonctionne bien. Effet D'ondulation sur RecyclerView

2
répondu ch3tanz 2014-12-05 09:21:12

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

2
répondu Madhu 2014-12-05 09:54:10