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