学习笔记
-
启动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
-
measure过程 是在
View#onMeasure
中通过调用setMeasureDimension
设置了测量值
而layout过程 是在View#layout
中通过setFrame
设置好了布局位置,
从调用时机看,measure过程是先设置好子View在设置父View,而layout过程恰好相反 -
自定义viewGrop一般需要去重写
onMeasure
方法,用来测量子view和自身的宽高,重写onLayout方法去给子View设置布局位置 -
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);
}
-
重写onMeasure时
setMeasureDimension
方法必须要被调用到,这个方法是真正用于设置view的测量值,不调用会报错 -
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
}
}