Android 5.0 - добавить верхний / нижний колонтитул в RecyclerView


123

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

Вот что у меня получилось:

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...

    layouManager = new LinearLayoutManager(getActivity());
    recyclerView.setLayoutManager(layouManager);

    LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    headerPlaceHolder = inflater.inflate(R.layout.view_header_holder_medium, null, false);
    layouManager.addView(headerPlaceHolder, 0);

   ...
}

LayoutManager, Кажется, обработка расположения объекта RecyclerViewэлементов. Так как я не мог найти какой - либо addHeaderView(View view)метод, я решил пойти с LayoutManager«S addView(View view, int position)метода и добавить мой взгляд заголовка в первом положении , чтобы действовать в качестве заголовка.

И вот здесь все становится еще уродливее:

java.lang.NullPointerException: Attempt to read from field 'android.support.v7.widget.RecyclerView$ViewHolder android.support.v7.widget.RecyclerView$LayoutParams.mViewHolder' on a null object reference
    at android.support.v7.widget.RecyclerView.getChildViewHolderInt(RecyclerView.java:2497)
    at android.support.v7.widget.RecyclerView$LayoutManager.addViewInt(RecyclerView.java:4807)
    at android.support.v7.widget.RecyclerView$LayoutManager.addView(RecyclerView.java:4803)
    at com.mathieumaree.showz.fragments.CategoryFragment.setRecyclerView(CategoryFragment.java:231)
    at com.mathieumaree.showz.fragments.CategoryFragment.access$200(CategoryFragment.java:47)
    at com.mathieumaree.showz.fragments.CategoryFragment$2.success(CategoryFragment.java:201)
    at com.mathieumaree.showz.fragments.CategoryFragment$2.success(CategoryFragment.java:196)
    at retrofit.CallbackRunnable$1.run(CallbackRunnable.java:41)
    at android.os.Handler.handleCallback(Handler.java:739)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:135)
    at android.app.ActivityThread.main(ActivityThread.java:5221)
    at java.lang.reflect.Method.invoke(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:372)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)

После нескольких NullPointerExceptionsпопыток вызвать addView(View view)в разные моменты создания Activity (также попытался добавить представление, когда все настроено, даже данные адаптера), я понял, что понятия не имею, правильный ли это способ сделать это (и он не похоже).

PS: Кроме GridLayoutManagerтого, LinearLayoutManagerбыло бы очень полезно решение, которое могло бы обрабатывать в дополнение к !


взгляните на этот stackoverflow.com/a/26573338/2127203
EC84B4

Проблема в коде адаптера. Это означает, что каким-то образом вы возвращаете нулевой просмотрщик в функции onCreateViewHolder.
Нео

Есть хороший способ добавить заголовок в StaggeredGridLayout stackoverflow.com/questions/42202735/…
Алексей Тимощенко

Ответы:


121

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

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;

public class RecyclerViewWithFooterAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private static final int FOOTER_VIEW = 1;
    private ArrayList<String> data; // Take any list that matches your requirement.
    private Context context;

    // Define a constructor
    public RecyclerViewWithFooterAdapter(Context context, ArrayList<String> data) {
        this.context = context;
        this.data = data;
    }

    // Define a ViewHolder for Footer view
    public class FooterViewHolder extends ViewHolder {
        public FooterViewHolder(View itemView) {
            super(itemView);
            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // Do whatever you want on clicking the item
                }
            });
        }
    }

    // Now define the ViewHolder for Normal list item
    public class NormalViewHolder extends ViewHolder {
        public NormalViewHolder(View itemView) {
            super(itemView);

            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // Do whatever you want on clicking the normal items
                }
            });
        }
    }

    // And now in onCreateViewHolder you have to pass the correct view
    // while populating the list item.

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View v;

        if (viewType == FOOTER_VIEW) {
            v = LayoutInflater.from(context).inflate(R.layout.list_item_footer, parent, false);
            FooterViewHolder vh = new FooterViewHolder(v);
            return vh;
        }

        v = LayoutInflater.from(context).inflate(R.layout.list_item_normal, parent, false);

        NormalViewHolder vh = new NormalViewHolder(v);

        return vh;
    }

    // Now bind the ViewHolder in onBindViewHolder
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

        try {
            if (holder instanceof NormalViewHolder) {
                NormalViewHolder vh = (NormalViewHolder) holder;

                vh.bindView(position);
            } else if (holder instanceof FooterViewHolder) {
                FooterViewHolder vh = (FooterViewHolder) holder;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // Now the critical part. You have return the exact item count of your list
    // I've only one footer. So I returned data.size() + 1
    // If you've multiple headers and footers, you've to return total count
    // like, headers.size() + data.size() + footers.size()

    @Override
    public int getItemCount() {
        if (data == null) {
            return 0;
        }

        if (data.size() == 0) {
            //Return 1 here to show nothing
            return 1;
        }

        // Add extra view to show the footer view
        return data.size() + 1;
    }

    // Now define getItemViewType of your own.

    @Override
    public int getItemViewType(int position) {
        if (position == data.size()) {
            // This is where we'll add footer.
            return FOOTER_VIEW;
        }

        return super.getItemViewType(position);
    }

    // So you're done with adding a footer and its action on onClick.
    // Now set the default ViewHolder for NormalViewHolder

    public class ViewHolder extends RecyclerView.ViewHolder {
        // Define elements of a row here
        public ViewHolder(View itemView) {
            super(itemView);
            // Find view by ID and initialize here
        }

        public void bindView(int position) {
            // bindView() method to implement actions
        }
    }
}

Приведенный выше фрагмент кода добавляет нижний колонтитул к файлу RecyclerView. Вы можете проверить этот репозиторий GitHub для проверки реализации добавления как верхнего, так и нижнего колонтитула.


2
Работает хорошо. Это должно быть отмечено как правильный ответ.
Нага Маллеш Маддали

1
Именно это я и сделал. Но что, если я хочу, чтобы мой RecyclerView адаптировал смещенный список? Первый элемент (заголовок) также будет смещен. :(
Neon Warge

Это руководство о том, как вы можете RecyclerViewдинамически заполнять свой . Вы можете контролировать каждый из элементов вашего RecyclerView. Пожалуйста, посмотрите раздел кода для рабочего проекта. Надеюсь, это поможет. github.com/comeondude/dynamic-recyclerview/wiki
Реаз Муршед

2
int getItemViewType (int position)- Вернуть тип представления элемента в позиции для повторного использования представления. Реализация этого метода по умолчанию возвращает 0, делая предположение о единственном типе представления для адаптера. В отличие от ListViewадаптеров, типы не обязательно должны быть смежными. Рассмотрите возможность использования ресурсов идентификатора для однозначной идентификации типов представления элементов. - Из документации. developer.android.com/reference/android/support/v7/widget/…
Реаз Муршед

4
Так много ручной работы для того, чем люди хотят заниматься всегда. Я не могу в это поверить ...
JohnyTex

28

Решить очень просто !!

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

Просто добавьте вид заголовка LinearLayout (вертикальный) + recyclerview + вид нижнего колонтитула внутри android.support.v4.widget.NestedScrollView .

Проверь это:

 <android.support.v4.widget.NestedScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

       <View
            android:id="@+id/header"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

        <android.support.v7.widget.RecyclerView
            android:id="@+id/list"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layoutManager="LinearLayoutManager"/>

        <View
            android:id="@+id/footer"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>
</android.support.v4.widget.NestedScrollView>

Добавьте эту строку кода для плавной прокрутки

RecyclerView v = (RecyclerView) findViewById(...);
v.setNestedScrollingEnabled(false);

Это приведет к потере всех характеристик RV, и RV попытается выложить всех держателей просмотров независимо layout_heightот RV.

Рекомендуется использовать для списка небольшого размера, такого как панель навигации, настройки и т. Д.


1
Работает у меня довольно просто
Hitesh Sahu

1
Это очень простой способ добавить верхние и нижние колонтитулы в представление ресайклера, когда верхние и нижние колонтитулы не должны повторяться в списке.
user1841702

34
Это очень простой способ потерять все преимущества RecyclerView- вы теряете реальную переработку и оптимизацию, которую она приносит.
Marcin Koziński

1
Я пробовал этот код, прокрутка не работает должным образом ... прокрутка стала слишком медленной ... пожалуйста, подскажите, можно ли что-нибудь сделать для этого
Нибха Джайн

2
использование вложенного scrollview приведет к тому, что recyclerview будет кэшировать все представление, и если размер представления recycler больше, прокрутка и время загрузки увеличится. Я предлагаю не использовать этот код
Тушар Саха

25

У меня была такая же проблема с Lollipop, и я создал два подхода для обертывания Recyclerviewадаптера. Один из них довольно прост в использовании, но я не уверен, как он будет вести себя с изменяющимся набором данных. Потому что он обертывает ваш адаптер, и вам нужно убедиться, что вы вызываете методы, как notifyDataSetChangedна правильном объекте адаптера.

У другого таких проблем быть не должно. Просто позвольте вашему обычному адаптеру расширить класс, реализовать абстрактные методы, и вы должны быть готовы. И вот они:

логи

HeaderRecyclerViewAdapterV1

import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;

/**
 * Created by sebnapi on 08.11.14.
 * <p/>
 * This is a Plug-and-Play Approach for adding a Header or Footer to
 * a RecyclerView backed list
 * <p/>
 * Just wrap your regular adapter like this
 * <p/>
 * new HeaderRecyclerViewAdapterV1(new RegularAdapter())
 * <p/>
 * Let RegularAdapter implement HeaderRecyclerView, FooterRecyclerView or both
 * and you are ready to go.
 * <p/>
 * I'm absolutely not sure how this will behave with changes in the dataset.
 * You can always wrap a fresh adapter and make sure to not change the old one or
 * use my other approach.
 * <p/>
 * With the other approach you need to let your Adapter extend HeaderRecyclerViewAdapterV2
 * (and therefore change potentially more code) but possible omit these shortcomings.
 * <p/>
 * TOTALLY UNTESTED - USE WITH CARE - HAVE FUN :)
 */
public class HeaderRecyclerViewAdapterV1 extends RecyclerView.Adapter {
    private static final int TYPE_HEADER = Integer.MIN_VALUE;
    private static final int TYPE_FOOTER = Integer.MIN_VALUE + 1;
    private static final int TYPE_ADAPTEE_OFFSET = 2;

    private final RecyclerView.Adapter mAdaptee;


    public HeaderRecyclerViewAdapterV1(RecyclerView.Adapter adaptee) {
        mAdaptee = adaptee;
    }

    public RecyclerView.Adapter getAdaptee() {
        return mAdaptee;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_HEADER && mAdaptee instanceof HeaderRecyclerView) {
            return ((HeaderRecyclerView) mAdaptee).onCreateHeaderViewHolder(parent, viewType);
        } else if (viewType == TYPE_FOOTER && mAdaptee instanceof FooterRecyclerView) {
            return ((FooterRecyclerView) mAdaptee).onCreateFooterViewHolder(parent, viewType);
        }
        return mAdaptee.onCreateViewHolder(parent, viewType - TYPE_ADAPTEE_OFFSET);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (position == 0 && holder.getItemViewType() == TYPE_HEADER && useHeader()) {
            ((HeaderRecyclerView) mAdaptee).onBindHeaderView(holder, position);
        } else if (position == mAdaptee.getItemCount() && holder.getItemViewType() == TYPE_FOOTER && useFooter()) {
            ((FooterRecyclerView) mAdaptee).onBindFooterView(holder, position);
        } else {
            mAdaptee.onBindViewHolder(holder, position - (useHeader() ? 1 : 0));
        }
    }

    @Override
    public int getItemCount() {
        int itemCount = mAdaptee.getItemCount();
        if (useHeader()) {
            itemCount += 1;
        }
        if (useFooter()) {
            itemCount += 1;
        }
        return itemCount;
    }

    private boolean useHeader() {
        if (mAdaptee instanceof HeaderRecyclerView) {
            return true;
        }
        return false;
    }

    private boolean useFooter() {
        if (mAdaptee instanceof FooterRecyclerView) {
            return true;
        }
        return false;
    }

    @Override
    public int getItemViewType(int position) {
        if (position == 0 && useHeader()) {
            return TYPE_HEADER;
        }
        if (position == mAdaptee.getItemCount() && useFooter()) {
            return TYPE_FOOTER;
        }
        if (mAdaptee.getItemCount() >= Integer.MAX_VALUE - TYPE_ADAPTEE_OFFSET) {
            new IllegalStateException("HeaderRecyclerViewAdapter offsets your BasicItemType by " + TYPE_ADAPTEE_OFFSET + ".");
        }
        return mAdaptee.getItemViewType(position) + TYPE_ADAPTEE_OFFSET;
    }


    public static interface HeaderRecyclerView {
        public RecyclerView.ViewHolder onCreateHeaderViewHolder(ViewGroup parent, int viewType);

        public void onBindHeaderView(RecyclerView.ViewHolder holder, int position);
    }

    public static interface FooterRecyclerView {
        public RecyclerView.ViewHolder onCreateFooterViewHolder(ViewGroup parent, int viewType);

        public void onBindFooterView(RecyclerView.ViewHolder holder, int position);
    }

}

HeaderRecyclerViewAdapterV2

import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;

/**
 * Created by sebnapi on 08.11.14.
 * <p/>
 * If you extend this Adapter you are able to add a Header, a Footer or both
 * by a similar ViewHolder pattern as in RecyclerView.
 * <p/>
 * If you want to omit changes to your class hierarchy you can try the Plug-and-Play
 * approach HeaderRecyclerViewAdapterV1.
 * <p/>
 * Don't override (Be careful while overriding)
 * - onCreateViewHolder
 * - onBindViewHolder
 * - getItemCount
 * - getItemViewType
 * <p/>
 * You need to override the abstract methods introduced by this class. This class
 * is not using generics as RecyclerView.Adapter make yourself sure to cast right.
 * <p/>
 * TOTALLY UNTESTED - USE WITH CARE - HAVE FUN :)
 */
public abstract class HeaderRecyclerViewAdapterV2 extends RecyclerView.Adapter {
    private static final int TYPE_HEADER = Integer.MIN_VALUE;
    private static final int TYPE_FOOTER = Integer.MIN_VALUE + 1;
    private static final int TYPE_ADAPTEE_OFFSET = 2;

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_HEADER) {
            return onCreateHeaderViewHolder(parent, viewType);
        } else if (viewType == TYPE_FOOTER) {
            return onCreateFooterViewHolder(parent, viewType);
        }
        return onCreateBasicItemViewHolder(parent, viewType - TYPE_ADAPTEE_OFFSET);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (position == 0 && holder.getItemViewType() == TYPE_HEADER) {
            onBindHeaderView(holder, position);
        } else if (position == getBasicItemCount() && holder.getItemViewType() == TYPE_FOOTER) {
            onBindFooterView(holder, position);
        } else {
            onBindBasicItemView(holder, position - (useHeader() ? 1 : 0));
        }
    }

    @Override
    public int getItemCount() {
        int itemCount = getBasicItemCount();
        if (useHeader()) {
            itemCount += 1;
        }
        if (useFooter()) {
            itemCount += 1;
        }
        return itemCount;
    }

    @Override
    public int getItemViewType(int position) {
        if (position == 0 && useHeader()) {
            return TYPE_HEADER;
        }
        if (position == getBasicItemCount() && useFooter()) {
            return TYPE_FOOTER;
        }
        if (getBasicItemType(position) >= Integer.MAX_VALUE - TYPE_ADAPTEE_OFFSET) {
            new IllegalStateException("HeaderRecyclerViewAdapter offsets your BasicItemType by " + TYPE_ADAPTEE_OFFSET + ".");
        }
        return getBasicItemType(position) + TYPE_ADAPTEE_OFFSET;
    }

    public abstract boolean useHeader();

    public abstract RecyclerView.ViewHolder onCreateHeaderViewHolder(ViewGroup parent, int viewType);

    public abstract void onBindHeaderView(RecyclerView.ViewHolder holder, int position);

    public abstract boolean useFooter();

    public abstract RecyclerView.ViewHolder onCreateFooterViewHolder(ViewGroup parent, int viewType);

    public abstract void onBindFooterView(RecyclerView.ViewHolder holder, int position);

    public abstract RecyclerView.ViewHolder onCreateBasicItemViewHolder(ViewGroup parent, int viewType);

    public abstract void onBindBasicItemView(RecyclerView.ViewHolder holder, int position);

    public abstract int getBasicItemCount();

    /**
     * make sure you don't use [Integer.MAX_VALUE-1, Integer.MAX_VALUE] as BasicItemViewType
     *
     * @param position
     * @return
     */
    public abstract int getBasicItemType(int position);

}

Отзывы и вилки приветствуются. Я буду использовать HeaderRecyclerViewAdapterV2самостоятельно и развивать, тестировать и публиковать изменения в будущем.

РЕДАКТИРОВАТЬ : @OvidiuLatcu Да, у меня были проблемы. На самом деле я перестал неявно компенсировать заголовок position - (useHeader() ? 1 : 0)и вместо этого создал для него общедоступный метод int offsetPosition(int position). Потому что, если вы установите OnItemTouchListenerRecyclerview, вы можете перехватить касание, получить координаты x, y касания, найти соответствующее дочернее представление, а затем вызвать, recyclerView.getChildPosition(...)и вы всегда получите несмещенное положение в адаптере! Это недостаток кода RecyclerView, я не вижу простого способа преодолеть это. Вот почему я теперь явно смещаю позиции, когда мне нужно, своим собственным кодом.


выглядит хорошо ! есть проблемы с этим? или можно смело пользоваться? : D
Овидиу Латку

1
@OvidiuLatcu см. Сообщение
seb

В этих реализациях кажется, что вы предположили, что количество верхних и нижних колонтитулов составляет только 1 каждый?
rishabhmhjn

@seb Версия 2 работает как шарм !! единственное, что мне нужно было изменить, - это условие получения нижнего колонтитула для методов onBindViewHolder и getItemViewType. Проблема в том, что если вы получите позицию с помощью position == getBasicItemCount (), она не вернет true для фактической последней позиции, но последняя позиция - 1. В итоге FooterView был помещен туда (не внизу). Мы исправили его, изменив условие на position == getBasicItemCount () + 1, и он отлично работал!
mmark

@seb версии 2 отлично работает. Огромное спасибо. я использую это. одна маленькая вещь, которую я предлагаю, - добавить ключевое слово final для функции переопределения.
RyanShao

10

Я не пробовал этого, но я бы просто добавил 1 (или 2, если вы хотите и верхний, и нижний колонтитул) к целому числу, возвращаемому getItemCount в вашем адаптере. Затем вы можете переопределить getItemViewTypeв своем адаптере, чтобы он возвращал другое целое число, когда i==0: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemViewType(int)

createViewHolderзатем передается целое число, из которого вы вернулись getItemViewType, что позволяет вам по-разному создавать или настраивать держатель представления для представления заголовка: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html# createViewHolder (android.view.ViewGroup , интервал )

Не забудьте вычесть единицу из переданного целого числа позиции bindViewHolder.


Я не думаю, что это работа recyclerview. Работа Recyclerviews состоит в том, чтобы просто повторно использовать представления. Лучше всего написать layoutmanager с реализацией верхнего или нижнего колонтитула.
IZI_Shadow_IZI

Спасибо @IanNewson за ваш ответ. Во-первых, кажется, что это решение работает, даже если используется только getItemViewType(int position) { return position == 0 ? 0 : 1; }( RecyclerViewнет getViewTypeCount()метода). С другой стороны, я согласен с @IZI_Shadow_IZI, я действительно чувствую, что LayoutManager должен быть тем, кто занимается такими вещами. Есть еще идеи?
MathieuMaree

@VieuMa, вы, наверное, оба правы, но я не знаю, как это сделать, и был уверен, что мое решение сработает. Лучше неоптимальное решение, чем отсутствие решения, которое у вас было раньше.
Ян Ньюсон

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

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

9

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

Вам нужно добавить библиотеку HFRecyclerView в свой проект или вы также можете получить ее из Gradle:

compile 'com.mikhaellopez:hfrecyclerview:1.0.0'

Это результат на изображении:

предварительный просмотр

РЕДАКТИРОВАТЬ:

Если вы просто хотите добавить поля вверху и / или внизу с помощью этой библиотеки: SimpleItemDecoration :

int offsetPx = 10;
recyclerView.addItemDecoration(new StartOffsetItemDecoration(offsetPx));
recyclerView.addItemDecoration(new EndOffsetItemDecoration(offsetPx));

Эта библиотека правильно добавляет представление в заголовок в LinearLayoutManager, но я хочу установить представление в качестве заголовка в GridLayoutManager, которое занимает всю ширину экрана. Может можно с этой библиотекой.
Вед

Нет, эта библиотека позволяет вам изменить первый и последний элемент recyclerView при адаптации (RecyclerView.Adapter). Вы можете без проблем использовать этот адаптер для GridView. Так что я думаю, что эта библиотека позволяет делать то, что вы хотите.
lopez.mikhael 09

6

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

Создал суть здесь: HeaderViewRecyclerAdapter.java

Основная функция, которую я хотел, - это интерфейс, аналогичный ListView, поэтому я хотел иметь возможность расширять представления в моем фрагменте и добавлять их в файл RecyclerViewin onCreateView. Это делается путем HeaderViewRecyclerAdapterпередачи адаптера для обертывания и вызова addHeaderViewи addFooterViewпередачи ваших расширенных представлений. Затем установите HeaderViewRecyclerAdapterэкземпляр как адаптер на RecyclerView.

Дополнительным требованием было то, что мне нужно было легко менять адаптеры, сохраняя при этом верхние и нижние колонтитулы, я не хотел иметь несколько адаптеров с несколькими экземплярами этих верхних и нижних колонтитулов. Таким образом, вы можете позвонить, setAdapterчтобы изменить обернутый адаптер, оставив без изменений верхние и нижние колонтитулы, с RecyclerViewуведомлением об изменении.


3

мой способ "держать это просто глупо" ... он тратит некоторые ресурсы, я знаю, но мне все равно, поскольку мой код остается простым, поэтому ... 1) добавить нижний колонтитул с видимостью GONE в ваш item_layout

  <LinearLayout
        android:id="@+id/footer"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        android:orientation="vertical"
        android:visibility="gone">
    </LinearLayout>

2) затем установите его видимым на последнем элементе

public void onBindViewHolder(ChannelAdapter.MyViewHolder holder, int position) {
        boolean last = position==data.size()-1;
        //....
        holder.footer.setVisibility(View.GONE);
        if (last && showFooter){
            holder.footer.setVisibility(View.VISIBLE);
        }
    }

сделайте обратное для заголовка


1

На основе решения @seb я создал подкласс RecyclerView.Adapter, который поддерживает произвольное количество верхних и нижних колонтитулов.

https://gist.github.com/mheras/0908873267def75dc746

Хотя кажется, что это решение, я также считаю, что этим должен управлять LayoutManager. К сожалению, он мне нужен сейчас, и у меня нет времени реализовать StaggeredGridLayoutManager с нуля (и даже не расширять его).

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


1

Вы можете использовать viewtype для решения этой проблемы, вот моя демонстрация: https://github.com/yefengfreedom/RecyclerViewWithHeaderFooterLoadingEmptyViewErrorView

  1. вы можете определить режим отображения вида ресайклера:

    общедоступный статический финальный int MODE_DATA = 0, MODE_LOADING = 1, MODE_ERROR = 2, MODE_EMPTY = 3, MODE_HEADER_VIEW = 4, MODE_FOOTER_VIEW = 5;

2. переопределить метод getItemViewType

 @Override
public int getItemViewType(int position) {
    if (mMode == RecyclerViewMode.MODE_LOADING) {
        return RecyclerViewMode.MODE_LOADING;
    }
    if (mMode == RecyclerViewMode.MODE_ERROR) {
        return RecyclerViewMode.MODE_ERROR;
    }
    if (mMode == RecyclerViewMode.MODE_EMPTY) {
        return RecyclerViewMode.MODE_EMPTY;
    }
    //check what type our position is, based on the assumption that the order is headers > items > footers
    if (position < mHeaders.size()) {
        return RecyclerViewMode.MODE_HEADER_VIEW;
    } else if (position >= mHeaders.size() + mData.size()) {
        return RecyclerViewMode.MODE_FOOTER_VIEW;
    }
    return RecyclerViewMode.MODE_DATA;
}

3. переопределить метод getItemCount

@Override
public int getItemCount() {
    if (mMode == RecyclerViewMode.MODE_DATA) {
        return mData.size() + mHeaders.size() + mFooters.size();
    } else {
        return 1;
    }
}

4. переопределить метод onCreateViewHolder. создать держатель представления по viewType

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (viewType == RecyclerViewMode.MODE_LOADING) {
        RecyclerView.ViewHolder loadingViewHolder = onCreateLoadingViewHolder(parent);
        loadingViewHolder.itemView.setLayoutParams(
                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, parent.getHeight() - mToolBarHeight)
        );
        return loadingViewHolder;
    }
    if (viewType == RecyclerViewMode.MODE_ERROR) {
        RecyclerView.ViewHolder errorViewHolder = onCreateErrorViewHolder(parent);
        errorViewHolder.itemView.setLayoutParams(
                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, parent.getHeight() - mToolBarHeight)
        );
        errorViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View v) {
                if (null != mOnErrorViewClickListener) {
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            mOnErrorViewClickListener.onErrorViewClick(v);
                        }
                    }, 200);
                }
            }
        });
        return errorViewHolder;
    }
    if (viewType == RecyclerViewMode.MODE_EMPTY) {
        RecyclerView.ViewHolder emptyViewHolder = onCreateEmptyViewHolder(parent);
        emptyViewHolder.itemView.setLayoutParams(
                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, parent.getHeight() - mToolBarHeight)
        );
        emptyViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View v) {
                if (null != mOnEmptyViewClickListener) {
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            mOnEmptyViewClickListener.onEmptyViewClick(v);
                        }
                    }, 200);
                }
            }
        });
        return emptyViewHolder;
    }
    if (viewType == RecyclerViewMode.MODE_HEADER_VIEW) {
        RecyclerView.ViewHolder headerViewHolder = onCreateHeaderViewHolder(parent);
        headerViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View v) {
                if (null != mOnHeaderViewClickListener) {
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            mOnHeaderViewClickListener.onHeaderViewClick(v, v.getTag());
                        }
                    }, 200);
                }
            }
        });
        return headerViewHolder;
    }
    if (viewType == RecyclerViewMode.MODE_FOOTER_VIEW) {
        RecyclerView.ViewHolder footerViewHolder = onCreateFooterViewHolder(parent);
        footerViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(final View v) {
                if (null != mOnFooterViewClickListener) {
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            mOnFooterViewClickListener.onFooterViewClick(v, v.getTag());
                        }
                    }, 200);
                }
            }
        });
        return footerViewHolder;
    }
    RecyclerView.ViewHolder dataViewHolder = onCreateDataViewHolder(parent);
    dataViewHolder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(final View v) {
            if (null != mOnItemClickListener) {
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        mOnItemClickListener.onItemClick(v, v.getTag());
                    }
                }, 200);
            }
        }
    });
    dataViewHolder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(final View v) {
            if (null != mOnItemLongClickListener) {
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        mOnItemLongClickListener.onItemLongClick(v, v.getTag());
                    }
                }, 200);
                return true;
            }
            return false;
        }
    });
    return dataViewHolder;
}

5. Отмените метод onBindViewHolder. привязать данные по viewType

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
    if (mMode == RecyclerViewMode.MODE_LOADING) {
        onBindLoadingViewHolder(holder, position);
    } else if (mMode == RecyclerViewMode.MODE_ERROR) {
        onBindErrorViewHolder(holder, position);
    } else if (mMode == RecyclerViewMode.MODE_EMPTY) {
        onBindEmptyViewHolder(holder, position);
    } else {
        if (position < mHeaders.size()) {
            if (mHeaders.size() > 0) {
                onBindHeaderViewHolder(holder, position);
            }
        } else if (position >= mHeaders.size() + mData.size()) {
            if (mFooters.size() > 0) {
                onBindFooterViewHolder(holder, position - mHeaders.size() - mData.size());
            }
        } else {
            onBindDataViewHolder(holder, position - mHeaders.size());
        }
    }
}

что, если ваша ссылка в будущем не работает?
Гопал Сингх Сирви,

Хороший вопрос, я отредактирую свой ответ и
опубликую

1

Вы можете использовать библиотеку SectionedRecyclerViewAdapter, чтобы сгруппировать свои элементы в разделы и добавить заголовок к каждому разделу, как на изображении ниже:

введите описание изображения здесь

Сначала вы создаете свой класс раздела:

class MySection extends StatelessSection {

    String title;
    List<String> list;

    public MySection(String title, List<String> list) {
        // call constructor with layout resources for this Section header, footer and items 
        super(R.layout.section_header, R.layout.section_item);

        this.title = title;
        this.list = list;
    }

    @Override
    public int getContentItemsTotal() {
        return list.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(list.get(position));
    }

    @Override
    public RecyclerView.ViewHolder getHeaderViewHolder(View view) {
        return new SimpleHeaderViewHolder(view);
    }

    @Override
    public void onBindHeaderViewHolder(RecyclerView.ViewHolder holder) {
        MyHeaderViewHolder headerHolder = (MyHeaderViewHolder) holder;

        // bind your header view here
        headerHolder.tvItem.setText(title);
    }
}

Затем вы настраиваете RecyclerView со своими разделами и меняете SpanSize заголовков с помощью GridLayoutManager:

// Create an instance of SectionedRecyclerViewAdapter 
SectionedRecyclerViewAdapter sectionAdapter = new SectionedRecyclerViewAdapter();

// Create your sections with the list of data
MySection section1 = new MySection("My Section 1 title", dataList1);
MySection section2 = new MySection("My Section 2 title", dataList2);

// Add your Sections to the adapter
sectionAdapter.addSection(section1);
sectionAdapter.addSection(section2);

// Set up a GridLayoutManager to change the SpanSize of the header
GridLayoutManager glm = new GridLayoutManager(getContext(), 2);
glm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
    @Override
    public int getSpanSize(int position) {
        switch(sectionAdapter.getSectionItemViewType(position)) {
            case SectionedRecyclerViewAdapter.VIEW_TYPE_HEADER:
                return 2;
            default:
                return 1;
        }
    }
});

// Set up your RecyclerView with the SectionedRecyclerViewAdapter
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(glm);
recyclerView.setAdapter(sectionAdapter);

0

Я знаю, что опаздываю, но только недавно мне удалось реализовать такой "addHeader" в адаптере. В моем проекте FlexibleAdapter вы можете вызвать setHeaderэлемент Sectionable , а затем позвонить showAllHeaders. Если вам нужен только 1 заголовок, тогда у первого элемента должен быть заголовок. Если вы удалите этот элемент, заголовок автоматически будет связан со следующим.

К сожалению, нижние колонтитулы не покрыты (пока).

FlexibleAdapter позволяет вам делать гораздо больше, чем просто создавать заголовки / разделы. Вам действительно стоит взглянуть: https://github.com/davideas/F flexibleAdapter .


0

Я бы просто добавил альтернативу всем этим реализациям HeaderRecyclerViewAdapter. CompoundAdapter:

https://github.com/negusoft/CompoundAdapter-android

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

AdapterGroup adapterGroup = new AdapterGroup();
adapterGroup.addAdapter(SingleAdapter.create(R.layout.header));
adapterGroup.addAdapter(new MyAdapter(...));

recyclerView.setAdapter(adapterGroup);

Он довольно простой и читаемый. Вы можете легко реализовать более сложный адаптер, используя тот же принцип.


0

recyclerview:1.2.0представляет класс ConcatAdapter, который объединяет несколько адаптеров в один. Таким образом, он позволяет создавать отдельные адаптеры верхнего / нижнего колонтитула и повторно использовать их в нескольких списках.

myRecyclerView.adapter = ConcatAdapter(headerAdapter, listAdapter, footerAdapter)

Взгляните на анонсирующую статью . Он содержит образец того, как отображать прогресс загрузки в верхнем и нижнем колонтитулах с помощью ConcatAdapter.

На данный момент, когда я публикую этот ответ, версия 1.2.0библиотеки находится в альфа-стадии, поэтому api может измениться. Вы можете проверить статус здесь .

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.