view绘制流程学习总结

学习笔记
  1. 启动Activity时,在ActivityThread#HandleResumeActivity把DecoreView添加进window然后用ViewRootImpl#setView将DecoreView和viewRootImpl建立关联,在setView中调用requestLayout开始进行View绘制流程
    具体调用 requestLayout ->scheduleTraversals(设置标记,拦截多余的刷新事件-> 建立同步屏障,为界面刷新做准备,将view绘制的事件包装成一个runable回调交给Choreographer,就是doTraversals方法封装成回调交给Choreographer>
    Choreographer#postCallback ->postCallbackDelayed ->postCallbackDelayedInternal -> scheduleFrameLocked ->scheduleVsyncLocked 直到scheduleVsyncLocked订阅了屏幕刷新信号
    当下一次屏幕刷新信号来时(这里有个常见误区,不是invalidate一调用就会立即绘制,而是先注册屏幕刷新信号,等下一帧信号来的时候才会进行绘制),会回调到Choreographer#doFrame,然后执行到上面的回调也就是ViewRootImpl#doTraversals(这里重置标志,清除同步屏障)
    到现在正式开始绘制流程doTraversals -> performTraversals ->perforomMeasure -> measure ->onMeasure ->performLayout->layout-> onLayout-> performDraw -> draw -> dispatchOnDraw ->onDraw

  2. measure过程 是在 View#onMeasure中通过调用setMeasureDimension设置了测量值
    而layout过程 是在View#layout中通过setFrame设置好了布局位置,
    从调用时机看,measure过程是先设置好子View在设置父View,而layout过程恰好相反

  3. 自定义viewGrop一般需要去重写onMeasure方法,用来测量子view和自身的宽高,重写onLayout方法去给子View设置布局位置

  4. ViewGrop#measureChildren -> measureChild -> getChildMeasureSpec
    这几个方法是自定义view是可能会用到的重要方法
    measureChildren 该方法等同于 遍历ViewGroup,用getChildMeasureSpec获取到它的测量宽高,然后调用measure测量子View

  • SDK源码
   // ViewGroup 
    protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }
    protected void  measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
  1. 重写onMeasure时 setMeasureDimension方法必须要被调用到,这个方法是真正用于设置view的测量值,不调用会报错

  2. onMeasure会接收两个measureSpec参数,代表父View给它的建议大小,它是一个32位的Int值,由前2位的specMode和后30位的specSize组合而成,specMode一共有三种类型,AT_MOST,EXACTLY,UNSPECIFIFD,就是由getChildMeasureSpec根据父容器的MeasureSpec和自身LayoutParams共同得出,这里DecoreView计算方式有点不一样,是根据ViewRootImpl#getRootMeasureSpec,因为它自己是顶级View了,通过屏幕宽高和自身·LayoutParams·得出

实战代码,实现流式布局
class FlowLayout(context: Context, attrs: AttributeSet?) : ViewGroup(context, attrs) {
    private var mHorizontalSpacing: Int = 0                 //每个item横向间距
    private var mVerticalSpacing: Int = 0                    //每个item横向间距
    private var allLines: MutableList<List<View>> = ArrayList() // 记录所有的行,一行一行的存储,用于layout
    private var lineHeights: MutableList<Int> = ArrayList() // 记录每一行的行高,用于layout


    init {
        initAttrs(attrs)
    }

    private fun initAttrs(attrs: AttributeSet?) {
        val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.FlowLayout, 0, 0)
        mHorizontalSpacing =
            typedArray.getDimension(R.styleable.FlowLayout_flowLayout_horizontalSpacing, dp2px(DEFAULT_HORIZONTAL_SPACING)).toInt()
        mVerticalSpacing =
            typedArray.getDimension(R.styleable.FlowLayout_flowLayout_verticalSpacing, dp2px(DEFAULT_VERTICAL_SPACING)).toInt()
        typedArray.recycle()
    }


    private fun clearMeasureParams() {
        allLines.clear()
        lineHeights.clear()
    }

    //度量
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        clearMeasureParams() //内存 抖动
        //先度量孩子
        val selfWidth = MeasureSpec.getSize(widthMeasureSpec)
        val selfHeight = MeasureSpec.getSize(heightMeasureSpec)
        var lineViews: MutableList<View> = ArrayList() //保存一行中的所有的view
        var lineWidthUsed = 0 //记录这行已经使用了多宽的size
        var lineHeight = 0 // 一行的行高
        var parentNeededWidth = 0 // measure过程中,子View要求的父ViewGroup的宽
        var parentNeededHeight = 0 // measure过程中,子View要求的父ViewGroup的高

        measureChildren(widthMeasureSpec,heightMeasureSpec)

        for (i in 0 until childCount) {
            val childView = getChildAt(i)

            if (childView.visibility != GONE) {

                //  获取子view的度量宽高
                val childMeasuredWidth = childView.measuredWidth
                val childMeasuredHeight = childView.measuredHeight

                //如果需要换行
                if (childMeasuredWidth + lineWidthUsed + mHorizontalSpacing > selfWidth) {

                    //一旦换行,我们就可以判断当前行需要的宽和高了,所以此时要记录下来
                    allLines.add(lineViews)
                    lineHeights.add(lineHeight)
                    parentNeededHeight += lineHeight + mVerticalSpacing
                    parentNeededWidth = max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing)
                    lineViews = ArrayList()
                    lineWidthUsed = 0
                    lineHeight = 0
                }
                // view 是分行layout的,所以要记录每一行有哪些view,这样可以方便layout布局
                lineViews.add(childView)
                //每行都会有自己的宽和高
                lineWidthUsed += childMeasuredWidth + mHorizontalSpacing
                lineHeight = max(lineHeight, childMeasuredHeight)

                //处理最后一行数据
                if (i == childCount - 1) {
                    allLines.add(lineViews)
                    lineHeights.add(lineHeight)
                    parentNeededHeight += lineHeight + mVerticalSpacing
                    parentNeededWidth = max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing)
                }
            }
        }

        //再度量自己,保存
        //根据子View的度量结果,来重新度量自己ViewGroup
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        val realWidth = if (widthMode == MeasureSpec.EXACTLY) selfWidth else parentNeededWidth
        val realHeight = if (heightMode == MeasureSpec.EXACTLY) selfHeight else parentNeededHeight
        setMeasuredDimension(realWidth, realHeight) //设置自己的宽高
    }

    //布局
    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        val lineCount = allLines.size
        var curL = paddingLeft
        var curT = paddingTop
        for (i in 0 until lineCount) {
            val lineViews = allLines[i]
            val lineHeight = lineHeights[i]
            for (j in lineViews.indices) {
                val view = lineViews[j]
                val left = curL
                val top = curT

                val right = left + view.measuredWidth
                val bottom = top + view.measuredHeight
                view.layout(left, top, right, bottom)
                curL = right + mHorizontalSpacing
            }
            curT += lineHeight + mVerticalSpacing
            curL = paddingLeft
        }
    }

    companion object {
        private const val TAG = "FlowLayout"
        private const val DEFAULT_HORIZONTAL_SPACING = 16f
        private const val DEFAULT_VERTICAL_SPACING = 8f
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值