web-dev-qa-db-fra.com

Affichage de la vue personnalisée sous l'élément recyclé View

Tout d’abord, j’ai vu cette question: Ajouter un fond coloré avec un texte/une icône sous la rangée balayée lorsqu’on utilise RecyclerView d’Android

Cependant, même si le titre indique "avec texte/icône", la réponse ne couvre que l'utilisation de l'objet Canvas pour dessiner un rectangle.

Maintenant, j'ai dessiné un rectangle vert implémenté; Cependant, je veux rendre plus évident ce qui va se passer en ajoutant le bouton "Terminé", comme sur la capture d'écran suivante.

 enter image description here

J'ai créé une vue personnalisée:

public class ChoosePlanView extends LinearLayout{

    public ChoosePlanView(Context context) {
        super(context);
        init();
    }

    public ChoosePlanView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public ChoosePlanView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    private void init(){
        View v = inflate(getContext(), R.layout.choose_plan_view, null);
        addView(v);
    }
}

Et la mise en page est très simple, composée d'un fond vert et de l'icône:

<LinearLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:orientation="vertical" 
Android:layout_width="match_parent"
Android:layout_height="match_parent"
Android:background="@color/choose_green">

    <ImageView
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:id="@+id/doneIcon"
        Android:layout_gravity="center_horizontal|right"
        Android:src="@mipmap/done"/>

</LinearLayout>

J'ai essayé d'utiliser la méthode OnChildDraw:

// ChoosePlanView choosePlanView; <- that was declared and initialized earlier, so I don't have to create a new ChoosePlanView everytime in OnChildDraw();

ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {    

/*
    onMove, onSwiped omitted
*/
  public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
       View itemView = viewHolder.itemView;

       if(dX < 0){
            choosePlanView.invalidate();
            //choosePlanView.setBackgroundResource(R.color.delete_red);
            choosePlanView.measure(itemView.getWidth(), itemView.getHeight());
            choosePlanView.layout(itemView.getRight() + (int) dX, itemView.getTop(), itemView.getRight(), itemView.getBottom());
            c.save();
            c.translate(choosePlanView.getRight() + (int) dX, viewHolder.getAdapterPosition()*itemView.getHeight());

            choosePlanView.draw(c);
            c.restore();
       }

       super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
  }
}

Et ça marche un peu (dessine ma ChoosePlanView), mais il y a deux problèmes.

Tout d'abord, il ne dessine qu'un petit carré vert entourant mon icône "Terminé" (la partie rouge de la vue de la capture d'écran ci-dessous provient de la ligne commentée choosePlanView.setBackgroundResource(R.color.delete_red);). La vue a la largeur correcte cependant, lorsqu'elle est cochée dans le débogueur.

La deuxième chose est l'endroit où l'icône est placée. J'ai précisé que je souhaitais qu'il soit centré horizontalement et toujours "collé" sur le côté droit de la vue. Quoi qu’il en soit, lorsque l’élément RecyclerView est balayé, l’arrière-plan rouge indique qu’il n’est pas placé au centre non plus.

J'ai essayé d'ajouter la ligne choosePlanView.invalidate(), parce que je pensais que cela se produisait, car la vue avait été créée plus tôt, mais il semble que le traçage ne change rien.

 enter image description here

12
spoko

L'utilisation de l'interface ItemTouchUiUtil offre une solution robuste à cela. Il vous permet d’avoir une vue de premier plan et d’arrière-plan dans votre ViewHolder et de déléguer la gestion de tous les balayages à la vue de premier plan. Voici un exemple que j’utilise pour balayer juste à supprimer.

Dans votre ItemTouchHelper.Callback :

public class ClipItemTouchHelper extends ItemTouchHelper.SimpleCallback {

    ...

    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        if (viewHolder != null){
            final View foregroundView = ((ClipViewHolder) viewHolder).clipForeground;

            getDefaultUIUtil().onSelected(foregroundView);
        }
    }

    @Override
    public void onChildDraw(Canvas c, RecyclerView recyclerView,
                            RecyclerView.ViewHolder viewHolder, float dX, float dY,
                            int actionState, boolean isCurrentlyActive) {
        final View foregroundView = ((ClipViewHolder) viewHolder).clipForeground;

        drawBackground(viewHolder, dX, actionState);

        getDefaultUIUtil().onDraw(c, recyclerView, foregroundView, dX, dY,
                actionState, isCurrentlyActive);
     }

    @Override
    public void onChildDrawOver(Canvas c, RecyclerView recyclerView,
                                RecyclerView.ViewHolder viewHolder, float dX, float dY,
                                int actionState, boolean isCurrentlyActive) {
        final View foregroundView = ((ClipViewHolder) viewHolder).clipForeground;

        drawBackground(viewHolder, dX, actionState);

        getDefaultUIUtil().onDrawOver(c, recyclerView, foregroundView, dX, dY,
                actionState, isCurrentlyActive);
    }

    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder){
        final View backgroundView = ((ClipViewHolder) viewHolder).clipBackground;
        final View foregroundView = ((ClipViewHolder) viewHolder).clipForeground;

        // TODO: should animate out instead. how?
        backgroundView.setRight(0);

        getDefaultUIUtil().clearView(foregroundView);
    }

    private static void drawBackground(RecyclerView.ViewHolder viewHolder, float dX, int actionState) {
        final View backgroundView = ((ClipViewHolder) viewHolder).clipBackground;

        if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
            //noinspection NumericCastThatLosesPrecision
            backgroundView.setRight((int) Math.max(dX, 0));
        }
    }
}

Et dans votre fichier de présentation, définissez les vues de premier plan et d’arrière-plan:

<LinearLayout
    xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:tools="http://schemas.Android.com/tools"
    Android:id="@+id/clipRow"
    Android:layout_width="match_parent"
    Android:layout_height="wrap_content"
    Android:orientation="vertical">

        <View style="@style/Divider"/>

        <FrameLayout
        xmlns:Android="http://schemas.Android.com/apk/res/Android"
        xmlns:tools="http://schemas.Android.com/tools"
        Android:layout_width="match_parent"
        Android:layout_height="wrap_content"
        Android:background="@drawable/row_selector"
        >

        <RelativeLayout
            Android:id="@+id/clipBackground"
            Android:layout_width="0dp"
            Android:layout_height="match_parent"
            Android:background="@color/swipe_bg"
            tools:layout_width="match_parent">

            <ImageView
                Android:layout_width="32dp"
                Android:layout_height="32dp"
                Android:layout_alignParentLeft="true"
                Android:layout_alignParentStart="true"
                Android:layout_centerVertical="true"
                Android:layout_marginLeft="12dp"
                Android:layout_marginStart="12dp"
                Android:focusable="false"
                Android:src="@drawable/ic_delete_24dp"
                tools:ignore="ContentDescription"/>

        </RelativeLayout>

        <RelativeLayout
            Android:id="@+id/clipForeground"
            Android:layout_width="match_parent"
            Android:layout_height="match_parent"
            Android:paddingBottom="@dimen/clip_vertical_margin"
            Android:paddingLeft="@dimen/clip_horizontal_margin"
            Android:paddingRight="@dimen/clip_horizontal_margin"
            Android:paddingTop="@dimen/clip_vertical_margin" >

            <CheckBox
                Android:id="@+id/favCheckBox"
                Android:layout_width="@dimen/view_image_size"
                Android:layout_height="@dimen/view_image_size"
                Android:layout_alignParentLeft="true"
                Android:layout_alignParentStart="true"
                Android:background="@Android:color/transparent"
                Android:button="@drawable/ic_star_outline_24dp"
                Android:clickable="true"
                Android:contentDescription="@string/content_favorite"
                />

             ...

        </RelativeLayout>

    </FrameLayout>

</LinearLayout>
16
Michael Updike

Au lieu de tirer dessus, je pense que vous devriez utiliser viewType

Implémente getItemViewType(int position) dans l'adaptateur, en renvoyant différents types, par exemple. 0 pour la normale, 1 pour swipped

et changez onCreateViewHolder etc. pour retourner une mise en page différente sur différents viewType

1
Derek Fung

J'ai réussi à faire en sorte que cela fonctionne avec la couleur et une image en étendant la réponse fournie par Sanvywell à l'adresse: Ajout d'un fond coloré avec du texte/une icône sous la rangée balayée lors de l'utilisation de RecyclerView d'Android

Je ne recréerai pas l'intégralité du post (vous pouvez suivre le lien). En résumé, je n’ai pas créé de vue complète à rendre lors du balayage, mais j’ai simplement ajouté une image bitmap au canevas, positionnée de manière appropriée sur l’élément RecyclerView.

0
HappyKatz