android 怎么自定义view

首先了解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)
    }
}