【问题标题】:How to create RecyclerView with multiple view types如何创建具有多种视图类型的 RecyclerView
【发布时间】:2014-12-02 10:03:37
【问题描述】:

来自Create dynamic lists with 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;
        }
    }

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

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

【问题讨论】:

标签: java android user-interface android-recyclerview


【解决方案1】:

是的,这是可能的。只需实现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;
        }
    }
}

【讨论】:

  • @GZ95 一般是可以的,因为不同的视图类型可以对应完全不同的视图布局。 ViewHolder 是 android 中常见的设计模式,描述为here。在它是可选使用之前,现在 RecyclerView 强制使用它。
  • 这就是我的观点,因为一个 RecyclerView.Adapter 中只有一个 ViewHolder 可用,你要如何添加多个?
  • 然后你必须在 onBindViewHolder() 方法中强制转换视图类型,我认为这违背了泛型类型的目的。顺便说一句,谢谢你的回答。
  • 您可以创建一个 BaseHolder 并将其扩展为所有需要的类型。然后添加一个抽象 setData,它将在实现持有者中被覆盖(覆盖?)。这样,您就可以让语言处理类型差异。虽然它只有在您拥有所有列表项都可以解释的单一数据集时才有效。
  • 如果您的 ViewHolders 驻留在您的 RecyclerView 适配器中,则它们应该是静态的
【解决方案2】:

Anton's solution 之后,我想出了这个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 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 can I replace a 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 can I replace a 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 的类型与 data[position] 的类型不匹配。

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

Building a RecyclerView LayoutManager – Part 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 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;
}

【讨论】:

    【解决方案3】:

    如果视图类型的布局很少,绑定逻辑简单,请关注Anton's solution。但是如果你需要管理复杂的布局和绑定逻辑,代码会很乱。

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

    基础 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);
        }
    
        @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 类,创建一个适配器类。

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

    通过扩展这个基类来创建类,然后实例化DataBinder类并重写抽象方法

    1. 获取项目计数
      返回 DataBinders 的总项数

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

    3. getDataBinder
      根据视图类型返回DataBinder实例

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

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

    我在 GitHub 上留下了更详细的解决方案和示例,如有需要请参考RecyclerView-MultipleViewTypeAdapter

    【讨论】:

    • 我对你的代码有点困惑,也许你可以帮助我,我不希望我的视图由列表中的位置定义,而是由它们的视图类型定义。看起来好像代码中的视图是根据它们的位置确定的,即。因此,如果我在位置 1 上显示视图 1,则显示位置 3、视图 3,其他一切都显示位置 2 的视图。我不想将我的视图基于位置,而是基于视图类型 - 所以如果我指定视图类型是图像,它应该显示图像。我该怎么做?
    • 抱歉,我无法完全理解您的问题...,但是您需要在某处编写逻辑以绑定位置和视图类型。
    • 这段代码没有混淆,这是一个 RecyclerView 适配器模式,这应该像问题的正确答案一样除外。关注@yqritc 的链接,花一点时间去发现,你就会有完美的 RecyclerView 模式,不同类型的布局。
    • 新手,public class DataBinder&lt;T extends RecyclerView.ViewHolder&gt; 有人可以告诉我&lt;T someClass&gt; 叫什么,所以如果我得到这个词,我可以用谷歌搜索。另外,当我说abstract public class DataBinder&lt;T extends RecyclerView.ViewHolder&gt; 时,这是否意味着这个类的类型是ViewHolder,所以结果是每个扩展这个类的类都属于viewHolder 类型是这个想法吗?
    • @cesards 你让我再次刷新了关于多态性的知识,哈哈……Java 还不错
    【解决方案4】:

    以下不是伪代码。我已经测试过了,它对我有用。

    我想在我的 recyclerview 中创建一个 headerview,然后在 header 下方显示一个用户可以点击的图片列表。

    我在我的代码中使用了一些开关,不知道这是否是最有效的方法,所以请随意给你的 cmets:

       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;
            }
        }
    
        //Overridden 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 it's 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));
                }
            }
        }
    

    【讨论】:

    • 这很好,如果我想在动态位置有多个标题怎么办?比如说,带有定义类别的标题的项目列表。您的解决方案似乎要求特殊标头位于预定的 int 位置。
    【解决方案5】:

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

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

    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
    }
    

    有一个适配器数据项的抽象。请注意,视图类型由特定视图持有者类(Kotlin 中的 KClass)的 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 需要在具体的适配器项类中被覆盖(类型安全方式)。

    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&lt;AdapterItem&gt; 的适配器的数据源。见下文。

    此解决方案的重要部分是视图持有者工厂,它将提供特定 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)
        }
    }
    

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

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

    这里是这个概念的一个例子:android-drawer-template

    它更进一步 - 一种充当微调器组件的视图类型,具有可选的适配器项。

    【讨论】:

      【解决方案6】:

      是的,有可能。

      编写一个通用的视图持有者:

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

      【讨论】:

      • 那么如何在活动中使用呢?类型是否应该通过方法传递?
      • 如何在Activity中使用这个Adapter?以及它如何识别列表中的类型
      【解决方案7】:

      其实我想改进一下Anton's answer

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

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

      【讨论】:

      • 是的,这可能有效,但会让其他开发人员感到困惑。同样来自文档Note: Integers must be in the range 0 to getViewTypeCount() - 1. IGNORE_ITEM_VIEW_TYPE can also be returned. 所以最好多写一点代码,不要使用黑客。
      • 我同意。那时我错过了那个特定的条款。
      • 这很有趣,因为这里的 RecyclerView.Adapter:getItemViewType() 文档developer.android.com/reference/android/support/v7/widget/… 建议 Dragas 发布的内容是您应该“考虑使用 id 资源来唯一标识项目视图类型”。显然没有注意到 getViewTypeCount() 的要求
      【解决方案8】:

      非常简单明了。

      只需覆盖适配器中的getItemViewType() 方法。根据数据返回不同的 itemViewType 值。例如,考虑具有成员 isMale 的 Person 类型对象,如果 isMale 为 true,则返回 1,isMale 为 false,在 getItemViewType() 方法中返回 2。

      现在来到createViewHolder (ViewGroup parent, int viewType),根据不同的viewType yon可以膨胀不同的布局文件。像下面这样:

       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 检查持有者是AdapterFemaleViewHolderAdapterMaleViewHolder 的实例,并相应地分配值。

      ViewHolder 可能是这样的

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

      【讨论】:

        【解决方案9】:

        您可以使用该库: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));
            }
        }
        

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

        【讨论】:

          【解决方案10】:

          是的,有可能。

          在你的适配器中 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();
              }
          
              @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();
              }
          }
          

          参考链接:Android RecyclerView Example – Multiple ViewTypes

          【讨论】:

          • 我重新格式化了我的代码以模仿这个 sn-p,它现在可以完美运行了。我遇到的问题是,在当前页面之外滑动时它会崩溃。不再崩溃!优秀的模型,。谢谢你 。干得好。
          • 在我看到这个之前几天找不到任何有用的东西,谢谢!
          【解决方案11】:

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

          RecyclerView 可以有任意数量的 viewholders,但为了更好的可读性,让我们看看如何创建一个有两个 ViewHolders 的视图。

          只需三个简单的步骤即可完成

          1. 覆盖public int getItemViewType(int position)
          2. 根据onCreateViewHolder()方法中的ViewType返回不同​​的ViewHolders
          3. 根据onBindViewHolder()方法中的itemViewType填充View

          这是一个小代码sn-p:

          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 将是小菜一碟。

          让我们看一个例子来证明我的观点。如果要显示两个布局 在其他位置这样做

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

          相关链接:

          查看the project 我已经实现了这个。

          【讨论】:

            【解决方案12】:

            使用 Kotlin 可以更轻松地实现视图类型。这是这个灯光库的示例https://github.com/Link184/KidAdapter

            recyclerView.setUp {
                withViewType {
                    withLayoutResId(R.layout.item_int)
                    withItems(mutableListOf(1, 2, 3, 4, 5, 6))
                    bind<Int> { // this - is adapter view hoder itemView, it - current item
                        intName.text = it.toString()
                    }
                }
            
            
                withViewType("SECOND_STRING_TAG") {
                    withLayoutResId(R.layout.item_text)
                    withItems(mutableListOf("eight", "nine", "ten", "eleven", "twelve"))
                    bind<String> {
                        stringName.text = it
                    }
                }
            }
            

            【讨论】:

              【解决方案13】:

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

              https://github.com/sockeqwe/AdapterDelegates

              public class CatAdapterDelegate extends AdapterDelegate<List<Animal>> {
              
                private LayoutInflater inflater;
              
                public CatAdapterDelegate(Activity activity) {
                  inflater = activity.getLayoutInflater();
                }
              
                @Override public boolean isForViewType(@NonNull List<Animal> items, int position) {
                  return items.get(position) instanceof Cat;
                }
              
                @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
                  return new CatViewHolder(inflater.inflate(R.layout.item_cat, parent, false));
                }
              
                @Override public void onBindViewHolder(@NonNull List<Animal> items, int position,
                    @NonNull RecyclerView.ViewHolder holder, @Nullable List<Object> payloads) {
              
                  CatViewHolder vh = (CatViewHolder) holder;
                  Cat cat = (Cat) items.get(position);
              
                  vh.name.setText(cat.getName());
                }
              
                static class CatViewHolder extends RecyclerView.ViewHolder {
              
                  public TextView name;
              
                  public CatViewHolder(View itemView) {
                    super(itemView);
                    name = (TextView) itemView.findViewById(R.id.name);
                  }
                }
              }
              
              public class AnimalAdapter extends ListDelegationAdapter<List<Animal>> {
              
                public AnimalAdapter(Activity activity, List<Animal> items) {
              
                  // DelegatesManager is a protected Field in ListDelegationAdapter
                  delegatesManager.addDelegate(new CatAdapterDelegate(activity))
                                  .addDelegate(new DogAdapterDelegate(activity))
                                  .addDelegate(new GeckoAdapterDelegate(activity))
                                  .addDelegate(23, new SnakeAdapterDelegate(activity));
              
                  // Set the items from super class.
                  setItems(items);
                }
              }
              

              【讨论】:

                【解决方案14】:

                您可以通过使getItemViewType() 返回该位置的预期viewType 值来处理多个视图类型RecyclerAdapter

                我准备了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 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();
                    }
                }
                

                您可以避免在onBindViewHolder() 中填充多个基于条件的viewHolder 数据,方法是为位置不同的viewHolder 中的相似视图分配相同的ID。

                【讨论】:

                  【解决方案15】:

                  如果您想将它与 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.RecyclerView
                                  android: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>
                  
                          <variable
                                  name="item"
                                  type="...presentation.somescreen.list.YourListItem"/>
                      </data>
                  
                     ...
                  
                  </layout>
                  

                  YourListItem 在哪里实现 BaseListItem

                  【讨论】:

                    【解决方案16】:

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

                    现在定义两个静态值在Recycler视图Adapter类级别,例如:private static int TYPE_CALL = 1;私有静态 int TYPE_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);
                        }
                    }
                    

                    现在在recyclerview适配器的onCreateViewHolder和onBindViewHolder方法中代码如下:

                    @NonNull
                    @Override
                    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
                        View view;
                        if (viewType == TYPE_CALL) { // for call layout
                            view = LayoutInflater.from(context).inflate(R.layout.item_call, viewGroup, false);
                            return new CallViewHolder(view);
                    
                        } else { // for email layout
                            view = LayoutInflater.from(context).inflate(R.layout.item_email, viewGroup, false);
                            return new EmailViewHolder(view);
                        }
                    }
                    
                    @Override
                    public 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));
                        }
                    }
                    

                    【讨论】:

                      【解决方案17】:

                      虽然选择的答案是正确的,但我只是想进一步阐述它。我发现了一个有用的Custom Adapter for multiple View Types in RecyclerView。 它的Kotlin version is here

                      自定义适配器如下:

                      public class CustomAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
                          private final Context context;
                          ArrayList<String> list; // ArrayList of your Data Model
                          final int VIEW_TYPE_ONE = 1;
                          final int VIEW_TYPE_TWO = 2;
                      
                          public CustomAdapter(Context context, ArrayList<String> list) { // you can pass other parameters in constructor
                              this.context = context;
                              this.list = list;
                          }
                      
                          private class ViewHolder1 extends RecyclerView.ViewHolder {
                      
                              TextView yourView;
                              ViewHolder1(final View itemView) {
                                  super(itemView);
                                  yourView = itemView.findViewById(R.id.yourView); // 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 here
                                  yourView.setText(list.get(position));
                              }
                          }
                      
                          private class ViewHolder2 extends RecyclerView.ViewHolder {
                      
                              TextView yourView;
                              ViewHolder2(final View itemView) {
                                  super(itemView);
                                  yourView = itemView.findViewById(R.id.yourView); // 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 here
                                  yourView.setText(list.get(position));
                              }
                          }
                      
                          @Override
                          public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
                             if (viewType == VIEW_TYPE_ONE) {
                                 return new ViewHolder1(LayoutInflater.from(context).inflate(R.layout.your_list_item_1, parent, false));
                             }
                             //if its not VIEW_TYPE_ONE then its VIEW_TYPE_TWO
                             return new ViewHolder2(LayoutInflater.from(context).inflate(R.layout.your_list_item_2, parent, false));
                      
                          }
                      
                          @Override
                          public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
                              if (list.get(position).type == Something) { // Put your condition, according to your requirements
                                  ((ViewHolder1) holder).bind(position);
                              } else {
                                  ((ViewHolder2) holder).bind(position);
                              }
                          }
                      
                          @Override
                          public int getItemCount() {
                              return list.size();
                          }
                      
                          @Override
                          public int getItemViewType(int position) {
                              // Here you can get decide from your model's ArrayList, which type of view you need to load. Like
                              if (list.get(position).type == Something) { // Put your condition, according to your requirements
                                  return VIEW_TYPE_ONE;
                              }
                              return VIEW_TYPE_TWO;
                          }
                      }
                      

                      【讨论】:

                        【解决方案18】:

                        如果有人有兴趣查看用 Kotlin 编写的超级简单的解决方案,请查看我刚刚创建的博文。博文中的示例基于创建 Sectioned RecyclerView:

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

                        【讨论】:

                        • 链接已损坏 - “嗯。我们无法找到该站点。我们无法连接到 brona.blog 上的服务器。”
                        【解决方案19】:

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

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

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

                        如果您需要具有不同 viewTypes 的视图,您可以为每个部分编写适配器,然后使用 ConcatAdapter 将所有它们合并到一个 recyclerview 中。

                        连接适配器

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

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

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

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

                        【讨论】:

                        • 非常感谢。这个答案很有帮助。需要在gradle中添加implementation "androidx.recyclerview:recyclerview:1.2.0-alpha04"
                        • 1.2.1 稳定版可用:implementation "androidx.recyclerview:recyclerview:1.2.1"。它非常适合我。
                        • 很好的解决方案,但是当类型混合时如何解决问题?例如,列表如下所示:SecondAdapter、FirstAdapter、FirstAdapter、ThirdAdapter、FirstAdapter、SecondAdapter、ThirdAdapter、FirsrtAdapter、SecondAdapter、ThirdAdapter...?
                        • 没有问题,你可以重复适配器,因为是适配器列表然后显示,你可以尝试添加适配器,然后用concataadapter设置recyclerview
                        【解决方案20】:

                        我首先推荐你阅读 Hannes Dorfmann 的 great article 关于这个话题。

                        当一个新的视图类型出现时,你必须编辑你的适配器并且你必须处理很多乱七八糟的事情。您的适配器应该打开以进行扩展,但关闭以进行修改。

                        您可以查看这两个项目,他们可以提供有关如何在 Adapter 中处理不同 ViewTypes 的想法:

                        【讨论】:

                          【解决方案21】:

                          我做了这样的事情。我通过了“fragmentType”并创建了两个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");
                              }
                          
                              @Override
                              public 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);
                              }
                          
                              @Override
                              public void onClick(View view) {
                                  if (mListener != null) {
                                      mListener.onLoyaltyCardCategoriesItemClicked(pos);
                                  }
                              }
                          }
                          
                          @NonNull
                          @Override
                          public 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;
                              }
                          }
                          
                          @Override
                          public 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);
                              }
                          }
                          
                          @Override
                          public int getItemCount() {
                              return 7;
                          }
                          

                          【讨论】:

                            【解决方案22】:

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

                            类模型

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

                            适配器代码

                            const val VIEW_TYPE_SECTION = 1
                            const 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.name
                                        itemView.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">
                            
                                <TextView
                                    android:id="@+id/text_name"
                                    android:layout_width="wrap_content"
                                    android:layout_height="wrap_content"
                                    tools:text="Name" />
                            
                                <TextView
                                    android: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 = dataSet
                            recyclerAdapter.notifyDataSetChanged()
                            

                            【讨论】:

                            • 这实际上非常适合sealed class,或者在这种特殊情况下 - sealed interface。拥有父 sealed 有助于确保在 if/when 条件下检查所有子节点
                            • 这绝对是添加部分的最佳方式。谢谢!
                            【解决方案23】:

                            我看到有很多很棒的答案,非常详细和广泛。就我而言,如果我几乎从零开始,一步一步地遵循推理,我总是能更好地理解事情。我建议您查看此链接,每当您有类似问题时,请搜索任何解决该问题的代码实验室。

                            Android Kotlin Fundamentals: Headers in RecyclerView

                            【讨论】:

                              猜你喜欢
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              • 1970-01-01
                              相关资源
                              最近更新 更多