Android

How to create RecyclerView with multiple view types

Как создать RecyclerView с несколькими типами представлений

Из Создание динамических списков с помощью 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 с несколькими типами представлений?

Переведено автоматически
Ответ 1

Да, это возможно. Просто реализуйте getItemViewType() и позаботьтесь о viewType параметре в onCreateViewHolder().

Итак, вы делаете что-то вроде:

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;
}
}
}
Ответ 2

Если макетов для типов представлений всего несколько, а логика привязки проста, следуйте решению Антона. Но код будет беспорядочным, если вам нужно управлять сложными макетами и логикой привязки.

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

Базовый класс 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();

......

}

Функции, необходимые для определения в этом классе, практически такие же, как в классе adapter при создании единого типа представления.

Для каждого типа представления создайте класс, расширив этот 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. getItemCount
    Возвращает общее количество элементов в DataBinders



  2. getItemViewType
    Определите логику сопоставления между позицией адаптера и типом представления.



  3. getDataBinder
    Возвращает экземпляр DataBinder на основе типа представления



  4. GetPosition
    Определяет логику преобразования в позицию адаптера из позиции в указанном DataBinder



  5. getBinderPosition
    Определяет логику преобразования в позицию в DataBinder из позиции адаптера



Я оставил более подробное решение и образцы на GitHub, поэтому, пожалуйста, обратитесь к RecyclerView-MultipleViewTypeAdapter, если вам нужно.

Ответ 3

Приведенное ниже не является псевдокодом. Я протестировал это, и у меня это сработало.

Я хотел создать headerview в моем recyclerview, а затем отобразить список изображений под заголовком, на которые пользователь может нажать.

Я использовал несколько переключателей в своем коде и не знаю, является ли это наиболее эффективным способом сделать это, поэтому не стесняйтесь оставлять свои комментарии:

   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));
}
}
}
Ответ 4

Вот полный пример, показывающий 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()
java android android-recyclerview