ฉันจะทำให้ WRAP_CONTENT ทำงานบน RecyclerView ได้อย่างไร


181

ฉันมีDialogFragmentที่มีRecyclerView(รายการการ์ด)

ภายในนี้RecyclerViewมีหนึ่งหรือมากกว่าCardViewsนั้นที่สามารถมีความสูงใด ๆ

ฉันต้องการให้DialogFragmentความสูงที่ถูกต้องตามCardViewsที่มีอยู่ภายใน

ปกตินี้จะง่ายผมจะตั้งwrap_contentบนRecyclerViewเช่นนี้

<android.support.v7.widget.RecyclerView ...
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"   
    android:clickable="true"   
    android:scrollbars="vertical" >

</android.support.v7.widget.RecyclerView>

เพราะฉันใช้สิ่งRecyclerViewนี้ไม่ทำงานดู:

https://issuetracker.google.com/issues/37001674

และ

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

ในหน้าเหล่านี้ทั้งสองคนแนะนำให้ขยายLinearLayoutManagerและแทนที่onMeasure()

ฉันแรกใช้LayoutManagerที่มีคนให้ในลิงค์แรก:

public static class WrappingLayoutManager extends LinearLayoutManager {

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

อย่างไรก็ตามสิ่งนี้ไม่ได้ผลเพราะ

heightSize = View.MeasureSpec.getSize(heightSpec);

match_parentส่งกลับค่าขนาดใหญ่มากที่ปรากฏจะเกี่ยวข้องกับ

โดยการแสดงความคิดเห็น height = heightSize;(ในกรณีสวิตช์ที่สอง) ฉันจัดการเพื่อให้ความสูงทำงาน แต่เฉพาะถ้าTextViewเด็กที่อยู่ภายในCardViewไม่ห่อข้อความของตัวเอง (ประโยคยาว)

ทันทีที่มีการTextViewตัดข้อความของตัวเองความสูงควรเพิ่ม แต่ไม่ มันคำนวณความสูงสำหรับประโยคยาว ๆ ว่าเป็นบรรทัดเดียวไม่ใช่เป็นเส้นตัด (2 หรือมากกว่า)

คำแนะนำใด ๆ เกี่ยวกับวิธีที่ฉันควรปรับปรุงสิ่งนี้LayoutManagerเพื่อRecyclerViewทำงานด้วยWRAP_CONTENTหรือไม่

แก้ไข: เครื่องมือจัดการโครงร่างนี้อาจใช้ได้กับคนส่วนใหญ่ แต่ก็ยังมีปัญหาในการเลื่อนและคำนวณความสูงของการตัดข้อความสั้น

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

4
ดูเหมือนว่าในที่สุด Google ก็สามารถแก้ไขได้ :Jan 22, 2016: This has been merged into the internal tree, should be available with the next version of support library.
Marcin Orlowski

คำตอบ:


187

จากการอัปเดตAndroid Support Library 23.2.1 WRAP_CONTENTทั้งหมดควรทำงานอย่างถูกต้อง

โปรดอัปเดตเวอร์ชันของห้องสมุดในgradleไฟล์หรือเพื่อเพิ่มเติม:

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

แก้ไขปัญหาบางอย่างเช่นแก้ไขข้อบกพร่องที่เกี่ยวข้องกับวิธีการวัดรายละเอียดต่างๆ

ตรวจสอบhttp://developer.android.com/tools/support-library/features.html#v7-recyclerview

คุณสามารถตรวจสอบประวัติการแก้ไขของ Library Support ได้


ฉันใช้ LayoutManager และทันทีที่ฉันอัปเดตให้23.2.0แอปของฉันหยุดทำงาน ดังนั้นฉันจึงทำตามคำตอบของคุณและฉันRecyclerViewไม่ได้ห่อ ทำไม?
Abubakar Oladeji

3
แก้ไขแล้ว. ฉันเขียน LayoutManager ที่กำหนดเองของฉันไปที่ wrap_content ของรายการใน Recycler View แต่หลังจากอัปเดตมันก็เริ่มทำงานล้มเหลวด้วยข้อยกเว้นตามดัชนี และเมื่อฉันลบมันมันก็ไม่ได้ผิดพลาดอีกครั้ง แต่มุมมองของฉันไม่ได้ถูกห่อจนกว่าฉันจะเริ่มต้นมุมมอง Recycler ด้วย LinearLayoutManager
Abubakar Oladeji

25
อย่าลืมโทรmRecyclerView.setNestedScrollingEnabled(false);มิฉะนั้นมุมมองรีไซเคิลจะยังคงจัดการการเลื่อนตัวเองแทนที่จะส่งเหตุการณ์ไปยังผู้ปกครอง
WindRider

4
สรุปเนื้อหาสำหรับ RecyclerView ยังไม่ได้รับการสนับสนุน ตรวจสอบmedium.com/@elye.project/…
Elye

1
นี่ไม่ใช่วิธีการแก้ปัญหาที่กำหนด แม้ใน Support 27 จะไม่ทำงาน วิธีการแก้ปัญหาจะถูกบอกในคำตอบถัดไปจาก orange01 เพื่อหุ้ม RecyclerView ภายใน RelativeLayout
คริสเตียน

65

UPDATE 2020/02/07
วิธีการนี้อาจป้องกันไม่ให้เกิดการรีไซเคิลและไม่ควรใช้ในชุดข้อมูลขนาดใหญ่

อัพเดท 05.07.2019

หากคุณกำลังใช้RecyclerViewภายในScrollViewเพียงแค่เปลี่ยนไปScrollView androidx.core.widget.NestedScrollViewภายในมุมมองนี้ไม่มีความจำเป็นที่จะแพ็คภายในRecyclerViewRelativeLayout

<androidx.core.widget.NestedScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

        <!-- other views -->

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/list"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <!-- other views -->

    </LinearLayout>

</androidx.core.widget.NestedScrollView>

ในที่สุดก็พบทางออกสำหรับปัญหานี้

ทั้งหมดที่คุณต้องทำคือการห่อในRecyclerView RelativeLayoutอาจมีมุมมองอื่นซึ่งอาจใช้งานได้

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

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</RelativeLayout>

4
ฉันไม่รู้ว่าทำไมทางออกของคุณถูกต้อง แต่มันใช้งานได้สำหรับฉัน ขอบคุณมาก <3
My Will

1
เหมือนกัน .. พยายามทำสิ่งต่าง ๆ แต่สิ่งนี้ได้ผล ไม่รู้ว่าทำไมหรืออย่างไร ขอบคุณมาก! FrameLayout ไม่ได้ผลสำหรับฉัน แต่ RelativeLayout ทำเช่นนั้น
sarora

2
สิ่งนี้ใช้ได้กับฉันด้วยเมื่อฉันใช้ RecyclerView ภายใน ConstraintLayout
อัลลัน Veloso

สิ่งนี้ไม่ได้ผลสำหรับฉันจนกว่าฉันจะตั้งค่าทั้งความสูงและความกว้างสำหรับ recyclerview เป็น "match_parent"
darkrider1287

สิ่งนี้ใช้ได้กับฉันฉันยังจำได้เมื่อฉันเห็นมันฉันเคยใช้มาแล้วในอดีตขอบคุณ @ orange01 !!
Ivor

53

นี่คือรุ่นที่ปรับปรุงแล้วของคลาสซึ่งดูเหมือนว่าจะทำงานและไม่มีปัญหาวิธีแก้ไขอื่น ๆ มี:

package org.solovyev.android.views.llm;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;

/**
 * {@link android.support.v7.widget.LinearLayoutManager} which wraps its content. Note that this class will always
 * wrap the content regardless of {@link android.support.v7.widget.RecyclerView} layout parameters.
 *
 * Now it's impossible to run add/remove animations with child views which have arbitrary dimensions (height for
 * VERTICAL orientation and width for HORIZONTAL). However if child views have fixed dimensions
 * {@link #setChildSize(int)} method might be used to let the layout manager know how big they are going to be.
 * If animations are not used at all then a normal measuring procedure will run and child views will be measured during
 * the measure pass.
 */
public class LinearLayoutManager extends android.support.v7.widget.LinearLayoutManager {

    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 int childSize = DEFAULT_CHILD_SIZE;
    private boolean hasChildSize;

    @SuppressWarnings("UnusedDeclaration")
    public LinearLayoutManager(Context context) {
        super(context);
    }

    @SuppressWarnings("UnusedDeclaration")
    public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    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 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, widthSpec, unspecified, childDimensions);
                    } else {
                        logMeasureWarning(i);
                    }
                }
                height += childDimensions[CHILD_HEIGHT];
                if (i == 0) {
                    width = childDimensions[CHILD_WIDTH];
                }
                if (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, heightSpec, childDimensions);
                    } else {
                        logMeasureWarning(i);
                    }
                }
                width += childDimensions[CHILD_WIDTH];
                if (i == 0) {
                    height = childDimensions[CHILD_HEIGHT];
                }
                if (width >= widthSize) {
                    break;
                }
            }
        }

        if ((vertical && height < heightSize) || (!vertical && width < widthSize)) {
            // we really should wrap the contents of the view, let's do it

            if (exactWidth) {
                width = widthSize;
            } else {
                width += getPaddingLeft() + getPaddingRight();
            }

            if (exactHeight) {
                height = heightSize;
            } else {
                height += getPaddingTop() + getPaddingBottom();
            }

            setMeasuredDimension(width, height);
        } else {
            // if calculated height/width exceeds requested height/width let's use default "onMeasure" implementation
            super.onMeasure(recycler, state, widthSpec, heightSpec);
        }
    }

    private void logMeasureWarning(int child) {
        if (BuildConfig.DEBUG) {
            Log.w("LinearLayoutManager", "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 widthSpec, int heightSpec, int[] dimensions) {
        final View child = recycler.getViewForPosition(position);

        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;

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

        final int childWidthSpec = getChildMeasureSpec(widthSpec, hPadding + hMargin + hDecoration, p.width, canScrollHorizontally());
        final int childHeightSpec = getChildMeasureSpec(heightSpec, 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;

        recycler.recycleView(child);
    }
}

นอกจากนี้ยังสามารถใช้ได้เป็นห้องสมุด เชื่อมโยงไปยังระดับที่เกี่ยวข้อง


ทำงานได้อย่างไม่มีที่ติสำหรับฉัน ขอบคุณ.
นาตาริโอ

10
กรุณาใช้รุ่นที่ทันสมัยจากgithubเพราะมันมีการเปลี่ยนแปลงมากตั้งแต่ฉันโพสต์คำตอบ
se.solovyev

ขอบคุณสำหรับการทำงาน ฉันมีปัญหาในการทำงานกับลูกที่มีความสูงต่างกัน ถ้าฉันมีลูก 150 คนต่อวันมันจะได้ผล หากหนึ่งในนั้นคือ 300dp คนสุดท้ายจะถูกซ่อน ความคิดใด ๆ
natario

3
โดยเฉพาะอย่างยิ่งฉันรู้สึกว่ามันเป็นมาตรการสำหรับเด็กมาก่อนonBindViewHolder()เรียกว่า นั่นเป็นสิ่งที่ไม่ดีเพราะ ณ จุดนั้นฉันเรียกเช่นholder.textView.setText(longText)นั้นเพื่อให้เด็กสูงขึ้น แต่ก็ไม่ได้สะท้อนความสูงของผู้รีไซเคิล หากคุณมีความคิดใด ๆ (เช่นการเปลี่ยนแปลงอย่างรวดเร็วของอะแดปเตอร์) ฉันจะขอบคุณ
natario

ขอบคุณ คำตอบ / ห้องสมุดของคุณช่วยฉันในการเอาชนะปัญหาของความสูงของแนวนอนRecyclerViewRecyclerViewภายในแนวตั้ง
Sufian

16

UPDATE

โดยอัปเดตไลบรารีสนับสนุน Android 23.2 WRAP_CONTENT ทั้งหมดควรทำงานอย่างถูกต้อง

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

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

คำตอบเดิม

ตามที่ได้รับคำตอบในคำถามอื่นคุณต้องใช้วิธีการดั้งเดิม onMeasure () เมื่อความสูงของมุมมองนักรีไซเคิลของคุณใหญ่กว่าความสูงของหน้าจอ เครื่องมือจัดการโครงร่างนี้สามารถคำนวณ ItemDecoration และสามารถเลื่อนได้มากขึ้น

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

คำตอบเดิม: https://stackoverflow.com/a/28510031/1577792


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

10

นี่คือรุ่นc # สำหรับ Android โมโน

/* 
* Ported by Jagadeesh Govindaraj (@jaganjan)
 *Copyright 2015 serso aka se.solovyev
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * Contact details
 *
 * Email: se.solovyev @gmail.com
 * Site:  http://se.solovyev.org
 */


using Android.Content;
using Android.Graphics;
using Android.Support.V4.View;
using Android.Support.V7.Widget;
using Android.Util;
using Android.Views;
using Java.Lang;
using Java.Lang.Reflect;
using System;
using Math = Java.Lang.Math;

namespace Droid.Helper
{
    public class WrapLayoutManager : LinearLayoutManager
    {
        private const int DefaultChildSize = 100;
        private static readonly Rect TmpRect = new Rect();
        private int _childSize = DefaultChildSize;
        private static bool _canMakeInsetsDirty = true;
        private static readonly int[] ChildDimensions = new int[2];
        private const int ChildHeight = 1;
        private const int ChildWidth = 0;
        private static bool _hasChildSize;
        private static  Field InsetsDirtyField = null;
        private static int _overScrollMode = ViewCompat.OverScrollAlways;
        private static RecyclerView _view;

        public WrapLayoutManager(Context context, int orientation, bool reverseLayout)
            : base(context, orientation, reverseLayout)
        {
            _view = null;
        }

        public WrapLayoutManager(Context context) : base(context)
        {
            _view = null;
        }

        public WrapLayoutManager(RecyclerView view) : base(view.Context)
        {
            _view = view;
            _overScrollMode = ViewCompat.GetOverScrollMode(view);
        }

        public WrapLayoutManager(RecyclerView view, int orientation, bool reverseLayout)
            : base(view.Context, orientation, reverseLayout)
        {
            _view = view;
            _overScrollMode = ViewCompat.GetOverScrollMode(view);
        }

        public void SetOverScrollMode(int overScrollMode)
        {
            if (overScrollMode < ViewCompat.OverScrollAlways || overScrollMode > ViewCompat.OverScrollNever)
                throw new ArgumentException("Unknown overscroll mode: " + overScrollMode);
            if (_view == null) throw new ArgumentNullException(nameof(_view));
            _overScrollMode = overScrollMode;
            ViewCompat.SetOverScrollMode(_view, overScrollMode);
        }

        public static int MakeUnspecifiedSpec()
        {
            return View.MeasureSpec.MakeMeasureSpec(0, MeasureSpecMode.Unspecified);
        }

        public override void OnMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec,
            int heightSpec)
        {
            var widthMode = View.MeasureSpec.GetMode(widthSpec);
            var heightMode = View.MeasureSpec.GetMode(heightSpec);

            var widthSize = View.MeasureSpec.GetSize(widthSpec);
            var heightSize = View.MeasureSpec.GetSize(heightSpec);

            var hasWidthSize = widthMode != MeasureSpecMode.Unspecified;
            var hasHeightSize = heightMode != MeasureSpecMode.Unspecified;

            var exactWidth = widthMode == MeasureSpecMode.Exactly;
            var exactHeight = heightMode == MeasureSpecMode.Exactly;

            var unspecified = MakeUnspecifiedSpec();

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

            var vertical = Orientation == Vertical;

            InitChildDimensions(widthSize, heightSize, vertical);

            var width = 0;
            var 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();

            var stateItemCount = state.ItemCount;
            var adapterItemCount = ItemCount;
            // 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 (var 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[ChildHeight];
                    if (i == 0)
                    {
                        width = ChildDimensions[ChildWidth];
                    }
                    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[ChildWidth];
                    if (i == 0)
                    {
                        height = ChildDimensions[ChildHeight];
                    }
                    if (hasWidthSize && width >= widthSize)
                    {
                        break;
                    }
                }
            }

            if (exactWidth)
            {
                width = widthSize;
            }
            else
            {
                width += PaddingLeft + PaddingRight;
                if (hasWidthSize)
                {
                    width = Math.Min(width, widthSize);
                }
            }

            if (exactHeight)
            {
                height = heightSize;
            }
            else
            {
                height += PaddingTop + PaddingBottom;
                if (hasHeightSize)
                {
                    height = Math.Min(height, heightSize);
                }
            }

            SetMeasuredDimension(width, height);

            if (_view == null || _overScrollMode != ViewCompat.OverScrollIfContentScrolls) return;
            var fit = (vertical && (!hasHeightSize || height < heightSize))
                      || (!vertical && (!hasWidthSize || width < widthSize));

            ViewCompat.SetOverScrollMode(_view, fit ? ViewCompat.OverScrollNever : ViewCompat.OverScrollAlways);
        }

        private void LogMeasureWarning(int child)
        {
#if DEBUG
            Log.WriteLine(LogPriority.Warn, "LinearLayoutManager",
                "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");
#endif
        }

        private void InitChildDimensions(int width, int height, bool vertical)
        {
            if (ChildDimensions[ChildWidth] != 0 || ChildDimensions[ChildHeight] != 0)
            {
                // already initialized, skipping
                return;
            }
            if (vertical)
            {
                ChildDimensions[ChildWidth] = width;
                ChildDimensions[ChildHeight] = _childSize;
            }
            else
            {
                ChildDimensions[ChildWidth] = _childSize;
                ChildDimensions[ChildHeight] = height;
            }
        }

        public void ClearChildSize()
        {
            _hasChildSize = false;
            SetChildSize(DefaultChildSize);
        }

        public void SetChildSize(int size)
        {
            _hasChildSize = true;
            if (_childSize == size) return;
            _childSize = size;
            RequestLayout();
        }

        private void MeasureChild(RecyclerView.Recycler recycler, int position, int widthSize, int heightSize,
            int[] dimensions)
        {
            View child = null;
            try
            {
                child = recycler.GetViewForPosition(position);
            }
            catch (IndexOutOfRangeException e)
            {
                Log.WriteLine(LogPriority.Warn, "LinearLayoutManager",
                    "LinearLayoutManager doesn't work well with animations. Consider switching them off", e);
            }

            if (child != null)
            {
                var p = child.LayoutParameters.JavaCast<RecyclerView.LayoutParams>()

                var hPadding = PaddingLeft + PaddingRight;
                var vPadding = PaddingTop + PaddingBottom;

                var hMargin = p.LeftMargin + p.RightMargin;
                var 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);

                var hDecoration = GetRightDecorationWidth(child) + GetLeftDecorationWidth(child);
                var vDecoration = GetTopDecorationHeight(child) + GetBottomDecorationHeight(child);

                var childWidthSpec = GetChildMeasureSpec(widthSize, hPadding + hMargin + hDecoration, p.Width,
                    CanScrollHorizontally());
                var childHeightSpec = GetChildMeasureSpec(heightSize, vPadding + vMargin + vDecoration, p.Height,
                    CanScrollVertically());

                child.Measure(childWidthSpec, childHeightSpec);

                dimensions[ChildWidth] = GetDecoratedMeasuredWidth(child) + p.LeftMargin + p.RightMargin;
                dimensions[ChildHeight] = 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)
                {
                   var klass = Java.Lang.Class.FromType (typeof (RecyclerView.LayoutParams));
                    InsetsDirtyField = klass.GetDeclaredField("mInsetsDirty");
                    InsetsDirtyField.Accessible = true;
                }
                InsetsDirtyField.Set(p, true);
            }
            catch (NoSuchFieldException e)
            {
                OnMakeInsertDirtyFailed();
            }
            catch (IllegalAccessException e)
            {
                OnMakeInsertDirtyFailed();
            }
        }

        private static void OnMakeInsertDirtyFailed()
        {
            _canMakeInsetsDirty = false;
#if DEBUG
            Log.Warn("LinearLayoutManager",
                "Can't make LayoutParams insets dirty, decorations measurements might be incorrect");
#endif
        }
    }
}

เกือบ ... แทนที่var p = (RecyclerView.LayoutParams) child.LayoutParametersด้วยvar p = child.LayoutParameters.JavaCast<RecyclerView.LayoutParams>()
Roubachof

ทำไมคุณถึงประกาศตัวแปรสมาชิกให้คงที่?
esskar

@esskar ฉันคิดว่าตัวแปรที่ใช้ในวิธีการคงที่ดูรุ่น java หากคุณมีข้อสงสัยใด ๆ
Jagadeesh Govindaraj

6

RecyclerViewเพิ่มการสนับสนุนสำหรับwrap_contentใน23.2.0ซึ่งเป็นรถ, 23.2.1 เป็นเพียงที่มั่นคงเพื่อให้คุณสามารถใช้:

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

คุณสามารถดูประวัติการแก้ไขได้ที่นี่:

https://developer.android.com/topic/libraries/support-library/revisions.html

บันทึก:

นอกจากนี้โปรดทราบว่าหลังจากอัปเดตห้องสมุดสนับสนุนแล้วRecyclerViewจะมีความเคารพwrap_contentเช่นกันmatch_parentหากคุณมีมุมมองรายการของRecyclerViewชุดmatch_parentหนึ่งมุมมองเดียวจะเต็มหน้าจอ


1
@Yvette ตกลงแน่นอน
ColdFusion


4

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

@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,
                widthSpec,
                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 childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                    getPaddingTop() + getPaddingBottom(), p.height);
        view.measure(widthSpec, childHeightSpec);
        measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
        measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
        recycler.recycleView(view);
    }
}

3

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

vRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                calculeRecyclerViewFullHeight();
            }
        });

ในการcalculeRecyclerViewFullHeightคำนวณRecyclerViewความสูงเต็มตามความสูงของลูกของมัน

protected void calculateSwipeRefreshFullHeight() {
        int height = 0;
        for (int idx = 0; idx < getRecyclerView().getChildCount(); idx++ ) {
            View v = getRecyclerView().getChildAt(idx);
            height += v.getHeight();
        }
        SwipeRefreshLayout.LayoutParams params = getSwipeRefresh().getLayoutParams();
        params.height = height;
        getSwipeRefresh().setLayoutParams(params);
    }

ในกรณีของฉันฉันRecyclerViewมีที่มีในSwipeRefreshLayoutเหตุผลที่ฉันตั้งความสูงไปSwipeRefreshViewและไม่ให้RecyclerViewแต่ถ้าคุณไม่ได้ใด ๆSwipeRefreshViewจากนั้นคุณสามารถตั้งค่าความสูงไปRecyclerViewแทน

แจ้งให้เราทราบว่าสิ่งนี้ช่วยคุณได้หรือไม่


ฉันจะรับเมธอด getRecyclerView () ได้อย่างไร
asubanovsky

@asubanovsky เป็นวิธีการที่จะส่งคืนRecyclerViewอินสแตนซ์ของคุณ
4gus71n

2
อย่าลืมลบ globalLayoutListener ของคุณใน onGlobalLayout ()
แมทธิว

3

ตอนนี้ใช้งานได้เมื่อพวกเขาวางจำหน่ายในเวอร์ชัน 23.2 ตามที่ระบุในโพสต์นี้ การโพสต์บล็อกอย่างเป็นทางการ

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


3

เพียงใส่ RecyclerView ของคุณไว้ใน NestedScrollView ทำงานได้อย่างสมบูรณ์แบบ

<android.support.v4.widget.NestedScrollView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:layout_marginBottom="25dp">
                <android.support.v7.widget.RecyclerView
                    android:id="@+id/kliste"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent" />
            </android.support.v4.widget.NestedScrollView>

2

ผมใช้บางส่วนของการแก้ปัญหาดังกล่าวข้างต้น แต่มันก็ทำงานให้แต่widthheight

  1. ถ้าระบุของคุณcompileSdkVersionมากกว่า23คุณโดยตรงสามารถใช้RecyclerViewให้ไว้ในห้องสมุดสนับสนุนของตนในมุมมองของรีไซเคิลเช่นสำหรับ23'com.android.support:recyclerview-v7:23.2.1'มันจะเป็น ไลบรารีการสนับสนุนเหล่านี้รองรับคุณสมบัติwrap_contentทั้งความกว้างและความสูง

คุณต้องเพิ่มลงในการอ้างอิงของคุณ

compile 'com.android.support:recyclerview-v7:23.2.1'
  1. หากคุณcompileSdkVersionอายุต่ำกว่า23ปีคุณสามารถใช้วิธีแก้ปัญหาด้านล่าง

ฉันพบกระทู้ Googleนี้เกี่ยวกับปัญหานี้ ในหัวข้อนี้มีเป็นหนึ่งในผลงานที่นำไปสู่การดำเนินงานของLinearLayoutManager

ฉันได้ทดสอบทั้งความสูงและความกว้างแล้วและก็ใช้ได้ดีสำหรับฉันในทั้งสองกรณี

/*
 * Copyright 2015 serso aka se.solovyev
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * Contact details
 *
 * Email: se.solovyev@gmail.com
 * Site:  http://se.solovyev.org
 */

package org.solovyev.android.views.llm;

import android.content.Context;
import android.graphics.Rect;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;

import java.lang.reflect.Field;

/**
 * {@link android.support.v7.widget.LinearLayoutManager} which wraps its content. Note that this class will always
 * wrap the content regardless of {@link android.support.v7.widget.RecyclerView} layout parameters.
 * <p/>
 * Now it's impossible to run add/remove animations with child views which have arbitrary dimensions (height for
 * VERTICAL orientation and width for HORIZONTAL). However if child views have fixed dimensions
 * {@link #setChildSize(int)} method might be used to let the layout manager know how big they are going to be.
 * If animations are not used at all then a normal measuring procedure will run and child views will be measured during
 * the measure pass.
 */
public class LinearLayoutManager 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 LinearLayoutManager(Context context) {
        super(context);
        this.view = null;
    }

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

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

    @SuppressWarnings("UnusedDeclaration")
    public LinearLayoutManager(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("LinearLayoutManager", "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("LinearLayoutManager", "LinearLayoutManager 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("LinearLayoutManager", "Can't make LayoutParams insets dirty, decorations measurements might be incorrect");
        }
    }
}

1

แทนการใช้ห้องสมุดใด ๆ ทางออกที่ง่ายที่สุดจนถึงรุ่นใหม่ออกมาเป็นเพียงการเปิดb.android.com/74772 คุณจะพบทางออกที่ดีที่สุดที่ทราบได้อย่างง่ายดาย

PS: b.android.com/74772#c50ได้ผลสำหรับฉัน


1

ฉันแนะนำให้คุณวาง recyclerview ในเลย์เอาต์อื่น ๆ (เลย์เอาท์ที่สัมพันธ์กันจะดีกว่า) จากนั้นเปลี่ยนความสูง / ความกว้างของ recyclerview เป็นจับคู่พาเรนต์กับเค้าโครงนั้นและตั้งค่าความสูง / ความกว้างของเค้าโครงพาเรนต์ให้เป็นเนื้อหาแบบตัด มันใช้งานได้สำหรับฉัน


0

แทนที่measureScrapChildเป็นรหัสติดตาม:

private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
        int heightSpec, int[] measuredDimension)
    {
        View view = recycler.GetViewForPosition(position);
        if (view != null)
        {
            MeasureChildWithMargins(view, widthSpec, heightSpec);
            measuredDimension[0] = view.MeasuredWidth;
            measuredDimension[1] = view.MeasuredHeight;
            recycler.RecycleView(view);
        }
    }

ฉันใช้ xamarin ดังนั้นนี่คือรหัส c # ฉันคิดว่านี่สามารถ "แปล" เป็น Java ได้อย่างง่ายดาย


0

อัปเดตมุมมองของคุณด้วยค่า Null แทนที่จะเป็นกลุ่มดูหลักใน Adaptor viewholder บนเมธอด CreateViewHolder

@Override
public AdapterItemSku.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

    View view = inflator.inflate(R.layout.layout_item, null, false);
    return new MyViewHolder(view);
}

0

คุณต้องใส่ FrameLayout เป็นมุมมองหลักจากนั้นใส่ RelativeLayout ด้วย ScrollView และอย่างน้อย RecyclerView ของคุณก็ใช้งานได้สำหรับฉัน

เคล็ดลับที่แท้จริงที่นี่คือ RelativeLayout ...

ยินดีที่ได้ช่วย.


-1

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

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