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

? MathieuMaree @ | Original: StackOverFlow

Я потратил момент пытаются выяснить способ добавить заголовок к 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), я решил пойти с методом addView(View view, int position) в LayoutManager и добавить мое мнение заголовка на первой позиции, чтобы действовать как заголовок.

Aaand это где вещи получают уродливее :

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) в разные моменты создания активность (также пытался добавить мнение, как только все настроено, даже данные адаптера ), я понял, что я понятия не имею, если этоправильный путь сделать это (и это не выглядит быть) .

Может кто-нибудь мне помочь? Или, по крайней мере, дать мне некоторое представление / новое направление для изучения ?

Спасибо заранее!

VieuMa

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



Top 5 Respuesta

1Ian Newson @

Я не пробовал это, но я бы просто добавить 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 .



2seb @

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

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

gists

https://gist.github.com/sebnapi/a2596ec3d1730ea47986 usage new HeaderRecyclerViewAdapterV1(new RegularAdapter()); https://gist.github.com/sebnapi/fde648c17616d9d3bcde usage RegularAdapter extends HeaderRecyclerViewAdapterV2

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) для него. Потому что, если вы установитеOnItemTouchListener на Recyclerview, вы можете перехватить прикосновение, найдите х, у координаты на ощупь, найти в соответствии вид ребенка, а затем вызвать recyclerView.getChildPosition(...), и вы всегда будете получать не- offsetted положение в адаптер ! Этоshortcomming в RecyclerView кодекса я не вижу простой способ преодолеть это. Вот почему я сейчас смещение позиции явные, когда мне нужно покупать мой собственный код .

3darnmason @

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

Создано суть здесь: https://gist.github.com/darnmason/7bbf8beae24fe7296c8a

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

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

4mato @

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

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

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

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