Recyclerview и обработка различных типов инфляции строк
Я пытаюсь работать с новой RecyclerView, но я не мог найти пример RecyclerView с различными типами строк/карт, которые раздуваются.
С ListView заменить на getViewTypeCount и getItemViewType, для обработки различных типов строк.
Я должен сделать это, как "старый" способ, или я должен делать что-то с LayoutManager? Мне было интересно, может ли кто-нибудь указать мне правильное направление. Потому что я могу найти примеры только с одним типом.
I хотите иметь список немного разных карт. Или я должен просто использовать scrollView С cardViews внутри него...сделайте это без адаптера и recyclerView?
9 ответов:
обработка логики строк / разделов, подобной UITableView iOS, не так проста в Android, как в iOS, однако при использовании RecyclerView - гибкость того, что вы можете сделать, намного больше.
в конце концов, это все о том, как вы выяснить, какой тип взгляда вы показываете в адаптере. Как только вы это выяснили, это должно быть легко плавать (не совсем, но, по крайней мере, у вас будет это сортировка).
адаптер предоставляет два метода, которые вы должны переопределить:
getItemViewType(int position)реализация этого метода по умолчанию всегда будет возвращать 0, указывая, что существует только 1 тип представления. В вашем случае это не так, и поэтому вам нужно найти способ, чтобы утверждать, какая строка соответствует какому типу просмотра. В отличие от iOS, которая управляет этим для вас с помощью строк и разделов, здесь у вас будет только один индекс, на который можно положиться, и вам нужно будет использовать свои навыки разработчика, чтобы знать, когда позиция коррелирует с заголовком раздела, а когда она коррелирует с нормальным ряд.
createViewHolder(ViewGroup parent, int viewType)вам все равно нужно переопределить этот метод, но обычно люди просто игнорируют параметр viewType. В соответствии с типом представления, вам нужно будет раздуть правильный ресурс макета и создать свой держатель представления соответственно. RecyclerView будет обрабатывать переработку различных типов представлений таким образом, чтобы избежать столкновения различных типов представлений.
если вы планируете использовать LayoutManager по умолчанию, например
LinearLayoutManager, вы должны быть хорошо идти. Если вы планируете при создании собственной реализации LayoutManager вам нужно будет работать немного сложнее. Единственный API, с которым вам действительно нужно работать, - этоfindViewByPosition(int position)что дает данный вид в определенном положении. Поскольку вы, вероятно, захотите выложить его по-разному в зависимости от того, что тип это представление, у вас есть несколько вариантов:
обычно при использовании шаблона ViewHolder вы устанавливаете тег вида с держателем вида. Вы можете использовать это во время выполнения в диспетчере разбивок чтобы узнать, какой тип представления, добавьте поле в держателе представления, которое выражает это.
поскольку вам понадобится функция, которая определяет, какая позиция коррелирует с каким типом представления, вы можете также сделать этот метод глобально доступным каким-то образом (возможно, одноэлементный класс, который управляет данными?), а затем вы можете просто запросить тот же метод в соответствии с позицией.
вот пример кода:
// in this sample, I use an object array to simulate the data of the list. // I assume that if the object is a String, it means I should display a header with a basic title. // If not, I assume it's a custom model object I created which I will use to bind my normal rows. private Object[] myData; public static final int ITEM_TYPE_NORMAL = 0; public static final int ITEM_TYPE_HEADER = 1; public class MyAdapter extends Adapter<ViewHolder> { @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == ITEM_TYPE_NORMAL) { View normalView = LayoutInflater.from(getContext()).inflate(R.layout.my_normal_row, null); return new MyNormalViewHolder(normalView); // view holder for normal items } else if (viewType == ITEM_TYPE_HEADER) { View headerRow = LayoutInflater.from(getContext()).inflate(R.layout.my_header_row, null); return new MyHeaderViewHolder(headerRow); // view holder for header items } } @Override public void onBindViewHolder(ViewHolder holder, int position) { final int itemType = getItemViewType(position); if (itemType == ITEM_TYPE_NORMAL) { ((MyNormalViewHolder)holder).bindData((MyModel)myData[position]); } else if (itemType == ITEM_TYPE_HEADER) { ((MyHeaderViewHolder)holder).setHeaderText((String)myData[position]); } } @Override public int getItemViewType(int position) { if (myData[position] instanceof String) { return ITEM_TYPE_HEADER; } else { return ITEM_TYPE_NORMAL; } } @Override public int getItemCount() { return myData.length; } }здесь пример того, как эти держатели вида должны выглядеть:
public MyHeaderViewHolder extends ViewHolder { private TextView headerLabel; public MyHeaderViewHolder(View view) { super(view); headerLabel = (TextView)view.findViewById(R.id.headerLabel); } public void setHeaderText(String text) { headerLabel.setText(text); } } public MyNormalViewHolder extends ViewHolder { private TextView titleLabel; private TextView descriptionLabel; public MyNormalViewHolder(View view) { super(view); titleLabel = (TextView)view.findViewById(R.id.titleLabel); descriptionLabel = (TextView)view.findViewById(R.id.descriptionLabel); } public void bindData(MyModel model) { titleLabel.setText(model.getTitle()); descriptionLabel.setText(model.getDescription()); } }конечно, в этом примере предполагается, что вы создали свой источник данных (myData) таким образом, что позволяет легко реализовать адаптер таким образом. В качестве примера я покажу вам, как я построю источник данных, который показывает список имен и заголовок для каждого изменения 1-й буквы имени (предположим, что список в алфавитном порядке) - аналогично тому, как список контактов будет выглядеть:
// Assume names & descriptions are non-null and have the same length. // Assume names are alphabetized private void processDataSource(String[] names, String[] descriptions) { String nextFirstLetter = ""; String currentFirstLetter; List<Object> data = new ArrayList<Object>(); for (int i = 0; i < names.length; i++) { currentFirstLetter = names[i].substring(0, 1); // get the 1st letter of the name // if the first letter of this name is different from the last one, add a header row if (!currentFirstLetter.equals(nextFirstLetter)) { nextFirstLetter = currentFirstLetter; data.add(nextFirstLetter); } data.add(new MyModel(names[i], descriptions[i])); } myData = data.toArray(); }в этом примере приходит, чтобы решить довольно конкретную проблему, но я надеюсь, что это дает вам хороший обзор о том, как обрабатывать различные типы строк в recycler, и позволяет сделать необходимые адаптации в вашем собственном коде, чтобы соответствовать вашим потребностям.
фокус в том, чтобы создать подклассы ViewHolder, а затем бросить их.
public class GroupViewHolder extends RecyclerView.ViewHolder { TextView mTitle; TextView mContent; public GroupViewHolder(View itemView) { super (itemView); // init views... } } public class ImageViewHolder extends RecyclerView.ViewHolder { ImageView mImage; public ImageViewHolder(View itemView) { super (itemView); // init views... } } private static final int TYPE_IMAGE = 1; private static final int TYPE_GROUP = 2;и потом, во время выполнения сделать что-то вроде этого:
@Override public int getItemViewType(int position) { // here your custom logic to choose the view type return position == 0 ? TYPE_IMAGE : TYPE_GROUP; } @Override public void onBindViewHolder (ViewHolder viewHolder, int i) { switch (viewHolder.getItemViewType()) { case TYPE_IMAGE: ImageViewHolder imageViewHolder = (ImageViewHolder) viewHolder; imageViewHolder.mImage.setImageResource(...); break; case TYPE_GROUP: GroupViewHolder groupViewHolder = (GroupViewHolder) viewHolder; groupViewHolder.mContent.setText(...) groupViewHolder.mTitle.setText(...); break; } }надеюсь, что это помогает.
согласно Gil great answer я решил, переопределив getItemViewType, как объяснил Гил. Его ответ велик и должен быть отмечен как правильный. В любом случае, я добавляю код для достижения результата:
в вашем адаптере ресайклера:
@Override public int getItemViewType(int position) { int viewType = 0; // add here your booleans or switch() to set viewType at your needed // I.E if (position == 0) viewType = 1; etc. etc. return viewType; } @Override public FileViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == 0) { return new MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.my_layout_for_first_row, parent, false)); } return new MyViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.my_other_rows, parent, false)); }делая это, вы можете установить любой пользовательский макет для любой строки!
Это довольно сложно, но это очень трудно, просто скопируйте приведенный ниже код, и вы сделали
package com.yuvi.sample.main; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import com.yuvi.sample.R; import java.util.List; /** * Created by yubraj on 6/17/15. */ public class NavDrawerAdapter extends RecyclerView.Adapter<NavDrawerAdapter.MainViewHolder> { List<MainOption> mainOptionlist; Context context; private static final int TYPE_PROFILE = 1; private static final int TYPE_OPTION_MENU = 2; private int selectedPos = 0; public NavDrawerAdapter(Context context){ this.mainOptionlist = MainOption.getDrawableDataList(); this.context = context; } @Override public int getItemViewType(int position) { return (position == 0? TYPE_PROFILE : TYPE_OPTION_MENU); } @Override public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { switch (viewType){ case TYPE_PROFILE: return new ProfileViewHolder(LayoutInflater.from(context).inflate(R.layout.row_profile, parent, false)); case TYPE_OPTION_MENU: return new MyViewHolder(LayoutInflater.from(context).inflate(R.layout.row_nav_drawer, parent, false)); } return null; } @Override public void onBindViewHolder(MainViewHolder holder, int position) { if(holder.getItemViewType() == TYPE_PROFILE){ ProfileViewHolder mholder = (ProfileViewHolder) holder; setUpProfileView(mholder); } else { MyViewHolder mHolder = (MyViewHolder) holder; MainOption mo = mainOptionlist.get(position); mHolder.tv_title.setText(mo.title); mHolder.iv_icon.setImageResource(mo.icon); mHolder.itemView.setSelected(selectedPos == position); } } private void setUpProfileView(ProfileViewHolder mholder) { } @Override public int getItemCount() { return mainOptionlist.size(); } public class MyViewHolder extends MainViewHolder{ TextView tv_title; ImageView iv_icon; public MyViewHolder(View v){ super(v); this.tv_title = (TextView) v.findViewById(R.id.tv_title); this.iv_icon = (ImageView) v.findViewById(R.id.iv_icon); v.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // Redraw the old selection and the new notifyItemChanged(selectedPos); selectedPos = getLayoutPosition(); notifyItemChanged(selectedPos); } }); } } public class ProfileViewHolder extends MainViewHolder{ TextView tv_name, login; ImageView iv_profile; public ProfileViewHolder(View v){ super(v); this.tv_name = (TextView) v.findViewById(R.id.tv_profile); this.iv_profile = (ImageView) v.findViewById(R.id.iv_profile); this.login = (TextView) v.findViewById(R.id.tv_login); } } public void trace(String tag, String message){ Log.d(tag , message); } public class MainViewHolder extends RecyclerView.ViewHolder { public MainViewHolder(View v) { super(v); } } }наслаждайтесь !!!!
мы можем достигнуть множественного взгляда на одиночном RecyclerView снизу пути : -
зависимости от Gradle поэтому добавьте ниже код: -
compile 'com.android.support:cardview-v7:23.0.1' compile 'com.android.support:recyclerview-v7:23.0.1'RecyclerView в XML
<android.support.v7.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent"/>Действие Кода
private RecyclerView mRecyclerView; private CustomAdapter mAdapter; private RecyclerView.LayoutManager mLayoutManager; private String[] mDataset = {“Data - one ”, “Data - two”, “Showing data three”, “Showing data four”}; private int mDatasetTypes[] = {DataOne, DataTwo, DataThree}; //view types ... mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView); mLayoutManager = new LinearLayoutManager(MainActivity.this); mRecyclerView.setLayoutManager(mLayoutManager); //Adapter is created in the last step mAdapter = new CustomAdapter(mDataset, mDataSetTypes); mRecyclerView.setAdapter(mAdapter);первый XML
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:card_view="http://schemas.android.com/apk/res-auto" android:id="@+id/cardview" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/ten" android:elevation="@dimen/hundered” card_view:cardBackgroundColor=“@color/black“> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding=“@dimen/ten"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text=“Fisrt” android:textColor=“@color/white“ /> <TextView android:id="@+id/temp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/ten" android:textColor="@color/white" android:textSize="30sp" /> </LinearLayout> </android.support.v7.widget.CardView>второй XML
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:card_view="http://schemas.android.com/apk/res-auto" android:id="@+id/cardview" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/ten" android:elevation="100dp" card_view:cardBackgroundColor="#00bcd4"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="@dimen/ten"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text=“DataTwo” android:textColor="@color/white" /> <TextView android:id="@+id/score" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/ten" android:textColor="#ffffff" android:textSize="30sp" /> </LinearLayout> </android.support.v7.widget.CardView>третий XML
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:card_view="http://schemas.android.com/apk/res-auto" android:id="@+id/cardview" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/ten" android:elevation="100dp" card_view:cardBackgroundColor="@color/white"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="@dimen/ten"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text=“DataThree” /> <TextView android:id="@+id/headline" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="@dimen/ten" android:textSize="25sp" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/ten" android:id="@+id/read_more" android:background="@color/white" android:text=“Show More” /> </LinearLayout> </android.support.v7.widget.CardView>теперь время сделать адаптер, и это главное для отображения различных -2 вид на тот же вид рециркулятора поэтому, пожалуйста, проверьте этот код фокус полностью :-
public class CustomAdapter extends RecyclerView.Adapter<CustomAdapter.ViewHolder> { private static final String TAG = "CustomAdapter"; private String[] mDataSet; private int[] mDataSetTypes; public static final int dataOne = 0; public static final int dataTwo = 1; public static final int dataThree = 2; public static class ViewHolder extends RecyclerView.ViewHolder { public ViewHolder(View v) { super(v); } } public class DataOne extends ViewHolder { TextView temp; public DataOne(View v) { super(v); this.temp = (TextView) v.findViewById(R.id.temp); } } public class DataTwo extends ViewHolder { TextView score; public DataTwo(View v) { super(v); this.score = (TextView) v.findViewById(R.id.score); } } public class DataThree extends ViewHolder { TextView headline; Button read_more; public DataThree(View v) { super(v); this.headline = (TextView) v.findViewById(R.id.headline); this.read_more = (Button) v.findViewById(R.id.read_more); } } public CustomAdapter(String[] dataSet, int[] dataSetTypes) { mDataSet = dataSet; mDataSetTypes = dataSetTypes; } @Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { View v; if (viewType == dataOne) { v = LayoutInflater.from(viewGroup.getContext()) .inflate(R.layout.weather_card, viewGroup, false); return new DataOne(v); } else if (viewType == dataTwo) { v = LayoutInflater.from(viewGroup.getContext()) .inflate(R.layout.news_card, viewGroup, false); return new DataThree(v); } else { v = LayoutInflater.from(viewGroup.getContext()) .inflate(R.layout.score_card, viewGroup, false); return new DataTwo(v); } } @Override public void onBindViewHolder(ViewHolder viewHolder, final int position) { if (viewHolder.getItemViewType() == dataOne) { DataOne holder = (DataOne) viewHolder; holder.temp.setText(mDataSet[position]); } else if (viewHolder.getItemViewType() == dataTwo) { DataThree holder = (DataTwo) viewHolder; holder.headline.setText(mDataSet[position]); } else { DataTwo holder = (DataTwo) viewHolder; holder.score.setText(mDataSet[position]); } } @Override public int getItemCount() { return mDataSet.length; } @Override public int getItemViewType(int position) { return mDataSetTypes[position]; } }вы можете также проверить это ссылке для получения дополнительной информации.
вы должны реализовать
getItemViewType()методRecyclerView.Adapter. По умолчаниюonCreateViewHolder(ViewGroup parent, int viewType)реализацияviewTypeэтот метод возвращает0. Во-первых, вам нужно просмотреть тип элемента в позиции для целей рециркуляции вида, и для этого вам нужно переопределитьgetItemViewType()метод, в котором вы можете пройтиviewTypeкоторый вернет ваше положение элемента. Пример кода приведен ниже@Override public MyViewholder onCreateViewHolder(ViewGroup parent, int viewType) { int listViewItemType = getItemViewType(viewType); switch (listViewItemType) { case 0: return new ViewHolder0(...); case 2: return new ViewHolder2(...); } } @Override public int getItemViewType(int position) { return position; } // and in the similar way you can set data according // to view holder position by passing position in getItemViewType @Override public void onBindViewHolder(MyViewholder viewholder, int position) { int listViewItemType = getItemViewType(position); // ... }
вы можете просто вернуть ItemViewType и использовать его. Смотрите ниже код:
@Override public int getItemViewType(int position) { Message item = messageList.get(position); // return my message layout if(item.getUsername() == Message.userEnum.I) return R.layout.item_message_me; else return R.layout.item_message; // return other message layout } @Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) { View view = LayoutInflater.from(viewGroup.getContext()).inflate(viewType, viewGroup, false); return new ViewHolder(view); }
вы можете использовать библиотеку: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-это простой вид держатель recycler view.
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)); } }для более подробной информации вы можете посмотреть документацию.
вы можете использовать эту библиотеку:
https://github.com/kmfish/MultiTypeListViewAdapter (написано мной)
- лучше повторно использовать код одной ячейки
- лучше расширение
- лучше развязки
настройки адаптера:
adapter = new BaseRecyclerAdapter(); adapter.registerDataAndItem(TextModel.class, LineListItem1.class); adapter.registerDataAndItem(ImageModel.class, LineListItem2.class); adapter.registerDataAndItem(AbsModel.class, AbsLineItem.class);для каждого элемента строки:
public class LineListItem1 extends BaseListItem<TextModel, LineListItem1.OnItem1ClickListener> { TextView tvName; TextView tvDesc; @Override public int onGetLayoutRes() { return R.layout.list_item1; } @Override public void bindViews(View convertView) { Log.d("item1", "bindViews:" + convertView); tvName = (TextView) convertView.findViewById(R.id.text_name); tvDesc = (TextView) convertView.findViewById(R.id.text_desc); tvName.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (null != attachInfo) { attachInfo.onNameClick(getData()); } } }); tvDesc.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (null != attachInfo) { attachInfo.onDescClick(getData()); } } }); } @Override public void updateView(TextModel model, int pos) { if (null != model) { Log.d("item1", "updateView model:" + model + "pos:" + pos); tvName.setText(model.getName()); tvDesc.setText(model.getDesc()); } } public interface OnItem1ClickListener { void onNameClick(TextModel model); void onDescClick(TextModel model); } }