Von https://developer.Android.com/preview/material/ui-widgets.html
Beim Erstellen von RecyclerView.Adapter
müssen wir ViewHolder
angeben, das mit dem Adapter verbunden wird.
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
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 also möglich, RecyclerView
mit mehreren Ansichtstypen zu erstellen?
Ja es ist möglich. Implementieren Sie einfach getItemViewType () und achten Sie auf den Parameter viewType
in onCreateViewHolder()
.
Also machst du so etwas wie:
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
class ViewHolder0 extends RecyclerView.ViewHolder {
...
public ViewHolder0(View itemView){
...
}
}
class ViewHolder2 extends RecyclerView.ViewHolder {
...
public ViewHolder2(View itemView){
...
}
@Override
public int getItemViewType(int position) {
// Just as an example, return 0 or 2 depending on position
// Note that unlike in ListView adapters, types don't have to be contiguous
return position % 2 * 2;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
switch (viewType) {
case 0: return new ViewHolder0(...);
case 2: return new ViewHolder2(...);
...
}
}
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
switch (holder.getItemViewType()) {
case 0:
ViewHolder0 viewHolder0 = (ViewHolder0)holder;
...
break;
case 2:
ViewHolder2 viewHolder2 = (ViewHolder2)holder;
...
break;
}
}
}
Wenn es nur wenige Layouts für Ansichtstypen gibt und Bindungslogiken einfach sind, folgen Sie der Lösung von Anton.
Der Code ist jedoch unübersichtlich, wenn Sie die komplexen Layouts und Bindungslogiken verwalten müssen.
Ich glaube, dass die folgende Lösung für jemanden nützlich ist, der mit komplexen Ansichtstypen umgehen muss.
Base DataBinder-Klasse
abstract public class DataBinder<T extends RecyclerView.ViewHolder> {
private DataBindAdapter mDataBindAdapter;
public DataBinder(DataBindAdapter dataBindAdapter) {
mDataBindAdapter = dataBindAdapter;
}
abstract public T newViewHolder(ViewGroup parent);
abstract public void bindViewHolder(T holder, int position);
abstract public int getItemCount();
......
}
Die Funktionen, die zur Definition in dieser Klasse erforderlich sind, entsprechen weitgehend der Adapterklasse beim Erstellen des einzelnen Ansichtstyps.
Erstellen Sie für jeden Ansichtstyp die Klasse, indem Sie diesen DataBinder erweitern.
Sample DataBinder-Klasse
public class Sample1Binder extends DataBinder<Sample1Binder.ViewHolder> {
private List<String> mDataSet = new ArrayList();
public Sample1Binder(DataBindAdapter dataBindAdapter) {
super(dataBindAdapter);
}
@Override
public ViewHolder newViewHolder(ViewGroup parent) {
View view = LayoutInflater.from(parent.getContext()).inflate(
R.layout.layout_sample1, parent, false);
return new ViewHolder(view);
}
@Override
public void bindViewHolder(ViewHolder holder, int position) {
String title = mDataSet.get(position);
holder.mTitleText.setText(title);
}
@Override
public int getItemCount() {
return mDataSet.size();
}
public void setDataSet(List<String> dataSet) {
mDataSet.addAll(dataSet);
}
static class ViewHolder extends RecyclerView.ViewHolder {
TextView mTitleText;
public ViewHolder(View view) {
super(view);
mTitleText = (TextView) view.findViewById(R.id.title_type1);
}
}
}
Erstellen Sie eine Adapterklasse, um DataBinder-Klassen zu verwalten.
Base DataBindAdapter-Klasse
abstract public class DataBindAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return getDataBinder(viewType).newViewHolder(parent);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
int binderPosition = getBinderPosition(position);
getDataBinder(viewHolder.getItemViewType()).bindViewHolder(viewHolder, binderPosition);
}
@Override
public abstract int getItemCount();
@Override
public abstract int getItemViewType(int position);
public abstract <T extends DataBinder> T getDataBinder(int viewType);
public abstract int getPosition(DataBinder binder, int binderPosition);
public abstract int getBinderPosition(int position);
......
}
Erstellen Sie die Klasse, indem Sie diese Basisklasse erweitern, und instantiieren Sie anschließend DataBinder-Klassen und überschreiben Sie abstrakte Methoden
getItemCount
Gibt die Gesamtelementanzahl der Datenbinder zurück
getItemViewType
Definieren Sie die Mapping-Logik zwischen Adapterposition und Ansichtstyp.
getDataBinder
Gibt die DataBinder-Instanz basierend auf dem Ansichtstyp zurück
getPosition
Definieren Sie die Konvertierungslogik an der Adapterposition von der Position im angegebenen DataBinder aus
getBinderPosition
Definieren Sie die Konvertierungslogik von der Adapterposition an die Position im DataBinder
Hoffe, diese Lösung wird hilfreich sein.
Ich habe mehr Details und Beispiele in GitHub hinterlassen, also verweisen Sie bitte auf den folgenden Link, falls Sie dies benötigen.
https://github.com/yqritc/RecyclerView-MultipleViewTypesAdapter
Das Folgende ist kein Pseudocode und ich habe es getestet und es hat für mich funktioniert.
Ich wollte in meinem Recycling-Fenster eine Kopfübersicht erstellen und dann unterhalb der Kopfzeile eine Liste mit Bildern anzeigen, auf die der Benutzer klicken kann.
Ich habe ein paar Schalter in meinem Code verwendet. Ich weiß nicht, ob dies der effizienteste Weg ist, also geben Sie bitte Ihre Kommentare an:
public class ViewHolder extends RecyclerView.ViewHolder{
//These are the general elements in the RecyclerView
public TextView place;
public ImageView pics;
//This is the Header on the Recycler (viewType = 0)
public TextView name, description;
//This constructor would switch what to findViewBy according to the type of viewType
public ViewHolder(View v, int viewType) {
super(v);
if (viewType == 0) {
name = (TextView) v.findViewById(R.id.name);
decsription = (TextView) v.findViewById(R.id.description);
} else if (viewType == 1) {
place = (TextView) v.findViewById(R.id.place);
pics = (ImageView) v.findViewById(R.id.pics);
}
}
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType)
{
View v;
ViewHolder vh;
// create a new view
switch (viewType) {
case 0: //This would be the header view in my Recycler
v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recyclerview_welcome, parent, false);
vh = new ViewHolder(v,viewType);
return vh;
default: //This would be the normal list with the pictures of the places in the world
v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.recyclerview_picture, parent, false);
vh = new ViewHolder(v, viewType);
v.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Intent intent = new Intent(mContext, nextActivity.class);
intent.putExtra("ListNo",mRecyclerView.getChildPosition(v));
mContext.startActivity(intent);
}
});
return vh;
}
}
//Overriden so that I can display custom rows in the recyclerview
@Override
public int getItemViewType(int position) {
int viewType = 1; //Default is 1
if (position == 0) viewType = 0; //if zero, it will be a header view
return viewType;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
//position == 0 means its the info header view on the Recycler
if (position == 0) {
holder.name.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(mContext,"name clicked", Toast.LENGTH_SHORT).show();
}
});
holder.description.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(mContext,"description clicked", Toast.LENGTH_SHORT).show();
}
});
//this means it is beyond the headerview now as it is no longer 0. For testing purposes, I'm alternating between two pics for now
} else if (position > 0) {
holder.place.setText(mDataset[position]);
if (position % 2 == 0) {
holder.pics.setImageDrawable(mContext.getResources().getDrawable(R.drawable.pic1));
}
if (position % 2 == 1) {
holder.pics.setImageDrawable(mContext.getResources().getDrawable(R.drawable.pic2));
}
}
}
Ja, es ist möglich.
Schreiben Sie einen generischen Ansichtshalter:
public abstract class GenericViewHolder extends RecyclerView.ViewHolder
{
public GenericViewHolder(View itemView) {
super(itemView);
}
public abstract void setDataOnView(int position);
}
erstellen Sie dann Ihre Ansichtsinhaber, und erweitern Sie den GenericViewHolder. Zum Beispiel dieses:
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);
if(title!= null)
this.dividerTxtV.setText(title);
}catch (Exception e){
new CustomError("Error!"+e.getMessage(), null, false, null, e);
}
}
}
dann sieht die RecyclerView.Adapter-Klasse wie folgt aus:
public class MyClassRecyclerViewAdapter extends RecyclerView.Adapter<MyClassRecyclerViewAdapter.GenericViewHolder> {
@Override
public int getItemViewType(int position) {
// depends on your 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);
}
// Cont. other view holders ...
return null;
}
@Override
public void onBindViewHolder(GenericViewHolder holder, int position) {
holder.setDataOnView(position);
}
Ja, es ist möglich . In Ihrem Adapter getItemViewType Layout so ....
public class MultiViewTypeAdapter extends RecyclerView.Adapter {
private ArrayList<Model>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<Model>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();
}
}
referenzlink: https://www.journaldev.com/12372/Android-recyclerview-example
RecyclerView kann eine beliebige Anzahl von Viewholdern enthalten, die Sie jedoch benötigen, aber zur besseren Lesbarkeit können Sie sehen, wie einer mit zwei ViewHolders erstellt wird.
Dies kann in drei einfachen Schritten erfolgen
public int getItemViewType(int position)
onCreateViewHolder()
zurückonBindViewHolder()
Methode aufHier ist ein kleiner Code-Ausschnitt
public class YourListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int LAYOUT_ONE= 0;
private static final int LAYOUT_TWO= 1;
@Override
public int getItemViewType(int position)
{
if(position==0)
return LAYOUT_ONE;
else
return LAYOUT_TWO;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view =null;
RecyclerView.ViewHolder viewHolder = null;
if(viewType==LAYOUT_ONE)
{
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.one,parent,false);
viewHolder = new ViewHolderOne(view);
}
else
{
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.two,parent,false);
viewHolder= new ViewHolderTwo(view);
}
return viewHolder;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
if(holder.getItemViewType()== LAYOUT_ONE)
{
// Typecast Viewholder
// Set Viewholder properties
// Add any click listener if any
}
else {
ViewHolderOne vaultItemHolder = (ViewHolderOne) holder;
vaultItemHolder.name.setText(displayText);
vaultItemHolder.name.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
.......
}
});
}
}
//**************** VIEW HOLDER 1 ******************//
public class ViewHolderOne extends RecyclerView.ViewHolder {
public TextView name;
public ViewHolderOne(View itemView) {
super(itemView);
name = (TextView)itemView.findViewById(R.id.displayName);
}
}
//**************** VIEW HOLDER 2 ******************//
public class ViewHolderTwo extends RecyclerView.ViewHolder{
public ViewHolderTwo(View itemView) {
super(itemView);
..... Do something
}
}
}
Meiner Meinung nach ist der Ausgangspunkt für diese Art von RecyclerView das Wissen über diese Methode. Da diese Methode optional überschrieben werden kann, ist sie in der RecylerView-Klasse standardmäßig nicht sichtbar. Daher fragen sich viele Entwickler (einschließlich mir), wo sie anfangen sollen .
Sehen wir uns ein Beispiel an, um meinen Standpunkt zu beweisen. Wenn Sie zwei Layouts anzeigen möchten Bei abwechselnden Positionen tun Sie dies
@Override
public int getItemViewType(int position)
{
if(position%2==0) // Even position
return LAYOUT_ONE;
else // Odd position
return LAYOUT_TWO;
}
Schauen Sie sich das Projekt an wo ich das umgesetzt habe
lassen Sie sich nach Antons Lösung dieses ViewHolder
einfallen, das verschiedene Arten von Layouts enthält/behandelt/delegiert. Sie sind sich jedoch nicht sicher, ob das Ersetzen des neuen Layouts funktionieren würde, wenn die ViewHolder
der Recycling-Ansicht nicht dem Typ des Daten-Roll-Ins entspricht.
Daher wird onCreateViewHolder(ViewGroup parent, int viewType)
nur aufgerufen, wenn ein neues Ansichtslayout benötigt wird.
getItemViewType(int position)
wird für das viewType
aufgerufen;
onBindViewHolder(ViewHolder holder, int position)
wird immer aufgerufen, wenn die Ansicht wiederverwendet wird (neue Daten werden eingelesen und versuchen, sie mit diesem ViewHolder
anzuzeigen).
Wenn onBindViewHolder
aufgerufen wird, muss das Layout der richtigen Ansicht angepasst und ViewHolder
aktualisiert werden.
Ist der Weg zum Ersetzen des Ansichtslayouts für dieses einzubringende ViewHolder
korrekt oder gibt es ein Problem? Schätzen Sie jeden Kommentar!
public int getItemViewType(int position) {
TypedData data = mDataSource.get(position);
return data.type;
}
public ViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
return ViewHolder.makeViewHolder(parent, viewType);
}
public void onBindViewHolder(ViewHolder holder,
int position) {
TypedData data = mDataSource.get(position);
holder.updateData(data);
}
///
public static class ViewHolder extends
RecyclerView.ViewHolder {
ViewGroup mParentViewGroup;
View mCurrentViewThisViewHolderIsFor;
int mDataType;
public TypeOneViewHolder mTypeOneViewHolder;
public TypeTwoViewHolder mTypeTwoViewHolder;
static ViewHolder makeViewHolder(ViewGroup vwGrp,
int dataType) {
View v = getLayoutView(vwGrp, dataType);
return new ViewHolder(vwGrp, v, viewType);
}
static View getLayoutView(ViewGroup vwGrp,
int dataType) {
int layoutId = getLayoutId(dataType);
return LayoutInflater.from(vwGrp.getContext())
.inflate(layoutId, null);
}
static int getLayoutId(int dataType) {
if (dataType == TYPE_ONE) {
return R.layout.type_one_layout;
} else if (dataType == TYPE_TWO) {
return R.layout.type_two_layout;
}
}
public ViewHolder(ViewGroup vwGrp, View v,
int dataType) {
super(v);
mDataType = dataType;
mParentViewGroup = vwGrp;
mCurrentViewThisViewHolderIsFor = v;
if (data.type == TYPE_ONE) {
mTypeOneViewHolder = new TypeOneViewHolder(v);
} else if (data.type == TYPE_TWO) {
mTypeTwoViewHolder = new TypeTwoViewHolder(v);
}
}
public void updateData(TypeData data) {
mDataType = data.type;
if (data.type == TYPE_ONE) {
mTypeTwoViewHolder = null;
if (mTypeOneViewHolder == null) {
View newView = getLayoutView(mParentViewGroup,
data.type);
/**
* how to replace new view with
the view in the parent
view container ???
*/
replaceView(mCurrentViewThisViewHolderIsFor,
newView);
mCurrentViewThisViewHolderIsFor = newView;
mTypeOneViewHolder =
new TypeOneViewHolder(newView);
}
mTypeOneViewHolder.updateDataTypeOne(data);
} else if (data.type == TYPE_TWO){
mTypeOneViewHolder = null;
if (mTypeTwoViewHolder == null) {
View newView = getLayoutView(mParentViewGroup,
data.type);
/**
* how to replace new view with
the view in the parent view
container ???
*/
replaceView(mCurrentViewThisViewHolderIsFor,
newView);
mCurrentViewThisViewHolderIsFor = newView;
mTypeTwoViewHolder =
new TypeTwoViewHolder(newView);
}
mTypeTwoViewHolder.updateDataTypeOne(data);
}
}
}
public static void replaceView(View currentView,
View newView) {
ViewGroup parent = (ViewGroup)currentView.getParent();
if(parent == null) {
return;
}
final int index = parent.indexOfChild(currentView);
parent.removeView(currentView);
parent.addView(newView, index);
}
Bearbeiten: ViewHolder hat das Mitglied mItemViewType, um die Ansicht zu halten
Bearbeiten: sieht aus wie in onBindViewHolder (ViewHolder-Halter, int-Position). Der übergebene ViewHolder wurde von getItemViewType (int-Position) aufgenommen (oder erstellt) ) um sicherzustellen, dass es sich um eine Übereinstimmung handelt, müssen Sie sich dort möglicherweise nicht darum kümmern, dass der ViewHolder-Typ nicht mit dem Typ von data [position] übereinstimmt. Weiß jemand mehr wie der ViewHolder im onBindViewHolder () abgeholt wird?
Bearbeiten: Sieht so aus, als würde das Recycling ViewHolder
nach Typ ausgewählt, also gibt es keinen Krieger.
Edit: http://wiresareobsolete.com/2014/09/building-a-recyclerview-layoutmanager-part-1/ beantwortet diese Frage.
Es bekommt das recycle ViewHolder
wie:
holder = getRecycledViewPool().getRecycledView(mAdapter.getItemViewType(offsetPosition));
oder erstelle eine neue, wenn du nicht den richtigen recycle ViewHolder
findest.
public ViewHolder getRecycledView(int viewType) {
final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
if (scrapHeap != null && !scrapHeap.isEmpty()) {
final int index = scrapHeap.size() - 1;
final ViewHolder scrap = scrapHeap.get(index);
scrapHeap.remove(index);
return scrap;
}
return null;
}
View getViewForPosition(int position, boolean dryRun) {
......
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
+ "position " + position + "(offset:" + offsetPosition + ")."
+ "state:" + mState.getItemCount());
}
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap via stable ids, if exists
if (mAdapter.hasStableIds()) {
holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrap = true;
}
}
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
if (holder == null) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view which does not have a ViewHolder");
} else if (holder.shouldIgnore()) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view that is ignored. You must call stopIgnoring before"
+ " returning this view.");
}
}
}
if (holder == null) { // fallback to recycler
// try recycler.
// Head to the shared pool.
if (DEBUG) {
Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared "
+ "pool");
}
holder = getRecycledViewPool()
.getRecycledView(mAdapter.getItemViewType(offsetPosition));
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this,
mAdapter.getItemViewType(offsetPosition));
if (DEBUG) {
Log.d(TAG, "getViewForPosition created new ViewHolder");
}
}
}
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
// do not update unless we absolutely have to.
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
if (DEBUG && holder.isRemoved()) {
throw new IllegalStateException("Removed holder should be bound and it should"
+ " come here only in pre-layout. Holder: " + holder);
}
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
mAdapter.bindViewHolder(holder, offsetPosition);
attachAccessibilityDelegate(holder.itemView);
bound = true;
if (mState.isPreLayout()) {
holder.mPreLayoutPosition = position;
}
}
final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
final LayoutParams rvLayoutParams;
if (lp == null) {
rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
holder.itemView.setLayoutParams(rvLayoutParams);
} else if (!checkLayoutParams(lp)) {
rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
holder.itemView.setLayoutParams(rvLayoutParams);
} else {
rvLayoutParams = (LayoutParams) lp;
}
rvLayoutParams.mViewHolder = holder;
rvLayoutParams.mPendingInvalidate = fromScrap && bound;
return holder.itemView;
}
Ich habe eine bessere Lösung, mit der mehrere Ansichtstypen deklarativ und typsicher erstellt werden können. Es ist in Kotlin geschrieben, was übrigens wirklich Nizza ist.
Einfache Ansichtshalter 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 des Adapterdatenelements. Beachten Sie, dass ein View-Typ durch einen Hash-Code einer bestimmten View-Holder-Klasse (KClass in Kotlin) dargestellt wird.
trait AdapterItem {
val viewType: Int
fun bindViewHolder(viewHolder: RecyclerView.ViewHolder)
}
abstract class AdapterItemBase<T>(val viewHolderClass: KClass<T>) : 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 Adapterelementklassen überschrieben werden (sicherer Typ)
class AdapterItemMedium(val icon: Drawable, val label: String, val onClick: () -> Unit) : AdapterItemBase<ViewHolderMedium>(ViewHolderMedium::class) {
override fun bindViewHolder(viewHolder: ViewHolderMedium) {
viewHolder.icon.setImageDrawable(icon)
viewHolder.label.setText(label)
viewHolder.itemView.setOnClickListener { onClick() }
}
}
Liste solcher AdapterItemMedium
-Objekte ist eine Datenquelle für den Adapter, der List<AdapterItem>
tatsächlich akzeptiert, siehe unten.
Ein wichtiger Teil dieser Lösung ist eine Ansichtshalterfabrik, die neue Instanzen eines bestimmten ViewHolder bereitstellt
class ViewHolderProvider {
private val viewHolderFactories = hashMapOf<Int, Pair<Int, Any>>()
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<T>(key: KClass<T>, 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<AdapterItem>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
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)
}
}
Nur 3 Schritte zum Erstellen eines neuen Ansichtstyps:
ViewHolderProvider
Hier ist ein Beispiel für dieses Konzept: Android-Schubladenvorlage .__ Es geht noch weiter - Ansichtstyp, der als Spinner-Komponente und auswählbare Adapterelemente fungiert.
Es ist sehr einfach und unkompliziert.
Überschreiben Sie einfach die getItemViewType () - Methode in Ihrem Adapter. Auf der Basis von Daten geben Sie verschiedene itemViewType-Werte zurück. B. ein Objekt des Typs Person mit einem Mitglied isMale. Wenn isMale auf true gesetzt ist, geben Sie 1 zurück und isMale auf false. Geben Sie in der getItemViewType () - Methode 2 zurück.
Kommt nun zum createViewHolder (ViewGroup-übergeordnetes Element, int viewType) , auf der Grundlage verschiedener viewType-Typen, die die unterschiedliche Layoutdatei aufblasen können. wie das Folgende
if (viewType ==1){
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.male,parent,false);
return new AdapterMaleViewHolder(view);
}
else{
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.female,parent,false);
return new AdapterFemaleViewHolder(view);
}
in onBindViewHolder (VH-Halter, int-Position) prüfen, wo der Besitzer eine Instanz von AdapterFemaleViewHolder
oder AdapterMaleViewHolder
durch instanceof
ist, und weist die Werte entsprechend zu.
ViewHolder Kann so sein
class AdapterMaleViewHolder extends RecyclerView.ViewHolder {
...
public AdapterMaleViewHolder(View itemView){
...
}
}
class AdapterFemaleViewHolder extends RecyclerView.ViewHolder {
...
public AdapterFemaleViewHolder(View itemView){
...
}
}
Eigentlich möchte ich die Antons Antwort verbessern.
Da getItemViewType(int position)
einen ganzzahligen Wert zurückgibt, können Sie die Layout-Ressourcen-ID zurückgeben, die Sie aufpumpen müssten. Auf diese Weise würden Sie einige Logik in der onCreateViewHolder(ViewGroup parent, int viewType)
-Methode speichern.
Ich würde auch nicht empfehlen, intensive Berechnungen in getItemCount()
durchzuführen, da diese bestimmte Funktion beim Rendern der Liste mindestens fünfmal aufgerufen wird und jedes Element außerhalb der sichtbaren Elemente gerendert wird. Da die notifyDatasetChanged()
-Methode final ist, können Sie sie leider nicht wirklich überschreiben, aber Sie können sie von einer anderen Funktion innerhalb des Adapters aufrufen.
Ich empfehle diese Bibliothek von Hannes Dorfmann. Es kapselt die gesamte Logik, die sich auf einen bestimmten Ansichtstyp bezieht, in einem separaten Objekt namens "AdapterDelegate". https://github.com/sockeqwe/AdapterDelegates
public class CatAdapterDelegate extends AdapterDelegate<List<Animal>> {
private LayoutInflater inflater;
public CatAdapterDelegate(Activity activity) {
inflater = activity.getLayoutInflater();
}
@Override public boolean isForViewType(@NonNull List<Animal> items, int position) {
return items.get(position) instanceof Cat;
}
@NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
return new CatViewHolder(inflater.inflate(R.layout.item_cat, parent, false));
}
@Override public void onBindViewHolder(@NonNull List<Animal> items, int position,
@NonNull RecyclerView.ViewHolder holder, @Nullable List<Object> payloads) {
CatViewHolder vh = (CatViewHolder) holder;
Cat cat = (Cat) items.get(position);
vh.name.setText(cat.getName());
}
static class CatViewHolder extends RecyclerView.ViewHolder {
public TextView name;
public CatViewHolder(View itemView) {
super(itemView);
name = (TextView) itemView.findViewById(R.id.name);
}
}
}
public class AnimalAdapter extends ListDelegationAdapter<List<Animal>> {
public AnimalAdapter(Activity activity, List<Animal> items) {
// DelegatesManager is a protected Field in ListDelegationAdapter
delegatesManager.addDelegate(new CatAdapterDelegate(activity))
.addDelegate(new DogAdapterDelegate(activity))
.addDelegate(new GeckoAdapterDelegate(activity))
.addDelegate(23, new SnakeAdapterDelegate(activity));
// Set the items from super class.
setItems(items);
}
}
Sie können mit mehreren ViewTypes RecyclerAdapter
umgehen, indem Sie getItemViewType()
veranlassen, den erwarteten Wert für viewType
für diese Position zurückzugeben
Ich habe eine MultipleViewTypeAdapter
für die Erstellung einer MCQ-Liste für Prüfungen vorbereitet, die eine Frage mit 2 oder mehr gültigen Antworten (Kontrollkästchenoptionen) und eine Frage mit einer einzelnen Antwort (Optionsfeldoptionen) auslösen kann.
Hierfür erhalte ich die Art der Frage aus der API-Antwort und habe diese verwendet, um zu entscheiden, welche Ansicht ich für diese Frage anzeigen muss.
public class MultiViewTypeAdapter extends RecyclerView.Adapter {
Context mContext;
ArrayList<Question> dataSet;
ArrayList<String> questions;
private Object radiobuttontype1;
//Viewholder to display Questions with checkboxes
public static class Checkboxtype2 extends RecyclerView.ViewHolder {
ImageView imgclockcheck;
CheckBox checkbox;
public Checkboxtype2(@NonNull View itemView) {
super(itemView);
imgclockcheck = (ImageView) itemView.findViewById(R.id.clockout_cbox_image);
checkbox = (CheckBox) itemView.findViewById(R.id.clockout_cbox);
}
}
//Viewholder to display Questions with radiobuttons
public static class Radiobuttontype1 extends RecyclerView.ViewHolder {
ImageView clockout_imageradiobutton;
RadioButton clockout_radiobutton;
TextView sample;
public radiobuttontype1(View itemView) {
super(itemView);
clockout_imageradiobutton = (ImageView) itemView.findViewById(R.id.clockout_imageradiobutton);
clockout_radiobutton = (RadioButton) itemView.findViewById(R.id.clockout_radiobutton);
sample = (TextView) itemView.findViewById(R.id.sample);
}
}
public MultiViewTypeAdapter(ArrayList<QueDatum> data, Context context) {
this.dataSet = data;
this.mContext = context;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
if (viewType.equalsIgnoreCase("1")) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.clockout_radio_list_row, viewGroup, false);
return new radiobuttontype1(view);
} else if (viewType.equalsIgnoreCase("2")) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.clockout_cbox_list_row, viewGroup, false);
view.setHorizontalFadingEdgeEnabled(true);
return new Checkboxtype2(view);
} else if (viewType.equalsIgnoreCase("3")) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.clockout_radio_list_row, viewGroup, false);
return new Radiobuttontype1(view);
} else if (viewType.equalsIgnoreCase("4")) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.clockout_radio_list_row, viewGroup, false);
return new Radiobuttontype1(view);
} else if (viewType.equalsIgnoreCase("5")) {
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.clockout_radio_list_row, viewGroup, false);
return new Radiobuttontype1(view);
}
return null;
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int viewType) {
if (viewType.equalsIgnoreCase("1")) {
options = dataSet.get(i).getOptions();
question = dataSet.get(i).getQuestion();
image = options.get(i).getValue();
((radiobuttontype1) viewHolder).clockout_radiobutton.setChecked(false);
((radiobuttontype1) viewHolder).sample.setText(question);
//loading image bitmap in the ViewHolder's View
Picasso.with(mContext)
.load(image)
.into(((radiobuttontype1) viewHolder).clockout_imageradiobutton);
} else if (viewType.equalsIgnoreCase("2")) {
options = (ArrayList<Clockout_questions_Option>) dataSet.get(i).getOptions();
question = dataSet.get(i).getQuestion();
image = options.get(i).getValue();
//loading image bitmap in the ViewHolder's View
Picasso.with(mContext)
.load(image)
.into(((Checkboxtype2) viewHolder).imgclockcheck);
} else if (viewType.equalsIgnoreCase("3")) {
//fit data to viewHolder for ViewType 3
} else if (viewType.equalsIgnoreCase("4")) {
//fit data to viewHolder for ViewType 4
} else if (viewType.equalsIgnoreCase("5")) {
//fit data to viewHolder for ViewType 5
}
}
@Override
public int getItemCount() {
return dataSet.size();
}
/**
* returns viewType for that position by picking the viewType value from the
* dataset
*/
@Override
public int getItemViewType(int position) {
return dataSet.get(position).getViewType();
}
}
Sie können das Ausfüllen mehrerer bedingter viewHolder-Daten in onBindViewHolder()
vermeiden, indem Sie für ähnliche Ansichten in viewHoldern, die sich in ihrer Position unterscheiden, dieselben IDs zuweisen.
Sie können die Bibliothek verwenden: https://github.com/vivchar/RendererRecyclerViewAdapter
mRecyclerViewAdapter = new RendererRecyclerViewAdapter(); /* included from library */
mRecyclerViewAdapter.registerRenderer(new SomeViewRenderer(SomeModel.TYPE, this));
mRecyclerViewAdapter.registerRenderer(...); /* you can use several types of cells */
`
Für jedes Element sollten Sie einen ViewRenderer, ViewHolder, SomeModel implementieren:
ViewHolder - es ist ein einfacher Ansichtshalter der Recycler-Ansicht.
SomeModel - es ist Ihr Modell mit ItemModel
-Schnittstelle
public class SomeViewRenderer extends ViewRenderer<SomeModel, SomeViewHolder> {
public SomeViewRenderer(final int type, final Context context) {
super(type, context);
}
@Override
public void bindView(@NonNull final SomeModel model, @NonNull final SomeViewHolder holder) {
holder.mTitle.setText(model.getTitle());
}
@NonNull
@Override
public SomeViewHolder createViewHolder(@Nullable final ViewGroup parent) {
return new SomeViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.some_item, parent, false));
}
}
Für weitere Details können Sie Dokumentationen anschauen.
View-Typ-Implementierung wird mit kotlin einfacher, hier ein Beispiel mit dieser Light-Bibliothek https://github.com/Link184/KidAdapter
recyclerView.setUp {
withViewType {
withLayoutResId(R.layout.item_int)
withItems(mutableListOf(1, 2, 3, 4, 5, 6))
bind<Int> { // this - is adapter view hoder itemView, it - current item
intName.text = it.toString()
}
}
withViewType("SECOND_STRING_TAG") {
withLayoutResId(R.layout.item_text)
withItems(mutableListOf("eight", "nine", "ten", "eleven", "twelve"))
bind<String> {
stringName.text = it
}
}
}