如何创build多个视图types的RecyclerView?
从https://developer.android.com/preview/material/ui-widgets.html
当我们创buildRecyclerView.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; } } @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; } }
那么,是否有可能创build多视图types的RecyclerView
?
是的,这是可能的。 只需实现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){ ... } @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; } } }
如果视图types的布局只是less数,绑定逻辑很简单,请按照Anton的解决scheme。
但是,如果您需要pipe理复杂的布局和绑定逻辑,代码将变得混乱。
我相信以下解决scheme对于需要处理复杂视图types的人员非常有用。
基础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(); ...... }
在创build单个视图types时,在这个类中定义的函数与适配器类非常相似。
对于每个视图types,通过扩展此DataBinder来创build类。
示例DataBinder类
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); } } }
为了pipe理DataBinder类,创build适配器类。
Base DataBindAdapter类
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); ...... }
通过扩展此基类来创build类,然后实例化DataBinder类并重写抽象方法
-
getItemCount
返回DataBinder的总项目数 -
getItemViewType
定义适配器位置和视图types之间的映射逻辑。 -
getDataBinder
根据视图types返回DataBinder实例 -
为getPosition
将转换逻辑定义为指定DataBinder中位置的适配器位置 -
getBinderPosition
将转换逻辑定义为适配器位置在DataBinder中的位置
希望这个解决scheme会有帮助。
我在GitHub中留下了更详细的解决scheme和示例,所以如果需要,请参考以下链接。
https://github.com/yqritc/RecyclerView-MultipleViewTypesAdapter
下面是不是伪代码,我已经testing它,它已经为我工作。
我想在我的recyclerview中创build一个headerview,然后在用户可以点击的标题下面显示一个图片列表。
我在我的代码中使用了几个开关,不知道这是否是最有效的方式来完成这个任务,所以请随时提出您的意见:
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)); } } }
是的,这是可能的。
写一个通用的视图持有者:
public abstract class GenericViewHolder extends RecyclerView.ViewHolder { public GenericViewHolder(View itemView) { super(itemView); } public abstract void setDataOnView(int position); }
然后创build您的视图持有人,并使其扩展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); } @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); } } }
那么RecyclerView.Adapter类将如下所示:
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); }
遵循Anton的解决scheme,拿出这个ViewHolder
来保存/处理/委托不同types的布局。 但不知道当回收视图的ViewHolder
不是数据滚入types时,replace新的布局是否可以工作。
所以基本上, onCreateViewHolder(ViewGroup parent, int viewType)
只有当需要新的视图布局时调用;
getItemViewType(int position)
将被调用viewType
;
在回收视图时总是调用onBindViewHolder(ViewHolder holder, int position)
(引入新数据并尝试使用该ViewHolder
进行显示)。
所以当onBindViewHolder
被调用时,需要放入正确的视图布局并更新ViewHolder
。
replaceViewHolder
的视图布局的方法是正确的吗?或者任何问题? 欣赏任何评论!
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); }
编辑: ViewHolder具有成员mItemViewType来保存视图
编辑:看起来像在onBindViewHolder(ViewHolder持有人,int位置)通过查看getItemViewType(int位置),以确保它是匹配,所以可能不需要担心的ViewHolder的types与数据[位置]的types不匹配。 有没有人知道更多onBindViewHolder()中的ViewHolder是如何拾取的?
编辑:看起来像回收ViewHolder
按types挑选,所以没有战士在那里。
编辑: http : //wiresareobsolete.com/2014/09/building-a-recyclerview-layoutmanager-part-1/回答这个问题。
它获得像ViewHolder
这样的回收ViewHolder
:
holder = getRecycledViewPool().getRecycledView(mAdapter.getItemViewType(offsetPosition));
或者如果没有find正确types的回收ViewHolder
,则创build一个新的。
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; }
我有一个更好的解决scheme,它允许以声明和types安全的方式创build多个视图types。 它写在Kotlin哪btw真的很好。
简单的视图持有人所有需要的视图types
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 }
有一个适配器数据项的抽象。 请注意,视图types由特定视图持有者类(KClass in Kotlin)的hashCode表示,
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) } }
只有bindViewHolder
需要在具体的适配器项类中被覆盖(types安全的方式)
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>
的适配器的数据源,见下面。
该解决scheme的重要部分是视图持有者工厂,它将提供特定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.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)) } }
简单的适配器类看起来像这样
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) } }
只有3个步骤来创build一个新的视图types:
- 创build一个视图持有者类
- 创build一个适配器项目类(从AdapterItemBase扩展)
- 在
ViewHolderProvider
注册视图持有者类
下面是这个概念的一个例子: android-drawer-template它更进一步 – 视图types作为一个微调组件,可选的适配器项目。
这是非常简单和直接的。
只需重写getItemViewType()方法在您的适配器。 在数据的基础上返回不同的itemViewType值。 例如,考虑具有成员isMale的Persontypes的对象,如果isMale为true,则返回1,并且isMale为false,在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 holder,int position)中 ,通过instanceof
检查持有者是AdapterFemaleViewHolder
或AdapterMaleViewHolder
的instanceof
并相应地分配值。
ViewHolder可能是这样的
class AdapterMaleViewHolder extends RecyclerView.ViewHolder { ... public AdapterMaleViewHolder(View itemView){ ... } } class AdapterFemaleViewHolder extends RecyclerView.ViewHolder { ... public AdapterFemaleViewHolder(View itemView){ ... } }
其实,我想提高安东的答案 。
由于getItemViewType(int position)
返回一个整数值,你可以返回你需要膨胀的布局资源ID。 这样你就可以在onCreateViewHolder(ViewGroup parent, int viewType)
方法中保存一些逻辑。
此外,我不会build议在getItemCount()
进行密集计算,因为该特定函数在呈现列表时以及在呈现每个项目超出可见项目时至less被调用5次。 可悲的是,因为notifyDatasetChanged()
方法是final的,所以你不能真正覆盖它,但是你可以从适配器中的另一个函数调用它。
你可以使用库: 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 */
`
对于每个项目,您应该实现一个ViewRenderer,ViewHolder,SomeModel:
ViewHolder – 这是一个简单的查看持有人的回收视图。
SomeModel – 它是带有ItemModel
接口的模型
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)); } }
欲了解更多详情,你可以看看文件。
有太懒的解决scheme提出…试试这个非常简单和快速的解决scheme,使多个布局在Recycler视图
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if(ChatFragment.isMe) { // isMe is boolean static field in ChatFragment class View view = inflater.inflate(R.layout.my_chat_layout, parent, false); MyViewHolder holder = new MyViewHolder(view); return holder; } else{ View view = inflater.inflate(R.layout.other_chat_layout, parent, false); MyViewHolder holder = new MyViewHolder(view); return holder; } }`
当你需要改变布局时使用这个
public static Boolean isMe;
改变isME(true或false)取决于你