ความสูงของมุมมอง Recycler ที่ซ้อนกันไม่ได้ห่อเนื้อหา


176

ฉันมีแอปพลิเคชั่นที่จัดการคอลเล็กชันหนังสือ (เช่นเพลย์ลิสต์)

ฉันต้องการแสดงรายการคอลเลกชันที่มีแนวตั้ง RecyclerView และในแต่ละแถวรายการของหนังสือในแนวนอน RecyclerView

เมื่อฉันตั้งค่า layout_height ของ RecyclerView แนวนอนด้านในเป็น 300dp จะแสดงอย่างถูกต้อง แต่เมื่อฉันตั้งค่าเป็น wrap_content จะไม่แสดงอะไรเลย ฉันต้องใช้ wrap_content เพราะฉันต้องการที่จะสามารถเปลี่ยนการจัดการรูปแบบโดยทางโปรแกรมเพื่อสลับระหว่างการแสดงผลในแนวตั้งและแนวนอน

ป้อนคำอธิบายรูปภาพที่นี่

คุณรู้หรือไม่ว่าฉันทำอะไรผิด

เลย์เอาต์ของฉัน:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@color/white">

    <com.twibit.ui.view.CustomSwipeToRefreshLayout
        android:id="@+id/swipe_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

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

            <android.support.v7.widget.RecyclerView
                android:id="@+id/shelf_collection_listview"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:paddingTop="10dp"/>

        </LinearLayout>

    </com.twibit.ui.view.CustomSwipeToRefreshLayout>
</LinearLayout>

เค้าโครงองค์ประกอบคอลเลกชัน:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:orientation="vertical">

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:background="#FFF">

        <!-- Simple Header -->

    </RelativeLayout>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="@string/empty_collection"
            android:id="@+id/empty_collection_tv"
            android:visibility="gone"
            android:gravity="center"/>

        <android.support.v7.widget.RecyclerView
            android:id="@+id/collection_book_listview"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/> <!-- android:layout_height="300dp" -->

    </FrameLayout>

</LinearLayout>

รายการหนังสือ

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="180dp"
              android:layout_height="220dp"
              android:layout_gravity="center">

        <ImageView
            android:id="@+id/shelf_item_cover"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:maxWidth="150dp"
            android:maxHeight="200dp"
            android:src="@drawable/placeholder"
            android:contentDescription="@string/cover"
            android:adjustViewBounds="true"
            android:background="@android:drawable/dialog_holo_light_frame"/>

</FrameLayout>

นี่คืออะแดปเตอร์สะสมของฉัน:

private class CollectionsListAdapter extends RecyclerView.Adapter<CollectionsListAdapter.ViewHolder> {
    private final String TAG = CollectionsListAdapter.class.getSimpleName();
    private Context mContext;

    // Create the ViewHolder class to keep references to your views
    class ViewHolder extends RecyclerView.ViewHolder {

        private final TextView mHeaderTitleTextView;
        private final TextView mHeaderCountTextView;

        private final RecyclerView mHorizontalListView;
        private final TextView mEmptyTextView;

        public ViewHolder(View view) {
            super(view);

            mHeaderTitleTextView = (TextView) view.findViewById(R.id.collection_header_tv);
            mHeaderCountTextView = (TextView) view.findViewById(R.id.collection_header_count_tv);

            mHorizontalListView = (RecyclerView) view.findViewById(R.id.collection_book_listview);
            mEmptyTextView = (TextView) view.findViewById(R.id.empty_collection_tv);
        }
    }


    public CollectionsListAdapter(Context context) {
        mContext = context;
    }


    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int i) {
        Log.d(TAG, "CollectionsListAdapter.onCreateViewHolder(" + parent.getId() + ", " + i + ")");
        // Create a new view by inflating the row item xml.
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.shelf_collection, parent, false);

        // Set the view to the ViewHolder
        ViewHolder holder = new ViewHolder(v);

        holder.mHorizontalListView.setHasFixedSize(false);
        holder.mHorizontalListView.setHorizontalScrollBarEnabled(true);

        // use a linear layout manager
        LinearLayoutManager mLayoutManager = new LinearLayoutManager(mContext);
        mLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
        holder.mHorizontalListView.setLayoutManager(mLayoutManager);

        return holder;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int i) {
        Log.d(TAG, "CollectionsListAdapter.onBindViewHolder(" + holder.getPosition() + ", " + i + ")");

        Collection collection = mCollectionList.get(i);
        Log.d(TAG, "Collection : " + collection.getLabel());

        holder.mHeaderTitleTextView.setText(collection.getLabel());
        holder.mHeaderCountTextView.setText("" + collection.getBooks().size());

        // Create an adapter if none exists
        if (!mBookListAdapterMap.containsKey(collection.getCollectionId())) {
            mBookListAdapterMap.put(collection.getCollectionId(), new BookListAdapter(getActivity(), collection));
        }

        holder.mHorizontalListView.setAdapter(mBookListAdapterMap.get(collection.getCollectionId()));

    }

    @Override
    public int getItemCount() {
        return mCollectionList.size();
    }
}

และสุดท้ายอะแดปเตอร์หนังสือ:

private class BookListAdapter extends RecyclerView.Adapter<BookListAdapter.ViewHolder> implements View.OnClickListener {
    private final String TAG = BookListAdapter.class.getSimpleName();

    // Create the ViewHolder class to keep references to your views
    class ViewHolder extends RecyclerView.ViewHolder {
        public ImageView mCoverImageView;

        public ViewHolder(View view) {
            super(view);
            mCoverImageView = (ImageView) view.findViewById(R.id.shelf_item_cover);
        }
    }

    @Override
    public void onClick(View v) {
        BookListAdapter.ViewHolder holder = (BookListAdapter.ViewHolder) v.getTag();
        int position = holder.getPosition();
        final Book book = mCollection.getBooks().get(position);

        // Click on cover image
        if (v.getId() == holder.mCoverImageView.getId()) {
            downloadOrOpenBook(book);
            return;
        }
    }

    private void downloadOrOpenBook(final Book book) {
        // do stuff
    }

    private Context mContext;
    private Collection mCollection;

    public BookListAdapter(Context context, Collection collection) {
        Log.d(TAG, "BookListAdapter(" + context + ", " + collection + ")");
        mCollection = collection;
        mContext = context;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int i) {
        Log.d(TAG, "onCreateViewHolder(" + parent.getId() + ", " + i + ")");
        // Create a new view by inflating the row item xml.
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.shelf_grid_item, parent, false);

        // Set the view to the ViewHolder
        ViewHolder holder = new ViewHolder(v);
        holder.mCoverImageView.setOnClickListener(BookListAdapter.this); // Download or Open

        holder.mCoverImageView.setTag(holder);

        return holder;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int i) {
        Log.d(TAG, "onBindViewHolder(" + holder.getPosition() + ", " + i + ")");

        Book book = mCollection.getBooks().get(i);

        ImageView imageView = holder.mCoverImageView;
        ImageLoader.getInstance().displayImage(book.getCoverUrl(), imageView);
    }

    @Override
    public int getItemCount() {
        return mCollection.getBooks().size();
    }
}

ทำไมคุณต้องใช้ wrap_content เพื่อเปลี่ยน layoutmanager ระหว่างแนวตั้งและแนวนอน
AmaJayJB

1
หากฉันตั้งค่า layout_height เป็นค่าเฉพาะ (ให้บอกความสูงขององค์ประกอบสำหรับตัวจัดการเลย์เอาต์แนวนอน) มันจะแสดงเฉพาะบรรทัดแรกของรายการด้วยตัวจัดการเลย์เอาต์แนวตั้ง
Twibit

แต่ recyclerView มี scrollView ในตัวดังนั้นคุณสามารถเลื่อนดูรายการต่าง ๆ ... หรือว่าฉันพลาดจุดนี่? ขออภัยที่พยายามเข้าใจ
AmaJayJB

ฉันต้องการเลื่อนดูรายการสะสม แต่ไม่ใช่รายชื่อหนังสือภายใน (ยกเว้นแนวนอน) บางอย่างเช่น ExpandableListView
Twibit

2
ฉันเผชิญคำถามเดียวกันกับคุณตอนนี้คุณแก้ไขหรือไม่?
VinceStyling

คำตอบ:


46

ปรับปรุง

ปัญหาต่าง ๆ ที่เกี่ยวข้องกับคุณลักษณะนี้ในรุ่น 23.2.0 ได้รับการแก้ไขใน 23.2.1 อัปเดตเป็นปัญหานั้นแทน

ด้วยการเปิดตัว Support Library เวอร์ชัน 23.2 RecyclerViewตอนนี้สนับสนุน!

อัปเดตbuild.gradleเป็น:

compile 'com.android.support:recyclerview-v7:23.2.1'

หรือรุ่นอื่น ๆ นอกเหนือจากนั้น

ข่าวประชาสัมพันธ์ฉบับนี้นำเสนอคุณสมบัติใหม่ที่น่าตื่นเต้นให้กับ LayoutManager API: การวัดอัตโนมัติ! วิธีนี้ช่วยให้ RecyclerView ปรับขนาดตัวเองตามขนาดของเนื้อหา ซึ่งหมายความว่าก่อนหน้านี้สถานการณ์ที่ไม่พร้อมใช้งานเช่นการใช้ WRAP_CONTENT สำหรับมิติของ RecyclerView นั้นเป็นไปได้ คุณจะพบว่า LayoutManagers ในตัวทั้งหมดสนับสนุนการวัดอัตโนมัติ

สามารถปิดใช้งานผ่านทางsetAutoMeasurementEnabled()ถ้าจำเป็น ตรวจสอบในรายละเอียดที่นี่


4
ระวัง! นี่คือข้อบกพร่องใหม่: stackoverflow.com/questions/35619022/ …
เดนิสเน

ที่อยู่ข้อผิดพลาดดังกล่าวข้างต้น?
razzledazzle

1
สิ่งนี้ไม่ทำงานอย่างถูกต้องเห็นพื้นที่ว่างระหว่างแต่ละรายการ
RAHULRSANNIDHI

2
โปรดระวังความสูงของเลย์เอาต์หลักที่มีมุมมองรีไซเลอร์ลูกต้องเป็นเนื้อหาที่ล้อมรอบ
RAHULRSANNIDHI

2
เวอร์ชัน23.2.1ของ RecyclerView ดูเหมือนว่าจะมีการแก้ไขข้อบกพร่องหลายอย่าง ทำงานได้ดีมากสำหรับเรา ถ้าไม่คุณสามารถลองด้วย24.0.0-alpha1
Joaquin Iurchuk

124

@ user2302510 โซลูชันทำงานได้ไม่ดีอย่างที่คุณคาดไว้ วิธีแก้ปัญหาเต็มรูปแบบสำหรับทั้งการหมุนและการเปลี่ยนแปลงข้อมูลแบบไดนามิกคือ:

public class MyLinearLayoutManager extends LinearLayoutManager {

    public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout)    {
        super(context, orientation, reverseLayout);
    }

    private int[] mMeasuredDimension = new int[2];

    @Override
    public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
                          int widthSpec, int heightSpec) {
        final int widthMode = View.MeasureSpec.getMode(widthSpec);
        final int heightMode = View.MeasureSpec.getMode(heightSpec);
        final int widthSize = View.MeasureSpec.getSize(widthSpec);
        final int heightSize = View.MeasureSpec.getSize(heightSpec);
        int width = 0;
        int height = 0;
        for (int i = 0; i < getItemCount(); i++) {
            measureScrapChild(recycler, i,
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    mMeasuredDimension);

            if (getOrientation() == HORIZONTAL) {
                width = width + mMeasuredDimension[0];
                if (i == 0) {
                    height = mMeasuredDimension[1];
                }
            } else {
                height = height + mMeasuredDimension[1];
                if (i == 0) {
                    width = mMeasuredDimension[0];
                }
            }
        }
        switch (widthMode) {
            case View.MeasureSpec.EXACTLY:
                width = widthSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        switch (heightMode) {
            case View.MeasureSpec.EXACTLY:
                height = heightSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        setMeasuredDimension(width, height);
    }

    private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                                   int heightSpec, int[] measuredDimension) {
        View view = recycler.getViewForPosition(position);
        if (view != null) {
            RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
            int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                    getPaddingLeft() + getPaddingRight(), p.width);
            int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                    getPaddingTop() + getPaddingBottom(), p.height);
            view.measure(childWidthSpec, childHeightSpec);
            measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
            measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
            recycler.recycleView(view);
        }
    }
}

3
โซลูชันของคุณทำงานได้ค่อนข้างดี แต่ดูเหมือนจะไม่คำนึงถึง ItemDecoration (เช่นตัวคั่น)
Twibit

11
ไม่แน่ใจว่าทำไม แต่การเลื่อนดูไม่ทำงาน ฉันลองใช้รหัสนี้ด้วยเลย์เอาต์เลื่อนแนวตั้ง ... เมื่อรายการน้อยกว่าความสูงของหน้าจอทุกอย่างทำงานได้ดี เมื่อมีรายการเกินกว่าที่จะสามารถแสดงบนหน้าจอการ srolling ไม่ทำงาน
Justin

1
สิ่งนี้ใช้ไม่ได้กับอนิเมชั่นการเปลี่ยนแปลงเนื้อหา - ขนาดของ recyclerview snaps มากกว่าการเติบโต / ย่อขนาดอย่างราบรื่น
zyamys

36
นี่คือรุ่นที่ปรับปรุงแล้วของคลาสที่ดูเหมือนว่าจะทำงานและไม่มีปัญหาวิธีแก้ไขปัญหาอื่น ๆ มี: github.com/serso/android-linear-layout-manager/blob/master/lib/ …
se.solovyev

2
ฉันใช้ recyclerview ภายใน scrollview และทำให้ recylerview ห่อเนื้อหาโดยใช้รหัสนี้ แต่การเลื่อนดูไม่ราบรื่น โซลูชันคือstackoverflow.com/a/32283439/3736955
Jemshit Iskenderov

39

โค้ดด้านบนทำงานได้ไม่ดีเมื่อคุณต้องการทำให้รายการของคุณ "wrap_content" เนื่องจากมันวัดทั้งความสูงและความกว้างของรายการด้วย MeasureSpec.UNSPECIFIED หลังจากปัญหาบางอย่างฉันได้แก้ไขโซลูชันนั้นตอนนี้รายการสามารถขยายได้ ความแตกต่างเพียงอย่างเดียวคือมันให้ความสูงหรือความกว้างของผู้ปกครอง MeasureSpec ขึ้นอยู่กับการวางแนวเค้าโครง

public class MyLinearLayoutManager extends LinearLayoutManager {

public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout)    {
    super(context, orientation, reverseLayout);
}

private int[] mMeasuredDimension = new int[2];

@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
                      int widthSpec, int heightSpec) {
    final int widthMode = View.MeasureSpec.getMode(widthSpec);
    final int heightMode = View.MeasureSpec.getMode(heightSpec);
    final int widthSize = View.MeasureSpec.getSize(widthSpec);
    final int heightSize = View.MeasureSpec.getSize(heightSpec);
    int width = 0;
    int height = 0;
    for (int i = 0; i < getItemCount(); i++) {


        if (getOrientation() == HORIZONTAL) {

            measureScrapChild(recycler, i,
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    heightSpec,
                    mMeasuredDimension);

            width = width + mMeasuredDimension[0];
            if (i == 0) {
                height = mMeasuredDimension[1];
            }
        } else {
            measureScrapChild(recycler, i,
                    widthSpec,
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    mMeasuredDimension);
            height = height + mMeasuredDimension[1];
            if (i == 0) {
                width = mMeasuredDimension[0];
            }
        }
    }
    switch (widthMode) {
        case View.MeasureSpec.EXACTLY:
            width = widthSize;
        case View.MeasureSpec.AT_MOST:
        case View.MeasureSpec.UNSPECIFIED:
    }

    switch (heightMode) {
        case View.MeasureSpec.EXACTLY:
            height = heightSize;
        case View.MeasureSpec.AT_MOST:
        case View.MeasureSpec.UNSPECIFIED:
    }

    setMeasuredDimension(width, height);
}

private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                               int heightSpec, int[] measuredDimension) {
    View view = recycler.getViewForPosition(position);
    recycler.bindViewToPosition(view, position);
    if (view != null) {
        RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
        int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                getPaddingLeft() + getPaddingRight(), p.width);
        int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                getPaddingTop() + getPaddingBottom(), p.height);
        view.measure(childWidthSpec, childHeightSpec);
        measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
        measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
        recycler.recycleView(view);
    }
}
}

ขอบคุณ! วิธีนี้ใช้ได้ผลดีที่สุดสำหรับฉัน ดูเหมือนว่า Denis จะไม่สามารถวัดได้เพียงพอและมุมมองยังคงเลื่อนได้ ดูเหมือนว่า Sinan จะประพฤติตนถูกต้องเมื่อมีลูกคนเดียว หมายเหตุ: มุมมองลูกของฉันถูกใช้wrap_content
kassim

เพื่อเปิดใช้งานการเลื่อนเมื่อมันกระทบกับขอบเขตของมุมมองผู้ปกครองฉันต้องใส่switch (heightMode) { case View.MeasureSpec.AT_MOST: if (height < heightSize) break; case View.MeasureSpec.EXACTLY: height = heightSize; case View.MeasureSpec.UNSPECIFIED: }
kassim

ทางออกที่ดีเยี่ยม มันทำงานได้อย่างสมบูรณ์แบบ ในบันทึกด้านข้างแทนที่จะใช้คอนสตรัคเตอร์ฉันเพิ่งสร้างตัวแปรอินสแตนซ์ส่วนตัวและวิธี setter / getter ที่มีค่าคงที่สุดท้ายขั้นสุดท้ายคงที่ (ในกรณีที่คนอื่นมีปัญหา) จากนั้นใช้เพื่อตรวจสอบการวางแนวผ่าน .
PGMacDesign

1
ฉันชนกับปัญหาเกี่ยวกับ Lollipop ขึ้นไป ScrollView ซึ่งทำให้ RecyclerView ทำงานอย่างไม่ถูกต้อง (ไม่มีการเหวี่ยง) เมื่อคุณพยายามเลื่อนด้านบน RecyclerView สำหรับการแก้ไขปัญหานี้คุณต้องใช้ NotScrollableRecyclerView (กำหนดเอง) และแทนที่ onInterceptTouchEvent วิธีการ onTouchEvent โดยไม่เรียกใช้การใช้งานระดับสูงของพวกเขาและคุณควรคืนค่าเท็จในทั้งสองวิธี
ultraon

งานนี้ แต่ผมไม่เข้าใจว่าทำไมมันจึงเป็นเรื่องที่ซับซ้อนโดยไม่จำเป็นที่จะเพียงแค่มีสิ่งวัดความสูงของตนหลังจากที่ความสูงของเด็กจะถูกคำนวณ ...
โจเฮอร์

22

ตัวจัดการโครงร่างที่มีอยู่ยังไม่รองรับเนื้อหาแบบตัดคำ

คุณสามารถสร้าง LayoutManager ใหม่ที่ขยายหนึ่งที่มีอยู่และแทนที่ onMeasure วิธีการวัดเนื้อหาตัด


1
นั่นไม่ใช่สิ่งที่ฉันคาดหวัง แต่ดูเหมือนจะเป็นจริง ดู: RecyclerView.LayoutManager & LinearLayoutManager
Twibit

@ yiğit, recyclerView จะรองรับการตัดเนื้อหาตามค่าเริ่มต้นหรือไม่
Sinan Kozak

3
มันขึ้นอยู่กับ LayoutManager ไม่ใช่ RecyclerView มันอยู่ในแผนงานของเรา
yigit

@yigit สิ่งนี้ไม่ได้ทำให้setHasFixedSize(boolean)ไร้ประโยชน์กับผู้จัดการเค้าโครงเริ่มต้นหรือไม่ อย่างไรก็ตามเป็นเรื่องดีที่ได้รู้ว่ามันอยู่ในแผนงาน หวังว่ามันจะออกมาเร็ว ๆ นี้
goncalossilva

ใช่เนื่องจาก LM ไม่สามารถมีขนาดคงที่ได้ถ้าขนาดของมันขึ้นอยู่กับเนื้อหาของอะแดปเตอร์ ข้อแตกต่างที่สำคัญคือว่า RV จะประมวลผลการอัพเดตก่อน onMeasure หรือก่อน onLayout นอกจากนี้เมื่อปรับขนาดแล้วมันก็ค่อนข้างยากที่จะทำแอนิเมชั่นการทำนายเนื่องจาก LM จำเป็นต้องรู้ขนาดสุดท้ายในการวัดตัวเองอย่างถูกต้องในขณะที่การจัดวางล่วงหน้าจะเกิดขึ้นหลังจากการวัด
yigit

22

ดังที่ @ yiğitพูดไว้คุณจะต้องแทนที่ onMeasure () ทั้ง @ user2302510 และ @DenisNek มีคำตอบที่ดี แต่ถ้าคุณต้องการสนับสนุน ItemDecoration คุณสามารถใช้ตัวจัดการเลย์เอาต์ที่กำหนดเองนี้ได้

และคำตอบอื่น ๆ ไม่สามารถเลื่อนได้เมื่อมีรายการมากกว่าที่จะแสดงบนหน้าจอ อันนี้ใช้ปริยายของ onMeasure () เมื่อมีรายการมากกว่าขนาดหน้าจอ

public class MyLinearLayoutManager extends LinearLayoutManager {

public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout)    {
    super(context, orientation, reverseLayout);
}

private int[] mMeasuredDimension = new int[2];

@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
                      int widthSpec, int heightSpec) {
    final int widthMode = View.MeasureSpec.getMode(widthSpec);
    final int heightMode = View.MeasureSpec.getMode(heightSpec);
    final int widthSize = View.MeasureSpec.getSize(widthSpec);
    final int heightSize = View.MeasureSpec.getSize(heightSpec);
    int width = 0;
    int height = 0;
    for (int i = 0; i < getItemCount(); i++) {
        measureScrapChild(recycler, i,
                View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                mMeasuredDimension);

        if (getOrientation() == HORIZONTAL) {
            width = width + mMeasuredDimension[0];
            if (i == 0) {
                height = mMeasuredDimension[1];
            }
        } else {
            height = height + mMeasuredDimension[1];
            if (i == 0) {
                width = mMeasuredDimension[0];
            }
        }
    }

    // If child view is more than screen size, there is no need to make it wrap content. We can use original onMeasure() so we can scroll view.
    if (height < heightSize && width < widthSize) {

        switch (widthMode) {
            case View.MeasureSpec.EXACTLY:
                width = widthSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        switch (heightMode) {
            case View.MeasureSpec.EXACTLY:
                height = heightSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        setMeasuredDimension(width, height);
    } else {
        super.onMeasure(recycler, state, widthSpec, heightSpec);
    }
}

private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                               int heightSpec, int[] measuredDimension) {

   View view = recycler.getViewForPosition(position);

   // For adding Item Decor Insets to view
   super.measureChildWithMargins(view, 0, 0);
    if (view != null) {
        RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
        int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                    getPaddingLeft() + getPaddingRight() + getDecoratedLeft(view) + getDecoratedRight(view), p.width);
            int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                    getPaddingTop() + getPaddingBottom() + getPaddingBottom() + getDecoratedBottom(view) , p.height);
            view.measure(childWidthSpec, childHeightSpec);

            // Get decorated measurements
            measuredDimension[0] = getDecoratedMeasuredWidth(view) + p.leftMargin + p.rightMargin;
            measuredDimension[1] = getDecoratedMeasuredHeight(view) + p.bottomMargin + p.topMargin;
            recycler.recycleView(view);
        }
    }
}

และถ้าคุณต้องการใช้กับ GridLayoutManager เพียงขยายจาก GridLayoutManager และเปลี่ยน

for (int i = 0; i < getItemCount(); i++)

ถึง

for (int i = 0; i < getItemCount(); i = i + getSpanCount())

4
นี่คือวิธีการแก้ปัญหาที่ทำงานได้อย่างถูกต้องฉันได้ทดสอบคนอื่น ๆ ทั้งหมดในชุดข้อความนี้และพวกเขาทุกคนมีปัญหา
wasyl

วิธีนี้ใช้ได้ผลดีสำหรับฉันเช่นกัน ปัญหาเดียวก็คือว่ามันไม่ถูกต้องบัญชีสำหรับข้อความที่ห่อในเด็ก ดูคำตอบของฉันด้านบนสำหรับวิธีแก้ปัญหานั้น
Bishbulb

สิ่งนี้ใช้ไม่ได้กับ gridlayout (ใช่ฉันใช้การเปลี่ยนแปลง) ครั้งแรกมันดูเหมือนว่าจะทำงานแล้วฉันเปลี่ยนทิศทางไปมาและความสูงจะเปลี่ยนไปเมื่อเทียบกับความสูงเดิม นอกจากนี้มุมมองจะไม่ทำงานตามที่คาดไว้ (เช่น onclicks ไม่ทำงานส่วนใหญ่ในขณะที่ใช้ gridlayoutmanager แบบธรรมดาที่ทำงาน)
Csabi

เราจะประสบความสำเร็จเช่นเดียวกันgridlayoutmanagerและstaggeredgridlayoutmanagerพิจารณาการนับระยะเวลาในใจได้อย่างไร
Amrut Bidri

MyLinearLayoutManager นี้ควรใช้กับมุมมองรีไซเคิลลูกหรือกับพาเรนต์หรือไม่ หรือทั้งคู่?
CommonSenseCode

21

อัพเดทมีนาคม 2559

โดยAndroid Support Library 23.2.1ของเวอร์ชันไลบรารี่สนับสนุน ดังนั้นWRAP_CONTENTทั้งหมดควรทำงานอย่างถูกต้อง

โปรดอัปเดตเวอร์ชันของไลบรารีในไฟล์ gradle

compile 'com.android.support:recyclerview-v7:23.2.1'

วิธีนี้ช่วยให้ RecyclerView ปรับขนาดตัวเองตามขนาดของเนื้อหา ซึ่งหมายความว่าก่อนหน้านี้สถานการณ์ที่ไม่พร้อมใช้งานเช่นการใช้ WRAP_CONTENT สำหรับมิติของ RecyclerView เป็นไปได้ในขณะนี้ที่เป็นอยู่ในขณะนี้เป็นไปได้

คุณจะต้องเรียกใช้setAutoMeasureEnabled (จริง)

แก้ไขข้อบกพร่องที่เกี่ยวข้องกับวิธีการวัดจำเพาะต่างๆในการอัพเดท

ตรวจสอบhttps://developer.android.com/topic/l ไลบรารี/support-library/features.html


14

คำตอบนี้ขึ้นอยู่กับโซลูชันที่ Denis Nek ให้มา มันแก้ปัญหาของการไม่คำนึงถึงการตกแต่งเหมือนวงเวียน

public class WrappingRecyclerViewLayoutManager extends LinearLayoutManager {

public WrappingRecyclerViewLayoutManager(Context context)    {
    super(context, VERTICAL, false);
}

public WrappingRecyclerViewLayoutManager(Context context, int orientation, boolean reverseLayout)    {
    super(context, orientation, reverseLayout);
}

private int[] mMeasuredDimension = new int[2];

@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
    final int widthMode = View.MeasureSpec.getMode(widthSpec);
    final int heightMode = View.MeasureSpec.getMode(heightSpec);
    final int widthSize = View.MeasureSpec.getSize(widthSpec);
    final int heightSize = View.MeasureSpec.getSize(heightSpec);
    int width = 0;
    int height = 0;
    for (int i = 0; i < getItemCount(); i++) {
        measureScrapChild(recycler, i,
                View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                mMeasuredDimension);
        if (getOrientation() == HORIZONTAL) {
            width = width + mMeasuredDimension[0];
            if (i == 0) {
                height = mMeasuredDimension[1];
            }
        } else {
            height = height + mMeasuredDimension[1];
            if (i == 0) {
                width = mMeasuredDimension[0];
            }
        }
    }
    switch (widthMode) {
        case View.MeasureSpec.EXACTLY:
            width = widthSize;
        case View.MeasureSpec.AT_MOST:
        case View.MeasureSpec.UNSPECIFIED:
    }

    switch (heightMode) {
        case View.MeasureSpec.EXACTLY:
            height = heightSize;
        case View.MeasureSpec.AT_MOST:
        case View.MeasureSpec.UNSPECIFIED:
    }

    setMeasuredDimension(width, height);
}

private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec, int heightSpec, int[] measuredDimension) {
    View view = recycler.getViewForPosition(position);
    if (view != null) {
        RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
        int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec, getPaddingLeft() + getPaddingRight(), p.width);
        int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec, getPaddingTop() + getPaddingBottom(), p.height);
        view.measure(childWidthSpec, childHeightSpec);
        Rect outRect = new Rect();
        calculateItemDecorationsForChild(view, outRect);
        measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
        measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin + outRect.bottom + outRect.top;
        recycler.recycleView(view);
    }
}

}


1
ในที่สุดสิ่งเดียวที่ทำงานได้อย่างถูกต้อง การทำงานที่ดี. ขอบคุณ!
falc0nit3

3
ฉันได้รับ java.lang.IndexOutOfBoundsException: ตำแหน่งรายการไม่ถูกต้อง 0 (0) จำนวนรายการ: 0 ข้อยกเว้น
RAHULRSANNIDHI

9

ทางเลือกอื่นในการขยาย LayoutManager สามารถเพียงแค่กำหนดขนาดของมุมมองด้วยตนเอง

จำนวนรายการต่อความสูงของแถว (หากรายการทั้งหมดมีความสูงเท่ากันและมีตัวคั่นรวมอยู่ในแถว)

LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) mListView.getLayoutParams();
params.height = mAdapter.getItemCount() * getResources().getDimensionPixelSize(R.dimen.row_height);
mListView.setLayoutParams(params);

ยังคงเป็นวิธีแก้ไขปัญหา แต่สำหรับกรณีพื้นฐานการทำงาน


6

ที่นี่ฉันได้พบวิธีแก้ไข: https://code.google.com/p/android/issues/detail?id=74772

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

การแก้ปัญหาคือการขยาย LayoutManager และแทนที่วิธีการ onMeasure ตามที่ @yigit แนะนำ

นี่คือรหัสในกรณีที่ลิงค์เสียชีวิต:

public static class MyLinearLayoutManager extends LinearLayoutManager {

    public MyLinearLayoutManager(Context context) {
        super(context);
    }

    private int[] mMeasuredDimension = new int[2];

    @Override
    public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
                          int widthSpec, int heightSpec) {
        final int widthMode = View.MeasureSpec.getMode(widthSpec);
        final int heightMode = View.MeasureSpec.getMode(heightSpec);
        final int widthSize = View.MeasureSpec.getSize(widthSpec);
        final int heightSize = View.MeasureSpec.getSize(heightSpec);

        measureScrapChild(recycler, 0,
                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                mMeasuredDimension);

        int width = mMeasuredDimension[0];
        int height = mMeasuredDimension[1];

        switch (widthMode) {
            case View.MeasureSpec.EXACTLY:
            case View.MeasureSpec.AT_MOST:
                width = widthSize;
                break;
            case View.MeasureSpec.UNSPECIFIED:
        }

        switch (heightMode) {
            case View.MeasureSpec.EXACTLY:
            case View.MeasureSpec.AT_MOST:
                height = heightSize;
                break;
            case View.MeasureSpec.UNSPECIFIED:
        }

        setMeasuredDimension(width, height);
    }

    private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                                   int heightSpec, int[] measuredDimension) {
        View view = recycler.getViewForPosition(position);
        if (view != null) {
            RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
            int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                    getPaddingLeft() + getPaddingRight(), p.width);
            int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                    getPaddingTop() + getPaddingBottom(), p.height);
            view.measure(childWidthSpec, childHeightSpec);
            measuredDimension[0] = view.getMeasuredWidth();
            measuredDimension[1] = view.getMeasuredHeight();
            recycler.recycleView(view);
        }
    }
}

บิงโก! ทำงานได้สมบูรณ์แบบในแนวนอน แต่ไม่ใช่แนวตั้ง
H Raval

5

ใช้วิธีแก้ปัญหาจาก @ sinan-kozak ยกเว้นแก้ไขข้อบกพร่องเล็กน้อย โดยเฉพาะเราไม่ควรใช้View.MeasureSpec.UNSPECIFIEDสำหรับทั้งความกว้างและความสูงเมื่อเรียกmeasureScrapChildว่าจะไม่ถูกต้องบัญชีสำหรับข้อความห่อในเด็ก แต่เราจะผ่านโหมดความกว้างและความสูงจากแม่ซึ่งจะอนุญาตให้สิ่งต่าง ๆ ทำงานได้ทั้งเค้าโครงแนวนอนและแนวตั้ง

public class MyLinearLayoutManager extends LinearLayoutManager {

public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout)    {
    super(context, orientation, reverseLayout);
}

private int[] mMeasuredDimension = new int[2];

@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
                      int widthSpec, int heightSpec) {
    final int widthMode = View.MeasureSpec.getMode(widthSpec);
    final int heightMode = View.MeasureSpec.getMode(heightSpec);
    final int widthSize = View.MeasureSpec.getSize(widthSpec);
    final int heightSize = View.MeasureSpec.getSize(heightSpec);
    int width = 0;
    int height = 0;
    for (int i = 0; i < getItemCount(); i++) {    
        if (getOrientation() == HORIZONTAL) {
            measureScrapChild(recycler, i,
                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                View.MeasureSpec.makeMeasureSpec(heightSize, heightMode),
                mMeasuredDimension);

            width = width + mMeasuredDimension[0];
            if (i == 0) {
                height = mMeasuredDimension[1];
            }
        } else {
            measureScrapChild(recycler, i,
                View.MeasureSpec.makeMeasureSpec(widthSize, widthMode),
                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                mMeasuredDimension);

            height = height + mMeasuredDimension[1];
            if (i == 0) {
                width = mMeasuredDimension[0];
            }
        }
    }

    // If child view is more than screen size, there is no need to make it wrap content. We can use original onMeasure() so we can scroll view.
    if (height < heightSize && width < widthSize) {

        switch (widthMode) {
            case View.MeasureSpec.EXACTLY:
                width = widthSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        switch (heightMode) {
            case View.MeasureSpec.EXACTLY:
                height = heightSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        setMeasuredDimension(width, height);
    } else {
        super.onMeasure(recycler, state, widthSpec, heightSpec);
    }
}

private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                               int heightSpec, int[] measuredDimension) {

   View view = recycler.getViewForPosition(position);

   // For adding Item Decor Insets to view
   super.measureChildWithMargins(view, 0, 0);
    if (view != null) {
        RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
        int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                    getPaddingLeft() + getPaddingRight() + getDecoratedLeft(view) + getDecoratedRight(view), p.width);
            int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                    getPaddingTop() + getPaddingBottom() + getDecoratedTop(view) + getDecoratedBottom(view) , p.height);
            view.measure(childWidthSpec, childHeightSpec);

            // Get decorated measurements
            measuredDimension[0] = getDecoratedMeasuredWidth(view) + p.leftMargin + p.rightMargin;
            measuredDimension[1] = getDecoratedMeasuredHeight(view) + p.bottomMargin + p.topMargin;
            recycler.recycleView(view);
        }
    }
}

`


ผมได้ทดสอบและการทำงาน แต่เฉพาะในกรณีที่ซ้อนอยู่ในRecyclerView อยากทำงานกับLinearLayout RelativeLayout
2968401

3

ใช่วิธีแก้ปัญหาที่แสดงในคำตอบทั้งหมดถูกต้องนั่นคือเราจำเป็นต้องปรับแต่งตัวจัดการรูปแบบเชิงเส้นเพื่อคำนวณความสูงของรายการลูกของมันแบบไดนามิกในเวลาทำงาน แต่คำตอบทั้งหมดไม่ทำงานตามที่คาดไว้โปรดตอบคำถามด้านล่างนี้สำหรับผู้จัดการรูปแบบที่กำหนดเองพร้อมการสนับสนุนการวางแนวทั้งหมด

public class MyLinearLayoutManager extends android.support.v7.widget.LinearLayoutManager {

private static boolean canMakeInsetsDirty = true;
private static Field insetsDirtyField = null;

private static final int CHILD_WIDTH = 0;
private static final int CHILD_HEIGHT = 1;
private static final int DEFAULT_CHILD_SIZE = 100;

private final int[] childDimensions = new int[2];
private final RecyclerView view;

private int childSize = DEFAULT_CHILD_SIZE;
private boolean hasChildSize;
private int overScrollMode = ViewCompat.OVER_SCROLL_ALWAYS;
private final Rect tmpRect = new Rect();

@SuppressWarnings("UnusedDeclaration")
public MyLinearLayoutManager(Context context) {
    super(context);
    this.view = null;
}

@SuppressWarnings("UnusedDeclaration")
public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
    super(context, orientation, reverseLayout);
    this.view = null;
}

@SuppressWarnings("UnusedDeclaration")
public MyLinearLayoutManager(RecyclerView view) {
    super(view.getContext());
    this.view = view;
    this.overScrollMode = ViewCompat.getOverScrollMode(view);
}

@SuppressWarnings("UnusedDeclaration")
public MyLinearLayoutManager(RecyclerView view, int orientation, boolean reverseLayout) {
    super(view.getContext(), orientation, reverseLayout);
    this.view = view;
    this.overScrollMode = ViewCompat.getOverScrollMode(view);
}

public void setOverScrollMode(int overScrollMode) {
    if (overScrollMode < ViewCompat.OVER_SCROLL_ALWAYS || overScrollMode > ViewCompat.OVER_SCROLL_NEVER)
        throw new IllegalArgumentException("Unknown overscroll mode: " + overScrollMode);
    if (this.view == null) throw new IllegalStateException("view == null");
    this.overScrollMode = overScrollMode;
    ViewCompat.setOverScrollMode(view, overScrollMode);
}

public static int makeUnspecifiedSpec() {
    return View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
}

@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
    final int widthMode = View.MeasureSpec.getMode(widthSpec);
    final int heightMode = View.MeasureSpec.getMode(heightSpec);

    final int widthSize = View.MeasureSpec.getSize(widthSpec);
    final int heightSize = View.MeasureSpec.getSize(heightSpec);

    final boolean hasWidthSize = widthMode != View.MeasureSpec.UNSPECIFIED;
    final boolean hasHeightSize = heightMode != View.MeasureSpec.UNSPECIFIED;

    final boolean exactWidth = widthMode == View.MeasureSpec.EXACTLY;
    final boolean exactHeight = heightMode == View.MeasureSpec.EXACTLY;

    final int unspecified = makeUnspecifiedSpec();

    if (exactWidth && exactHeight) {
        // in case of exact calculations for both dimensions let's use default "onMeasure" implementation
        super.onMeasure(recycler, state, widthSpec, heightSpec);
        return;
    }

    final boolean vertical = getOrientation() == VERTICAL;

    initChildDimensions(widthSize, heightSize, vertical);

    int width = 0;
    int height = 0;

    // it's possible to get scrap views in recycler which are bound to old (invalid) adapter entities. This
    // happens because their invalidation happens after "onMeasure" method. As a workaround let's clear the
    // recycler now (it should not cause any performance issues while scrolling as "onMeasure" is never
    // called whiles scrolling)
    recycler.clear();

    final int stateItemCount = state.getItemCount();
    final int adapterItemCount = getItemCount();
    // adapter always contains actual data while state might contain old data (f.e. data before the animation is
    // done). As we want to measure the view with actual data we must use data from the adapter and not from  the
    // state
    for (int i = 0; i < adapterItemCount; i++) {
        if (vertical) {
            if (!hasChildSize) {
                if (i < stateItemCount) {
                    // we should not exceed state count, otherwise we'll get IndexOutOfBoundsException. For such items
                    // we will use previously calculated dimensions
                    measureChild(recycler, i, widthSize, unspecified, childDimensions);
                } else {
                    logMeasureWarning(i);
                }
            }
            height += childDimensions[CHILD_HEIGHT];
            if (i == 0) {
                width = childDimensions[CHILD_WIDTH];
            }
            if (hasHeightSize && height >= heightSize) {
                break;
            }
        } else {
            if (!hasChildSize) {
                if (i < stateItemCount) {
                    // we should not exceed state count, otherwise we'll get IndexOutOfBoundsException. For such items
                    // we will use previously calculated dimensions
                    measureChild(recycler, i, unspecified, heightSize, childDimensions);
                } else {
                    logMeasureWarning(i);
                }
            }
            width += childDimensions[CHILD_WIDTH];
            if (i == 0) {
                height = childDimensions[CHILD_HEIGHT];
            }
            if (hasWidthSize && width >= widthSize) {
                break;
            }
        }
    }

    if (exactWidth) {
        width = widthSize;
    } else {
        width += getPaddingLeft() + getPaddingRight();
        if (hasWidthSize) {
            width = Math.min(width, widthSize);
        }
    }

    if (exactHeight) {
        height = heightSize;
    } else {
        height += getPaddingTop() + getPaddingBottom();
        if (hasHeightSize) {
            height = Math.min(height, heightSize);
        }
    }

    setMeasuredDimension(width, height);

    if (view != null && overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS) {
        final boolean fit = (vertical && (!hasHeightSize || height < heightSize))
                || (!vertical && (!hasWidthSize || width < widthSize));

        ViewCompat.setOverScrollMode(view, fit ? ViewCompat.OVER_SCROLL_NEVER : ViewCompat.OVER_SCROLL_ALWAYS);
    }
}

private void logMeasureWarning(int child) {
    if (BuildConfig.DEBUG) {
        Log.w("MyLinearLayoutManager", "Can't measure child #" + child + ", previously used dimensions will be reused." +
                "To remove this message either use #setChildSize() method or don't run RecyclerView animations");
    }
}

private void initChildDimensions(int width, int height, boolean vertical) {
    if (childDimensions[CHILD_WIDTH] != 0 || childDimensions[CHILD_HEIGHT] != 0) {
        // already initialized, skipping
        return;
    }
    if (vertical) {
        childDimensions[CHILD_WIDTH] = width;
        childDimensions[CHILD_HEIGHT] = childSize;
    } else {
        childDimensions[CHILD_WIDTH] = childSize;
        childDimensions[CHILD_HEIGHT] = height;
    }
}

@Override
public void setOrientation(int orientation) {
    // might be called before the constructor of this class is called
    //noinspection ConstantConditions
    if (childDimensions != null) {
        if (getOrientation() != orientation) {
            childDimensions[CHILD_WIDTH] = 0;
            childDimensions[CHILD_HEIGHT] = 0;
        }
    }
    super.setOrientation(orientation);
}

public void clearChildSize() {
    hasChildSize = false;
    setChildSize(DEFAULT_CHILD_SIZE);
}

public void setChildSize(int childSize) {
    hasChildSize = true;
    if (this.childSize != childSize) {
        this.childSize = childSize;
        requestLayout();
    }
}

private void measureChild(RecyclerView.Recycler recycler, int position, int widthSize, int heightSize, int[] dimensions) {
    final View child;
    try {
        child = recycler.getViewForPosition(position);
    } catch (IndexOutOfBoundsException e) {
        if (BuildConfig.DEBUG) {
            Log.w("MyLinearLayoutManager", "MyLinearLayoutManager doesn't work well with animations. Consider switching them off", e);
        }
        return;
    }

    final RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) child.getLayoutParams();

    final int hPadding = getPaddingLeft() + getPaddingRight();
    final int vPadding = getPaddingTop() + getPaddingBottom();

    final int hMargin = p.leftMargin + p.rightMargin;
    final int vMargin = p.topMargin + p.bottomMargin;

    // we must make insets dirty in order calculateItemDecorationsForChild to work
    makeInsetsDirty(p);
    // this method should be called before any getXxxDecorationXxx() methods
    calculateItemDecorationsForChild(child, tmpRect);

    final int hDecoration = getRightDecorationWidth(child) + getLeftDecorationWidth(child);
    final int vDecoration = getTopDecorationHeight(child) + getBottomDecorationHeight(child);

    final int childWidthSpec = getChildMeasureSpec(widthSize, hPadding + hMargin + hDecoration, p.width, canScrollHorizontally());
    final int childHeightSpec = getChildMeasureSpec(heightSize, vPadding + vMargin + vDecoration, p.height, canScrollVertically());

    child.measure(childWidthSpec, childHeightSpec);

    dimensions[CHILD_WIDTH] = getDecoratedMeasuredWidth(child) + p.leftMargin + p.rightMargin;
    dimensions[CHILD_HEIGHT] = getDecoratedMeasuredHeight(child) + p.bottomMargin + p.topMargin;

    // as view is recycled let's not keep old measured values
    makeInsetsDirty(p);
    recycler.recycleView(child);
}

private static void makeInsetsDirty(RecyclerView.LayoutParams p) {
    if (!canMakeInsetsDirty) {
        return;
    }
    try {
        if (insetsDirtyField == null) {
            insetsDirtyField = RecyclerView.LayoutParams.class.getDeclaredField("mInsetsDirty");
            insetsDirtyField.setAccessible(true);
        }
        insetsDirtyField.set(p, true);
    } catch (NoSuchFieldException e) {
        onMakeInsertDirtyFailed();
    } catch (IllegalAccessException e) {
        onMakeInsertDirtyFailed();
    }
}

private static void onMakeInsertDirtyFailed() {
    canMakeInsetsDirty = false;
    if (BuildConfig.DEBUG) {
        Log.w("MyLinearLayoutManager", "Can't make LayoutParams insets dirty, decorations measurements might be incorrect");
    }
}
}

3

ขณะนี้ห้องสมุดสนับสนุน Android รองรับคุณสมบัติ WRAP_CONTENT ด้วยเช่นกัน เพียงนำเข้าสิ่งนี้ในระดับเสียงของคุณ

compile 'com.android.support:recyclerview-v7:23.2.0'

และทำ!


2

จากผลงานของ Denis Nek มันจะทำงานได้ดีถ้าผลรวมของความกว้างของรายการมีขนาดเล็กกว่าขนาดของคอนเทนเนอร์ นอกเหนือจากนั้นมันจะทำให้ recyclerview ไม่สามารถเลื่อนได้และจะแสดงชุดย่อยของข้อมูล

เพื่อแก้ไขปัญหานี้ฉันได้แก้ไขวิธีการแก้ปัญหาเพื่อให้เลือกขนาดและขนาดที่คำนวณได้ขั้นต่ำ ดูด้านล่าง:

package com.linkdev.gafi.adapters;

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

import com.linkdev.gafi.R;

public class MyLinearLayoutManager extends LinearLayoutManager {

    public MyLinearLayoutManager(Context context, int orientation, boolean reverseLayout)    {
        super(context, orientation, reverseLayout);
        this.c = context;
    }


    private Context c;
    private int[] mMeasuredDimension = new int[2];


    @Override
    public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
                          int widthSpec, int heightSpec) {
        final int widthMode = View.MeasureSpec.getMode(widthSpec);
        final int heightMode = View.MeasureSpec.getMode(heightSpec);
        final int widthSize = View.MeasureSpec.getSize(widthSpec);
        final int heightSize = View.MeasureSpec.getSize(heightSpec);



        int width = 0;
        int height = 0;
        for (int i = 0; i < getItemCount(); i++) {
            measureScrapChild(recycler, i,
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    mMeasuredDimension);

            if (getOrientation() == HORIZONTAL) {
                width = width + mMeasuredDimension[0];
                if (i == 0) {
                    height = mMeasuredDimension[1];
                }
            } else {
                height = height + mMeasuredDimension[1];
                if (i == 0) {
                    width = mMeasuredDimension[0];
                }
            }
        }


        switch (widthMode) {
            case View.MeasureSpec.EXACTLY:
                width = widthSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        switch (heightMode) {
            case View.MeasureSpec.EXACTLY:
                height = heightSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        int widthDesired = Math.min(widthSize,width);
        setMeasuredDimension(widthDesired, height);
    }

    private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                                   int heightSpec, int[] measuredDimension) {
        View view = recycler.getViewForPosition(position);
        if (view != null) {
            RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
            int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                    getPaddingLeft() + getPaddingRight(), p.width);
            int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                    getPaddingTop() + getPaddingBottom(), p.height);
            view.measure(childWidthSpec, childHeightSpec);
            measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
            measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
            recycler.recycleView(view);
        }
    }}

1

ฉันได้ลองวิธีแก้ปัญหาทั้งหมดแล้วมันมีประโยชน์มาก แต่ก็ใช้ได้ดีสำหรับฉัน

public class  LinearLayoutManager extends android.support.v7.widget.LinearLayoutManager {

    public LinearLayoutManager(Context context, int orientation, boolean reverseLayout)    {
        super(context, orientation, reverseLayout);
    }

    private int[] mMeasuredDimension = new int[2];

    @Override
    public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state,
                          int widthSpec, int heightSpec) {
        final int widthMode = View.MeasureSpec.getMode(widthSpec);
        final int heightMode = View.MeasureSpec.getMode(heightSpec);
        final int widthSize = View.MeasureSpec.getSize(widthSpec);
        final int heightSize = View.MeasureSpec.getSize(heightSpec);
        int width = 0;
        int height = 0;
        for (int i = 0; i < getItemCount(); i++) {


            if (getOrientation() == HORIZONTAL) {

                measureScrapChild(recycler, i,
                        View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                        heightSpec,
                        mMeasuredDimension);

                width = width + mMeasuredDimension[0];
                if (i == 0) {
                    height = mMeasuredDimension[1];
                }
            } else {
                measureScrapChild(recycler, i,
                        widthSpec,
                        View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                        mMeasuredDimension);
                height = height + mMeasuredDimension[1];
                if (i == 0) {
                    width = mMeasuredDimension[0];
                }
            }
        }

        if (height < heightSize || width < widthSize) {

            switch (widthMode) {
                case View.MeasureSpec.EXACTLY:
                    width = widthSize;
                case View.MeasureSpec.AT_MOST:
                case View.MeasureSpec.UNSPECIFIED:
            }

            switch (heightMode) {
                case View.MeasureSpec.EXACTLY:
                    height = heightSize;
                case View.MeasureSpec.AT_MOST:
                case View.MeasureSpec.UNSPECIFIED:
            }

            setMeasuredDimension(width, height);
        } else {
            super.onMeasure(recycler, state, widthSpec, heightSpec);
        }
    }

    private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                                   int heightSpec, int[] measuredDimension) {
        View view = recycler.getViewForPosition(position);
        recycler.bindViewToPosition(view, position);
        if (view != null) {
            RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();
            int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                    getPaddingLeft() + getPaddingRight(), p.width);
            int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                    getPaddingTop() + getPaddingBottom(), p.height);
            view.measure(childWidthSpec, childHeightSpec);
            measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
            measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
            recycler.recycleView(view);
        }
    }
}

0

เพียงห่อเนื้อหาโดยใช้ RecyclerView ด้วย Grid Layout

ภาพ: Recycler เป็นเค้าโครง GridView

เพียงใช้ GridLayoutManager เช่นนี้:

RecyclerView.LayoutManager mRecyclerGrid=new GridLayoutManager(this,3,LinearLayoutManager.VERTICAL,false);
mRecyclerView.setLayoutManager(mRecyclerGrid);

คุณสามารถกำหนดจำนวนรายการที่จะปรากฏในแถว (แทนที่ 3)

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