首先了解view的绘制流程:
所以onmeasure ---测量view
onlayout---确定view大小----》所以继承ViewGroup必须要重写onlayout,确定子view
而onDraw----是继承view时候需要操作的。
所以:自定义ViewGroup一般是利用现有的组件根据特定的布局方式来组成新的组件。
自定义View,一般是没有现成的view
序列化,大概有这个意思,不一定对。
自定义序列化: IOT
协议比如:物联网:蓝牙:传递的数据 串口 协议:
onmeasure的测量 是先从子布局开始还是先从父布局开始的?
----根据算法来控制的,比如view pageer就是父布局开始
MeasureSpec是什么
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/** @hide */
@IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
@Retention(RetentionPolicy.SOURCE)
public @interface MeasureSpecMode {}
----是view里面的一个类---我们知道int 是32位
------上面代码里的30,就是高两位是00,后面30位---》所以这组成里MeasureSpec
-------高两位表示UNSPECIFIED,EXACTLY,AT_MOST
关于getChildMeasureSpec(int spec, int padding, int childDimension)算法
第一个参数,父亲给的,
第二个参数,父亲的
第三个参数,孩子需要的
-----》根据UNSPECIFIED,EXACTLY,AT_MOST来计算
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let them have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
下面是一个流式布局的例子:
* desc : 官方FlexboxLayout 流式布局
*/
class FlowLayout(context: Context) : ViewGroup(context) {
private val mHorizontalSpacing = dp2px(16f) //每个item横向间距
private val mVerticalSpacing = dp2px(8f) //每个item竖向间距
private var allLines:MutableList<MutableList<View>> = ArrayList<MutableList<View>>() //记录所有的行,一行一行保存,用于layout
var lineHeights:ArrayList<Int> = ArrayList()//记录每一行的行高,用于layout
constructor(context: Context,attrs:AttributeSet):this(context){
// 在这里处理从 XML 布局 --序列化格式--建值对
}
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : this(context, attrs) {
// 在这里处理从 XML 布局文件中传入的属性和样式
}
//初始化,因为是onMeasure递归的方式,所以要放在onMeasure
private fun clearMeasureParams(){
// allLines =ArrayList<MutableList<View>>()
// lineHeights = ArrayList()
allLines.clear()
lineHeights.clear()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
clearMeasureParams()// 内存抖动
// super.onMeasure(widthMeasureSpec, heightMeasureSpec)
//先度量孩子
val childCount =childCount
//ViewGroup解析父控件给我的宽高
val selfWidth = MeasureSpec.getSize(widthMeasureSpec)
val selfHeight =MeasureSpec.getSize(heightMeasureSpec)
//measure过程中 子view要求的父viewgroup的宽高
var parentNeededWidth =0;
var parentNeededHeight =0;
val linView = ArrayList<View>()
var lineWidthUsed =0 //记录这一行的以及使用了多宽的size
var lineHeight =0 //一行的杭高
for (i in 0 until childCount){
val childview = getChildAt(i)
val childlayoutParams = childview.layoutParams
if (childview.visibility != GONE) {
val childWidthMeasureSpec = getChildMeasureSpec(
widthMeasureSpec,
paddingLeft + paddingRight,
childlayoutParams.width
)
val childHeightMeasureSpec1 = getChildMeasureSpec(
heightMeasureSpec,
paddingTop + paddingBottom,
childlayoutParams.height
)
childview.measure(childWidthMeasureSpec, childHeightMeasureSpec1)
//获取子view的度量高度
val childMeasureWidth = childview.measuredWidth
val childmeasuredHeight = childview.measuredHeight
linView.add(childview)
//是否药换行
if (childMeasureWidth + lineWidthUsed + mHorizontalSpacing > selfWidth) {
//换行判断当前行所需要的宽和高,所以要记录下来
allLines.add(linView)
lineHeights.add(lineHeight) //行高。。这个if语句会缺少最后一行,
parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing
parentNeededWidth =
Math.max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing)
linView.clear()
lineWidthUsed = 0
lineHeight = 0
}
//每行自己的宽高
lineWidthUsed = lineWidthUsed + childMeasureWidth + mHorizontalSpacing
lineHeight = Math.max(lineHeight, childmeasuredHeight)
//最后一行,
if (i == childCount - 1) {
allLines.add(linView)
lineHeights.add(lineHeight)
parentNeededHeight = parentNeededHeight + lineHeight + mVerticalSpacing
parentNeededWidth =
Math.max(parentNeededWidth, lineWidthUsed + mHorizontalSpacing)
}
}
}
//度量自己
//根据子view的度量结果,来重新度量自己的viewGroup
//作为一个viewgroup,他自己也是个view,他的大小也需要根据他的父亲提供的宽高来度量
var withmode: Int = MeasureSpec.getMode(widthMeasureSpec)
var heightmode: Int = MeasureSpec.getMode(heightMeasureSpec)
//确切的值 EXACTLY
val realWidth =if(withmode ==MeasureSpec.EXACTLY) selfWidth else parentNeededWidth
val realHeight =if(heightmode ==MeasureSpec.EXACTLY) selfHeight else parentNeededWidth
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.get(i)
val lineHeight = lineHeights.get(i)
for (j in 0 until Lineviews.size){//其中一行
val view = Lineviews.get(j)//这一行的view
//计算边界
val left =curl //左边届
val top= curT //上边界
val right = left+measuredWidth//左边届
val botton = top +measuredHeight //上边界
view.layout(left,top,right,botton)
curl =right+mHorizontalSpacing//下一个左边
}
curT =curT +lineHeight+mVerticalSpacing //下一个top
curl = paddingLeft //每一个新的行要重制左边届
}
// view.layout(left,top,right,bottom)
}
}