How to create RecyclerView with multiple view type?
来自https://developer.android.com/preview/material/ui-widgets.html
当我们创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | 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; } } |
那么,是否可以使用多种视图类型创建
是的,这是可能的。只需实现getItemViewType(),并处理
所以你做的事情如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | 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; } } } |
如果视图类型的布局只有几个,绑定逻辑很简单,请遵循Anton的解决方案。
但是,如果您需要管理复杂的布局和绑定逻辑,代码将会很混乱。
我相信以下解决方案对需要处理复杂视图类型的人有用。
基础DataBinder类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | 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); } } } |
要管理DataBinder类,请创建适配器类。
Base DataBindAdapter类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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); ...... } |
通过扩展此基类来创建类,然后实例化DataBinder类并覆盖抽象方法
getItemCount
返回DataBinders的总项数
getItemViewType
定义适配器位置和视图类型之间的映射逻辑。
getDataBinder
根据视图类型返回DataBinder实例
为getPosition
从指定DataBinder中的位置将转换逻辑定义到适配器位置
getBinderPosition
从适配器位置将转换逻辑定义到DataBinder中的位置
希望这个解决方案会有所帮助。
我在GitHub中留下了更多细节解决方案和样本,如果需要,请参考以下链接。
https://github.com/yqritc/RecyclerView-MultipleViewTypesAdapter
下面不是伪代码,我测试了它,它对我有用。
我想在我的recyclerview中创建一个headerview,然后在标题下方显示一个用户可以单击的图片列表。
我在代码中使用了一些开关,不知道这是否是最有效的方法,所以请随意发表您的意见:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 | 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)); } } } |
对的,这是可能的。
写一个通用视图持有者:
1 2 3 4 5 6 7 8 | public abstract class GenericViewHolder extends RecyclerView.ViewHolder { public GenericViewHolder(View itemView) { super(itemView); } public abstract void setDataOnView(int position); } |
然后创建视图持有者并使它们扩展GenericViewHolder。例如,这一个:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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类将如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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); } |
为不同的布局创建不同的ViewHolder
RecyclerView可以拥有您想要的任意数量的视图,但为了更好的可读性,我们可以看看如何使用两个ViewHolders创建一个。
It can be done in three simple steps
这是一个小代码片段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | 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 } } } |
getItemViewType(int position)是关键
在我看来,创建这种recyclerView的起点是这种方法的知识。因为这个方法是可选的覆盖因此它默认在RecylerView类中不可见,这反过来又使许多开发人员(包括我)想知道从哪里开始。
一旦你知道这个方法存在,创建这样的RecyclerView将是一个很小的步骤。
Lets see one example to prove my point. If you want to show two layout
at alternate positions do this
1 2 3 4 5 6 7 8 | @Override public int getItemViewType(int position) { if(position%2==0) // Even position return LAYOUT_ONE; else // Odd position return LAYOUT_TWO; } |
相关链接:
查看我已实现此项目的项目
对的,这是可能的。
在您的适配器getItemViewType布局像这样....
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | 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(); } } |
供参考链接:https://www.journaldev.com/12372/android-recyclerview-example
按照Anton的解决方案,拿出这个
但是当回收视图的
基本上,
只有在需要新的视图布局时才会调用
在回收视图时总是调用
所以当调用
更换
感谢任何评论!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | 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 holder,int position)中传入的ViewHolder已经通过查看getItemViewType(int position)来获取(或创建)以确保它是匹配的,所以可能不必担心ViewHolder的type与数据[position]的类型不匹配。
有谁知道onBindViewHolder()中的ViewHolder是如何被拾取的?
编辑:看起来回收
编辑:http://wiresareobsolete.com/2014/09/building-a-recyclerview-layoutmanager-part-1/回答这个问题。
它获得了回收
1 | holder = getRecycledViewPool().getRecycledView(mAdapter.getItemViewType(offsetPosition)); |
如果没有找到正确类型的recycle
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | 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; } |
我有一个更好的解决方案,允许以声明和类型安全的方式创建多个视图类型。它是用Kotlin写的,顺便说一句,这真的很棒。
所有必需视图类型的简单视图持有者
1 2 3 4 | 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 } |
有一个适配器数据项的抽象。请注意,视图类型由特定视图持有者类的hashCode表示(Kotlin中的KClass)
1 2 3 4 5 6 7 8 9 10 11 12 | 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) } } |
只有
1 2 3 4 5 6 7 | 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() } } } |
此类
此解决方案的重要部分是视图持有者工厂,它将提供特定ViewHolder的新实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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)) } } |
简单的适配器类看起来像这样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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个步骤:
以下是此概念的示例:android-drawer-template
它更进一步 - 视图类型充当微调器组件,可选适配器项。
它非常简单直接。
只需覆盖适配器中的getItemViewType()方法即可。在数据的基础上返回不同的itemViewType值。例如
考虑具有成员isMale的Person类型的对象,如果isMale为true,则返回1并且isMale为false,在getItemViewType()方法中返回2。
现在来到createViewHolder(ViewGroup parent,int viewType),在不同的viewType的基础上,yon可以膨胀不同的布局文件。如下
1 2 3 4 5 6 7 8 | 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)中,检查holder是
ViewHolder可能是这样的
1 2 3 4 5 6 7 8 9 10 11 12 13 | class AdapterMaleViewHolder extends RecyclerView.ViewHolder { ... public AdapterMaleViewHolder(View itemView){ ... } } class AdapterFemaleViewHolder extends RecyclerView.ViewHolder { ... public AdapterFemaleViewHolder(View itemView){ ... } } |
我推荐这个来自Hannes Dorfmann的图书馆。它将与特定视图类型相关的所有逻辑封装在名为"AdapterDelegate"的单独对象中。
https://github.com/sockeqwe/AdapterDelegates
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | 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); } } |
实际上,我想改进安东的答案。
由于
此外,我不建议在
您可以通过使
我准备了一个
为此,我从API响应中获取问题的类型,并使用它来决定我必须为该问题显示哪个视图。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | 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(); } } |
您可以在
使用kotlin可以更轻松地查看类型实现,以下是使用此轻型库的示例https://github.com/Link184/KidAdapter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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 } } } |
您可以使用该库:https://github.com/vivchar/RendererRecyclerViewAdapter
1 2 3 | 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 - 它是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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)); } } |
有关详细信息,您可以查看文档。