Recycler源码解读<一> layoutChunk 函数分析

详细解析了RecyclerView中的layoutChunk方法,这是RecyclerView布局流程中的关键部分。文章深入分析了如何从Recycler中取出View对象,进行布局、测量及定位的过程,并讨论了布局方向、View测量和空间消耗的计算。

此为recyclerView 布局中最终落脚方法了。先看整个源码。

    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
        View view = layoutState.next(recycler);
        if (view == null) {
            if (DEBUG && layoutState.mScrapList == null) {
                throw new RuntimeException("received null view when unexpected");
            }
            // if we are laying out views in scrap, this may return null which means there is
            // no more items to layout.
            result.mFinished = true;
            return;
        }
        LayoutParams params = (LayoutParams) view.getLayoutParams();
        if (layoutState.mScrapList == null) {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addView(view);
            } else {
                addView(view, 0);
            }
        } else {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addDisappearingView(view);
            } else {
                addDisappearingView(view, 0);
            }
        }
        measureChildWithMargins(view, 0, 0);
        result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
        int left, top, right, bottom;
        if (mOrientation == VERTICAL) {
            if (isLayoutRTL()) {
                right = getWidth() - getPaddingRight();
                left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
            } else {
                left = getPaddingLeft();
                right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
            }
            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                bottom = layoutState.mOffset;
                top = layoutState.mOffset - result.mConsumed;
            } else {
                top = layoutState.mOffset;
                bottom = layoutState.mOffset + result.mConsumed;
            }
        } else {
            top = getPaddingTop();
            bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);

            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                right = layoutState.mOffset;
                left = layoutState.mOffset - result.mConsumed;
            } else {
                left = layoutState.mOffset;
                right = layoutState.mOffset + result.mConsumed;
            }
        }
        // We calculate everything with View's bounding box (which includes decor and margins)
        // To calculate correct layout position, we subtract margins.
        layoutDecoratedWithMargins(view, left, top, right, bottom);
        if (DEBUG) {
            Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
                    + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
                    + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
        }
        // Consume the available space if the view is not removed OR changed
        if (params.isItemRemoved() || params.isItemChanged()) {
            result.mIgnoreConsumed = true;
        }
        result.mFocusable = view.isFocusable();
    }

*************************************************************************************************************************************************

开始源码分析:

        View view = layoutState.next(recycler); 从recycler 取出View对象。


        /**
         * Gets the view for the next element that we should layout.

         获取接下来需要布局的View。同时更新目前的item的索引。
         * Also updates current item index to the next item, based on {@link #mItemDirection}
         *
         * @return The next element that we should layout.
         */

        如果获取的view为空。那么直接返回了。意味着recyview已经布局完毕。这个地方是结束标志,重要。接下来就是正常的布局View的逻辑。
        if (view == null) {
            if (DEBUG && layoutState.mScrapList == null) {
                throw new RuntimeException("received null view when unexpected");
            }
            // if we are laying out views in scrap, this may return null which means there is
            // no more items to layout.
            result.mFinished = true;
            return;
        }

  实际情况,布局是测量的后一步。布局之前必须测量。子View还没添加进入ViewGroup,也没有测量。下面的步骤就是。  LayoutParams params = (LayoutParams) view.getLayoutParams();

获取View的布局参数。如果废弃的mScrapList  为空的逻辑。第一次布局为空。
        if (layoutState.mScrapList == null) { 

// 判断布局的方向,分为从顶部和从底部两个方向。
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addView(view);

//将View加入child 里面。暂时不分析里面的细节。
            } else {
                addView(view, 0);
            }
        } else {
            if (mShouldReverseLayout == (layoutState.mLayoutDirection
                    == LayoutState.LAYOUT_START)) {
                addDisappearingView(view);
            } else {
                addDisappearingView(view, 0);
            }
        }

//对View进行测量。实际上大家发现,这个过程把加入,测量,布局都融合一块了。细节暂时不分析。    

 measureChildWithMargins(view, 0, 0);

//测量完毕,分析此View 消耗了多少的空间。此处如果是纵向的布局,会分析此View占据的高度。加上padingTop和paddingBottom

result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);

 

  int left, top, right, bottom;
        if (mOrientation == VERTICAL) {
            if (isLayoutRTL()) {

              //垂直布局,但是方向是从右往做开始布局。
                right = getWidth() - getPaddingRight();
                left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
            } else {
                left = getPaddingLeft();
                right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
            }

           /./计算view布局的bottom和top值,那么需要检查布局的顺序是从上到下,还是从下到上。
            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                bottom = layoutState.mOffset;
                top = layoutState.mOffset - result.mConsumed;
            } else {

               //从上往下的布局。
                top = layoutState.mOffset;
                bottom = layoutState.mOffset + result.mConsumed;
            }
        } else { 

         //线性布局约束的水平方向。同理。不在分析。
            top = getPaddingTop();
            bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);

            if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
                right = layoutState.mOffset;
                left = layoutState.mOffset - result.mConsumed;
            } else {
                left = layoutState.mOffset;
                right = layoutState.mOffset + result.mConsumed;
            }
        }

     //开始对View进行layout。这一步之后View就布局彻底结束。

   layoutDecoratedWithMargins(view, left, top, right, bottom);

 // Consume the available space if the view is not removed OR changed

 //设置mIgnoreConsumed的值。此值最后啥用得返回到上层调用。后续章节分析。
        if (params.isItemRemoved() || params.isItemChanged()) {
            result.mIgnoreConsumed = true;
        }

// 设置是否聚焦。因为recycler的布局可以布局到聚焦点结束这么个变量。所以此处传出去。
        result.mFocusable = view.isFocusable();

此方法整体分析完毕了。后续会把recyview的布局和事件传递整体分析出来。

 

 

class MainViewModel(application: Application) : AndroidViewModel(application) { private val _appInfo = MutableLiveData<String>() val appInfo: LiveData<String> = _appInfo.apply { value = getAppInfo() } private fun getAppInfo(): String { val context = getApplication<AppApplication>().applicationContext var appInfo = AppUtils.getAppName(context) + ":v" + AppUtils.getVersionName(context) if (getAppReleaseTime().isNotEmpty()) { appInfo += "_" + getAppReleaseTime() } return appInfo } private fun getAppReleaseTime(): String { val appReleaseTime = "" val context = getApplication<AppApplication>().applicationContext context.packageManager.getApplicationInfo( "com.nxg.app", PackageManager.GET_META_DATA ).metaData.getString("RELEASE_TIME") return appReleaseTime } private val _doubleColorBallNum = MutableLiveData<MutableList<List<TextBean>>>() val doubleColorBallNum: LiveData<MutableList<List<TextBean>>> = _doubleColorBallNum private fun createDoubleColorBallNum(): MutableList<List<TextBean>> { val data = mutableListOf<List<TextBean>>() for (i in 0..6) { val numberList = mutableListOf<TextBean>() val bound = if (i < 6) { 33 } else { 16 } LogUtil.i("MainViewModel", "i = $i, bound $bound") for (j in 1..bound) { numberList.add( TextBean( if (j < 10) "0$j" else { j.toString() } ) ) } //LogUtil.i("MainViewModel", "i = $i, before $numberList") numberList.shuffle() //LogUtil.i("MainViewModel", "i = $i, after $numberList") data.add(i, numberList) } return data } fun refreshDoubleColorBallNum() { _doubleColorBallNum.value = createDoubleColorBallNum() } /** * 生成6个红色双色球号码 */ private fun createSixRedDoubleColorBall(): List<String> { val redBalls = mutableListOf<String>() for (j in 1..33) { redBalls.add( if (j < 10) "0$j" else { j.toString() } ) } LogUtil.i("MainViewModel", "createSixRedDoubleColorBall before $redBalls") redBalls.shuffle() LogUtil.i("MainViewModel", "createSixRedDoubleColorBall shuffle after $redBalls") val balls = redBalls.subList(0, 6) LogUtil.i("MainViewModel", "createSixRedDoubleColorBall subList after $balls") balls.sort() LogUtil.i("MainViewModel", "createSixRedDoubleColorBall sort after $balls") return balls } /** * 生成1个蓝色双色球号码 */ private fun createSingleBlueDoubleColorBall(): String { val blueBall = Random().nextInt(16) + 1 LogUtil.i("MainViewModel", "createSingleBlueDoubleColorBall $blueBall") return if (blueBall < 10) "0$blueBall" else { blueBall.toString() } } /** * 随机生成6个红色双色球号码+1个蓝色双色球号码 */ fun createSevenDoubleColorBall(): List<String> { val balls = mutableListOf<String>() val redBalls = createSixRedDoubleColorBall() balls.addAll(redBalls) val blueBall = createSingleBlueDoubleColorBall() balls.add(blueBall) LogUtil.i("MainViewModel", "createSevenDoubleColorBall $balls") return balls } } 转Android java
最新发布
06-18
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值