Android 5.0 - เพิ่มส่วนหัว / ส่วนท้ายให้กับ RecyclerView


123

ฉันใช้เวลาสักครู่ในการพยายามหาวิธีเพิ่มส่วนหัวให้กับ a RecyclerViewไม่สำเร็จ

นี่คือสิ่งที่ฉันได้รับจนถึงตอนนี้:

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

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

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

   ...
}

LayoutManagerดูเหมือนว่าจะจัดการวัตถุจำหน่ายของRecyclerViewรายการ ขณะที่ผมไม่สามารถหาใด ๆaddHeaderView(View view)วิธีฉันตัดสินใจที่จะไปกับLayoutManager's addView(View view, int position)วิธีการและเพื่อเพิ่มมุมมองที่ส่วนหัวของฉันในตำแหน่งแรกที่จะทำหน้าที่เป็นส่วนหัว

และนี่คือสิ่งที่น่าเกลียด:

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

หลังจากNullPointerExceptionsพยายามโทรหลายครั้งaddView(View view)ในช่วงเวลาต่างๆของการสร้างกิจกรรม (ลองเพิ่มมุมมองเมื่อตั้งค่าทุกอย่างแล้วแม้กระทั่งข้อมูลของอะแดปเตอร์) ฉันรู้ว่าฉันไม่รู้ว่านี่เป็นวิธีที่ถูกต้องหรือไม่ (และ ดูไม่เป็น)

PS: นอกจากนี้วิธีแก้ปัญหาที่สามารถจัดการได้GridLayoutManagerนอกเหนือจากที่LinearLayoutManagerจะได้รับการชื่นชมจริงๆ!



ปัญหาอยู่ในรหัสอะแดปเตอร์ หมายความว่าคุณกำลังคืนค่าตัวดูว่างในฟังก์ชัน onCreateViewHolder
นีโอ

มีวิธีที่ดีในการเพิ่มส่วนหัวใน
StaggeredGridLayout

คำตอบ:


121

ฉันต้องเพิ่มส่วนท้ายในRecyclerViewและที่นี่ฉันกำลังแบ่งปันข้อมูลโค้ดของฉันเพราะฉันคิดว่ามันอาจมีประโยชน์ โปรดตรวจสอบความคิดเห็นภายในโค้ดเพื่อความเข้าใจที่ดีขึ้นเกี่ยวกับขั้นตอนโดยรวม

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

import java.util.ArrayList;

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

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

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

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

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

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

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

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

        View v;

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

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

        NormalViewHolder vh = new NormalViewHolder(v);

        return vh;
    }

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

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

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

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

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

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

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

    // Now define getItemViewType of your own.

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

        return super.getItemViewType(position);
    }

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

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

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

ข้อมูลโค้ดด้านบนจะเพิ่มส่วนท้ายให้กับไฟล์RecyclerView. คุณสามารถตรวจสอบที่เก็บ GitHub นี้เพื่อตรวจสอบการใช้งานการเพิ่มทั้งส่วนหัวและส่วนท้าย


2
ทำได้ดี. ควรทำเครื่องหมายว่าเป็นคำตอบที่ถูกต้อง
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

แก้ง่ายมาก !!

ฉันไม่ชอบแนวคิดที่จะมีตรรกะภายในอะแดปเตอร์เป็นประเภทมุมมองอื่นเพราะทุกครั้งที่ตรวจสอบประเภทมุมมองก่อนที่จะส่งคืนมุมมอง โซลูชันด้านล่างนี้หลีกเลี่ยงการตรวจสอบเพิ่มเติม

เพียงแค่เพิ่ม LinearLayout ส่วนหัว (แนวตั้ง) มุมมองมุมมอง + + recyclerview ท้ายภายในandroid.support.v4.widget.NestedScrollView

ลองดู:

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

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

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

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

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

เพิ่มบรรทัดของรหัสนี้เพื่อการเลื่อนที่ราบรื่น

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

สิ่งนี้จะสูญเสียประสิทธิภาพของ RV ทั้งหมดและ RV จะพยายามจัดวางผู้ถือมุมมองทั้งหมดโดยไม่คำนึงถึงlayout_heightRV

แนะนำให้ใช้สำหรับรายการขนาดเล็กเช่นลิ้นชักการนำทางหรือการตั้งค่าเป็นต้น


1
ทำงานให้ฉันค่อนข้างง่าย
Hitesh Sahu

1
นี่เป็นวิธีง่ายๆในการเพิ่มส่วนหัวและส่วนท้ายให้กับมุมมองของผู้รีไซเคิลเมื่อส่วนหัวและส่วนท้ายไม่ต้องทำซ้ำภายในรายการ
user1841702

34
นี่เป็นวิธีง่ายๆในการสูญเสียข้อดีทั้งหมดที่RecyclerViewนำมา - คุณสูญเสียการรีไซเคิลจริงและการเพิ่มประสิทธิภาพที่ได้รับ
Marcin Koziński

1
ฉันลองใช้รหัสนี้การเลื่อนไม่ทำงานอย่างถูกต้อง ... มันเลื่อนช้าเกินไป .. ขอแนะนำว่าสามารถทำอะไรได้บ้าง
Nibha Jain

2
การใช้ scrollview ที่ซ้อนกันจะทำให้ Recyclerview แคชมุมมองทั้งหมดและหากขนาดของมุมมองรีไซเคิลมีการเลื่อนมากขึ้นและเวลาในการโหลดจะเพิ่มขึ้น ฉันไม่แนะนำให้ใช้รหัสนี้
Tushar Saha

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 Code ฉันไม่เห็นวิธีง่ายๆในการเอาชนะสิ่งนี้ นี่คือเหตุผลที่ตอนนี้ฉันหักล้างตำแหน่งที่ชัดเจนเมื่อฉันต้องการด้วยรหัสของฉันเอง


ดูดี ! มีปัญหากับมันหรือไม่? หรือเราสามารถใช้ได้อย่างปลอดภัย? : D
Ovidiu Latcu

1
@OvidiuLatcu ดูโพสต์
ก.ย.

ในการใช้งานเหล่านี้ดูเหมือนว่าคุณได้สมมติว่าจำนวนส่วนหัวและส่วนท้ายเป็นเพียง 1 รายการต่อกัน?
rishabhmhjn

@seb Version 2งานอย่างจับใจ !! สิ่งเดียวที่ฉันต้องแก้ไขคือเงื่อนไขในการรับส่วนท้ายทั้งบนเมธอด onBindViewHolder และ getItemViewType ปัญหาคือถ้าคุณได้รับตำแหน่งโดยใช้ตำแหน่ง == getBasicItemCount () มันจะไม่คืนค่าจริงสำหรับตำแหน่งสุดท้ายจริง แต่ตำแหน่งสุดท้าย - 1 มันจบลงด้วยการวาง FooterView ที่นั่น (ไม่ใช่ที่ด้านล่าง) เราแก้ไขโดยเปลี่ยนเงื่อนไขเป็น position == 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 คุณอาจจะถูกทั้งคู่ แต่ตอนนี้ฉันไม่รู้ว่าจะทำอย่างไรและฉันค่อนข้างมั่นใจว่าโซลูชันของฉันจะได้ผล โซลูชันที่ไม่เหมาะสมดีกว่าไม่มีวิธีแก้ปัญหาซึ่งเป็นสิ่งที่คุณเคยมีมาก่อน
Ian Newson

@VieuMa การแยกส่วนหัวและส่วนท้ายลงในอะแดปเตอร์หมายความว่าควรจัดการเลย์เอาต์ทั้งสองประเภทที่ร้องขอการเขียนตัวจัดการเลย์เอาต์ของคุณเองหมายถึงการนำไปใช้ใหม่สำหรับเลย์เอาต์ทั้งสองประเภท
Ian Newson

เพียงแค่สร้างอะแด็ปเตอร์ที่ห่ออะแดปเตอร์ใด ๆ และเพิ่มการสนับสนุนสำหรับมุมมองส่วนหัวที่ดัชนี 0 HeaderView ใน listview จะสร้าง edge case จำนวนมากและค่าที่เพิ่มจะน้อยที่สุดเนื่องจากเป็นปัญหาที่ง่ายในการแก้ไขโดยใช้อะแดปเตอร์ wrapper
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 ดังนั้นฉันต้องการที่จะสามารถที่จะขยายมุมมองในส่วนของฉันและเพิ่มเข้าไปในRecyclerView onCreateViewทำได้โดยการสร้างการHeaderViewRecyclerAdapterส่งผ่านอะแดปเตอร์ที่จะพันและโทรaddHeaderViewและaddFooterViewส่งผ่านมุมมองที่สูงเกินจริง จากนั้นตั้งค่าHeaderViewRecyclerAdapterอินสแตนซ์เป็นอะแดปเตอร์บนRecyclerView.

ข้อกำหนดเพิ่มเติมคือฉันต้องสามารถเปลี่ยนอะแดปเตอร์ได้อย่างง่ายดายในขณะที่ยังคงรักษาส่วนหัวและส่วนท้ายไว้ฉันไม่ต้องการมีอะแดปเตอร์หลายตัวที่มีหลายอินสแตนซ์ของส่วนหัวและส่วนท้ายเหล่านี้ ดังนั้นคุณสามารถโทรsetAdapterขอเปลี่ยนอะแดปเตอร์ที่ห่อไว้โดยปล่อยให้ส่วนหัวและส่วนท้ายเหมือนเดิมพร้อมกับRecyclerViewได้รับแจ้งการเปลี่ยนแปลง


3

วิธี "ทำให้มันง่ายโง่" ของฉัน ... มันเสียทรัพยากรไปบ้างฉันรู้ แต่ฉันไม่สนใจเพราะรหัสของฉันเรียบง่ายดังนั้น ... 1) เพิ่มส่วนท้ายที่มีการเปิดเผยไปยัง 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 อะแดปเตอร์ที่รองรับส่วนหัวและส่วนท้ายตามจำนวนที่กำหนด

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. แทนที่ 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. แทนที่เมธอด 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());
        }
    }
}

จะเกิดอะไรขึ้นหากลิงค์ของคุณเสียในอนาคต?
Gopal Singh 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 ด้วยส่วนของคุณและเปลี่ยน 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ในSectionableshowAllHeadersรายการแล้วคุณเรียก หากคุณต้องการเพียง 1 ส่วนหัวรายการแรกควรมีส่วนหัว หากคุณลบรายการนี้ส่วนหัวจะเชื่อมโยงกับรายการถัดไปโดยอัตโนมัติ

ขออภัยส่วนท้ายยังไม่ครอบคลุม (ยัง)

FlexibleAdapter ช่วยให้คุณทำอะไรได้มากกว่าสร้างส่วนหัว / ส่วน คุณควรมีลักษณะ: https://github.com/davideas/FlexibleAdapter


0

ฉันจะเพิ่มทางเลือกให้กับการใช้งาน HeaderRecyclerViewAdapter ทั้งหมด CompoundAdapter:

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

เป็นแนวทางที่ยืดหยุ่นกว่าเนื่องจากคุณสามารถสร้าง AdapterGroup จาก Adapters ได้ สำหรับตัวอย่างส่วนหัวให้ใช้อะแดปเตอร์ของคุณตามที่เป็นอยู่พร้อมกับอะแดปเตอร์ที่มีหนึ่งรายการสำหรับส่วนหัว:

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

recyclerView.setAdapter(adapterGroup);

มันค่อนข้างง่ายและอ่านได้ คุณสามารถใช้อะแดปเตอร์ที่ซับซ้อนมากขึ้นได้อย่างง่ายดายโดยใช้หลักการเดียวกัน


0

recyclerview:1.2.0แนะนำคลาสConcatAdapterซึ่งเชื่อมต่ออะแด็ปเตอร์หลายตัวเข้าด้วยกัน ดังนั้นจึงอนุญาตให้สร้างอะแดปเตอร์ส่วนหัว / ส่วนท้ายแยกกันและนำมาใช้ซ้ำในหลายรายการ

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

ลองดูที่การประกาศบทความ ConcatAdapterมันมีตัวอย่างวิธีการแสดงความคืบหน้าในการโหลดในส่วนหัวและส่วนท้ายใช้

ในขณะที่ฉันโพสต์คำตอบนี้เวอร์ชัน1.2.0ของไลบรารีอยู่ในขั้นอัลฟ่าดังนั้น API อาจมีการเปลี่ยนแปลง คุณสามารถตรวจสอบสถานะได้ที่นี่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.