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)메서드를 찾을 수 없어서 LayoutManageraddView(View view, int position)메서드를 사용하여 첫 번째 위치에 헤더보기를 추가하여 헤더 역할을하기로 결정했습니다.

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)(모든 것이 설정되면 뷰를 추가하려고 시도했습니다. 어댑터의 데이터도) 이것이 올바른 방법인지 모르겠다는 것을 깨달았습니다 (그리고 그것은 보이지 않습니다).

PS : 또한, 처리 할 수있는 솔루션 GridLayoutManager받는 사람뿐만 아니라이 LinearLayoutManager정말 감사하겠습니다!



문제는 어댑터 코드에 있습니다. 이는 어떻게 든 onCreateViewHolder 함수에서 null 뷰 홀더를 반환한다는 것을 의미합니다.
네오

StaggeredGridLayout에 헤더를 추가하는 방법 좋은 방법이 stackoverflow.com/questions/42202735/...은
알렉세이 Timoshchenko

답변:


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가 시차 목록을 조정하도록하려면 어떻게해야합니까? 첫 번째 요소 (헤더)도 엇갈리게 표시됩니다. :(
Neon Warge

RecyclerView동적으로 채우는 방법에 대한 자습서 입니다. 의 각 요소를 제어 할 수 있습니다 RecyclerView. 작업중인 프로젝트의 코드 섹션을 참조하십시오. 도움이되기를 바랍니다. github.com/comeondude/dynamic-recyclerview/wiki
Reaz Murshed

2
int getItemViewType (int position)-보기 재활용을 위해 해당 위치에있는 항목의보기 유형을 반환합니다. 이 메소드의 기본 구현은 어댑터에 대한 단일보기 유형을 가정하여 0을 리턴합니다. ListView어댑터 와 달리 유형은 연속적 일 필요가 없습니다. 항목보기 유형을 고유하게 식별하려면 id 리소스를 사용하는 것이 좋습니다. -문서에서. developer.android.com/reference/android/support/v7/widget/...
Reaz Murshed에게

3
사람들이 항상하고 싶어하는 일에 대한 수작업이 너무 많습니다. 믿을 수 없어요 ...
JohnyTex

28

해결하기 매우 간단합니다 !!

보기를 반환하기 전에보기 유형을 확인할 때마다 어댑터 내부에 논리를 다른보기 유형으로 사용하는 것은 마음에 들지 않습니다. 아래 솔루션은 추가 검사를 피합니다.

android.support.v4.widget.NestedScrollView 안에 LinearLayout (수직) 헤더 뷰 + recyclerview + 푸터 뷰를 추가하기 만하면 됩니다.

이것 좀 봐:

 <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_heightRV의

Nav 서랍 또는 설정 등과 같은 작은 크기 목록에 사용하는 것이 좋습니다.


1
나를 매우 간단 근무
Hitesh Sahu

1
이것은 머리글과 바닥 글이 목록 내에서 반복 될 필요가 없을 때 리사이클 러 뷰에 머리글과 바닥 글을 추가하는 매우 간단한 방법입니다.
user1841702 jul.

34
이것은 RecyclerView가져 오는 모든 이점을 잃는 매우 간단한 방법입니다 . 실제 재활용과 최적화를 잃게됩니다.
마르신 Koziński

1
나는 그것이 너무 느린 스크롤되었다 ... 제대로 작동하지 않는 스크롤이 코드를 시도 .. 그것에 대해 뭔가 할 수 있다면 PLZ 제안
Nibha 자이나교

2
중첩 된 scrollview를 사용하면 recyclerview가 모든보기를 캐시하고 recycler보기의 크기가 더 많이 스크롤되고로드 시간이 늘어납니다. 나는이 코드를 사용하지 않는 것이 좋습니다
Tushar 사하

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
Ovidiu Latcu 2014

1
@OvidiuLatcu 참조 게시물
seb

이러한 구현에서 머리글과 바닥 글의 수를 각각 1 개로 가정 한 것 같습니다.
rishabhmhjn 2015 년

@seb 버전 2 는 매력처럼 작동합니다 !! 수정해야 할 유일한 것은 onBindViewHolder 및 getItemViewType 메서드 모두에서 바닥 글을 가져 오는 조건입니다. 문제는 position == getBasicItemCount ()를 사용하여 위치를 얻는 경우 실제 마지막 위치에 대해 true를 반환하지 않지만 마지막 위치-1. 결국 FooterView가 맨 아래에 배치되지 않습니다. 조건을 == getBasicItemCount () + 1 위치로 변경하는 문제를 해결했으며 훌륭하게 작동했습니다!
mmark

@seb 버전 2는 매우 훌륭하게 작동합니다. 정말 고마워. 나는 그것을 사용하고있다. 내가 제안하는 한 가지 작은 것은 재정의 기능에 '최종'키워드를 추가하는 것입니다.
RyanShao

10

나는 이것을 시도하지 않았지만 어댑터의 getItemCount가 반환하는 정수에 1 (또는 머리글과 바닥 글을 모두 원하는 경우 2)을 추가합니다. 그런 다음 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)

에 전달 된 위치 정수에서 1을 빼는 것을 잊지 마십시오 bindViewHolder.


나는 그것이 recyclerview의 일이라고 생각하지 않습니다. Recyclerviews 작업은 단순히 뷰를 재활용하는 것입니다. 머리글 또는 바닥 글 구현으로 레이아웃 관리자를 작성하는 것이 좋습니다.
IZI_Shadow_IZI

답장을 보내 주신 @IanNewson에게 감사드립니다. 첫째,이 솔루션은 getItemViewType(int position) { return position == 0 ? 0 : 1; }( 메서드 RecyclerView가 없음) 만 사용하더라도 작동하는 것 같습니다 getViewTypeCount(). 반면에 저는 @IZI_Shadow_IZI에 동의합니다. 저는 LayoutManager가 이런 종류의 일을 처리해야한다는 느낌을 받았습니다. 다른 생각은 없나요?
MathieuMaree

@VieuMa 당신은 아마 둘 다 옳지 만, 현재 어떻게 해야할지 모르겠고 내 솔루션이 작동 할 것이라고 확신했습니다. 차선책은 이전에 있었던 솔루션이없는 것보다 낫습니다.
Ian Newson

@VieuMa는 또한 헤더와 푸터를 어댑터로 추상화한다는 것은 요청 된 두 가지 유형의 레이아웃을 모두 처리해야 함을 의미하고, 자신의 레이아웃 관리자를 작성한다는 것은 두 가지 유형의 레이아웃을 모두 다시 구현하는 것을 의미합니다.
Ian Newson

어댑터를 랩핑하고 인덱스 0에 헤더 뷰에 대한 지원을 추가하는 어댑터를 생성하기 만하면됩니다. Listview의 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에서 뷰를 헤더로 설정하고 싶습니다. 이 라이브러리로 가능할 수 있습니다.
ved

아니요,이 라이브러리를 사용하면 조정시 recyclerView의 첫 번째 및 마지막 요소를 변경할 수 있습니다 (RecyclerView.Adapter). 이 어댑터를 GridView에 문제없이 사용할 수 있습니다. 그래서 저는이 도서관이 당신이 원하는 것을 할 수 있다고 생각합니다.
lopez.mikhael

6

결국 다른 어댑터를 래핑하고 머리글 및 바닥 글보기를 추가하는 방법을 제공하기 위해 자체 어댑터를 구현했습니다.

여기에 요점 생성 : HeaderViewRecyclerAdapter.java

내가 원했던 주요 기능은 ListView와 유사한 인터페이스 였기 때문에 내 Fragment에서 뷰를 확장하고 .NET에 추가 할 수 있기를 원 RecyclerView했습니다 onCreateView. 이것은 작성하여 수행됩니다 HeaderViewRecyclerAdapter어댑터를 되풀이하는 전달 및 호출 addHeaderView하고 addFooterView당신의 팽창 의견을 전달합니다. 그런 다음 HeaderViewRecyclerAdapter인스턴스를 어댑터로 설정합니다 .RecyclerView .

추가 요구 사항은 머리글과 바닥 글을 유지하면서 어댑터를 쉽게 교체 할 수 있어야한다는 것이 었습니다. 이러한 머리글과 바닥 글의 여러 인스턴스가있는 여러 어댑터를 갖고 싶지 않았습니다. 따라서 setAdapter헤더와 바닥 글은 그대로두고 래핑 된 어댑터를 변경하기 위해 호출 할 수 있으며 변경 사항에 대한 RecyclerView알림을받을 수 있습니다.


3

내 "간단하게 어리석게 유지"하는 방법 ... 일부 리소스를 낭비하지만, 내 코드가 단순하게 유지되므로 신경 쓰지 않습니다. 1) 항목 _ 레이아웃에 가시성이있는 바닥 글 추가

  <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. 일부 리사이클 러보기 표시 모드를 정의 할 수 있습니다.

    public static final 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());
        }
    }
}

향후 링크가 끊어지면 어떨까요?
고팔 싱 Sirvi

좋은 질문 엔 내 대답을 편집하고 내 여기에 코드를 게시 할 예정입니다
yefeng

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를 설정하고 GridLayoutManager로 헤더의 SpanSize를 변경합니다.

// 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/FlexibleAdapter를 꼭 봐야합니다 .


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

매우 간단하고 읽기 쉽습니다. 동일한 원칙을 사용하여 더 복잡한 어댑터를 쉽게 구현할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.