How do I make WRAP_CONTENT work on a RecyclerView

后端 未结 19 1844
自闭症患者
自闭症患者 2020-11-22 12:49

I have a DialogFragment that contains a RecyclerView (a list of cards).

Within this RecyclerView are one or more CardVi

19条回答
  •  天涯浪人
    2020-11-22 13:49

    Here is the c# version for mono 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()
    
                    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
            }
        }
    }
    

提交回复
热议问题