Есть ли аналог addHeaderView для RecyclerView?
Я ищу эквивалент addHeaderView для представления переработчика. В основном я хочу, чтобы изображение с двумя кнопками было добавлено в качестве заголовка к списку. Есть ли другой способ добавить представление заголовка в представление переработчика? Пример для руководства будет полезным
РЕДАКТИРОВАТЬ 2 (добавлены фрагменты макетов):
После добавления операторов журнала кажется, что getViewType всегда получает только позицию 0. Это приводит к тому, что onCreateView загружает только один макет:
10-26 16:32:53.766 5449-5449/co.testapp I/logger info﹕ Adapter-> getItemCount: 5
10-26 16:32:53.766 5449-5449/co.testapp I/logger info﹕ Adapter-> getItemCount: 5
10-26 16:32:53.766 5449-5449/co.testapp I/logger info﹕ Adapter-> getItemCount: 5
10-26 16:32:53.766 5449-5449/co.testapp I/logger info﹕ Adapter-> getItemCount: 5
10-26 16:32:53.766 5449-5449/co.testapp I/logger info﹕ Adapter-> getItemCount: 5
10-26 16:32:53.766 5449-5449/co.testapp I/logger info﹕ Adapter-> getItemCount: 5
10-26 16:32:53.766 5449-5449/co.testapp I/logger info﹕ Adapter-> getItemCount: 5
10-26 16:32:53.766 5449-5449/co.testapp I/logger info﹕ Adapter-> getItemCount: 5
10-26 16:32:53.766 5449-5449/co.testapp I/logger info﹕ Adapter-> getItemCount: 5
10-26 16:32:53.766 5449-5449/co.testapp I/logger info﹕ Adapter-> getItemCount: 5
10-26 16:32:53.766 5449-5449/co.testapp I/logger info﹕ Adapter-> getItemCount: 5
10-26 16:32:53.766 5449-5449/co.testapp I/logger info﹕ Adapter-> getItemViewType position: 0
10-26 16:32:53.766 5449-5449/co.testapp I/logger info﹕ Adapter-> getItemViewType position: 0
10-26 16:32:53.766 5449-5449/co.testapp I/logger info﹕ Adapter-> getItemViewType position: 0
10-26 16:32:53.766 5449-5449/co.testapp I/logger info﹕ Adapter-> onCreateViewHolder, viewtype: 0
10-26 16:32:53.766 5449-5449/co.testapp I/logger info﹕ Adapter-> onBindViewHolder, viewType: 0
Фрагмент перехода для загрузки CommentFragment:
@Override
public void onPhotoFeedItemClick(View view, int position) {
if (fragmentManager == null)
fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
if (view.getId() == R.id.button_comment){
CommentFragment commentFragment = CommentFragment.newInstance("","", position);
fragmentTransaction.add(R.id.main_activity, commentFragment,"comment_fragment_tag");
fragmentTransaction.addToBackStack(Constants.TAG_COMMENTS);
fragmentTransaction.commit();
}
}
OnCreateView фрагмента:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_comment, container, false);
mRecyclerView = (RecyclerView) view.findViewById(R.id.list_recylclerview);
mRecyclerView.setLayoutManager(new LinearLayoutManager(_context));
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mAdapter = new CommentAdapter(R.layout.row_list_comments, R.layout.row_header_comments, _context, comments);
mRecyclerView.setAdapter(mAdapter);
return view;
}
Фрагмент, содержащий повторное представление:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
tools:context="co.testapp.fragments.CommentFragment"
android:background="@color/white">
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/list_recylclerview"
android:layout_width="match_parent"
android:layout_height="200dp" />
</RelativeLayout>
</RelativeLayout>
Расположение строк комментариев:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_margin="10dp"
android:background="@color/white">
<!--Profile Picture-->
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:id="@+id/profile_picture"
android:background="@color/blue_testapp"/>
<!--Name-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="First Name Last Name"
android:textSize="16dp"
android:textColor="@color/blue_testapp"
android:id="@+id/name_of_poster"
android:layout_toRightOf="@id/profile_picture"
/>
<!--Comment-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:layout_marginTop="-5dp"
android:text="This is a test comment"
android:textSize="14dp"
android:textColor="@color/black"
android:id="@+id/comment"
android:layout_below="@id/name_of_poster"
android:layout_toRightOf="@id/profile_picture"/>
</RelativeLayout>
Заголовок
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="wrap_content"
android:layout_height="300dp"
android:id="@+id/header_photo"
android:layout_gravity="center_horizontal"/>
</LinearLayout>
Код адаптера (спасибо Гистеру за то, что я начал):
public class CommentAdapter extends RecyclerView.Adapter<ViewHolder>{
private final int rowCardLayout;
public static Context mContext;
private final int headerLayout;
private final String [] comments;
private static final int HEADER = 0;
private static final int OTHER = 0;
public CommentAdapter(int rowCardLayout, int headerLayout, Context context, String [] comments) {
this.rowCardLayout = rowCardLayout;
this.mContext = context;
this.comments = comments;
this.headerLayout = headerLayout;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
logger.i("onCreateViewHolder, viewtype: " + i); //viewtype always returns 0 so OTHER layout is never inflated
if (i == HEADER) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(headerLayout, viewGroup, false);
return new ViewHolderHeader(v);
}
else if (i == OTHER){
View v = LayoutInflater.from(viewGroup.getContext()).inflate(rowCardLayout, viewGroup, false);
return new ViewHolderComments(v);
}
else
throw new RuntimeException("Could not inflate layout");
}
@Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
logger.i("onBindViewHolder, viewType: " + i);
if (viewHolder instanceof ViewHolderComments)
((ViewHolderComments) viewHolder).comment.setText(comments[i].toString());
if (viewHolder instanceof ViewHolderHeader)
((ViewHolderHeader) viewHolder).header.setImageResource(R.drawable.image2);
else {
logger.e("no instance of viewholder found");
}
}
@Override
public int getItemCount() {
int count = comments.length + 1;
logger.i("getItemCount: " + count);
return count;
}
@Override
public int getItemViewType(int position) {
logger.i("getItemViewType position: " + position);
if (position == HEADER)
return HEADER;
else
return OTHER;
}
public static class ViewHolderComments extends RecyclerView.ViewHolder {
public TextView comment;
public ImageView image;
public ViewHolderComments(View itemView) {
super(itemView);
comment = (TextView) itemView.findViewById(R.id.comment);
image = (ImageView) itemView.findViewById(R.id.image);
}
}
public static class ViewHolderHeader extends RecyclerView.ViewHolder {
public final ImageView header;
public ViewHolderHeader(View itemView){
super(itemView);
header = (ImageView) itemView.findViewById(R.id.header_photo);
}
}
}
Используя приведенный выше код, отображается только макет заголовка, так как viewType всегда равен 0. Это выглядит так. Если я заставлю другой макет, это будет выглядеть так:
21 ответ
Там нет простого способа, как listview.addHeaderView()
но вы можете добиться этого, добавив тип в свой адаптер для заголовка.
Вот пример
public class HeaderAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_HEADER = 0;
private static final int TYPE_ITEM = 1;
String[] data;
public HeaderAdapter(String[] data) {
this.data = data;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_ITEM) {
//inflate your layout and pass it to view holder
return new VHItem(null);
} else if (viewType == TYPE_HEADER) {
//inflate your layout and pass it to view holder
return new VHHeader(null);
}
throw new RuntimeException("there is no type that matches the type " + viewType + " + make sure your using types correctly");
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof VHItem) {
String dataItem = getItem(position);
//cast holder to VHItem and set data
} else if (holder instanceof VHHeader) {
//cast holder to VHHeader and set data for header.
}
}
@Override
public int getItemCount() {
return data.length + 1;
}
@Override
public int getItemViewType(int position) {
if (isPositionHeader(position))
return TYPE_HEADER;
return TYPE_ITEM;
}
private boolean isPositionHeader(int position) {
return position == 0;
}
private String getItem(int position) {
return data[position - 1];
}
class VHItem extends RecyclerView.ViewHolder {
TextView title;
public VHItem(View itemView) {
super(itemView);
}
}
class VHHeader extends RecyclerView.ViewHolder {
Button button;
public VHHeader(View itemView) {
super(itemView);
}
}
}
Легко и многоразово ItemDecoration
Статические заголовки могут быть легко добавлены с ItemDecoration
и без каких-либо дальнейших изменений.
// add the decoration. done.
HeaderDecoration headerDecoration = new HeaderDecoration(/* init */);
recyclerView.addItemDecoration(headerDecoration);
Украшение также можно использовать повторно, так как нет необходимости изменять адаптер или RecyclerView
совсем.
Пример кода, приведенный ниже, потребует добавления вида сверху, который можно просто надуть, как и все остальное. Это может выглядеть так:
Почему статический?
Если вам просто нужно отобразить текст и изображения, то это решение для вас - нет возможности для взаимодействия с пользователем, например, для кнопок или просмотра пейджеров, так как оно будет просто отображено в верхней части вашего списка.
Обработка пустого списка
Если нет вида для украшения, украшение не будет нарисовано. Вам все равно придется обрабатывать пустой список самостоятельно. (Одним из возможных путей решения этой проблемы является добавление фиктивного элемента в адаптер.)
Код
Вы можете найти полный исходный код здесь на GitHub, включая Builder
чтобы помочь с инициализацией декоратора, или просто используйте приведенный ниже код и укажите свои собственные значения для конструктора.
Пожалуйста, не забудьте установить правильный layout_height
на ваш взгляд. например match_parent
может не работать должным образом.
public class HeaderDecoration extends RecyclerView.ItemDecoration {
private final View mView;
private final boolean mHorizontal;
private final float mParallax;
private final float mShadowSize;
private final int mColumns;
private final Paint mShadowPaint;
public HeaderDecoration(View view, boolean scrollsHorizontally, float parallax, float shadowSize, int columns) {
mView = view;
mHorizontal = scrollsHorizontally;
mParallax = parallax;
mShadowSize = shadowSize;
mColumns = columns;
if (mShadowSize > 0) {
mShadowPaint = new Paint();
mShadowPaint.setShader(mHorizontal ?
new LinearGradient(mShadowSize, 0, 0, 0,
new int[]{Color.argb(55, 0, 0, 0), Color.argb(55, 0, 0, 0), Color.argb(3, 0, 0, 0)},
new float[]{0f, .5f, 1f},
Shader.TileMode.CLAMP) :
new LinearGradient(0, mShadowSize, 0, 0,
new int[]{Color.argb(55, 0, 0, 0), Color.argb(55, 0, 0, 0), Color.argb(3, 0, 0, 0)},
new float[]{0f, .5f, 1f},
Shader.TileMode.CLAMP));
} else {
mShadowPaint = null;
}
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
// layout basically just gets drawn on the reserved space on top of the first view
mView.layout(parent.getLeft(), 0, parent.getRight(), mView.getMeasuredHeight());
for (int i = 0; i < parent.getChildCount(); i++) {
View view = parent.getChildAt(i);
if (parent.getChildAdapterPosition(view) == 0) {
c.save();
if (mHorizontal) {
c.clipRect(parent.getLeft(), parent.getTop(), view.getLeft(), parent.getBottom());
final int width = mView.getMeasuredWidth();
final float left = (view.getLeft() - width) * mParallax;
c.translate(left, 0);
mView.draw(c);
if (mShadowSize > 0) {
c.translate(view.getLeft() - left - mShadowSize, 0);
c.drawRect(parent.getLeft(), parent.getTop(), mShadowSize, parent.getBottom(), mShadowPaint);
}
} else {
c.clipRect(parent.getLeft(), parent.getTop(), parent.getRight(), view.getTop());
final int height = mView.getMeasuredHeight();
final float top = (view.getTop() - height) * mParallax;
c.translate(0, top);
mView.draw(c);
if (mShadowSize > 0) {
c.translate(0, view.getTop() - top - mShadowSize);
c.drawRect(parent.getLeft(), parent.getTop(), parent.getRight(), mShadowSize, mShadowPaint);
}
}
c.restore();
break;
}
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (parent.getChildAdapterPosition(view) < mColumns) {
if (mHorizontal) {
if (mView.getMeasuredWidth() <= 0) {
mView.measure(View.MeasureSpec.makeMeasureSpec(parent.getMeasuredWidth(), View.MeasureSpec.AT_MOST),
View.MeasureSpec.makeMeasureSpec(parent.getMeasuredHeight(), View.MeasureSpec.AT_MOST));
}
outRect.set(mView.getMeasuredWidth(), 0, 0, 0);
} else {
if (mView.getMeasuredHeight() <= 0) {
mView.measure(View.MeasureSpec.makeMeasureSpec(parent.getMeasuredWidth(), View.MeasureSpec.AT_MOST),
View.MeasureSpec.makeMeasureSpec(parent.getMeasuredHeight(), View.MeasureSpec.AT_MOST));
}
outRect.set(0, mView.getMeasuredHeight(), 0, 0);
}
} else {
outRect.setEmpty();
}
}
}
Обратите внимание: проект GitHub - это моя личная игровая площадка. Он не был тщательно протестирован, поэтому библиотеки пока нет.
Что оно делает?
ItemDecoration
является дополнительным рисунком к элементу списка. В этом случае украшение рисуется в верхней части первого элемента.
Представление измеряется и размечается, затем оно обращается к вершине первого элемента. Если добавлен эффект параллакса, он также будет обрезан до правильных границ.
Будет показано, как сделать заголовок с элементами в представлении Recycler.
Шаг 1- Добавьте зависимость в ваш файл Gradle.
compile 'com.android.support:recyclerview-v7:23.2.0'
// CardView
compile 'com.android.support:cardview-v7:23.2.0'
Cardview используется для украшения.
Шаг 2. Сделайте три XML-файла. Один для основного действия. Второй для расположения заголовка. Третий для расположения элемента списка.
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<android.support.v7.widget.RecyclerView
android:id="@+id/my_recycler_view"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
</RelativeLayout>
header.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto">
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardElevation="2dp">
<TextView
android:id="@+id/txtHeader"
android:gravity="center"
android:textColor="#000000"
android:textSize="@dimen/abc_text_size_large_material"
android:background="#DCDCDC"
android:layout_width="match_parent"
android:layout_height="50dp" />
</android.support.v7.widget.CardView>
</LinearLayout>
list.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:app="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardElevation="1dp">
<TextView
android:id="@+id/txtName"
android:text="abc"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</android.support.v7.widget.CardView>
</LinearLayout>
</LinearLayout>
Шаг 3- Создайте три бобовых класса.
Header.java
public class Header extends ListItem {
private String header;
public Header(){}
public String getHeader() {
return header;
}
public void setHeader(String header) {
this.header = header;
}
}
ContentItem.java
public class ContentItem extends ListItem {
private String name;
private String rollnumber;
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
public String getRollnumber() {
return rollnumber;
}
public void setRollnumber(String rollnumber) {
this.rollnumber = rollnumber;
}
}
ListItem.java
public class ListItem {
private String name;
public ListItem(){}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
Шаг 4- Создайте адаптер с именем MyRecyclerAdapter.java
public class MyRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
private static final int TYPE_HEADER = 0;
private static final int TYPE_ITEM = 1;
//Header header;
List<ListItem> list;
public MyRecyclerAdapter(List<ListItem> headerItems)
{
this.list = headerItems;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if(viewType==TYPE_HEADER)
{
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.header, parent, false);
return new VHHeader(v);
}
else
{
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list, parent, false);
return new VHItem(v);
}
// return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if(holder instanceof VHHeader)
{
// VHHeader VHheader = (VHHeader)holder;
Header currentItem = (Header) list.get(position);
VHHeader VHheader = (VHHeader)holder;
VHheader.txtTitle.setText(currentItem.getHeader());
}
else if(holder instanceof VHItem)
{
ContentItem currentItem = (ContentItem) list.get(position);
VHItem VHitem = (VHItem)holder;
VHitem.txtName.setText(currentItem.getName());
}
}
public int getItemViewType(int position) {
if(isPositionHeader(position))
return TYPE_HEADER;
return TYPE_ITEM;
}
private boolean isPositionHeader(int position)
{
return list.get(position) instanceof Header;
}
@Override
public int getItemCount() {
return list.size();
}
class VHHeader extends RecyclerView.ViewHolder{
TextView txtTitle;
public VHHeader(View itemView) {
super(itemView);
this.txtTitle = (TextView)itemView.findViewById(R.id.txtHeader);
}
}
class VHItem extends RecyclerView.ViewHolder{
TextView txtName;
public VHItem(View itemView) {
super(itemView);
this.txtName = (TextView)itemView.findViewById(R.id.txtName);
}
}
}
Шаг 5- В MainActivity добавьте следующий код:
public class MainActivity extends AppCompatActivity
{
RecyclerView recyclerView;
List<List<ListItem>> arraylist;
MyRecyclerAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = (RecyclerView)findViewById(R.id.my_recycler_view);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
adapter = new MyRecyclerAdapter(getList());
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setAdapter(adapter);
}
private ArrayList<ListItem> getList() {
ArrayList<ListItem> arrayList=new ArrayList<>();
for(int j=0;j<=4;j++) {
Header header=new Header();
header.setHeader("header"+j);
arrayList.add(header);
for (int i = 0; i <= 3; i++) {
ContentItem item = new ContentItem();
item.setRollnumber(i + "");
item.setName("A" + i);
arrayList.add(item);
}
}
return arrayList;
}
}
Функция getList() динамически генерирует данные для заголовков и элементов списка.
Не стесняйтесь использовать мою библиотеку, доступную здесь.
Это давайте создадим заголовок View
для любого RecyclerView
который использует LinearLayoutManager
или же GridLayoutManager
с помощью простого вызова метода.
Если вы хотите, чтобы заголовок легко повторно через несколько списков, посмотрите на версию 1.2.0 изrecyclerview
библиотека. Он вводит класс ConcatAdapter, который объединяет несколько адаптеров в один. Таким образом, вы можете создать адаптер заголовка и легко комбинировать его с любым другим адаптером, например:
myRecyclerView.adapter = ConcatAdapter(headerAdapter, listAdapter)
В статье с объявлением содержится пример того, как отображать прогресс загрузки в верхнем и нижнем колонтитулах с помощьюConcatAdapter
.
На данный момент, когда я отправляю этот ответ, версия 1.2.0
библиотеки находится в альфа-стадии, поэтому api может измениться. Вы можете проверить статус здесь.
Вы можете просто разместить свой заголовок и RecyclerView в NestedScrollView:
<android.support.v4.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<include
layout="@layout/your_header"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/list_recylclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
Для правильной работы прокрутки необходимо отключить вложенную прокрутку в RecyclerView:
myRecyclerView.setNestedScrollingEnabled(false);
Добиться этого можно с помощью библиотеки https://github.com/luizgrp/SectionedRecyclerViewAdapter, в ней есть понятие "Разделы", где у какого Раздела есть Верхний колонтитул, Нижний колонтитул и Содержимое (список элементов). В вашем случае вам может понадобиться только один раздел, но вы можете иметь много:
1) Создайте собственный класс раздела:
class MySection extends StatelessSection {
List<String> myList = Arrays.asList(new String[] {"Item1", "Item2", "Item3" });
public MySection() {
// call constructor with layout resources for this Section header, footer and items
super(R.layout.section_header, R.layout.section_footer, R.layout.section_item);
}
@Override
public int getContentItemsTotal() {
return myList.size(); // number of items of this section
}
@Override
public RecyclerView.ViewHolder getItemViewHolder(View view) {
// return a custom instance of ViewHolder for the items of this section
return new MyItemViewHolder(view);
}
@Override
public void onBindItemViewHolder(RecyclerView.ViewHolder holder, int position) {
MyItemViewHolder itemHolder = (MyItemViewHolder) holder;
// bind your view here
itemHolder.tvItem.setText(myList.get(position));
}
}
2) Создайте пользовательский ViewHolder для элементов:
class MyItemViewHolder extends RecyclerView.ViewHolder {
private final TextView tvItem;
public MyItemViewHolder(View itemView) {
super(itemView);
tvItem = (TextView) itemView.findViewById(R.id.tvItem);
}
}
3) Настройте ваш ReclyclerView с помощью SectionedRecyclerViewAdapter
// Create an instance of SectionedRecyclerViewAdapter
SectionedRecyclerViewAdapter sectionAdapter = new SectionedRecyclerViewAdapter();
MySection mySection = new MySection();
// Add your Sections
sectionAdapter.addSection(mySection);
// Set up your RecyclerView with the SectionedRecyclerViewAdapter
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerView.setAdapter(sectionAdapter);
Я знаю, что это старый вопрос, но все равно хочу дать ответ.
Здесь есть ( ), которые решают проблему за вас. Все, что вам нужно сделать, это определить макет представления заголовка и создать фиктивный адаптер с одним элементом. В Kotlin это занимает буквально несколько строк.
class HeaderAdapter(private val inflater: LayoutInflater) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = object : RecyclerView.ViewHolder(inflater.inflate(yourViewLayoutId, parent, false)) {}
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {}
override fun getItemCount() = 1
}
После этого все, что вам нужно сделать, это добавить адаптер заголовка и оригинальный адаптер в
ConcatAdapter
recyclerView.adapter = ConcatAdapter(headerAdapter, adapter)
См. документыДокументацию для получения более подробной информации и сложных случаев.
Основываясь на этом посте, я создал подкласс RecyclerView.Adapter, который поддерживает произвольное количество верхних и нижних колонтитулов.
https://gist.github.com/mheras/0908873267def75dc746
Хотя это кажется решением, я также думаю, что этим должен управлять LayoutManager. К сожалению, мне это нужно сейчас, и у меня нет времени, чтобы реализовать StaggeredGridLayoutManager с нуля (и даже не расширять его).
Я все еще проверяю это, но вы можете попробовать это, если хотите. Пожалуйста, дайте мне знать, если вы обнаружите какие-либо проблемы с ним.
Нативный API не имеет такой функции addHeader, но имеет понятие "addItem".
Мне удалось включить эту особенность заголовков и расширений для нижних колонтитулов, а также в мой проект FlexibleAdapter. Я назвал это прокручиваемыми колонтитулами.
Вот как они работают:
Прокручиваемые верхние и нижние колонтитулы - это специальные элементы, которые прокручиваются вместе со всеми остальными, но они не относятся к основным элементам (бизнес-элементам) и всегда обрабатываются адаптером рядом с основными элементами. Эти предметы постоянно находятся на первой и последней позициях.
О них можно многое рассказать, лучше прочитать подробную вики-страницу.
Кроме того, FlexibleAdapter позволяет создавать заголовки / разделы, а также делать их липкими и десятки других функций, таких как расширяемые элементы, бесконечная прокрутка, расширения пользовательского интерфейса и т. Д. - все в одной библиотеке!
Может быть, обернуть заголовок и переработчик в координатор:
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:elevation="0dp">
<View
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll" />
</android.support.design.widget.AppBarLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
First - extends RecyclerView.Adapter<RecyclerView.ViewHolder>
public class MenuAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
После - переопределить метод getItemViewTpe *** Более важно
@Override
public int getItemViewType(int position) {
return position;
}
метод onCreateViewHolder
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.menu_item, parent, false);
View header = LayoutInflater.from(parent.getContext()).inflate(R.layout.menu_header_item, parent, false);
Log.d("onCreateViewHolder", String.valueOf(viewType));
if (viewType == 0) {
return new MenuLeftHeaderViewHolder(header, onClickListener);
} else {
return new MenuLeftViewHolder(view, onClickListener);
}
}
метод onBindViewHolder
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (position == 0) {
MenuHeaderViewHolder menuHeaderViewHolder = (MenuHeaderViewHolder) holder;
menuHeaderViewHolder.mTitle.setText(sMenuTitles[position]);
menuHeaderViewHolder.mImage.setImageResource(sMenuImages[position]);
} else {
MenuViewHolder menuLeftViewHolder = (MenuLeftViewHolder) holder;
menuViewHolder.mTitle.setText(sMenuTitles[position]);
menuViewHolder.mImage.setImageResource(sMenuImages[position]);
}
}
в финише реализует статический класс ViewHolders
public static class MenuViewHolder extends RecyclerView.ViewHolder
public static class MenuLeftHeaderViewHolder extends RecyclerView.ViewHolder
Существует еще одно решение, которое охватывает все приведенные выше варианты использования: CompoundAdapter: https://github.com/negusoft/CompoundAdapter-android
Вы можете создать группу AdapterGroup, которая будет содержать ваш адаптер как есть, а также адаптер с одним элементом для представления заголовка. Код прост и читабелен:
AdapterGroup adapterGroup = new AdapterGroup();
adapterGroup.addAdapter(SingleAdapter.create(R.layout.header));
adapterGroup.addAdapter(new CommentAdapter(...));
recyclerView.setAdapter(adapterGroup);
AdapterGroup также допускает вложение, поэтому для адаптера с разделами вы можете создать AdapterGroup для каждого раздела. Затем поместите все разделы в корневую группу AdapterGroup.
HeaderView зависит от LayoutManager. Ни один из LayoutManager по умолчанию не поддерживает это и, вероятно, не будет. HeaderView в ListView создает много сложности без каких-либо существенных преимуществ.
Я бы предложил создать базовый класс адаптера, который добавляет элементы для заголовков, если таковые имеются. Не забудьте переопределить методы notify*, чтобы корректно их компенсировать в зависимости от того, присутствует заголовок или нет.
Я сделал реализацию, основанную на @hister's, для моих личных целей, но используя наследование.
Я скрываю механизмы детализации реализации (например, добавить 1 к itemCount
, вычтите 1 из position
) в абстрактном суперклассе HeadingableRecycleAdapter
, реализуя необходимые методы из адаптера, как onBindViewHolder
, getItemViewType
а также getItemCount
делая эти методы окончательными и предоставляя клиенту новые методы со скрытой логикой:
onAddViewHolder(RecyclerView.ViewHolder holder, int position)
,onCreateViewHolder(ViewGroup parent)
,itemCount()
Вот HeadingableRecycleAdapter
класс и клиент. Я оставил заголовок немного жестко запрограммированным, потому что он соответствует моим потребностям.
public abstract class HeadingableRecycleAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int HEADER_VIEW_TYPE = 0;
@LayoutRes
private int headerLayoutResource;
private String headerTitle;
private Context context;
public HeadingableRecycleAdapter(@LayoutRes int headerLayoutResourceId, String headerTitle, Context context) {
this.headerLayoutResource = headerLayoutResourceId;
this.headerTitle = headerTitle;
this.context = context;
}
public Context context() {
return context;
}
@Override
public final RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == HEADER_VIEW_TYPE) {
return new HeaderViewHolder(LayoutInflater.from(context).inflate(headerLayoutResource, parent, false));
}
return onCreateViewHolder(parent);
}
@Override
public final void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
int viewType = getItemViewType(position);
if (viewType == HEADER_VIEW_TYPE) {
HeaderViewHolder vh = (HeaderViewHolder) holder;
vh.bind(headerTitle);
} else {
onAddViewHolder(holder, position - 1);
}
}
@Override
public final int getItemViewType(int position) {
return position == 0 ? 0 : 1;
}
@Override
public final int getItemCount() {
return itemCount() + 1;
}
public abstract int itemCount();
public abstract RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent);
public abstract void onAddViewHolder(RecyclerView.ViewHolder holder, int position);
}
@PerActivity
public class IngredientsAdapter extends HeadingableRecycleAdapter {
public static final String TITLE = "Ingredients";
private List<Ingredient> itemList;
@Inject
public IngredientsAdapter(Context context) {
super(R.layout.layout_generic_recyclerview_cardified_header, TITLE, context);
}
public void setItemList(List<Ingredient> itemList) {
this.itemList = itemList;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
return new ViewHolder(LayoutInflater.from(context()).inflate(R.layout.item_ingredient, parent, false));
}
@Override
public void onAddViewHolder(RecyclerView.ViewHolder holder, int position) {
ViewHolder vh = (ViewHolder) holder;
vh.bind(itemList.get(position));
}
@Override
public int itemCount() {
return itemList == null ? 0 : itemList.size();
}
private String getQuantityFormated(double quantity, String measure) {
if (quantity == (long) quantity) {
return String.format(Locale.US, "%s %s", String.valueOf(quantity), measure);
} else {
return String.format(Locale.US, "%.1f %s", quantity, measure);
}
}
class ViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.text_ingredient)
TextView txtIngredient;
ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
void bind(Ingredient ingredient) {
String ingredientText = ingredient.getIngredient();
txtIngredient.setText(String.format(Locale.US, "%s %s ", getQuantityFormated(ingredient.getQuantity(),
ingredient.getMeasure()), Character.toUpperCase(ingredientText.charAt(0)) +
ingredientText
.substring(1)));
}
}
}
Здесь какой-то предмет декорации для переработчика
public class HeaderItemDecoration extends RecyclerView.ItemDecoration {
private View customView;
public HeaderItemDecoration(View view) {
this.customView = view;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
customView.layout(parent.getLeft(), 0, parent.getRight(), customView.getMeasuredHeight());
for (int i = 0; i < parent.getChildCount(); i++) {
View view = parent.getChildAt(i);
if (parent.getChildAdapterPosition(view) == 0) {
c.save();
final int height = customView.getMeasuredHeight();
final int top = view.getTop() - height;
c.translate(0, top);
customView.draw(c);
c.restore();
break;
}
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (parent.getChildAdapterPosition(view) == 0) {
customView.measure(View.MeasureSpec.makeMeasureSpec(parent.getMeasuredWidth(), View.MeasureSpec.AT_MOST),
View.MeasureSpec.makeMeasureSpec(parent.getMeasuredHeight(), View.MeasureSpec.AT_MOST));
outRect.set(0, customView.getMeasuredHeight(), 0, 0);
} else {
outRect.setEmpty();
}
}
}
Наверное, http://alexzh.com/tutorials/multiple-row-layouts-using-recyclerview/ поможет. Он использует только RecyclerView и CardView. Вот адаптер:
public class DifferentRowAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<CityEvent> mList;
public DifferentRowAdapter(List<CityEvent> list) {
this.mList = list;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
switch (viewType) {
case CITY_TYPE:
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_city, parent, false);
return new CityViewHolder(view);
case EVENT_TYPE:
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_event, parent, false);
return new EventViewHolder(view);
}
return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
CityEvent object = mList.get(position);
if (object != null) {
switch (object.getType()) {
case CITY_TYPE:
((CityViewHolder) holder).mTitle.setText(object.getName());
break;
case EVENT_TYPE:
((EventViewHolder) holder).mTitle.setText(object.getName());
((EventViewHolder) holder).mDescription.setText(object.getDescription());
break;
}
}
}
@Override
public int getItemCount() {
if (mList == null)
return 0;
return mList.size();
}
@Override
public int getItemViewType(int position) {
if (mList != null) {
CityEvent object = mList.get(position);
if (object != null) {
return object.getType();
}
}
return 0;
}
public static class CityViewHolder extends RecyclerView.ViewHolder {
private TextView mTitle;
public CityViewHolder(View itemView) {
super(itemView);
mTitle = (TextView) itemView.findViewById(R.id.titleTextView);
}
}
public static class EventViewHolder extends RecyclerView.ViewHolder {
private TextView mTitle;
private TextView mDescription;
public EventViewHolder(View itemView) {
super(itemView);
mTitle = (TextView) itemView.findViewById(R.id.titleTextView);
mDescription = (TextView) itemView.findViewById(R.id.descriptionTextView);
}
}
}
А вот и сущность:
public class CityEvent {
public static final int CITY_TYPE = 0;
public static final int EVENT_TYPE = 1;
private String mName;
private String mDescription;
private int mType;
public CityEvent(String name, String description, int type) {
this.mName = name;
this.mDescription = description;
this.mType = type;
}
public String getName() {
return mName;
}
public void setName(String name) {
this.mName = name;
}
public String getDescription() {
return mDescription;
}
public void setDescription(String description) {
this.mDescription = description;
}
public int getType() {
return mType;
}
public void setType(int type) {
this.mType = type;
}
}
Прошло несколько лет, но на всякий случай, если кто-нибудь прочтет это позже...
Используя приведенный выше код, отображается только макет заголовка, поскольку viewType всегда равен 0.
Проблема в объявлении константы:
private static final int HEADER = 0;
private static final int OTHER = 0; <== bug
Если вы объявите их оба равными нулю, вы всегда получите ноль!
Вы можете создать addHeaderView и использовать
adapter.addHeaderView(View)
,
Этот код построить addHeaderView
для более чем одного заголовка. заголовки должны иметь:
android:layout_height="wrap_content"
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_ITEM = -1;
public class MyViewSHolder extends RecyclerView.ViewHolder {
public MyViewSHolder (View view) {
super(view);
}
// put you code. for example:
View mView;
...
}
public class ViewHeader extends RecyclerView.ViewHolder {
public ViewHeader(View view) {
super(view);
}
}
private List<View> mHeaderViews = new ArrayList<>();
public void addHeaderView(View headerView) {
mHeaderViews.add(headerView);
}
@Override
public int getItemCount() {
return ... + mHeaderViews.size();
}
@Override
public int getItemViewType(int position) {
if (mHeaderViews.size() > position) {
return position;
}
return TYPE_ITEM;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType != TYPE_ITEM) {
//inflate your layout and pass it to view holder
return new ViewHeader(mHeaderViews.get(viewType));
}
...
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int basePosition1) {
if (holder instanceof ViewHeader) {
return;
}
int basePosition = basePosition1 - mHeaderViews.size();
...
}
}
Я реализовал тот же подход, что и в ответе EC84B4, но я абстрагировал RecycleViewAdapter и сделал его легко восстанавливаемым с помощью интерфейсов.
Итак, чтобы использовать мой подход, вы должны добавить в свой проект следующие базовые классы и интерфейсы:
1) Интерфейс, который предоставляет данные для адаптера (коллекция универсального типа T и дополнительных параметров (при необходимости) универсального типа P)
public interface IRecycleViewListHolder<T,P>{
P getAdapterParameters();
T getItem(int position);
int getSize();
}
2) Фабрика для привязки ваших товаров (заголовок / элемент):
public interface IViewHolderBinderFactory<T,P> {
void bindView(RecyclerView.ViewHolder holder, int position,IRecycleViewListHolder<T,P> dataHolder);
}
3) Фабрика для viewHolders (заголовок / элементы):
public interface IViewHolderFactory {
RecyclerView.ViewHolder provideInflatedViewHolder(int viewType, LayoutInflater layoutInflater,@NonNull ViewGroup parent);
}
4) Base class for Adapter with Header:
public class RecycleViewHeaderBased<T,P> extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
public final static int HEADER_TYPE = 1;
public final static int ITEM_TYPE = 0;
private final IRecycleViewListHolder<T, P> dataHolder;
private final IViewHolderBinderFactory<T,P> binderFactory;
private final IViewHolderFactory viewHolderFactory;
public RecycleViewHeaderBased(IRecycleViewListHolder<T,P> dataHolder, IViewHolderBinderFactory<T,P> binderFactory, IViewHolderFactory viewHolderFactory) {
this.dataHolder = dataHolder;
this.binderFactory = binderFactory;
this.viewHolderFactory = viewHolderFactory;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
return viewHolderFactory.provideInflatedViewHolder(viewType,layoutInflater,parent);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
binderFactory.bindView(holder, position,dataHolder);
}
@Override
public int getItemViewType(int position) {
if(position == 0)
return HEADER_TYPE;
return ITEM_TYPE;
}
@Override
public int getItemCount() {
return dataHolder.getSize()+1;
}
}
Usage example:
1) IRecycleViewListHolder implementation:
public class AssetTaskListData implements IRecycleViewListHolder<Map.Entry<Integer, Integer>, GroupedRecord> {
private List<Map.Entry<Integer, Integer>> assetCountList;
private GroupedRecord record;
public AssetTaskListData(Map<Integer, Integer> assetCountListSrc, GroupedRecord record) {
this.assetCountList = new ArrayList<>();
for(Object entry: assetCountListSrc.entrySet().toArray()){
Map.Entry<Integer,Integer> entryTyped = (Map.Entry<Integer,Integer>)entry;
assetCountList.add(entryTyped);
}
this.record = record;
}
@Override
public GroupedRecord getAdapterParameters() {
return record;
}
@Override
public Map.Entry<Integer, Integer> getItem(int position) {
return assetCountList.get(position-1);
}
@Override
public int getSize() {
return assetCountList.size();
}
}
2) IViewHolderBinderFactory implementation:
public class AssetTaskListBinderFactory implements IViewHolderBinderFactory<Map.Entry<Integer, Integer>, GroupedRecord> {
@Override
public void bindView(RecyclerView.ViewHolder holder, int position, IRecycleViewListHolder<Map.Entry<Integer, Integer>, GroupedRecord> dataHolder) {
if (holder instanceof AssetItemViewHolder) {
Integer assetId = dataHolder.getItem(position).getKey();
Integer assetCount = dataHolder.getItem(position).getValue();
((AssetItemViewHolder) holder).bindItem(dataHolder.getAdapterParameters().getRecordId(), assetId, assetCount);
} else {
((AssetHeaderViewHolder) holder).bindItem(dataHolder.getAdapterParameters());
}
}
}
3) IViewHolderFactory implementation:
public class AssetTaskListViewHolderFactory implements IViewHolderFactory {
private IPropertyTypeIconMapper iconMapper;
private ITypeCaster caster;
public AssetTaskListViewHolderFactory(IPropertyTypeIconMapper iconMapper, ITypeCaster caster) {
this.iconMapper = iconMapper;
this.caster = caster;
}
@Override
public RecyclerView.ViewHolder provideInflatedViewHolder(int viewType, LayoutInflater layoutInflater, @NonNull ViewGroup parent) {
if (viewType == RecycleViewHeaderBased.HEADER_TYPE) {
AssetBasedHeaderItemBinding item = DataBindingUtil.inflate(layoutInflater, R.layout.asset_based_header_item, parent, false);
return new AssetHeaderViewHolder(item.getRoot(), item, caster);
}
AssetBasedListItemBinding item = DataBindingUtil.inflate(layoutInflater, R.layout.asset_based_list_item, parent, false);
return new AssetItemViewHolder(item.getRoot(), item, iconMapper, parent.getContext());
}
}
4) Deriving adapter
public class AssetHeaderTaskListAdapter extends RecycleViewHeaderBased<Map.Entry<Integer, Integer>, GroupedRecord> {
public AssetHeaderTaskListAdapter(IRecycleViewListHolder<Map.Entry<Integer, Integer>, GroupedRecord> dataHolder,
IViewHolderBinderFactory binderFactory,
IViewHolderFactory viewHolderFactory) {
super(dataHolder, binderFactory, viewHolderFactory);
}
}
5) Instantiate adapter class:
private void setUpAdapter() {
Map<Integer, Integer> objectTypesCountForGroupedTask = groupedTaskRepository.getObjectTypesCountForGroupedTask(this.groupedRecordId);
AssetTaskListData assetTaskListData = new AssetTaskListData(objectTypesCountForGroupedTask, getGroupedRecord());
adapter = new AssetHeaderTaskListAdapter(assetTaskListData,new AssetTaskListBinderFactory(),new AssetTaskListViewHolderFactory(iconMapper,caster));
assetTaskListRecycler.setAdapter(adapter);
}
P.S.: AssetItemViewHolder, AssetBasedListItemBinding, etc. my application own structures that should be swapped by your own, for your own purposes.
Это мое решение, в котором используется абстрактный класс, расширяющий ваш адаптер.
abstract class HeaderAdapter<T : RecyclerView.ViewHolder>(headerView: View)
: RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var headerView: View = headerView
set(value) {
notifyItemChanged(HEADER_POSITION)
field = value
}
companion object {
const val TYPE_HEADER = -1
const val HEADER_POSITION = 0
}
abstract val itemCountWithoutHeader: Int
abstract fun getViewHolder(parent: ViewGroup, viewType: Int): T
abstract fun onBindItemViewHolder(holder: T, position: Int)
open fun getItemsItemViewType(position: Int): Int {
return super.getItemViewType(position)
}
final override fun getItemCount(): Int = itemCountWithoutHeader + 1
final override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == TYPE_HEADER) HeaderHolder(headerView) else getViewHolder(parent, viewType)
}
final override fun getItemViewType(position: Int): Int {
return if (position == HEADER_POSITION) TYPE_HEADER else getItemsItemViewType(position)
}
final override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (position != HEADER_POSITION) onBindItemViewHolder(holder as T, position-1)
}
class HeaderHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
}