Implement multiple ViewHolder types in RecycleView adapter

后端 未结 9 1668
迷失自我
迷失自我 2020-12-13 16:34

It\'s maybe a discussion not a question.

Normal way to implement multiple types

As you know, if we want to implement multiple types in RecyclerView

相关标签:
9条回答
  • 2020-12-13 16:41

    I like to use single responsability classes, as logic is not mixed.

    Using the second example, you can quickly turn in spaguetti code, and if you like to check nullability, you are forced to declare "everything" as nullable.

    0 讨论(0)
  • 2020-12-13 16:42

    I kind of use the first one.

    I use a companion object to declare the static fields, which I use in my implementation.

    This project was written in kotlin, but here is how I implemented an Adapter:

    /**
     * Created by Geert Berkers.
     */
    class CustomAdapter(
        private val objects: List<Any>,
    ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    
        companion object {
            const val FIRST_CELL          = 0
            const val SECOND_CELL         = 1
            const val THIRD_CELL          = 2
            const val OTHER_CELL          = 3
    
            const val FirstCellLayout     = R.layout.first_cell
            const val SecondCellLayout    = R.layout.second_cell
            const val ThirdCellLayout     = R.layout.third_cell
            const val OtherCellLayout     = R.layout.other_cell
        }
    
        override fun getItemCount(): Int  = 4
    
        override fun getItemViewType(position: Int): Int = when (position) {
            objects[0] -> FIRST_CELL
            objects[1] -> SECOND_CELL
            objects[2] -> THIRD_CELL
            else -> OTHER_CELL
        }
    
        override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
            when (viewType) {
    
                FIRST_CELL -> {
                    val view = inflateLayoutView(FirstCellLayout, parent)
                    return FirstCellViewHolder(view)
                }
    
                SECOND_CELL -> {
                    val view = inflateLayoutView(SecondCellLayout, parent)
                    return SecondCellViewHolder(view)
                }
    
                THIRD_CELL -> {
                    val view = inflateLayoutView(ThirdCellLayout, parent)
                    return ThirdCellViewHolder(view)
                }
    
                else -> {
                    val view = inflateLayoutView(OtherCellLayout, parent)
                    return OtherCellViewHolder(view)
                }
            }
        }
    
        fun inflateLayoutView(viewResourceId: Int, parent: ViewGroup?, attachToRoot: Boolean = false): View =
            LayoutInflater.from(parent?.context).inflate(viewResourceId, parent, attachToRoot)
    
        override fun onBindViewHolder(holder: RecyclerView.ViewHolder?, position: Int) {
            val itemViewTpe = getItemViewType(position)
    
            when (itemViewTpe) {
    
                FIRST_CELL -> {
                    val firstCellViewHolder = holder as FirstCellViewHolder
                    firstCellViewHolder.bindObject(objects[position])
                }
    
                SECOND_CELL -> {
                    val secondCellViewHolder = holder as SecondCellViewHolder
                    secondCellViewHolder.bindObject(objects[position])
                }
    
                THIRD_CELL -> {
                    val thirdCellViewHolder = holder as ThirdCellViewHolder
                    thirdCellViewHolder.bindObject(objects[position])
                }
    
                OTHER_CELL -> {
                    // Do nothing. This only displays a view
                }
            }
        }
    }
    

    And here is an example of a ViewHolder:

    class FirstCellViewHolder(view: View) : RecyclerView.ViewHolder(view) {
    
        fun bindMedication(object: Object) = with(object) {
            itemView.setOnClickListener {
                openObject(object)
            }
        }
    
        private fun openObject(object: Object) {
            val context = App.instance
            val intent = DisplayObjectActivity.intent(context, object)
            intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
            context.startActivity(intent)
        }
    
    }
    
    0 讨论(0)
  • 2020-12-13 16:47

    Personally I like approach suggested by Yigit Boyar in this talk (fast forward to 31:07). Instead of returning a constant int from getItemViewType(), return the layout id directly, which is also an int and is guaranteed to be unique:

    
        @Override
        public int getItemViewType(int position) {
            switch (position) {
                case 0:
                    return R.layout.first;
                case 1:
                    return R.layout.second;
                default:
                    return R.layout.third;
            }
        }
    
    

    This will allow you to have following implementation in onCreateViewHolder():

    
        @Override
        public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
            View view = inflater.inflate(viewType, parent, false);
    
            MyViewHolder holder = null;
            switch (viewType) {
                case R.layout.first:
                    holder = new FirstViewHolder(view);
                    break;
                case R.layout.second:
                    holder = new SecondViewHolder(view);
                    break;
                case R.layout.third:
                    holder = new ThirdViewHolder(view);
                    break;
            }
            return holder;
        }
    
    

    Where MyViewHolder is an abstract class:

    
        public static abstract class MyViewHolder extends RecyclerView.ViewHolder {
    
            public MyViewHolder(View itemView) {
                super(itemView);
    
                // perform action specific to all viewholders, e.g.
                // ButterKnife.bind(this, itemView);
            }
    
            abstract void bind(Item item);
        }
    
    

    And FirstViewHolder is following:

    
        public static class FirstViewHolder extends MyViewHolder {
    
            @BindView
            TextView title;
    
            public FirstViewHolder(View itemView) {
                super(itemView);
            }
    
            @Override
            void bind(Item item) {
                title.setText(item.getTitle());
            }
        }
    
    

    This will make onBindViewHolder() to be one-liner:

    
        @Override
        public void onBindViewHolder(MyViewHolder holder, int position) {
            holder.bind(dataList.get(holder.getAdapterPosition()));
        }
    
    

    Thus, you'd have each ViewHolder separated, where bind(Item) would be responsible to perform actions specific to that ViewHolder only.

    0 讨论(0)
  • 2020-12-13 16:47

    I use both, whatever is better for current task. I do respect the Single Responcibility principle. Each ViewHolder should do one task.

    If I have different view holder logic for different item types - I implement different view holders.

    If views for some different item types can be cast to same type and used without checks (for example, if list header and list footer are simple but different views) -- there is no sence in creating identical view holders with different views.

    That's the point. Different logic - different ViewHolders. Same logic - same ViewHolders.

    The ImageView and TextView example. If your view holder has some logic (for example, setting value) and it is different for different view types -- you should not mix them.

    This is bad example:

    class MultipleViewHolder extends RecyclerView.ViewHolder{
        TextView textView;
        ImageView imageView;
    
        MultipleViewHolder(View itemView, int type){
            super(itemView);
            if(type == 0){
                textView = (TextView)itemView.findViewById(xx);
            }else{
                imageView = (ImageView)itemView.findViewById(xx);
            }
        }
    
        void setItem(Drawable image){
            imageView.setImageDrawable(image);
        }
    
        void setItem(String text){
            textView.setText(text);
        }
    }
    

    If your ViewHolders don't have any logic, just holding views, it might be OK for simple cases. for example, if you bind views this way:

    @Override
    public void onBindViewHolder(ItemViewHolderBase holder, int position) {
        holder.setItem(mValues.get(position), position);
        if (getItemViewType(position) == 0) {
            holder.textView.setText((String)mItems.get(position));
        } else {
            int res = (int)mItems.get(position);
            holder.imageView.setImageResource(res);
        }
    }
    
    0 讨论(0)
  • 2020-12-13 16:48

    I use this approach intensively: http://frogermcs.github.io/inject-everything-viewholder-and-dagger-2-example/ In short:

    1. Inject map of view holder factories into adapter.
    2. Delegate onCreateViewHolder to injected factories.
    3. Define onBind on similar on base view holder so that you can call it with retrieved data in onBindViewHolder.
    4. Choose factory depending on getItemViewType (by either instanceOf or comparing field value).

    Why?

    It cleanly separates every view holder from the rest of app. If you use autofactory from google, you can easily inject dependencies required for every view holder. If you need to notify parent of some event, just create new interface, implement it in parent view (activity) and expose it in dagger. (pro tip: instead of initialising factories from their providers, simply specify that each required item's factory depends on factory that autofactory gives you and dagger will provide that for you).

    We use it for +15 view holders and adapter has only to grow by ~3 lines for each new added (getItemViewType checks).

    0 讨论(0)
  • 2020-12-13 16:55

    I use 2nd method without conditional, works great with 100+ items in list.

    public class SafeHolder extends RecyclerView.ViewHolder
    {
        public final ImageView m_ivImage;
    public final ImageView m_ivRarity;
    public final TextView m_tvItem;
    public final TextView m_tvDesc;
    public final TextView m_tvQuantity;
    
    public SafeHolder(View itemView) {
        super(itemView);
        m_ivImage   =(ImageView)itemView.findViewById(R.id.safeimage_id);
        m_ivRarity   =(ImageView)itemView.findViewById(R.id.saferarity_id);
        m_tvItem    = (TextView) itemView.findViewById(R.id.safeitem_id);
        m_tvDesc     = (TextView) itemView.findViewById(R.id.safedesc_id);
        m_tvQuantity = (TextView) itemView.findViewById(R.id.safequantity_id);
    }
    }
    
    0 讨论(0)
提交回复
热议问题