【问题标题】:Recyclerview with GridLayout Manager inside another recycerviewRecyclerview 与另一个 recyclerview 中的 GridLayoutManager
【发布时间】:2016-02-18 10:15:34
【问题描述】:

我有一个 recyclerview,我正在为这个 recyclerview 设置适配器。在 recyclerview 的列表项中,我有另一个 recyclerview。

我已将 layout_height 作为 wrap_content 分配给内部 Recylerview。 现在我将 GridLayoutManager 设置为这个 recyclerview,但是这个 recyclerview 的内容变得不可见。

我已尝试将 CustomLinearLayout 管理器设置为此 recyclerview 并且效果很好。我试图找到一个自定义的 GridLayoutManager 来在 Recyclerview 上设置它,但我找不到任何这样的文档来给我一个自定义的 GridLayout 管理器,比如 CustomLinearLayout Manger。

如果有人知道如何在另一个 recyclerview 内的 Recyclerview 上设置 GridLayoutManager,请指导我。

非常感谢。

【问题讨论】:

    标签: android android-recyclerview gridlayoutmanager


    【解决方案1】:

    请按照我的要求进行定制:

    根据您的要求进行一些调整

    public class WrapHorizontalGridLayoutManager extends GridLayoutManager {
    
        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 RecyclerView view;
    
        private int childSize = DEFAULT_CHILD_SIZE;
        private boolean hasChildSize;
        private int overScrollMode = ViewCompat.OVER_SCROLL_ALWAYS;
        private final Rect tmpRect = new Rect();
        private final int spanCount;
    
        private int[] mMeasuredDimension = new int[2];
    
    
        public WrapHorizontalGridLayoutManager(RecyclerView view, int spanCount, int orientation, boolean reverseLayout) {
            super(view.getContext(), spanCount, orientation, reverseLayout);
            this.spanCount=spanCount;
            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
    
            int loopOverAdapeterItemCount = (adapterItemCount % 2 == 1) ? adapterItemCount + 1 : adapterItemCount;
    
            for (int i = 0; i <loopOverAdapeterItemCount/2; 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) {
                        Log.e("spanCount ",spanCount+" ");
                        height = childDimensions[CHILD_HEIGHT]*spanCount;
                    }
                    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();  //TODO adjacement
                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");
            }
        }
    
    
    }
    

    【讨论】:

    • 这工作正常,非常感谢。我想问的另一个问题是,当我将 Recyclerview 与 gridLayoutManager 一起使用时,我正在使用这个 recyclerview 在网格中显示图像。现在我想显示具有可变跨度的图像。就像如果我有 1 张照片,我只需要显示一行和一列。如果有 2 张照片:将显示 2 张照片,每张照片的宽度相同。如果有 3:我想显示 2 行。第 1 行将显示 1 张全宽图像,第 2 行将显示 2 张半宽图像。我也检查了 getSpanSize 方法,但它给了我特定位置的跨度大小。
    • 请投票并接受:),如果您有其他问题,请单独发布
    猜你喜欢
    • 2017-01-16
    • 1970-01-01
    • 2015-08-24
    • 2020-02-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多