Android——RecyclerView——LayoutManager全部源码翻译及注释 3000行代码 看到崩溃

本文详细解析了RecyclerView的LayoutManager的源码,包括布局、滚动子视图、子视图管理等功能,涉及ChildHelper、ViewBoundsCheck等关键组件。通过源码分析,阐述了LayoutManager如何实现自定义布局,以及在不同场景下的应用,如垂直滚动列表、网格布局等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

总结:LayoutManager的任务是

布局子视图

滚动子视图

在滚动过程中根据子视图在布局中所处的位置,决定何时添加子视图和回收视图


 

一个LayoutManager负责在一个RecyclerView中测量和定位项目视图,以及决定什么时候回收那些对用户不可见的itemviews。

通过更改RecyclerView的LayoutManager,可以用来实现一个标准的垂直滚动列表,统一的网格,交错的网格(瀑布流),水平滚动集合等等。

如果LayoutManager指定一个默认的构造函数或者一个带有这样的标签的构造函数:Context,AttributeSet,int,int,RecyclerView会在加载item布局的时候实例化并且设置LayoutManager。

最常用的属性从(Context,AttributeSet,int,int)中获取。

以防万一一个LayoutManager指定了两个构造函数,非默认构造函数将优先。

public static abstract class LayoutManager{

ChildHelper mChildHelper;

RecyclerView mRecyclerView;

 

//该回调用于检索有关RecyclerView及其子View中的水平方向的信息。

private final ViewBoundsCheck.CallbackmHorizontalBoundCheckCallback =

                new ViewBoundsCheck.Callback(){

                    //取得子item数量

                    @Override

                    public int getChildCount(){

                       returnLayoutManager.this.getChildCount();

                    }

 

                    //取得当前的RecyclerView

                    @Override

                    public View getParent() {

                        return mRecyclerView;

                   }

 

                    //取得某个item

                    @Override

                    public View getChildAt(intindex) {

                        returnLayoutManager.this.getChildAt(index);

                    }

 

                    //取得左边缘起始位置(就是相当于定位这个rv在布局中的位置)

                    @Override

                    public int getParentStart(){

                        returnLayoutManager.this.getPaddingLeft();

                    }

 

                    //取得右边缘位置

                    @Override

                    public int getParentEnd() {

                        returnLayoutManager.this.getWidth() - LayoutManager.this.getPaddingRight();

                    }

 

                    //这里取得左边缘开始位置的方法看不太懂

                   @Override

                    public intgetChildStart(View view) {

                        finalRecyclerView.LayoutParams params = (RecyclerView.LayoutParams)

                               view.getLayoutParams();

                        return LayoutManager.this.getDecoratedLeft(view)- params.leftMargin;

                    }

 

                    @Override

                    public int getChildEnd(Viewview) {

                        finalRecyclerView.LayoutParams params = (RecyclerView.LayoutParams)

                               view.getLayoutParams();

                        returnLayoutManager.this.getDecoratedRight(view) + params.rightMargin;

                    }

                };

 

//相对应的,这里检索rv相关和子view垂直相关

private final ViewBoundsCheck.CallbackmVerticalBoundCheckCallback =

                new ViewBoundsCheck.Callback(){

                    @Override

                    public int getChildCount(){

                        returnLayoutManager.this.getChildCount();

                   }

 

                    @Override

                    public View getParent() {

                        return mRecyclerView;

                    }

 

                    @Override

                    public View getChildAt(intindex) {

                       returnLayoutManager.this.getChildAt(index);

                    }

 

                    @Override

                    public int getParentStart(){

                        returnLayoutManager.this.getPaddingTop();

                    }

 

                    @Override

                    public int getParentEnd() {

                        returnLayoutManager.this.getHeight()

                                -LayoutManager.this.getPaddingBottom();

                    }

 

                    @Override

                    public intgetChildStart(View view) {

                        finalRecyclerView.LayoutParams params = (RecyclerView.LayoutParams)

                               view.getLayoutParams();

                        returnLayoutManager.this.getDecoratedTop(view) - params.topMargin;

                    }

 

                    @Override

                    public int getChildEnd(Viewview) {

                        finalRecyclerView.LayoutParams params = (RecyclerView.LayoutParams)

                               view.getLayoutParams();

                        returnLayoutManager.this.getDecoratedBottom(view) + params.bottomMargin;

                    }

                };

 

//实用程序对象用于检查父RecyclerView对象的边界。

ViewBoundsCheck mHorizontalBoundCheck = newViewBoundsCheck(mHorizontalBoundCheckCallback);

       ViewBoundsCheck mVerticalBoundCheck = newViewBoundsCheck(mVerticalBoundCheckCallback);

 

//这个柔滑滚动也值得学习

@Nullable

SmoothScroller mSmoothScroller;

 

//请求简单动画的标志

boolean mRequestedSimpleAnimations = false;

 

//是否被加到视图的标志

boolean mIsAttachedToWindow = false;

 

//自动测量的标志

boolean mAutoMeasure = false;

 

//LayoutManager有自己更严格的测量缓存的机制,以避免重新测量一个孩子如果给予的控件比之前测量的大。

//启用测量缓存功能

private boolean mMeasurementCacheEnabled =true;

 

//启用预取item功能

private boolean mItemPrefetchEnabled =true;

 

//当预取发生的时候,GapWorkder负责编写,以跟踪最大数量的视图,由collectInitialPrefetchPositions(int,LayoutPrefetchRegistry)请求或者collectAdjacentPrefetchPositions(int,int,State,LayoutPrefetchRegistry)调用。如果通过

collectInitialPrefetchPositions(int,LayoutPrefetchRegistry)进行扩展,将在布局时重置以防止初始预取(通常很大,因为他们是与预期的孩子数量成正比)永久扩展缓存。(真的感觉好严格)

int mPrefetchMaxCountObserved;

 

//如果是true,那么mPrefetchMaxCountObserved将只有在下一个布局中可用,并且应该被重置。

booleanmPrefetchMaxObservedInInitialPrefetch;

 

//这些测量规格可能是传递给RecyclerView的onMeasure()方法或者是RecyclerView创造的假的测量规格。

例如,当布局在运行的时候,RecyclerView总是将这些规格设置为EXACTLY因为LayoutManager无法再布局的过程中调整RecyclerView的大小。

另外,为了能够在未指定的测量规格中使用提示,RecyclerView将检查API级别并将大小设置为0到M前以避免可能造成的任何问题,因为他们可能造成腐败的价值(性能浪费)。

如果他们设置模式成未明确状态,那么旧的明天就没有责任去分享大小。

private int mWidthMode, mHeightMode;

private int mWidth, mHeight;

 

//LayoutManager用于根据位置请求将被预取item的接口。有明确的距离到viewport,表示优先级。

public interface LayoutPrefetchRegistry {

           void addPosition(int layoutPosition, int pixelDistance);

       }

 

//设置RecyclerView,如果是空,求全部初始化。否则就设置。并且设置测量规格为EXACTLY(这些规格就如同view的onMeasure一般)

void setRecyclerView(RecyclerViewrecyclerView) {

           if (recyclerView == null) {

                mRecyclerView = null;

                mChildHelper = null;

                mWidth = 0;

                mHeight = 0;

           } else {

                mRecyclerView = recyclerView;

                mChildHelper =recyclerView.mChildHelper;

                mWidth =recyclerView.getWidth();

                mHeight = recyclerView.getHeight();

           }

           mWidthMode = MeasureSpec.EXACTLY;

           mHeightMode = MeasureSpec.EXACTLY;

       }

 

void setMeasureSpecs(int wSpec, int hSpec){

           mWidth = MeasureSpec.getSize(wSpec);

           mWidthMode = MeasureSpec.getMode(wSpec);

           if (mWidthMode == MeasureSpec.UNSPECIFIED &&!ALLOW_SIZE_IN_UNSPECIFIED_SPEC) {

                mWidth = 0;

           }

 

           mHeight = MeasureSpec.getSize(hSpec);

           mHeightMode = MeasureSpec.getMode(hSpec);

           if (mHeightMode == MeasureSpec.UNSPECIFIED &&!ALLOW_SIZE_IN_UNSPECIFIED_SPEC) {

                mHeight = 0;

           }

       }

 

//在使用自动测量的时候,在测量过程中计算布局后调用这个方法。

它只是遍历所有的子孩子计算一个边界框然后调用setMeasuredDimension(Rect,int,int)。

LayoutManagers(子类)可以覆盖该方法如果他们需要以不同的方式处理边框。

例如,GridLayoutManager覆盖该方法,以确保即使列是空,GridLayoutManager仍然测量宽度足以包括它。

void setMeasuredDimensionFromChildren(intwidthSpec, int heightSpec) {

           final int count = getChildCount();

           if (count == 0) {

               mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);

                return;

           }

           int minX = Integer.MAX_VALUE;

           int minY = Integer.MAX_VALUE;

           int maxX = Integer.MIN_VALUE;

           int maxY = Integer.MIN_VALUE;

 

               //开循环取得最小边界

for (int i = 0; i < count; i++) {

                View child = getChildAt(i);

                final Rect bounds =mRecyclerView.mTempRect;

               getDecoratedBoundsWithMargins(child, bounds);

                if (bounds.left < minX) {

                    minX = bounds.left;

                }

                if (bounds.right > maxX) {

                    maxX = bounds.right;

               }

                if (bounds.top < minY) {

                    minY = bounds.top;

                }

                if (bounds.bottom > maxY) {

                    maxY = bounds.bottom;

                }

           }

           mRecyclerView.mTempRect.set(minX, minY, maxX, maxY);

           setMeasuredDimension(mRecyclerView.mTempRect, widthSpec, heightSpec);

       }

 

//设定子孩子给定的边界框的测量尺寸并且测量规格已被传入onMeasure(int,int)。它是在测量过结果传递期间在RecyclerView调用LayoutManager#onLayoutChildren(Recycler,State)之后被调用。

这个方法应该调用setMeasuredDimension(int,int)。

默认实现将RecyclerView的填充添加到给定的边界框然后将该值限制在给定的测量规格范围之内。

只有当LayoutManager选择进入自动测量API时才会调用此方法

public void setMeasuredDimension(RectchildrenBounds, int wSpec, int hSpec) {

            //被使用的宽度=孩子矩形宽度+左边距+右边距

           int usedWidth = childrenBounds.width() + getPaddingLeft() +getPaddingRight();

           int usedHeight = childrenBounds.height() + getPaddingTop() +getPaddingBottom();

           int width = chooseSize(wSpec, usedWidth, getMinimumWidth());

           int height = chooseSize(hSpec, usedHeight, getMinimumHeight());

           setMeasuredDimension(width, height);

       }

 

//在底层RecyclerView上调用这个方法

public void requestLayout() {

           if(mRecyclerView != null) {

                mRecyclerView.requestLayout();

           }

       }

 

//(调用RecyclerView的断言方法)断言在布局中间或者在滚动,如果没有的话就会抛出异常。

public void assertInLayoutOrScroll(Stringmessage) {

           if (mRecyclerView != null) {

               mRecyclerView.assertInLayoutOrScroll(message);

            }

       }

 

//从给定的规格和参数中选择最接近所需尺寸的尺寸并且遵从它。

public static int chooseSize(int spec, intdesired, int min) {

           final int mode = View.MeasureSpec.getMode(spec);

           final int size = View.MeasureSpec.getSize(spec);

            //这里就类似于View中onMeasure那一块的源码了

           switch (mode) {

                case View.MeasureSpec.EXACTLY:

                    return size;

                case View.MeasureSpec.AT_MOST:

                    return Math.min(size,Math.max(desired, min));

                caseView.MeasureSpec.UNSPECIFIED:

                default:

                    return Math.max(desired,min);

           }

       }

 

public voidassertNotInLayoutOrScroll(String message) {

           if (mRecyclerView != null) {

               mRecyclerView.assertNotInLayoutOrScroll(message);

           }

       }

 

//1.定义布局是否应由RecyclerView或LayoutManager他自己来处理布局的测量。

2.如果需要的话,该方法通常由值为true的LayoutManager调用来支持WRAP_CONTENT。3.如果您正在使用公共的LayoutManager,但想要自定义测量逻辑,你可以用这个方法调用这个方法并覆盖。

4.AutoMeasure是布局管理员轻松包装其内容或处理RecyclerView的父级提供的各种规格方便的便捷机制。

5.它通过调用onLayoutChildren(Recycler,State)来工作在RecyclerView#onMeasure(int,int)调用期间,然后计算希望的尺寸基于子孩子的位置。当期望所有的存在的RecyclerView中的动画能力时,他才会这样做。

自动测量的工作如下:

6.布局管理器应该调用setAutoMeasureEnabled(true)方法来启用他。

7.所有的框架LayoutManagers使用auto-measure。

8.当onMeasure被调用的时候,如果被分享的规格是EXACTLY,RecyclerView将仅仅会调用LayoutManager的onMeasure方法并且返回,同时不做任何布局测量。

9.如果其中有一个规格不是EXACT,RecyclerView将会使用调用onMeasure方法开启布局处理。这将会处理所有的等待中的Adapter更新并且决定是否去运行一个预布局

10.如果它决定那么做了,它将会首先调用onLayoutChildren(Recycler, State)方法用isPreLayout()方法设置成true。

11.暂时,getWidth()方法和getHeight()方法将会仍然返回RecyclerView的宽度和高度作为最后的布局的计算。

12.在处理一个预布局的情形的时候,RecyclerView将会调用onLayoutChildren(Recycler, State)和isMeasuring()设置成true并且isPreLayout()设置成false。

13.这个LayoutManager可以通过getHeight(),getHeightMode(),getWidth(),getWidthMode()测量规格。

14.在布局测量之后,RecyclerView设置被测量的宽度和高度通过计算绑定的盒子为子孩子。(加上RecyclerView的边距)。

15.LayoutManager可以重写setMeasuredDimension(Rect, int, int)去选择不同的值。举个例子,GridLayoutManager重写这个值去处理垂直有3列但是只有两个item的情况。他应该仍然测量他的宽度适用于3个item而不是两个。

16.任何跟随着对于RecyclerView的测量的方法将会运行onLayoutChildren(Recycler, State)用State#isMeasuring()设置成true并且State#isPreLayout()设置成false。RecyclerView将会注意那些views被真正的添加/移除/改变为了动画以至于LayoutManager应该不用担心他们并且处理每一个onLayoutChildren(Recycler,State)的调用就好像它是最后一个。

17.当测量完成了并且RecyclerView的onLayout(boolean, int, int, int, int)方法被调用,RecyclerView检查是否它已经做过了布局的计算在测量传递期间,如果它确实是这样的话,它重用那个信息。这将仍然决定去调用onLayoutChildren(Recycler, State)方法如果上一个测量规格是不同的从final的规格或者Adapter内容已经改变了在measure和layout方法调用之间。

18.最后,动画被计算并且运行如常。

public void setAutoMeasureEnabled(booleanenabled) {

           mAutoMeasure = enabled;

       }

 

//返回值:LayoutManager有没有使用自动测量的API

public boolean isAutoMeasureEnabled() {

           return mAutoMeasure;

       }

 

//注释未翻译

//大体意思:支持预布局item动画

public boolean supportsPredictiveItemAnimations(){

           return false;

       }

//注释未翻译

//大体意思:设置item预取为启用状态

public final voidsetItemPrefetchEnabled(boolean enable

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值