Android 5.0 - додайте колонтитул / колонтитул до RecyclerView


122

Я провів хвилину, намагаючись придумати спосіб додавання заголовка до 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' 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)в різні моменти створення активності (також спробував додати представлення, коли все налаштовано, навіть дані адаптера), я зрозумів, що поняття не маю, чи правильно це зробити (і це не здається).

PS: Також рішення, яке могло б впоратися з GridLayoutManagerдодатком до LinearLayoutManagerсправді, було б дуже вдячне!


подивіться на цю stackoverflow.com/a/26573338/2127203
EC84B4

Проблема в коді адаптера. Це означає, що якимось чином ви повертаєте нульовий переглядач у функції onCreateViewHolder.
Нео

Є хороший спосіб, як додати заголовок до StaggeredGridLayout stackoverflow.com/questions/42202735/…
Олексій Тимощенко

Відповіді:


120

Мені довелося додати колонтитул до свого, 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
Добре працює. Це слід позначити як правильну відповідь.
Naga Mallesh Maddali

1
Це саме те, що я зробив. Але що робити, якщо я хотів, щоб мій RecyclerView адаптував поетапний список? Перший елемент (Заголовок) також буде поетапно. :(
Неонова Варга

Це підручник про те, як можна RecyclerViewдинамічно заповнювати свої дані . Ви можете контролювати кожен із своїх елементів RecyclerView. Перегляньте розділ коду робочого проекту. Сподіваюся, це може допомогти. github.com/comeondude/dynamic-recyclerview/wiki
Реаз Муршед

2
int getItemViewType (int position)- Поверніть тип подання предмета в положення для цілей переробки огляду. Реалізація за замовчуванням цього методу повертає 0, роблячи припущення про тип перегляду для адаптера. На відміну від ListViewадаптерів, типи не повинні бути суміжними. Подумайте про використання ідентифікаційних ресурсів для унікального визначення типів перегляду елементів. - З документації. developer.android.com/reference/android/support/v7/widget/…
Reaz Murshed

3
Стільки ручної роботи за те, що люди хочуть робити завжди. Я не можу повірити ...
JohnyTex

28

Дуже просто вирішити !!

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

Просто додайте перегляд заголовка LinearLayout (вертикальний) + recilerview + подання нижнього колонтитула всередині 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

Рекомендується використовувати для списків невеликих розмірів, таких як Nav lading або налаштування тощо.


1
Працювало для мене досить просто
Хітеш Саху

1
Це дуже простий спосіб додати заголовки та колонтитули до подання рециркулятора, коли заголовки та колонтитули не потрібно повторювати у списку.
користувач1841702

34
Це дуже простий спосіб втратити всі переваги, які RecyclerViewприносить - ви втрачаєте фактичну переробку та оптимізацію, яку вона приносить.
Marcin Koziński

1
Я спробував цей код, прокрутка не працює належним чином ... вона стала занадто повільною прокруткою. Прошу запропонувати, якщо можна щось зробити для цього
Nibha Jain

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

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себе і еволюціонувати, перевіряти та розміщувати зміни в майбутньому.

EDIT : @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. Проблема полягає в тому, що якщо ви отримаєте позицію за допомогою позиції == getBasicItemCount (), вона не поверне справжню для фактичної останньої позиції, а останню позицію - 1. Це в кінцевому підсумку помістило FooterView туди (не внизу). Ми виправили це, змінивши умову на позицію == getBasicItemCount () + 1, і це спрацювало чудово!
mmark

@seb версія 2 працює так чудово. дуже дякую. я використовую його. Я пропоную одну невелику річ - додати ключове слово для завершення функції.
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 , int)

Не забудьте відняти одне число від позиції, до якої було передано ціле число bindViewHolder.


Я не думаю, що це робота рециркулятора. Завдання Recyclerviews - це просто переробити представлення даних. Написати менеджер верстки з реалізацією заголовка або колонтитула - це шлях.
IZI_Shadow_IZI

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

@VieuMa ви, мабуть, обидва праві, але я не знаю, як це зробити на даний момент, і я був впевнений, що моє рішення спрацює. Субоптимальне рішення краще, ніж жодне рішення, яке ви мали раніше.
Ян Ньюсон

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

просто створіть адаптер, який обертає будь-який адаптер і додає підтримку перегляду заголовка в індексі 0. HeaderView в listview створює безліч крайових справ і додана вартість мінімальна, враховуючи, що це легко вирішити за допомогою адаптера для обгортки.
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, який займає всю ширину екрана. Чи можливо це за допомогою цієї бібліотеки.
ve

Ні, ця бібліотека дозволяє змінювати перший і останній елемент утилізатораView при адаптації (RecyclerView.Adapter). Ви можете без перешкод використовувати цей адаптер для GridView. Тому я думаю, що ця бібліотека дозволяє робити те, що ти хочеш.
lopez.mikhael

6

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

Тут створено суть: HeaderViewRecyclerAdapter.java

Головною особливістю, яку я хотів, був аналогічний інтерфейс до ListView, тому я хотів мати можливість завищити перегляди у своєму фрагменті та додати їх до RecyclerViewвходу 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. override метод 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.override метод getItemCount

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

4.override метод 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());
        }
    }
}

що робити, якщо ваше посилання в майбутньому порушиться?
Гопал Сінгх Сірві

Хороше запитання. Я відредагую свою відповідь і опублікую тут свій код
yefeng

1

Ви можете використовувати бібліотеку SecctedRecyclerViewAdapter, щоб згрупувати свої елементи по секціях та додати заголовок до кожного розділу, як на зображенні нижче:

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

Спочатку ви створите свій клас секцій:

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на секретний елемент, а потім зателефонуватиshowAllHeaders . Якщо вам потрібен лише 1 заголовок, тоді перший пункт повинен мати заголовок. Якщо ви видалите цей елемент, то заголовок автоматично зв’язується з наступним.

На жаль, колонтитули ще не накриті.

FlexibleAdapter дозволяє зробити набагато більше, ніж створювати заголовки / розділи. Ви дійсно повинні мати вигляд: https://github.com/davideas/F prilagodAdapter .


0

Я б просто додати альтернативу всім тим, хто реалізує HeaderRecyclerViewAdapter. CompoundAdapter:

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

Це більш гнучкий підхід, оскільки ви можете створити AdapterGroup з адаптерів. Для прикладу заголовка використовуйте адаптер таким, який він є, а також адаптер, що містить один елемент для заголовка:

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

recyclerView.setAdapter(adapterGroup);

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.