【问题标题】:Implement multiple ViewHolder types in RecycleView adapter在 RecyclerView 适配器中实现多个 ViewHolder 类型
【发布时间】:2017-09-24 13:51:46
【问题描述】:

这可能是一个讨论而不是一个问题。

实现多种类型的正常方式

如你所知,如果我们想在RecyclerView中实现多个类型,我们应该提供多个CustomViewHolder扩展RecyclerView.ViewHolder

例如,

class TextViewHolder extends RecyclerView.ViewHolder{
    TextView textView;
}

class ImageViewHolder extends RecyclerView.ViewHolder{
    ImageView imageView;
}

然后我们必须重写getItemViewType。并在onCreateViewHolder 中构造TextViewHolderImageViewHolder

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (viewType == 0) {
        return new ImageViewHolder(mLayoutInflater.inflate(R.layout.item_image, parent, false));
    } else {
        return new TextViewHolder(mLayoutInflater.inflate(R.layout.item_text, parent, false));
    }
} 

上面的代码是正常的,但还有另一种方法。

另一种方式

我认为只有一个CustomViewHolder 就足够了。

 class MultipleViewHolder extends RecyclerView.ViewHolder{
    TextView textView;
    ImageView imageView;

    MultipleViewHolder(View itemView, int type){
       if(type == 0){
         textView = (TextView)itemView.findViewById(xx);
       }else{
         imageView = (ImageView)itemView.findViewById(xx);
       }
    }
 }

您在开发工作中使用哪种方式?

【问题讨论】:

  • 对我来说.. 第一种方法更好,更容易阅读..
  • 在设计您的应用程序或其任何组件时,请始终考虑使用 SOLID 原则。
  • 这可能不是您期望的答案,但您是否已经考虑过使用 Epoxy ?它真的让你的生活更轻松
  • @Benjamin 也许您可以使用 Epoxy 添加示例答案?

标签: android android-layout android-recyclerview android-view android-viewholder


【解决方案1】:

我个人喜欢Yigit Boyarthis talk 中建议的方法(快进到 31:07)。不要从getItemViewType()返回constant int,而是直接返回布局id,它也是一个int,保证唯一:

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

这将允许您在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; }

MyViewHolder 是一个抽象类:

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

FirstViewHolder 正在关注:

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

这将使onBindViewHolder() 成为单行:

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

因此,您将每个ViewHolder 分开,其中bind(Item) 将负责执行特定于该ViewHolder 的操作。

【讨论】:

  • 太好了,抽象方法bind(Item item)设计得很好。
  • 我一直在使用它。嗯,差不多。将 resourceId 作为 ViewType 返回真是太棒了哈哈哈。真的很棒。没想到我的适配器会变得更好。感谢分享 =)
  • 太棒了,正是我想要的,又漂亮又干净。我必须强制转换视图以使覆盖工作 - 例如MyViewHolder viewHolder = (MyViewHolder) holder
  • 当我实现这段代码时,我得到了更好的解决方案,但滚动出现了一些问题。我认为这是在创建新的视图持有者时发生的。你能帮忙吗?
  • @azizbekian 在这种情况下我们如何实现内部视图点击传递位置?我已经对此stackoverflow.com/questions/57925191/… 提出了一个问题和一个补充。还没有运气。
【解决方案2】:

我喜欢使用单一职责类,因为逻辑没有混合。

使用第二个示例,您可以快速上交 spaguetti 代码,如果您想检查可空性,则必须将“所有内容”声明为可空性。

【讨论】:

    【解决方案3】:

    我两者都用,无论哪个更适合当前任务。我确实尊重单一职责原则。每个 ViewHolder 应该执行一项任务。

    如果我对不同的项目类型有不同的视图持有者逻辑 - 我实现不同的视图持有者。

    如果某些不同项目类型的视图可以强制转换为相同类型并且无需检查即可使用(例如,如果列表页眉和列表页脚是简单但不同的视图)--创建具有不同视图的相同视图持有者是没有意义的.

    这就是重点。不同的逻辑 - 不同的 ViewHolders。相同的逻辑 - 相同的 ViewHolders。

    ImageView 和 TextView 示例。 如果你的视图持有者有一些逻辑(例如,设置值)并且对于不同的视图类型是不同的——你不应该混合它们。

    这是一个不好的例子:

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

    如果您的 ViewHolders 没有任何逻辑,只是持有视图,那么对于简单的情况可能没问题。例如,如果您以这种方式绑定视图:

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

    【讨论】:

      【解决方案4】:

      这可能不是您期望的答案,但这里有一个使用 Epoxy 的示例,它确实让您的生活更轻松:

      首先定义模型:

      @EpoxyModelClass(layout = R.layout.header_view_model)
      public abstract class HeaderViewModel extends EpoxyModel<TextView> {
      
          @EpoxyAttribute
          String title;
      
          @Override
          public void bind(TextView view) {
              super.bind(view);
              view.setText(title);
          }
      
      }
      
      @EpoxyModelClass(layout = R.layout.drink_view_model)
      public abstract class DrinkViewModel extends EpoxyModel<View> {
      
          @EpoxyAttribute
          Drink drink;
      
          @EpoxyAttribute
          Presenter presenter;
      
          @Override
          public void bind(View view) {
              super.bind(view);
      
              final TextView title = view.findViewById(R.id.title);
              final TextView description = view.findViewById(R.id.description);
      
              title.setText(drink.getTitle());
              description.setText(drink.getDescription());
              view.setOnClickListener(v -> presenter.drinkClicked(drink));
          }
      
          @Override
          public void unbind(View view) {
              view.setOnClickListener(null);
              super.unbind(view);
          }
      
      }
      
      @EpoxyModelClass(layout = R.layout.food_view_model)
      public abstract class FoodViewModel extends EpoxyModel<View> {
      
          @EpoxyAttribute
          Food food;
      
          @EpoxyAttribute
          Presenter presenter;
      
          @Override
          public void bind(View view) {
              super.bind(view);
      
              final TextView title = view.findViewById(R.id.title);
              final TextView description = view.findViewById(R.id.description);
              final TextView calories = view.findViewById(R.id.calories);
      
              title.setText(food.getTitle());
              description.setText(food.getDescription());
              calories.setText(food.getCalories());
              view.setOnClickListener(v -> presenter.foodClicked(food));
          }
      
          @Override
          public void unbind(View view) {
              view.setOnClickListener(null);
              super.unbind(view);
          }
      
      }
      

      然后你定义你的Controller:

      public class DrinkAndFoodController extends Typed2EpoxyController<List<Drink>, List<Food>> {
      
          @AutoModel
          HeaderViewModel_ drinkTitle;
      
          @AutoModel
          HeaderViewModel_ foodTitle;
      
          private final Presenter mPresenter;
      
          public DrinkAndFoodController(Presenter presenter) {
              mPresenter = presenter;
          }
      
          @Override
          protected void buildModels(List<Drink> drinks, List<Food> foods) {
              if (!drinks.isEmpty()) {
                  drinkTitle
                          .title("Drinks")
                          .addTo(this);
                  for (Drink drink : drinks) {
                      new DrinkViewModel_()
                              .id(drink.getId())
                              .drink(drink)
                              .presenter(mPresenter)
                              .addTo(this);
                  }
              }
      
              if (!foods.isEmpty()) {
                  foodTitle
                          .title("Foods")
                          .addTo(this);
                  for (Food food : foods) {
                      new FoodViewModel_()
                              .id(food.getId())
                              .food(food)
                              .presenter(mPresenter)
                              .addTo(this);
                  }
              }
          }
      }
      

      初始化你的Controller:

      DrinkAndFodController mController = new DrinkAndFoodController(mPresenter);
      mController.setSpanCount(1);
      
      final GridLayoutManager layoutManager = new GridLayoutManager(getContext(), 1);
      layoutManager.setSpanSizeLookup(mController.getSpanSizeLookup());
      mRecyclerView.setLayoutManager(layoutManager);
      mRecyclerView.setAdapter(mController.getAdapter());
      

      最后,您可以像这样轻松地添加数据:

      final List<Drink> drinks = mManager.getDrinks();
      final List<Food> foods = mManager.getFoods();
      mController.setData(drinks, foods);
      

      你会得到一个看起来像这样的列表:

      Drinks
      Drink 1
      Drink 2
      Drink 3
      ...
      Foods
      Food1
      Food2
      Food3
      Food4
      ...
      

      更多信息您可以查看wiki

      【讨论】:

        【解决方案5】:

        第二个是错误的,因为当 ViewHolders 被回收时,它会产生意想不到的行为。我考虑在绑定期间更改可见性,但对于大量视图来说它的性能不够。 RecyclerView 中的 Recycler 存储每种类型的 ViewHolders,因此第一种方式性能更高。

        【讨论】:

        • 我不同意你的看法。因为如果你在这个例子中传递了0ImageView 就不会被初始化。
        • @LiJianixn ImageView 将永远存在,即它会膨胀并占用内存空间。您只会隐藏它,它不会也不会破坏视图或打开以供回收,除非与其父级分离。您可以从其父级中删除ImageView,但是除了无目的创建视图然后删除它的额外负载之外还有什么意义,因为您从未打算使用它。
        • 为什么总是会膨胀?如果传递0,则项目视图没有ImageView 的子项,因为layout.xml 不包含ImageView。但是如果你通过1,它就会被夸大。
        • 嗯...我明白了,我假设您会在相同的布局中添加 ImageViewTextView。由于显而易见的原因,这仍然是一种非常糟糕的方法。查看我之前关于 SOLID 设计原则的评论。
        • 如您所见R.layout.item_imageR.layout.item_text。我永远不会把它们放在同一个布局中。
        【解决方案6】:

        我有点用第一个。

        我使用伴随对象来声明我在实现中使用的静态字段。

        这个项目是用 kotlin 编写的,但这是我实现适配器的方式:

        /**
         * 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
                    }
                }
            }
        }
        

        这是一个 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)
            }
        
        }
        

        【讨论】:

          【解决方案7】:

          在这里您可以使用动态方法调度。下面我分享一下我的想法。 //活动代码

          public class MainActivity extends AppCompatActivity {
          
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
              RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
              recyclerView.setLayoutManager(new LinearLayoutManager(this));
              ArrayList<Object> dataList = new ArrayList<>();
              dataList.add("Apple");
              dataList.add("Orange");
              dataList.add("Cherry");
              dataList.add("Papaya");
              dataList.add("Grapes");
              dataList.add(100);
              dataList.add(200);
              dataList.add(300);
              dataList.add(400);
              ViewAdapter viewAdapter = new ViewAdapter(dataList);
              recyclerView.setAdapter(viewAdapter);
          
          }
          

          }

          //适配器代码

          public class ViewAdapter extends RecyclerView.Adapter<BaseViewHolder> {
          private ArrayList<Object> dataList;
          public ViewAdapter(ArrayList<Object> dataList) {
              this.dataList = dataList;
          }
          
          @Override
          public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
              BaseViewHolder baseViewHolder;
          
              if(viewType == 0) {
                  View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_one,parent,false);
                  baseViewHolder  = new ViewHolderOne(view);
              }else  {
                  View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_two,parent,false);
                  baseViewHolder  = new ViewHolderSecond(view);
              }
              return baseViewHolder;
          }
          
          @Override
          public void onBindViewHolder(BaseViewHolder holder, int position) {
              holder.bindData(dataList.get(position));
          }
          
          @Override
          public int getItemViewType(int position) {
              Object obj = dataList.get(position);
              int type = 0;
              if(obj instanceof Integer) {
                  type = 0;
              }else if(obj instanceof String) {
                  type = 1;
              }
              return type;
          }
          
          @Override
          public int getItemCount() {
              return dataList != null ? dataList.size() : 0;
          }
          

          }

          //基本视图持有者代码。

          public abstract class BaseViewHolder<T> extends RecyclerView.ViewHolder {
          public BaseViewHolder(View itemView) {
              super(itemView);
          }
          
          public abstract void bindData(T data);
          

          }

          //查看持有者一号源代码。

          public class ViewHolderOne extends BaseViewHolder<Integer> {
          
          private TextView txtView;
          public ViewHolderOne(View itemView) {
              super(itemView);
              txtView = itemView.findViewById(R.id.txt_number);
          }
          
          @Override
          public void bindData(Integer data) {
              txtView.setText("Number:" + data);
          }
          

          }

          //查看支架二

          public class ViewHolderSecond extends BaseViewHolder<String> {
          
          private TextView textView;
          public ViewHolderSecond(View itemView) {
              super(itemView);
              textView = itemView.findViewById(R.id.txt_string);
          }
          
          @Override
          public void bindData(String data) {
              textView.setText("Text:" + data);
          }
          

          }

          对于项目来源: enter link description here

          【讨论】:

            【解决方案8】:

            我大量使用这种方法: http://frogermcs.github.io/inject-everything-viewholder-and-dagger-2-example/ 简而言之:

            1. 将视图支架工厂的地图注入适配器。
            2. onCreateViewHolder 委托给注入的工厂。
            3. 在基本视图持有者上的相似处定义onBind,以便您可以使用onBindViewHolder 中的检索数据调用它。
            4. 根据getItemViewType 选择工厂(通过instanceOf 或比较字段值)。

            为什么?

            它将每个视图持有者与应用程序的其余部分清晰地分开。 如果您使用来自 google 的 autofactory,您可以轻松地注入每个视图持有者所需的依赖项。如果您需要通知父级某些事件,只需创建新接口,在父视图(活动)中实现它并在匕首中公开它。 (专业提示:不要从他们的提供者那里初始化工厂,只需指定每个所需项目的工厂取决于 autofactory 为您提供的工厂,而 dagger 将为您提供)。

            我们将它用于 +15 视图持有者,并且适配器只需为每个新添加的增加约 3 行(getItemViewType 检查)。

            【讨论】:

              【解决方案9】:

              我使用不带条件的第二种方法,适用于列表中的 100 多个项目。

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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2019-10-28
                • 1970-01-01
                • 2022-01-14
                • 2015-09-29
                • 1970-01-01
                • 2018-06-06
                • 2016-06-18
                • 2020-06-06
                相关资源
                最近更新 更多