Реализовать несколько типов ViewHolder в адаптере RecycleView

возможно, это обсуждение, а не вопрос.

обычный способ реализации нескольких типов

как вы знаете, если мы хотим реализовать несколько типов в RecyclerView, мы должны обеспечить несколько CustomViewHolder расширения RecyclerView.ViewHolder.

для exmpale,

class TextViewHolder extends RecyclerView.ViewHolder{
    TextView textView;
}

class ImageViewHolder extends RecyclerView.ViewHolder{
    ImageView imageView;
}

тогда мы должны переопределить getItemViewType.И в onCreateViewHolder построить TextViewHolder или ImageViewHolder.

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

каким образом вы используете в своей разработке?

9 ответов


лично мне нравится подход, предложенный Боярская Йигит на этот разговор (перемотка вперед 31:07). Вместо того, чтобы возвращать константа int С getItemViewType(), возвращает идентификатор макета напрямую, который также является 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 is следующий:


    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 только.


Мне нравится использовать одиночные классы ответственности, так как логика не смешивается.

используя второй пример, вы можете быстро включить код spaguetti, и если вам нравится проверять nullability, вы вынуждены объявить "все" как nullable.


Я использую оба, что лучше для текущих задач. Я уважаю принцип единой ответственности. Каждый 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);
    }
}

это может быть не тот ответ, который вы ожидаете, но вот пример использования эпоксидной, который действительно делает вашу жизнь проще:

сначала вы определяете свои модели:

@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.


второй глючит, потому что когда ViewHolders перерабатываются, он производит неожиданное поведение. Я рассмотрел возможность изменения видимости во время привязки, но она недостаточно эффективна для большого количества представлений. Recycler внутри RecyclerView хранит ViewHolders для каждого типа, поэтому первый способ более эффективен.


Я вроде как использую первый.

Я использую объект-компаньон для объявления статических полей, которые я использую в своей реализации.

этот проект был написан в Котлине, но вот как я реализовал адаптер:

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

}

здесь вы можете использовать динамический метод отправки. Ниже я делюсь своей идеей. // Код Действия

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

}

//View Holder Один Исходный Код.

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

}

для исходного проекта : Введите описание ссылки здесь


Я интенсивно использую этот подход: http://frogermcs.github.io/inject-everything-viewholder-and-dagger-2-example/ Короче:

  1. впрысните карту фабрик держателя взгляда в переходнику.
  2. делегат onCreateViewHolder чтобы вводили заводы.
  3. определение onBind на аналогичном держателе базового вида, чтобы вы могли вызвать его с полученными данными в onBindViewHolder.
  4. выбрать в зависимости от фабрики getItemViewType (либо instanceOf или сравнение значение поля.)

почему?

он чисто отделяет каждый держатель вида от остальной части приложения. Если вы используете autofactory из google вы можете легко вводить зависимости, необходимые для каждого держателя представления. Если вам нужно уведомить родителя о каком-либо событии, просто создайте новый интерфейс, реализуйте его в Родительском представлении (activity) и выставьте его в dagger. (pro tip: вместо инициализации фабрик от их поставщиков просто укажите, что фабрика каждого требуемого элемента зависит от фабрики это autofactory дает вам и Кинжал обеспечит это для вас).

мы используем его для + 15 держателей просмотра, и адаптер должен расти только на ~3 строки для каждого нового добавленного (getItemViewType проверка).


Я использую 2-й метод без условного, отлично работает со 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);
}
}