如何创建具有多种视图类型的RecyClerView

创建动态列表与回收

当我们创建RecyclerView.Adapter时,我们必须指定将与适配器绑定的ViewHolder

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;}}
@Overridepublic 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;}
@Overridepublic void onBindViewHolder(ViewHolder holder, int position) {holder.mTextView.setText(mDataset[position]);}
@Overridepublic int getItemCount() {return mDataset.length;}}

是否可以创建具有多种视图类型的RecyclerView

572443 次浏览

是的,这是可能的。只需实现获取视频类型getItemViewType(),并处理onCreateViewHolder()中的viewType参数。

所以你可以这样做:

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){...}
@Overridepublic 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 contiguousreturn position % 2 * 2;}
@Overridepublic RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {switch (viewType) {case 0: return new ViewHolder0(...);case 2: return new ViewHolder2(...);...}}
@Overridepublic 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;}}}

安东的解决方案之后,我想出了这个ViewHolder,它包含/处理/委托不同类型的布局。

但是我不确定当回收视图的ViewHolder不是数据卷入的类型时,替换新布局是否有效。

所以基本上,onCreateViewHolder(ViewGroup parent, int viewType)仅在需要新的视图布局时调用;

getItemViewType(int position)将被称为viewType

循环视图时总是调用onBindViewHolder(ViewHolder holder, int position)(引入新数据并尝试与ViewHolder一起显示)。

因此,当调用onBindViewHolder时,需要将其放入正确的视图布局并更新ViewHolder

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 extendsRecyclerView.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 can I replace a new view withthe view in the parentview 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 can I replace a new view withthe view in the parent viewcontainer?*/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);}

ViewHolder有成员mItemViewType来保存视图。

它看起来像在onBindViewHolder(ViewHolder持有人,int位置)中,传入的ViewHolder已通过查看getItemViewType(int位置)来拾取(或创建)以确保它是匹配的,因此可能无需担心ViewHolder的类型与data[位置]的类型不匹配。

看起来回收ViewHolder是按类型挑选的,所以那里没有战士。

建立一个回收视图布局管理器-第1部分回答了这个问题。

它获得回收ViewHolder,如:

holder = getRecycledViewPool().getRecycledView(mAdapter.getItemViewType(offsetPosition));

或者创建一个新的,如果没有找到正确类型的回收ViewHolder

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 existsif (mAdapter.hasStableIds()) {holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);if (holder != null) {// update positionholder.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;}

如果视图类型的布局很少并且绑定逻辑很简单,请遵循安东的解决方案。但是如果您需要管理复杂的布局和绑定逻辑,代码会很混乱。

我相信以下解决方案对于需要处理复杂视图类型的人很有用。

基本DataBinder类

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();
......
}

在创建单一视图类型时,需要在此类中定义的函数与适配器类几乎相同。

对于每种视图类型,通过扩展此DataBinder创建类。

示例DataBinder类

public class Sample1Binder extends DataBinder<Sample1Binder.ViewHolder> {
private List<String> mDataSet = new ArrayList();
public Sample1Binder(DataBindAdapter dataBindAdapter) {super(dataBindAdapter);}
@Overridepublic ViewHolder newViewHolder(ViewGroup parent) {View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_sample1, parent, false);return new ViewHolder(view);}
@Overridepublic void bindViewHolder(ViewHolder holder, int position) {String title = mDataSet.get(position);holder.mTitleText.setText(title);}
@Overridepublic 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);}}}

为了管理DataBinder类,请创建一个适配器类。

基本DataBindAdapter类

abstract public class DataBindAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
@Overridepublic RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {return getDataBinder(viewType).newViewHolder(parent);}
@Overridepublic void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {int binderPosition = getBinderPosition(position);getDataBinder(viewHolder.getItemViewType()).bindViewHolder(viewHolder, binderPosition);}
@Overridepublic abstract int getItemCount();
@Overridepublic 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);
......
}

通过扩展此基类创建类,然后实例化DataBinder类并覆盖抽象方法

  1. 获取商品数量
    返回DataBinders的项目总数

  2. 获取商品类型
    定义适配器位置和视图类型之间的映射逻辑。

  3. 获取数据源
    根据视图类型返回DataBinder实例

  4. 获取位置
    从指定DataBinder中的位置定义转换逻辑到适配器位置

  5. 获取绑定位置
    从适配器位置定义转换逻辑到DataBinder中的位置

我在GitHub上留下了更详细的解决方案和示例,所以如果需要,请参考回收器

下面的不是伪代码。我已经测试过了,它已经为我工作了。

我想在我的回收视图中创建一个标题视图,然后在标题下方显示一个用户可以单击的图片列表。

我在代码中使用了一些开关,不知道这是否是最有效的方法,所以请随时发表评论:

   public class ViewHolder extends RecyclerView.ViewHolder{
//These are the general elements in the RecyclerViewpublic 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 viewTypepublic 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);}}}

@Overridepublic ViewHolder onCreateViewHolder(ViewGroup parent,int viewType){View v;ViewHolder vh;// create a new viewswitch (viewType) {case 0: //This would be the header view in my Recyclerv = 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 worldv = LayoutInflater.from(parent.getContext()).inflate(R.layout.recyclerview_picture, parent, false);vh = new ViewHolder(v, viewType);v.setOnClickListener(new View.OnClickListener(){
@Overridepublic void onClick(View v) {Intent intent = new Intent(mContext, nextActivity.class);intent.putExtra("ListNo",mRecyclerView.getChildPosition(v));mContext.startActivity(intent);}});return vh;}}
//Overridden so that I can display custom rows in the recyclerview@Overridepublic int getItemViewType(int position) {int viewType = 1; //Default is 1if (position == 0) viewType = 0; //If zero, it will be a header viewreturn viewType;}
@Overridepublic void onBindViewHolder(ViewHolder holder, int position) {//position == 0 means it's the info header view on the Recyclerif (position == 0) {holder.name.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(mContext,"name clicked", Toast.LENGTH_SHORT).show();}});holder.description.setOnClickListener(new View.OnClickListener() {@Overridepublic 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));}}}

我有一个更好的解决方案,它允许以声明和类型安全的方式创建多个视图类型。它是用静态编程语言编写的,顺便说一句,非常好。

适用于所有必需视图类型的简单视图持有者

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

有一个适配器数据项的抽象。请注意,视图类型由特定视图持有者类的hashCode表示(静态编程语言中的KClass)

trait AdapterItem {val viewType: Intfun 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)}}

只有bindViewHolder需要在具体的适配器项类中重写(类型安全方式)。

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() }}}

此类AdapterItemMedium对象的列表是实际接受List<AdapterItem>的适配器的数据源。见下文。

这个解决方案的重要部分是一个视图持有者工厂,它将提供特定ViewHolder的新实例:

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.ViewHolderval 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))}}

简单的适配器类看起来像这样:

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)}}

创建新视图类型有只有三个步骤:

  1. 创建一个视图持有者类
  2. 创建适配器项类(从AdapterItemBase扩展)
  3. ViewHolderProvider中注册视图持有者类

下面是这个概念的一个例子:android-抽屉-模板

它走得更远-一个视图类型,充当微调器组件,具有可选择的适配器项。

是的,这是可能的。

写一个泛型视图持有者:

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

然后创建你的视图持有者并使它们扩展GenericViewHolder。例如,这个:

     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);}
@Overridepublic 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);}}}

然后RecyclerView. Adapter类将看起来像这样:

public class MyClassRecyclerViewAdapter extends RecyclerView.Adapter<MyClassRecyclerViewAdapter.GenericViewHolder> {
@Overridepublic int getItemViewType(int position) {// depends on your problemswitch (position) {case : return VIEW_TYPE1;case : return VIEW_TYPE2;...}}
@Overridepublic 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;}
@Overridepublic void onBindViewHolder(GenericViewHolder holder, int position) {holder.setDataOnView(position);}

实际上,我想改进安东的回答

由于getItemViewType(int position)返回一个整数值,您可以返回需要膨胀的布局资源ID。这样您就可以在onCreateViewHolder(ViewGroup parent, int viewType)方法中节省一些逻辑。

此外,我不建议在getItemCount()中进行密集计算,因为该特定函数在渲染列表时至少被调用5次,以及在渲染可见项目之外的每个项目时。可悲的是,由于notifyDatasetChanged()方法是Final,你不能真正覆盖它,但你可以从适配器中的另一个函数调用它。

这是非常简单和直接的。

只需覆盖适配器中的获取视频类型getItemViewType()方法。在数据的基础上返回不同的itemViewType值。例如,考虑一个Person类型的对象,其中有一个成员是isM,如果isM为真,则返回1,isM为假,在获取视频类型getItemViewType()方法中返回2。

现在来到CreateViewHolder(ViewGroup父类,int viewType)获取视图类型的属性,根据不同的viewType,你可以膨胀不同的布局文件。如下所示:

 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);}

onBindViewHolder(VH持有者,int位置)中,检查持有人是AdapterFemaleViewHolderAdapterMaleViewHolder的实例,并相应地分配值。

ViewHolder可能是这样的

    class AdapterMaleViewHolder extends RecyclerView.ViewHolder {...public AdapterMaleViewHolder(View itemView){...}}
class AdapterFemaleViewHolder extends RecyclerView.ViewHolder {...public AdapterFemaleViewHolder(View itemView){...}}

您可以使用库: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 */

对于每个项目,你应该实现一个ViewRen的,ViewHolder的,一些模型:

ViewHolder-它是回收器视图的简单视图持有者。

它是你的模型与ItemModel接口

public class SomeViewRenderer extends ViewRenderer<SomeModel, SomeViewHolder> {
public SomeViewRenderer(final int type, final Context context) {super(type, context);}
@Overridepublic void bindView(@NonNull final SomeModel model, @NonNull final SomeViewHolder holder) {holder.mTitle.setText(model.getTitle());}
@NonNull@Overridepublic SomeViewHolder createViewHolder(@Nullable final ViewGroup parent) {return new SomeViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.some_item, parent, false));}}

有关更多详细信息,您可以查看留档。

是的,这是可能的。

在您的适配器getItemViewType布局像这样……

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();}
@Overridepublic 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;}
@Overridepublic 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;}}
@Overridepublic 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);
}}}
@Overridepublic int getItemCount() {return dataSet.size();}}

参考链接:Android回收实例-多个视图类型

为不同的布局创建不同的ViewHolder

在此处输入图片描述

RecyClerView可以拥有任意数量的ViewHolders,但为了更好的易读性,让我们看看如何使用两个ViewHolders创建一个。

这可以通过三个简单的步骤来完成

  1. 重写public int getItemViewType(int position)
  2. 基于onCreateViewHolder()方法中的ViewType返回不同的ViewHolders
  3. 基于onBindViewHolder()方法中的itemViewType填充视图

这是一个小代码片段:

public class YourListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int LAYOUT_ONE = 0;private static final int LAYOUT_TWO = 1;
@Overridepublic int getItemViewType(int position){if(position==0)return LAYOUT_ONE;elsereturn LAYOUT_TWO;}
@Overridepublic 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;}
@Overridepublic 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() {@Overridepublic 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}}}

getItemViewType(int位置)是关键。

在我看来,创建这种回收器视图的起点是对这个方法的了解。由于这个方法是可选的覆盖,默认情况下它在RecylerView类中不可见,这反过来又让许多开发人员(包括我)想知道从哪里开始。

一旦你知道这个方法的存在,创建这样的RecyclerView将是小菜一碟。

让我们看一个例子来证明我的观点。如果你想显示两个布局在不同的位置做这个

@Overridepublic int getItemViewType(int position){if(position%2==0)       // Even positionreturn LAYOUT_ONE;else                   // Odd positionreturn LAYOUT_TWO;}

相关链接:

看看该项目我在哪里实现了这个。

使用静态编程语言,视图类型的实现变得更加容易。这是这个轻库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 itemintName.text = it.toString()}}

withViewType("SECOND_STRING_TAG") {withLayoutResId(R.layout.item_text)withItems(mutableListOf("eight", "nine", "ten", "eleven", "twelve"))bind<String> {stringName.text = it}}}

我推荐Hannes Dorfmann的这个库。它将与特定视图类型相关的所有逻辑封装在一个名为“Adapter的代表”的单独对象中。

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 ListDelegationAdapterdelegatesManager.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);}}

您可以通过使getItemViewType()返回该位置的预期viewType值来处理multipleViewTypesRecyclerAdapter

我准备了一个MultipleViewTypeAdapter,用于构建一个MCQ列表,用于考试,可能会抛出一个问题,该问题可能有两个或多个有效答案(复选框选项)和一个答案问题(单选按钮选项)。

为此,我从API响应中获取问题类型,并用它来决定我必须为该问题显示哪个视图。

public class MultiViewTypeAdapter extends RecyclerView.Adapter {
Context mContext;ArrayList<Question> dataSet;ArrayList<String> questions;private Object radiobuttontype1;

//Viewholder to display Questions with checkboxespublic 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@Overridepublic 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;}
@Overridepublic 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 ViewPicasso.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 ViewPicasso.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}}
@Overridepublic int getItemCount() {return dataSet.size();}
/*** Returns viewType for that position by picking the viewType value from the*     dataset*/@Overridepublic int getItemViewType(int position) {return dataSet.get(position).getViewType();}}

您可以通过为不同位置的viewHolder之间的类似视图分配相同的id来避免onBindViewHolder()中基于多个条件的viewHolder数据填充。

如果您想将其与Android数据绑定结合使用,请查看https://github.com/evant/binding-collection-adapter-它是迄今为止我见过的多种视图类型RecyclerView的最佳解决方案。

你可以像这样使用它

var items: AsyncDiffPagedObservableList<BaseListItem> =AsyncDiffPagedObservableList(GenericDiff)
val onItemBind: OnItemBind<BaseListItem> =OnItemBind { itemBinding, _, item -> itemBinding.set(BR.item, item.layoutRes) }

然后在列表所在的布局中:

 <androidx.recyclerview.widget.RecyclerViewandroid:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"app:enableAnimations="@{false}"app:scrollToPosition="@{viewModel.scrollPosition}"
app:itemBinding="@{viewModel.onItemBind}"app:items="@{viewModel.items}"
app:reverseLayoutManager="@{true}"/>

您的列表项必须实现BaseListItem接口,如下所示:

interface BaseListItem {val layoutRes: Int}

项目视图应该如下所示:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variablename="item"type="...presentation.somescreen.list.YourListItem"/></data>
...
</layout>

其中YourListItem实现了BaseListItem

首先,您必须创建两个布局XML文件。之后,在回收视图适配器中TYPE_CALL和TYPE_EMAIL是适配器类中分别为1和2的两个静态值。

现在定义两个静态值在Recycler视图Adapter类级别,例如:私有静态intTYPE_CALL=1;私有静态intTYPE_EMAIL=2;

现在创建具有多个视图的视图持有者,如下所示:

class CallViewHolder extends RecyclerView.ViewHolder {
private TextView txtName;private TextView txtAddress;
CallViewHolder(@NonNull View itemView) {super(itemView);txtName = itemView.findViewById(R.id.txtName);txtAddress = itemView.findViewById(R.id.txtAddress);}}
class EmailViewHolder extends RecyclerView.ViewHolder {
private TextView txtName;private TextView txtAddress;
EmailViewHolder(@NonNull View itemView) {super(itemView);txtName = itemView.findViewById(R.id.txtName);txtAddress = itemView.findViewById(R.id.txtAddress);}}

现在在回收视图适配器中的onCreateViewHolder和onBindViewHolder方法中编写如下代码:

@NonNull@Overridepublic RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {View view;if (viewType == TYPE_CALL) { // for call layoutview = LayoutInflater.from(context).inflate(R.layout.item_call, viewGroup, false);return new CallViewHolder(view);
} else { // for email layoutview = LayoutInflater.from(context).inflate(R.layout.item_email, viewGroup, false);return new EmailViewHolder(view);}}
@Overridepublic void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {if (getItemViewType(position) == TYPE_CALL) {((CallViewHolder) viewHolder).setCallDetails(employees.get(position));} else {((EmailViewHolder) viewHolder).setEmailDetails(employees.get(position));}}

虽然选择的答案是正确的,但我只是想进一步阐述一下。我发现了一个有用的自定义适配器为多个视图类型在回收静态编程语言版本在这里.

自定义适配器如下:

public class CustomAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {private final Context context;ArrayList<String> list; // ArrayList of your Data Modelfinal int VIEW_TYPE_ONE = 1;final int VIEW_TYPE_TWO = 2;
public CustomAdapter(Context context, ArrayList<String> list) { // you can pass other parameters in constructorthis.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); // Initialize your All views prensent in list items}void bind(int position) {// This method will be called anytime a list item is created or update its data// Do your stuff hereyourView.setText(list.get(position));}}
private class ViewHolder2 extends RecyclerView.ViewHolder {
TextView yourView;ViewHolder2(final View itemView) {super(itemView);yourView = itemView.findViewById(R.id.yourView); // Initialize your All views prensent in list items}void bind(int position) {// This method will be called anytime a list item is created or update its data//Do your stuff hereyourView.setText(list.get(position));}}
@Overridepublic 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));}//if its not VIEW_TYPE_ONE then its VIEW_TYPE_TWOreturn new ViewHolder2(LayoutInflater.from(context).inflate(R.layout.your_list_item_2, parent, false));
}
@Overridepublic void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {if (list.get(position).type == Something) { // Put your condition, according to your requirements((ViewHolder1) holder).bind(position);} else {((ViewHolder2) holder).bind(position);}}
@Overridepublic int getItemCount() {return list.size();}
@Overridepublic int getItemViewType(int position) {// Here you can get decide from your model's ArrayList, which type of view you need to load. Likeif (list.get(position).type == Something) { // Put your condition, according to your requirementsreturn VIEW_TYPE_ONE;}return VIEW_TYPE_TWO;}}

如果有人有兴趣看到用静态编程语言编写的超级简单的解决方案,请查看我刚刚创建的博客文章。博客文章中的示例基于创建分段回收视图:

https://brona.blog/2020/06/sectioned-recyclerview-in-three-steps/

比以往任何时候都简单,忘记ViewType。不建议在一个适配器内使用多个视图类型。它会弄乱代码并打破单一责任原则,因为现在适配器需要处理逻辑以知道要膨胀哪个视图。

现在想象一下在大团队中工作,每个团队都必须使用其中一个视图类型功能。在不同视图类型中工作的所有团队接触相同的适配器将是一团糟。这可以使用ConcatAdapter解决,在那里你隔离适配器。一个接一个地编码它们,然后将它们合并在一个视图中。

recyclerview:1.2.0-alpha04开始,您现在可以使用ConcatAdapter

如果您需要具有不同viewTypes的视图,您可以为每个部分编写Adapters,然后使用ConcatAdapter将所有这些视图合并到一个回收视图中。

连接适配器

此图像显示了一个回收视图具有的三种不同的视图类型,页眉、内容和页脚。

在此处输入图片描述

您只需为每个部分创建一个适配器,然后只需使用ConcatAdapter将它们合并到一个回收视图中:

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

在此处输入图片描述

这就是您需要知道的全部。如果您想处理加载状态,例如在加载发生后删除最后一个适配器,您可以使用负载状态

我首先推荐你阅读汉尼斯·多夫曼关于这个话题的伟大的文章

当一个新的视图类型出现时,你必须编辑你的适配器,你必须处理这么多混乱的事情。你的适配器应该是开放用于扩展,但关闭用于修改。

你可以检查这两个项目,他们可以给出如何在Adapter中处理不同ViewType的想法:

我做了这样的事情。我通过了“片段类型”并创建了两个ViewHolders,在此基础上,我将我的布局相应地分类到一个可以有不同LayoutsLayoutManagers的适配器中

private Context mContext;protected IOnLoyaltyCardCategoriesItemClicked mListener;private String fragmentType;private View view;
public LoyaltyCardsCategoriesRecyclerViewAdapter(Context context, IOnLoyaltyCardCategoriesItemClicked itemListener, String fragmentType) {this.mContext = context;this.mListener = itemListener;this.fragmentType = fragmentType;}
public class LoyaltyCardCategoriesFragmentViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private ImageView lc_categories_iv;private TextView lc_categories_name_tv;private int pos;
public LoyaltyCardCategoriesFragmentViewHolder(View v) {super(v);view.setOnClickListener(this);lc_categories_iv = (ImageView) v.findViewById(R.id.lc_categories_iv);lc_categories_name_tv = (TextView) v.findViewById(R.id.lc_categories_name_tv);}
public void setData(int pos) {this.pos = pos;lc_categories_iv.setImageResource(R.mipmap.ic_launcher);lc_categories_name_tv.setText("Loyalty Card Categories");}
@Overridepublic void onClick(View view) {if (mListener != null) {mListener.onLoyaltyCardCategoriesItemClicked(pos);}}}
public class MyLoyaltyCardsFragmentTagViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public ImageButton lc_categories_btn;private int pos;
public MyLoyaltyCardsFragmentTagViewHolder(View v) {super(v);lc_categories_btn = (ImageButton) v.findViewById(R.id.lc_categories_btn);lc_categories_btn.setOnClickListener(this);}
public void setData(int pos) {this.pos = pos;lc_categories_btn.setImageResource(R.mipmap.ic_launcher);}
@Overridepublic void onClick(View view) {if (mListener != null) {mListener.onLoyaltyCardCategoriesItemClicked(pos);}}}
@NonNull@Overridepublic RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {if (fragmentType.equalsIgnoreCase(Constants.LoyaltyCardCategoriesFragmentTag)) {view = LayoutInflater.from(mContext).inflate(R.layout.loyalty_cards_categories_frag_item, parent, false);return new LoyaltyCardCategoriesFragmentViewHolder(view);} else if (fragmentType.equalsIgnoreCase(Constants.MyLoyaltyCardsFragmentTag)) {view = LayoutInflater.from(mContext).inflate(R.layout.my_loyalty_cards_categories_frag_item, parent, false);return new MyLoyaltyCardsFragmentTagViewHolder(view);} else {return null;}}
@Overridepublic void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {if (fragmentType.equalsIgnoreCase(Constants.LoyaltyCardCategoriesFragmentTag)) {((LoyaltyCardCategoriesFragmentViewHolder) holder).setData(position);} else if (fragmentType.equalsIgnoreCase(Constants.MyLoyaltyCardsFragmentTag)) {((MyLoyaltyCardsFragmentTagViewHolder) holder).setData(position);}}
@Overridepublic int getItemCount() {return 7;}

这是一个完整的示例,显示了具有两种类型的RecyclerView,视图类型由对象决定。

类模型

open class RecyclerViewItemclass SectionItem(val title: String) : RecyclerViewItem()class ContentItem(val name: String, val number: Int) : RecyclerViewItem()

适配器代码

const val VIEW_TYPE_SECTION = 1const val VIEW_TYPE_ITEM = 2
class UserAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var data = listOf<RecyclerViewItem>()
override fun getItemViewType(position: Int): Int {if (data[position] is SectionItem) {return VIEW_TYPE_SECTION}return VIEW_TYPE_ITEM}
override fun getItemCount(): Int {return data.size}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {if (viewType == VIEW_TYPE_SECTION) {return SectionViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_user_section, parent, false))}return ContentViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_user_content, parent, false))}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {val item = data[position]if (holder is SectionViewHolder && item is SectionItem) {holder.bind(item)}if (holder is ContentViewHolder && item is ContentItem) {holder.bind(item)}}
internal inner class SectionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {fun bind(item: SectionItem) {itemView.text_section.text = item.title}}
internal inner class ContentViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {fun bind(item: ContentItem) {itemView.text_name.text = item.nameitemView.text_number.text = item.number.toString()}}}

item_user_section.xml

<?xml version="1.0" encoding="utf-8"?><TextView xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/text_section"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="#eee"android:padding="16dp" />

item_user_content.xml

<?xml version="1.0" encoding="utf-8"?><LinearLayout 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:orientation="horizontal"android:padding="32dp">
<TextViewandroid:id="@+id/text_name"android:layout_width="wrap_content"android:layout_height="wrap_content"tools:text="Name" />
<TextViewandroid:id="@+id/text_number"android:layout_width="wrap_content"android:layout_height="wrap_content" />
</LinearLayout>

示例使用

val dataSet = arrayListOf<RecyclerViewItem>(SectionItem("A1"),ContentItem("11", 11),ContentItem("12", 12),ContentItem("13", 13),
SectionItem("A2"),ContentItem("21", 21),ContentItem("22", 22),
SectionItem("A3"),ContentItem("31", 31),ContentItem("32", 32),ContentItem("33", 33),ContentItem("33", 34),)
recyclerAdapter.data = dataSetrecyclerAdapter.notifyDataSetChanged()

我看到有很多很棒的答案,非常详细和广泛。就我而言,如果我从头开始,一步一步地遵循推理,我总是能更好地理解事情。我建议你查看这个链接,每当你有类似的问题时,搜索任何解决这个问题的codelabs。

Android静态编程语言基础:RecyClerView中的标头