Android 5.0 - Thêm đầu trang / chân trang vào RecyclerView


122

Tôi đã dành một chút thời gian để cố gắng tìm ra cách thêm tiêu đề vào a RecyclerView, nhưng không thành công.

Đây là những gì tôi nhận được cho đến nay:

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

   ...
}

Các LayoutManagerdường như là xử lý các bố trí của đối tượng RecyclerViewmục. Vì tôi không thể tìm thấy bất kỳ addHeaderView(View view)phương pháp nào , tôi quyết định LayoutManagersử dụng addView(View view, int position)phương thức của và thêm chế độ xem tiêu đề của tôi ở vị trí đầu tiên để hoạt động như một tiêu đề.

Aa và đây là lúc mọi thứ trở nên xấu hơn:

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)

Sau nhiều lần NullPointerExceptionscố gắng gọi addView(View view)vào các thời điểm khác nhau của quá trình tạo Hoạt động (cũng đã thử thêm chế độ xem sau khi mọi thứ được thiết lập, thậm chí cả dữ liệu của Bộ điều hợp), tôi nhận ra rằng tôi không biết liệu đây có phải là cách phù hợp để thực hiện không (và nó trông không giống).

Tái bút: Ngoài ra, một giải pháp có thể xử lý việc GridLayoutManagerbổ sung LinearLayoutManagersẽ thực sự được đánh giá cao!



Vấn đề là ở mã Bộ điều hợp. Nó có nghĩa là, bằng cách nào đó bạn đang trả về trình xem null trong hàm onCreateViewHolder.
Neo

Có một cách hay để thêm tiêu đề vào
StprisredGridLayout

Câu trả lời:


121

Tôi đã phải thêm chân trang vào của mình RecyclerViewvà ở đây tôi đang chia sẻ đoạn mã của mình vì tôi nghĩ nó có thể hữu ích. Vui lòng kiểm tra các nhận xét bên trong mã để hiểu rõ hơn về quy trình tổng thể.

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
        }
    }
}

Đoạn mã trên thêm chân trang vào RecyclerView. Bạn có thể kiểm tra kho lưu trữ GitHub này để kiểm tra việc triển khai thêm cả đầu trang và chân trang.


2
Hoạt động tốt. Đây phải được đánh dấu là câu trả lời đúng.
Naga Mallesh Maddali

1
Đây chính xác là những gì tôi đã làm. Nhưng điều gì sẽ xảy ra nếu tôi muốn RecyclerView của mình điều chỉnh danh sách so le? Phần tử đầu tiên (Tiêu đề) cũng sẽ được so le. :(
Neon Warge

Đây là một hướng dẫn về cách bạn có thể điền RecyclerViewđộng của mình . Bạn có thể kiểm soát từng yếu tố của mình RecyclerView. Vui lòng xem phần mã cho một dự án đang hoạt động. Hy vọng nó có thể giúp ích. github.com/comeondude/dynamic-recyclerview/wiki
Reaz Murshed

2
int getItemViewType (int position)- Trả lại kiểu xem của mục tại vị trí cho mục đích xem tái chế. Việc triển khai mặc định của phương thức này trả về 0, tạo ra giả định về một kiểu xem duy nhất cho bộ điều hợp. Không giống như ListViewbộ điều hợp, các loại không cần phải liền nhau. Cân nhắc sử dụng tài nguyên id để xác định duy nhất các loại chế độ xem mục. - Từ tài liệu. developer.android.com/reference/android/support/v7/widget/…
Reaz Murshed

3
Quá nhiều công việc thủ công cho một thứ mà mọi người luôn muốn làm. Tôi không thể tin được ...
JohnyTex

28

Rất đơn giản để giải quyết !!

Tôi không thích ý tưởng có logic bên trong bộ điều hợp là một loại chế độ xem khác vì mỗi lần nó kiểm tra loại chế độ xem trước khi trả lại chế độ xem. Giải pháp dưới đây tránh kiểm tra thêm.

Chỉ cần thêm chế độ xem tiêu đề LinearLayout (dọc) + chế độ xem tái chế + chế độ xem chân trang bên trong android.support.v4.widget.NestedScrollView .

Kiểm tra cái này:

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

Thêm dòng mã này để cuộn mượt mà

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

Điều này sẽ làm mất tất cả hiệu suất RV và RV sẽ cố gắng sắp xếp tất cả những người có chế độ xem bất kể layout_heightRV là gì

Được đề xuất sử dụng cho danh sách kích thước nhỏ như ngăn điều hướng hoặc cài đặt, v.v.


1
Làm việc cho tôi khá đơn giản
Hitesh Sahu

1
Đây là một cách rất đơn giản để thêm đầu trang và chân trang vào chế độ xem tái chế khi đầu trang và chân trang không phải lặp lại trong danh sách.
user1841702

34
Đây là một cách rất đơn giản làm mất đi tất cả những lợi thế RecyclerViewmang lại - bạn mất đi việc tái chế thực tế và sự tối ưu mà nó mang lại.
Marcin Koziński

1
Tôi đã thử mã này, di chuyển không hoạt động đúng ... nó trở nên di chuyển quá chậm .. plz đề nghị nếu có thể làm điều gì đó cho rằng
Nibha Jain

2
việc sử dụng chế độ xem cuộn lồng nhau sẽ khiến chế độ xem tái chế lưu vào bộ nhớ cache tất cả chế độ xem và nếu kích thước chế độ xem trình tái chế nhiều hơn thì thời gian cuộn và tải sẽ tăng lên. Tôi đề nghị không sử dụng mã này
Tushar Saha

25

Tôi đã gặp vấn đề tương tự trên Lollipop và đã tạo hai cách tiếp cận để quấn Recyclerviewbộ điều hợp. Một cái khá dễ sử dụng, nhưng tôi không chắc nó sẽ hoạt động như thế nào với một tập dữ liệu đang thay đổi. Bởi vì nó bao bọc bộ điều hợp của bạn và bạn cần đảm bảo mình gọi các phương thức như notifyDataSetChangedtrên đúng đối tượng bộ điều hợp.

Cái khác không nên gặp vấn đề như vậy. Chỉ cần để bộ điều hợp thông thường của bạn mở rộng lớp, triển khai các phương thức trừu tượng và bạn đã sẵn sàng. Và chúng đây:

gists

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

}

Phản hồi và dĩa được đánh giá cao. Tôi sẽ tự sử dụng HeaderRecyclerViewAdapterV2và phát triển, thử nghiệm và đăng các thay đổi trong tương lai.

CHỈNH SỬA : @OvidiuLatcu Có, tôi đã gặp một số vấn đề. Trên thực tế, tôi đã ngừng bù đắp Tiêu đề một cách ngầm định position - (useHeader() ? 1 : 0)và thay vào đó, tôi đã tạo một phương thức công khai int offsetPosition(int position)cho nó. Bởi vì nếu bạn đặt OnItemTouchListenertrên Recyclerview, bạn có thể chặn lần chạm, lấy tọa độ x, y của lần chạm, tìm chế độ xem con và sau đó gọi recyclerView.getChildPosition(...)và bạn sẽ luôn nhận được vị trí không bị lệch trong bộ điều hợp! Đây là một đoạn ngắn trong Mã RecyclerView, tôi không thấy có phương pháp dễ dàng để khắc phục điều này. Đây là lý do tại sao bây giờ tôi bù đắp các vị trí rõ ràng khi tôi cần bằng mã của riêng mình .


có vẻ tốt! có bất kỳ rắc rối với nó? hoặc chúng ta có thể sử dụng nó một cách an toàn? : D
Ovidiu Latcu

1
@OvidiuLatcu xem bài đăng
SEB

Trong những triển khai này, có vẻ như bạn đã giả định số lượng đầu trang và chân trang chỉ là 1?
rishabhmhjn

@seb Phiên bản 2 hoạt động như một sự quyến rũ !! điều duy nhất tôi cần sửa đổi là điều kiện để có được footer trên cả hai phương thức onBindViewHolder và getItemViewType. Vấn đề là nếu bạn nhận được vị trí bằng cách sử dụng position == getBasicItemCount (), nó sẽ không trả về true cho vị trí cuối cùng thực tế, mà là vị trí cuối cùng - 1. Cuối cùng, nó đã đặt FooterView ở đó (không phải ở dưới cùng). Chúng tôi đã sửa lỗi thay đổi điều kiện thành vị trí == getBasicItemCount () + 1 và nó hoạt động tốt!
mmark

@seb phiên bản 2 hoạt động quá tuyệt vời. cám ơn rất nhiều. tôi đang sử dụng nó. một điều nhỏ tôi đề nghị là thêm từ khóa 'cuối cùng' cho chức năng ghi đè.
RyanShao

10

Tôi chưa thử điều này, nhưng tôi chỉ cần thêm 1 (hoặc 2, nếu bạn muốn cả đầu trang và chân trang) vào số nguyên được trả về bởi getItemCount trong bộ điều hợp của bạn. Sau đó, bạn có thể ghi đè getItemViewTypetrong bộ điều hợp của mình để trả về một số nguyên khác khi i==0: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html#getItemViewType(int)

createViewHoldersau đó được chuyển vào số nguyên mà bạn trả về getItemViewType, cho phép bạn tạo hoặc định cấu hình trình giữ chế độ xem khác nhau cho chế độ xem tiêu đề: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html# createViewHolder (android.view.ViewGroup , int)

Đừng quên trừ một từ số nguyên vị trí được chuyển đến bindViewHolder.


Tôi không nghĩ đó là công việc của người tái chế. Công việc Recyclerviews chỉ đơn giản là tái chế các lượt xem. Viết một trình quản lý bố cục với triển khai đầu trang hoặc chân trang là cách tốt nhất.
IZI_Shadow_IZI

Cảm ơn @IanNewson đã trả lời. Đầu tiên, giải pháp này dường như đang hoạt động, ngay cả khi chỉ sử dụng getItemViewType(int position) { return position == 0 ? 0 : 1; }( RecyclerViewkhông có getViewTypeCount()phương thức). Mặt khác, tôi đồng ý với @IZI_Shadow_IZI, tôi thực sự có cảm giác LayoutManager nên là người xử lý những việc này. Bất kỳ ý tưởng nào khác?
MathieuMaree

@VieuMa có lẽ bạn đều đúng, nhưng tôi không biết làm thế nào để làm điều đó hiện tại và tôi khá chắc chắn rằng giải pháp của mình sẽ hoạt động. Một giải pháp không tối ưu tốt hơn là không có giải pháp, đó là những gì bạn đã có trước đây.
Ian Newson

@VieuMa cũng vậy, việc trừu tượng hóa đầu trang và chân trang vào bộ điều hợp có nghĩa là nó sẽ xử lý cả hai loại bố cục được yêu cầu, viết trình quản lý bố cục của riêng bạn có nghĩa là thực hiện lại cho cả hai loại bố cục.
Ian Newson

chỉ cần tạo một bộ điều hợp bao bọc bất kỳ bộ điều hợp nào và thêm hỗ trợ cho chế độ xem tiêu đề ở chỉ mục 0. HeaderView trong listview tạo ra rất nhiều trường hợp cạnh và giá trị gia tăng là tối thiểu do đó là một vấn đề dễ giải quyết bằng cách sử dụng bộ điều hợp trình bao bọc.
yigit

9

Bạn có thể sử dụng thư viện GitHub này cho phép thêm Header và / hoặc Footer trong RecyclerView của mình theo cách đơn giản nhất có thể.

Bạn cần thêm thư viện HFRecyclerView trong dự án của mình hoặc bạn cũng có thể lấy nó từ Gradle:

compile 'com.mikhaellopez:hfrecyclerview:1.0.0'

Đây là kết quả trong hình ảnh:

Xem trước

BIÊN TẬP:

Nếu bạn chỉ muốn thêm một lề ở trên cùng và / hoặc dưới cùng với thư viện này: SimpleItemDecoration :

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

Thư viện này thêm chế độ xem đúng cách trên tiêu đề trong LinearLayoutManager nhưng tôi muốn đặt chế độ xem làm tiêu đề trong GridLayoutManager, chiếm toàn bộ chiều rộng của màn hình. Có thể nó là có thể với thư viện này.
ved

Không, thư viện này cho phép bạn thay đổi phần tử đầu tiên và cuối cùng của một RecyclerView lúc thích ứng (RecyclerView.Adapter). Bạn có thể sử dụng bộ điều hợp này cho GridView mà không gặp sự cố. Vì vậy, tôi nghĩ rằng thư viện này cho phép làm những gì bạn muốn.
lopez.mikhael Ngày

6

Tôi đã kết thúc việc triển khai bộ điều hợp của riêng mình để bọc bất kỳ bộ điều hợp nào khác và cung cấp các phương pháp để thêm chế độ xem đầu trang và chân trang.

Đã tạo ý chính tại đây: HeaderViewRecyclerAdapter.java

Tính năng chính mà tôi muốn là một giao diện tương tự như ListView, vì vậy tôi muốn có thể tăng các chế độ xem trong Fragment của mình và thêm chúng vào RecyclerViewtrong onCreateView. Điều này được thực hiện bằng cách tạo một HeaderViewRecyclerAdapterchuyển đổi bộ điều hợp được bọc và gọi addHeaderViewaddFooterViewchuyển các chế độ xem tăng cao của bạn. Sau đó đặt HeaderViewRecyclerAdapterphiên bản làm bộ điều hợp trên RecyclerView.

Một yêu cầu bổ sung là tôi cần có thể dễ dàng hoán đổi các bộ điều hợp trong khi vẫn giữ đầu trang và chân trang, tôi không muốn có nhiều bộ điều hợp với nhiều phiên bản của các đầu trang và chân trang này. Vì vậy, bạn có thể gọi setAdapterđể thay đổi bộ điều hợp được bọc, giữ nguyên đầu trang và chân trang, với RecyclerViewthông báo về sự thay đổi.


3

cách "giữ cho nó đơn giản ngu ngốc" của tôi ... nó lãng phí một số tài nguyên, tôi biết, nhưng tôi không quan tâm vì mã của tôi luôn đơn giản nên ... 1) thêm chân trang với khả năng hiển thị GONE vào item_layout của bạn

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

2) sau đó đặt nó hiển thị trên mục cuối cùng

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

làm ngược lại cho tiêu đề


1

Dựa trên giải pháp của @ seb, tôi đã tạo một lớp con của RecyclerView.Adapter hỗ trợ một số lượng đầu trang và chân trang tùy ý.

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

Mặc dù nó có vẻ là một giải pháp, tôi cũng nghĩ rằng điều này nên được quản lý bởi LayoutManager. Thật không may, tôi cần nó ngay bây giờ và tôi không có thời gian để triển khai StprisredGridLayoutManager từ đầu (thậm chí không mở rộng từ nó).

Tôi vẫn đang thử nghiệm nó, nhưng bạn có thể dùng thử nếu muốn. Vui lòng cho tôi biết nếu bạn tìm thấy bất kỳ vấn đề nào với nó.


1

Bạn có thể sử dụng viewtype để giải quyết vấn đề này, đây là bản demo của tôi: https://github.com/yefengfreedom/RecyclerViewWithHeaderFooterLoadingEmptyViewErrorView

  1. bạn có thể xác định một số chế độ hiển thị chế độ xem tái chế:

    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. ghi đè mothod 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. ghi đè phương thức getItemCount

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

4. ghi đè phương thức onCreateViewHolder. tạo chế độ xem bằng 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. Ghi đè phương thức onBindViewHolder. liên kết dữ liệu bằng 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());
        }
    }
}

điều gì sẽ xảy ra nếu liên kết của bạn sẽ bị hỏng trong tương lai?
Gopal Singh Sirvi,

Tốt question.I sẽ sửa câu trả lời của tôi và gửi mã của tôi ở đây
Yefeng

1

Bạn có thể sử dụng thư viện SecuredRecyclerViewAdapter để nhóm các mục của mình thành các phần và thêm tiêu đề vào mỗi phần, như trong hình ảnh bên dưới:

nhập mô tả hình ảnh ở đây

Trước tiên, bạn tạo lớp phần của mình:

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

Sau đó, bạn thiết lập RecyclerView với các phần của mình và thay đổi SpanSize của các tiêu đề bằng 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

Tôi biết mình đến muộn, nhưng chỉ gần đây tôi mới có thể triển khai "addHeader" như vậy cho Bộ điều hợp. Trong tôi FlexibleAdapter dự án bạn có thể gọi setHeadervề một Sectionable mục, sau đó bạn gọishowAllHeaders . Nếu bạn chỉ cần 1 tiêu đề thì mục đầu tiên phải có tiêu đề. Nếu bạn xóa mục này, thì tiêu đề sẽ tự động được liên kết với mục tiếp theo.

Thật không may, chân trang chưa được che (chưa).

FlexAdapter cho phép bạn làm được nhiều việc hơn là tạo tiêu đề / phần. Bạn thực sự nên xem: https://github.com/davideas/FlexibleAdapter .


0

Tôi sẽ chỉ thêm một giải pháp thay thế cho tất cả các triển khai HeaderRecyclerViewAdapter đó. CompoundAdapter:

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

Đây là một cách tiếp cận linh hoạt hơn, vì bạn có thể tạo một Nhóm điều hợp từ Bộ điều hợp. Đối với ví dụ về tiêu đề, hãy sử dụng bộ điều hợp của bạn như nó vốn có, cùng với bộ điều hợp chứa một mục cho tiêu đề:

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

recyclerView.setAdapter(adapterGroup);

Nó khá đơn giản và dễ đọc. Bạn có thể triển khai bộ điều hợp phức tạp hơn một cách dễ dàng bằng cách sử dụng cùng một nguyên tắc.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.