1062 Stimmen

Wie man RecyclerView mit mehreren Ansichtstypen erstellt

Von Erstellen von dynamischen Listen mit RecyclerView:

Wenn wir einen RecyclerView.Adapter erstellen, müssen wir einen ViewHolder angeben, der sich mit dem Adapter verbindet.

public class MyAdapter extends RecyclerView.Adapter {

    private String[] mDataset;

    public MyAdapter(String[] myDataset) {
        mDataset = myDataset;
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {
        public TextView mTextView;
        public ViewHolder(TextView v) {
            super(v);
            mTextView = v;
        }
    }

    @Override
    public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.some_layout, parent, false);

        //findViewById...

        ViewHolder vh = new ViewHolder(v);
        return vh;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.mTextView.setText(mDataset[position]);
    }

    @Override
    public int getItemCount() {
        return mDataset.length;
    }
}

Ist es möglich, einen RecyclerView mit mehreren Ansichtstypen zu erstellen?

26voto

Islam Assi Punkte 933

Ja, es ist möglich.

Schreiben Sie ein generisches View-Holder:

public abstract class GenericViewHolder extends RecyclerView.ViewHolder
{
    public GenericViewHolder(View itemView) {
        super(itemView);
    }

    public abstract void setDataOnView(int position);
}

Erstellen Sie dann Ihre View-Holder und lassen Sie sie von GenericViewHolder erben. Zum Beispiel dieser:

public class SectionViewHolder extends GenericViewHolder{
    public final View mView;
    public final TextView dividerTxtV;

    public SectionViewHolder(View itemView) {
        super(itemView);
        mView = itemView;
        dividerTxtV = (TextView) mView.findViewById(R.id.dividerTxtV);
    }

    @Override
    public void setDataOnView(int position) {
        try {
            String title= sections.get(position); // Fügen Sie hier Ihren Datenzugriff ein
            if(title!= null)
                this.dividerTxtV.setText(title);
        }catch (Exception e){
            new CustomError("Fehler!"+e.getMessage(), null, false, null, e);
        }
    }
}

Dann wird die RecyclerView.Adapter-Klasse wie folgt aussehen:

public class MyClassRecyclerViewAdapter extends RecyclerView.Adapter {

@Override
public int getItemViewType(int position) {
     // abhängig von Ihrem Problem
     switch (position) {
         case : return VIEW_TYPE1;
         case : return VIEW_TYPE2;
         ...
     }
}

    @Override
   public GenericViewHolder onCreateViewHolder(ViewGroup parent, int viewType)  {
    View view;
    if(viewType == VIEW_TYPE1){
        view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout1, parent, false);
        return new SectionViewHolder(view);
    }else if( viewType == VIEW_TYPE2){
        view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout2, parent, false);
        return new OtherViewHolder(view);
    }
    // Weitere View-Holder ...
    return null;
   }

@Override
public void onBindViewHolder(GenericViewHolder holder, int position) {
    holder.setDataOnView(position);
}

19voto

Gastón Saillén Punkte 10484

Einfacher als je zuvor, vergiss ViewTypes. Es wird nicht empfohlen, mehrere ViewTypes innerhalb eines Adapters zu verwenden. Es wird den Code durcheinanderbringen und das Single Responsibility Principle brechen, da der Adapter jetzt Logik benötigt, um zu wissen, welche Ansicht aufgeblasen werden soll.

Stellen Sie sich nun vor, in großen Teams zu arbeiten, in denen jedes Team an einer dieser ViewTypes-Funktionen arbeiten muss. Es wird ein Durcheinander sein, wenn alle Teams, die an den verschiedenen ViewTypes arbeiten, denselben Adapter berühren. Dies wird durch die Verwendung von ConcatAdapter gelöst, bei dem Sie die Adapter isolieren. Kodieren Sie sie einzeln und fügen Sie sie dann einfach in eine Ansicht ein.

Ab recyclerview:1.2.0-alpha04 können Sie jetzt ConcatAdapter verwenden.

Wenn Sie eine Ansicht mit verschiedenen ViewTypes benötigen, können Sie einfach die Adapter für jeden Abschnitt schreiben und einfach ConcatAdapter verwenden, um sie alle in einem RecyclerView zu merge.

ConcatAdapter

In diesem Bild sind drei verschiedene ViewTypes zu sehen, die ein RecyclerView hat: Header, Inhalt und Fußzeile.

Geben Sie hier eine Bildbeschreibung ein

Sie erstellen nur einen Adapter für jeden Abschnitt und verwenden dann ConcatAdapter, um sie in einem RecyclerView zu mergen:

val firstAdapter: FirstAdapter = …
val secondAdapter: SecondAdapter = …
val thirdAdapter: ThirdAdapter = …
val concatAdapter = ConcatAdapter(firstAdapter, secondAdapter,
thirdAdapter)
recyclerView.adapter = concatAdapter

Geben Sie hier eine Bildbeschreibung ein

Das ist alles, was Sie wissen müssen. Wenn Sie den Ladezustand handhaben möchten, z.B. den letzten Adapter nach einiger Ladezeit entfernen, können Sie LoadState verwenden.

Für weitere Informationen lesen Sie den Beitrag von Florina Muntenescu hier https://medium.com/androiddevelopers/merge-adapters-sequentially-with-mergeadapter-294d2942127a

18voto

Sayan Manna Punkte 584

Ja, es ist möglich.

In Ihrem Adapter getItemViewType Layout wie folgt ....

public class MultiViewTypeAdapter extends RecyclerView.Adapter {

    private ArrayList dataSet;
    Context mContext;
    int total_types;
    MediaPlayer mPlayer;
    private boolean fabStateVolume = false;

    public static class TextTypeViewHolder extends RecyclerView.ViewHolder {

        TextView txtType;
        CardView cardView;

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

            this.txtType = (TextView) itemView.findViewById(R.id.type);
            this.cardView = (CardView) itemView.findViewById(R.id.card_view);
        }
    }

    public static class ImageTypeViewHolder extends RecyclerView.ViewHolder {

        TextView txtType;
        ImageView image;

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

            this.txtType = (TextView) itemView.findViewById(R.id.type);
            this.image = (ImageView) itemView.findViewById(R.id.background);
        }
    }

    public static class AudioTypeViewHolder extends RecyclerView.ViewHolder {

        TextView txtType;
        FloatingActionButton fab;

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

            this.txtType = (TextView) itemView.findViewById(R.id.type);
            this.fab = (FloatingActionButton) itemView.findViewById(R.id.fab);
        }
    }

    public MultiViewTypeAdapter(ArrayList data, Context context) {
        this.dataSet = data;
        this.mContext = context;
        total_types = dataSet.size();
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View view;
        switch (viewType) {
            case Model.TEXT_TYPE:
                view = LayoutInflater.from(parent.getContext()).inflate(R.layout.text_type, parent, false);
                return new TextTypeViewHolder(view);
            case Model.IMAGE_TYPE:
                view = LayoutInflater.from(parent.getContext()).inflate(R.layout.image_type, parent, false);
                return new ImageTypeViewHolder(view);
            case Model.AUDIO_TYPE:
                view = LayoutInflater.from(parent.getContext()).inflate(R.layout.audio_type, parent, false);
                return new AudioTypeViewHolder(view);
        }
        return null;
    }

    @Override
    public int getItemViewType(int position) {

        switch (dataSet.get(position).type) {
            case 0:
                return Model.TEXT_TYPE;
            case 1:
                return Model.IMAGE_TYPE;
            case 2:
                return Model.AUDIO_TYPE;
            default:
                return -1;
        }
    }

    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int listPosition) {

        Model object = dataSet.get(listPosition);
        if (object != null) {
            switch (object.type) {
                case Model.TEXT_TYPE:
                    ((TextTypeViewHolder) holder).txtType.setText(object.text);

                    break;
                case Model.IMAGE_TYPE:
                    ((ImageTypeViewHolder) holder).txtType.setText(object.text);
                    ((ImageTypeViewHolder) holder).image.setImageResource(object.data);
                    break;
                case Model.AUDIO_TYPE:

                    ((AudioTypeViewHolder) holder).txtType.setText(object.text);

            }
        }
    }

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

Für den Referenzlink: Android RecyclerView Beispiel - Mehrere ViewTypes

11voto

Asad Ali Choudhry Punkte 4419

Auch wenn die ausgewählte Antwort korrekt ist, möchte ich sie gerne weiter ausführen. Ich habe einen nützlichen Custom Adapter für mehrere Ansichtstypen im RecyclerView gefunden. Die Kotlin-Version ist hier.

Der benutzerdefinierte Adapter sieht wie folgt aus:

public class CustomAdapter extends RecyclerView.Adapter {
    private final Context context;
    ArrayList list; // ArrayList Ihres Datenmodells
    final int VIEW_TYPE_ONE = 1;
    final int VIEW_TYPE_TWO = 2;

    public CustomAdapter(Context context, ArrayList list) { // Sie können andere Parameter im Konstruktor übergeben
        this.context = context;
        this.list = list;
    }

    private class ViewHolder1 extends RecyclerView.ViewHolder {

        TextView yourView;
        ViewHolder1(final View itemView) {
            super(itemView);
            yourView = itemView.findViewById(R.id.yourView); // Initialisieren Sie alle Ansichten in den Listenelementen
        }
        void bind(int position) {
            // Diese Methode wird jedes Mal aufgerufen, wenn ein Listenelement erstellt oder aktualisiert wird
            // Führen Sie hier Ihre Aufgaben durch
            yourView.setText(list.get(position));
        }
    }

    private class ViewHolder2 extends RecyclerView.ViewHolder {

        TextView yourView;
        ViewHolder2(final View itemView) {
            super(itemView);
            yourView = itemView.findViewById(R.id.yourView); // Initialisieren Sie alle Ansichten in den Listenelementen
        }
        void bind(int position) {
            // Diese Methode wird jedes Mal aufgerufen, wenn ein Listenelement erstellt oder aktualisiert wird
            // Führen Sie hier Ihre Aufgaben durch
            yourView.setText(list.get(position));
        }
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == VIEW_TYPE_ONE) {
            return new ViewHolder1(LayoutInflater.from(context).inflate(R.layout.your_list_item_1, parent, false));
        }
        // Wenn es nicht VIEW_TYPE_ONE ist, dann ist es VIEW_TYPE_TWO
        return new ViewHolder2(LayoutInflater.from(context).inflate(R.layout.your_list_item_2, parent, false));

    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (list.get(position).type == Something) { // Setzen Sie Ihre Bedingung gemäß Ihren Anforderungen
            ((ViewHolder1) holder).bind(position);
        } else {
            ((ViewHolder2) holder).bind(position);
        }
    }

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

    @Override
    public int getItemViewType(int position) {
        // Hier können Sie anhand des ArrayList Ihres Modells entscheiden, welchen Ansichtstyp Sie laden müssen. Zum Beispiel
        if (list.get(position).type == Something) { // Setzen Sie Ihre Bedingung gemäß Ihren Anforderungen
            return VIEW_TYPE_ONE;
        }
        return VIEW_TYPE_TWO;
    }
}

7voto

Michal Faber Punkte 131

Ich habe eine bessere Lösung, die es ermöglicht, mehrere Ansichtstypen auf eine deklarative und typsichere Weise zu erstellen. Es ist in Kotlin geschrieben, was übrigens wirklich schön ist.

Einfache View Holder für alle erforderlichen Ansichtstypen

class ViewHolderMedium(itemView: View) : RecyclerView.ViewHolder(itemView) {
    val icon: ImageView = itemView.findViewById(R.id.icon) as ImageView
    val label: TextView = itemView.findViewById(R.id.label) as TextView
}

Es gibt eine Abstraktion von Adapter-Datenpunkten. Beachten Sie, dass ein Ansichtstyp durch einen Hashcode der jeweiligen View Holder-Klasse (KClass in Kotlin) repräsentiert wird

trait AdapterItem {
   val viewType: Int
   fun bindViewHolder(viewHolder: RecyclerView.ViewHolder)
}

abstract class AdapterItemBase(val viewHolderClass: KClass) : AdapterItem {
   override val viewType: Int = viewHolderClass.hashCode()
   abstract fun bindViewHolder(viewHolder: T)
   override fun bindViewHolder(viewHolder: RecyclerView.ViewHolder) {
       bindViewHolder(viewHolder as T)
   }
}

Nur bindViewHolder muss in konkreten Adapter-Elementklassen überschrieben werden (typsichere Art und Weise).

class AdapterItemMedium(val icon: Drawable, val label: String, val onClick: () -> Unit) : AdapterItemBase(ViewHolderMedium::class) {
    override fun bindViewHolder(viewHolder: ViewHolderMedium) {
        viewHolder.icon.setImageDrawable(icon)
        viewHolder.label.setText(label)
        viewHolder.itemView.setOnClickListener { onClick() }
    }
}

Die Liste solcher AdapterItemMedium-Objekte ist eine Datenquelle für den Adapter, der tatsächlich List akzeptiert. Siehe unten.

Der wichtige Teil dieser Lösung ist eine View Holder-Fabrik, die frische Instanzen eines bestimmten ViewHolders bereitstellt:

class ViewHolderProvider {
    private val viewHolderFactories = hashMapOf>()

    fun provideViewHolder(viewGroup: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val (layoutId: Int, f: Any) = viewHolderFactories.get(viewType)
        val viewHolderFactory = f as (View) -> RecyclerView.ViewHolder
        val view = LayoutInflater.from(viewGroup.getContext()).inflate(layoutId, viewGroup, false)
        return viewHolderFactory(view)
    }

    fun registerViewHolderFactory(key: KClass, layoutId: Int, viewHolderFactory: (View) -> T) {
        viewHolderFactories.put(key.hashCode(), Pair(layoutId, viewHolderFactory))
    }
}

Und die einfache Adapterklasse sieht so aus:

public class MultitypeAdapter(val items: List) : RecyclerView.Adapter() {

   val viewHolderProvider = ViewHolderProvider() // inject ex Dagger2

   init {
        viewHolderProvider!!.registerViewHolderFactory(ViewHolderMedium::class, R.layout.item_medium, { itemView ->
            ViewHolderMedium(itemView)
        })
   }

   override fun getItemViewType(position: Int): Int {
        return items[position].viewType
    }

    override fun getItemCount(): Int {
        return items.size()
    }

    override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): RecyclerView.ViewHolder? {
        return viewHolderProvider!!.provideViewHolder(viewGroup, viewType)
    }

    override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
        items[position].bindViewHolder(viewHolder)
    }
}

Es gibt nur drei Schritte, um einen neuen Ansichtstyp zu erstellen:

  1. Erstellen Sie eine View Holder-Klasse
  2. Erstellen Sie eine Adapter-Item-Klasse (die von AdapterItemBase erbt)
  3. Registrieren Sie die View Holder-Klasse in ViewHolderProvider

Hier ist ein Beispiel dieses Konzepts: android-drawer-template.

Es geht sogar noch weiter - ein Ansichtstyp, der als Spinner-Komponente fungiert, mit auswählbaren Adapter-Elementen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X