[Java & Kotlin] Créer un RecyclerView avec plusieurs sélections

RecyclerView n'a aucune fonction

Je suis moi-même un partisan de ListView depuis environ un an depuis que j'ai commencé à développer Android. J'ai pensé: "Parce que ** RecyclerView n'a pas de fonction **, ** C'est une version dégradée de ListView? ** Cela semble difficile et difficile à utiliser, ** Physiologiquement impossible **". ListView a Divider, FastScroll et ChoiceMode. Et pourtant, le successeur ** RecyclerView n'a rien **! Je pense qu'il fut un temps où tout le monde le pensait.

Cependant, en réalité, RecyclerView est plus facile à manipuler. C'est pourquoi il est florissant. Même si ListView est plus facile à implémenter, je vais l'implémenter avec RecyclerView. Cela peut être le résultat de s'entraîner à créer ** RecyclerAdapter désespérément toute la nuit, mais compte tenu de l'extensibilité plus tard, il est préférable de l'implémenter avec RecyclerView. Il a trahi ListView et est maintenant un partisan de RecyclerView.

Disons que l'attrait pratique de RecyclerView est ici, et nous entrerons dans le sujet principal. Il y a beaucoup de bons articles écrits par des adeptes de RecyclerView, alors veuillez les lire également (désolé de les avoir rendus abonnés).

Notions de base de RecyclerViewJe viens de résumer les bases de RecyclerView

Je souhaite également sélectionner dans RecyclerView

Cela signifie que vous souhaitez créer des éléments sélectionnables. C'est un RecyclerView qui n'a aucune fonctionnalité, mais ** modifions-le en un SelectableRecyclerView que je peux sélectionner directement **. Tel est le contenu de cet article. Cette fonctionnalité est presque certainement implémentée dans les applications récentes, mais étonnamment, il y a peu d'articles qui l'expliquent, j'ai donc écrit cet article.

Fonctionnalités sélectionnables de la vue Recycler

Comme il est implémenté dans diverses applications, vous savez probablement de quel type de fonction il s'agit, mais je vais faire une liste de fonctions pour le moment.

・ Se comporte généralement comme une vue de recyclage normale ・ Appuyez et maintenez un élément pour entrer en mode de sélection ・ En mode de sélection, vous pouvez également sélectionner avec NormalClick (vous pouvez également sélectionner avec un appui long) -Cliquez sur l'élément sélectionné pour le désélectionner ・ Lorsque tous les éléments sont désélectionnés, le mode de sélection est automatiquement désactivé. ・ Vous pouvez obtenir l'élément sélectionné ・ Dans certains cas, vous pouvez toujours accéder au mode de sélection.

Est-ce à propos de ça? Je pense que ces fonctions sont communes même s'il existe des différences de fonctions selon l'application. Si vous avez besoin d'autre chose, veuillez l'implémenter vous-même. Cette extensibilité est également un bon point de RecyclerView.

environnement

Il s'appelle SelectableRecyclerView, j'ai donc pensé en faire une vue, mais compte tenu de l'extensibilité, ce n'est pas le cas. Les fonctions requises diffèrent d'une personne à l'autre. Donc cette fois je vais l'implémenter avec Adapter. Avant cela, j'écrirai sur cet environnement. J'écrirai sur la base du Kotlin de base, mais comme il semble y avoir une demande en Java, j'écrirai également l'implémentation en Java de l'adaptateur lui-même. (Parce que je suis un débutant en Java, merci de me dire si vous faites une erreur)

· Java 8 ・ Kotlin 1.4 ・ Android Studio 4.0.1 ・ SDK cible 30 ・ SDK minimum 24 ・ Construire des outils 30.0.1

Essayez de le rendre sélectionnable

Adaptateur et support

<détails>

Version Kotlin (cliquez pour agrandir) </ summary>
Depuis que je l'ai écrit en Java au début, Holder utilise Java. Kotlin est un dieu qui peut utiliser celui de Java tel quel.

SelectableAdapterWithKotlin.kt


class SelectableAdapterWithKotlin(private val context: Context, private val itemDataList: List<String>, private val isAlwaysSelectable: Boolean): RecyclerView.Adapter<SelectableHolder>(){

    //Lorsque isAlwaysSelectable est activé, sélectionnez le mode depuis le début
    private var isSelectableMode = isAlwaysSelectable
    private val selectedItemPositions = mutableSetOf<Int>()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SelectableHolder {
        return SelectableHolder(LayoutInflater.from(parent.context).inflate(R.layout.view_multi_url_card, parent, false))
    }

    @SuppressLint("SetTextI18n")
    override fun onBindViewHolder(holder: SelectableHolder, position: Int) {
        with(holder) {
            mainTextView.text = itemDataList[position]
            subTextView.text = "position $position"

            //Si cet élément est sélectionné, cochez-le (Afficher l'image de ✓)
            checkLayout.visibility = if (isSelectedItem(position)) View.VISIBLE else View.GONE

            cardView.setOnClickListener {

                //Traitez comme un clic normal lorsque vous n'êtes pas en mode sélection
                if (!isSelectableMode && !isAlwaysSelectable) Toast.makeText(context, "Normal click", Toast.LENGTH_SHORT).show()
                else {
                    if (isSelectedItem(position)) removeSelectedItem(position)
                    else addSelectedItem(position)

                    onBindViewHolder(holder, position)
                }
            }
            cardView.setOnLongClickListener {

                //Cliquez longuement pour entrer en mode de sélection
                if (isSelectedItem(position)) removeSelectedItem(position)
                else addSelectedItem(position)

                onBindViewHolder(holder, position)

                return@setOnLongClickListener true
            }
        }
    }

    override fun getItemCount(): Int = itemDataList.size

    //Passer le jeu qui enregistre la position de l'élément sélectionné vers l'extérieur
    fun getSelectedItemPositions() = selectedItemPositions.toSet()
    
    //Vérifiez si l'élément de la position spécifiée est sélectionné
    private fun isSelectedItem(position: Int): Boolean = (selectedItemPositions.contains(position))

    //Entrer en mode de sélection lorsque vous n'êtes pas en mode de sélection
    private fun addSelectedItem(position: Int){
        if(selectedItemPositions.isEmpty() && !isAlwaysSelectable){
            isSelectableMode = true
            Toast.makeText(context, "Selectable Mode ON", Toast.LENGTH_SHORT).show()
        }
        selectedItemPositions.add(position)
    }

    //Si le dernier est désélectionné en mode sélection, désactiver le mode sélection
    private fun removeSelectedItem(position: Int){
        selectedItemPositions.remove(position)
        if(selectedItemPositions.isEmpty() && !isAlwaysSelectable){
            isSelectableMode = false
            Toast.makeText(context, "Selectable Mode OFF", Toast.LENGTH_SHORT).show()
        }
    }
}

<détails>

Version Java (cliquez pour développer) </ summary>

SelectableHolder.java


public class SelectableHolder extends RecyclerView.ViewHolder {

    public TextView mainTextView;
    public TextView subTextView;
    public CardView cardView;
    public ConstraintLayout checkLayout;

    public SelectableHolder(View itemView) {
        super(itemView);

        mainTextView = itemView.findViewById(R.id.VMU_MainText);
        subTextView = itemView.findViewById(R.id.VMU_SubText);
        cardView = itemView.findViewById(R.id.VMU_CardView);
        checkLayout = itemView.findViewById(R.id.VMU_CheckLayout);
    }
}

SelectableAdapter.java


public class SelectableAdapter extends RecyclerView.Adapter<SelectableHolder> {

    private Context context;
    private List<String> itemDataList;

    private Boolean isSelectableMode;
    private Boolean isAlwaysSelectable;
    private Set<Integer> selectedItemPositionsSet = new ArraySet<>();

    public SelectableAdapter(Context context, List<String> itemDataList, Boolean isAlwaysSelectable){
        this.context = context;
        this.itemDataList = itemDataList;
        this.isAlwaysSelectable = isAlwaysSelectable;

        //Lorsque isAlwaysSelectable est activé, sélectionnez le mode depuis le début
        isSelectableMode = isAlwaysSelectable;
    }

    @NonNull
    @Override
    public SelectableHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new SelectableHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.view_multi_url_card, parent, false));
    }

    @SuppressLint("SetTextI18n")
    @Override
    public void onBindViewHolder(@NonNull final SelectableHolder holder, final int position) {
        holder.mainTextView.setText(itemDataList.get(position));
        holder.subTextView.setText("position " + position);

        //Si cet élément est sélectionné, cochez-le (Afficher l'image de ✓)
        if(isSelectedItem(position)){
            holder.checkLayout.setVisibility(View.VISIBLE);
        }
        else {
            holder.checkLayout.setVisibility(View.GONE);
        }

        holder.cardView.setOnClickListener(view -> {

            //Traitez comme un clic normal lorsque vous n'êtes pas en mode sélection
            if(!isSelectableMode && !isAlwaysSelectable) Toast.makeText(context, "Normal click", Toast.LENGTH_SHORT).show();
            else {
                if(isSelectedItem(position)) removeSelectedItem(position);
                else addSelectedItem(position);

                onBindViewHolder(holder, position);
            }
        });

        holder.cardView.setOnLongClickListener(view -> {

            //Cliquez longuement pour entrer en mode de sélection
            if (isSelectedItem(position)) removeSelectedItem(position);
            else addSelectedItem(position);

            onBindViewHolder(holder, position);

            return true;
        });
    }

    @Override
    public int getItemCount() {
        return itemDataList.size();
    }

    //Passer le jeu qui enregistre la position de l'élément sélectionné vers l'extérieur
    Set<Integer> getSelectedItemPositions(){
        return selectedItemPositionsSet;
    }

    //Vérifiez si l'élément de la position spécifiée est sélectionné
    private Boolean isSelectedItem(int position){
        return selectedItemPositionsSet.contains(position);
    }

    //Entrer en mode de sélection lorsque vous n'êtes pas en mode de sélection
    private void addSelectedItem(int position){
        if(selectedItemPositionsSet.isEmpty() && !isAlwaysSelectable) {
            isSelectableMode = true;
            Toast.makeText(context, "Selectable Mode ON", Toast.LENGTH_SHORT).show();
        }
        selectedItemPositionsSet.add(position);
    }

    //Si le dernier est désélectionné en mode sélection, désactiver le mode sélection
    private void removeSelectedItem(int position){
        selectedItemPositionsSet.remove(position);
        if(selectedItemPositionsSet.isEmpty() && !isAlwaysSelectable){
            isSelectableMode = false;
            Toast.makeText(context, "Selectable Mode OFF", Toast.LENGTH_SHORT).show();
        }
    }
}

Activity L'activité est écrite en Kotlin. Je n'écris pas grand-chose en premier lieu, donc je pense qu'il peut être facilement porté. Lorsque vous appuyez sur le bouton, la position de l'élément sélectionné est reçue de l'adaptateur (getSelectedItemPositions), et la valeur est obtenue et affichée en la comparant à la liste détenue par l'activité.

Code ci-dessous (cliquez pour développer)

MainActivity.kt


class MainActivity : AppCompatActivity(){

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val itemDataList = listOf("poméranien", "caniche jouet", "Chien shiba", "Bouledogue", "Daxfund", "Dobelman", "beagle", "Labrador retriever", "Golden retriever", "Husky sibérien")
        val selectableAdapter = SelectableAdapterWithKotlin(this, itemDataList, false)

        AM_RecyclerView.apply {
            setHasFixedSize(false)
            adapter = selectableAdapter
            layoutManager = LinearLayoutManager(this@MainActivity)
        }

        AM_Button.setOnClickListener {
            MaterialAlertDialogBuilder(this)
                .setTitle("Élément sélectionné")
                .setMessage(selectableAdapter.getSelectedItemPositions().joinToString(separator = "\n") { itemDataList[it] })
                .setPositiveButton("OK", null)
                .show()
        }
    }
}

Disposition

C'est un peu compliqué d'indiquer qu'il est sélectionné. Cette fois, l'état de la sélection est affiché en affichant / masquant la vue, mais vous pouvez également mettre Vérifier dans la case à cocher.

Code ci-dessous (cliquez pour développer)

view_multi_url_card.xml


<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <androidx.cardview.widget.CardView
        android:id="@+id/VMU_CardView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="16dp"
        android:layout_marginBottom="8dp"
        android:foreground="?android:attr/selectableItemBackground"
        app:cardCornerRadius="8dp"
        app:cardBackgroundColor="@color/colorBackgroundZ4"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <androidx.cardview.widget.CardView
                android:id="@+id/cardView"
                android:layout_width="0dp"
                android:layout_height="0dp"
                app:cardBackgroundColor="@android:color/transparent"
                app:cardElevation="0dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintDimensionRatio="1:1"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent">

                <androidx.constraintlayout.widget.ConstraintLayout
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layout_gravity="center">

                    <ImageView
                        android:id="@+id/VMU_Image"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:layout_gravity="center"
                        android:scaleType="centerCrop"
                        android:src="@drawable/im_dog"
                        app:layout_constraintBottom_toBottomOf="parent"
                        app:layout_constraintEnd_toEndOf="parent"
                        app:layout_constraintStart_toStartOf="parent"
                        app:layout_constraintTop_toTopOf="parent" />

                    <androidx.constraintlayout.widget.ConstraintLayout
                        android:id="@+id/VMU_CheckLayout"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:alpha="0.9"
                        android:background="@color/colorAccent"
                        android:visibility="gone">

                        <ImageView
                            android:layout_width="match_parent"
                            android:layout_height="match_parent"
                            android:layout_gravity="center"
                            android:layout_marginStart="16dp"
                            android:layout_marginTop="16dp"
                            android:layout_marginEnd="16dp"
                            android:layout_marginBottom="16dp"
                            android:scaleType="fitCenter"
                            android:src="@drawable/ic_check"
                            app:layout_constraintBottom_toBottomOf="parent"
                            app:layout_constraintEnd_toEndOf="parent"
                            app:layout_constraintStart_toStartOf="parent"
                            app:layout_constraintTop_toTopOf="parent" />

                    </androidx.constraintlayout.widget.ConstraintLayout>

                </androidx.constraintlayout.widget.ConstraintLayout>
            </androidx.cardview.widget.CardView>

            <LinearLayout
                android:id="@+id/linearLayout"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_marginStart="16dp"
                android:layout_marginTop="16dp"
                android:layout_marginEnd="16dp"
                android:layout_marginBottom="16dp"
                android:orientation="vertical"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toEndOf="@+id/cardView"
                app:layout_constraintTop_toTopOf="parent">

                <TextView
                    android:id="@+id/VMU_MainText"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:ellipsize="end"
                    android:singleLine="true"
                    android:text="Texte principal"
                    android:textColor="@color/colorChar"
                    android:textSize="14sp" />

                <TextView
                    android:id="@+id/VMU_SubText"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="8dp"
                    android:ellipsize="end"
                    android:gravity="end"
                    android:singleLine="true"
                    android:text="Sous-texte"
                    android:textColor="@color/colorCharSec"
                    android:textSize="12sp" />

            </LinearLayout>
        </androidx.constraintlayout.widget.ConstraintLayout>
    </androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>

activity_main.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/colorBackgroundZ3"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/AM_RecyclerView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>

    <com.google.android.material.button.MaterialButton
        android:id="@+id/AM_Button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_margin="8dp"
        android:text="Afficher les éléments sélectionnés" />

</LinearLayout>

Gradle Pour utiliser RecyclerView, vous devez ajouter à Gradle. Cette fois, j'utilise aussi CardView, etc. donc je dois aussi les écrire.

Code ci-dessous (cliquez pour développer)

build.gradle(app)


dependencies {
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
    implementation 'androidx.cardview:cardview:1.0.0'
    implementation 'com.google.android.material:material:1.2.0'
}

J'ai essayé de bouger

Je l'ai essayé sur Pixel 3 (Android 11). La version Kotlin et la version Java fonctionnent de la même manière. 実機で動いているZIF

finalement

Cette fois, j'ai créé un RecyclerView sélectionnable. Dans le GIF ci-dessus, isAlwaysSelectable est false, mais si vous le définissez sur true, il sera toujours sélectionnable. Il peut y avoir des erreurs dans Java, donc si vous en trouvez une, veuillez commenter. Veuillez faire LGTM même si cela est utile ou non (c'est un mensonge. Veuillez commenter si cela ne vous aide pas. Je vous aiderai autant que possible)

Recommended Posts