美文网首页
[安卓开发日记] 自定义View

[安卓开发日记] 自定义View

作者: chopperhl | 来源:发表于2020-04-08 01:11 被阅读0次

    一、自定义View三个步骤:onMeasure,onLayout,onDraw

    onMeasure 一定要在onLayout之前,之后再onLayout
    ViewGroup一般不用处理onDraw

    二、onMeasure方法,测量布局

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
    

    MeasureSpec

    MeasureSpec 是View的一个内部类,他可以用一个int同时表示一个宽度或者高度的mode和size
    一个int的32位,高两位表示mode,剩余30位表示size

    mode 包含 UNSPECIFIED, EXACTLY, AT_MOST,其值的大小如下

    private static final int MODE_SHIFT = 30;
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    public static final int EXACTLY     = 1 << MODE_SHIFT;
    public static final int AT_MOST     = 2 << MODE_SHIFT;
    

    调用MeasureSpec.makeMeasureSpec(int size,int mode) 可以计算出合并后MeasureSpec值

    三种模式的含义:UNSPECIFIED一般是类似ScrollView的高这种不确定值,EXACTLY一般是MatchParent和传入具体值的情况,AT_MOST一般是WRAP_CONTENT的情况,只有一个父布局限制他的最大值

    实际情况会比上面复杂这里贴出关键代码

    /**
      * specMode是自定义容器的mode
      * size是自定义容器的size
      * childDimension 是子ViewLayoutParams的宽或者高
      **/
    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 him 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;
            }
    

    Measure流程如下

    1. DecorView 调用measure(int widthMeasureSpec, int heightMeasureSpec)方法
    2. measure方法中,这里会处理measure缓存等问题,一般不需要覆写,默认会在这里调用onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    3. 如果是ViewGroup容器,则会先剔除Padding和Margin,然后计算出child的MeasureSpec并调用子View的measure(int widthMeasureSpec, int heightMeasureSpec)方法。子view又会走相同的流程形成递归
    4. 计算测量结果后,在onMeasure中调用setMeasuredDimension(int measuredWidth, int measuredHeight),来设置测量后的宽高。

    三、onLayout方法,定位View

    非容器的View一般不用实现onLayout方法

    @Override
    protected void onLayout(int left, int top, int right, int bottom) {
        super.onLayout(left, top, right, bottom);
    }
    

    这里从getPaddingLeft和getPaddingTop开始,遍历子View逐一计算它应该放置的left,right,top,bottom
    调用子View的onLayout,子View逐级调用onLayout。

    getWidth和getHeight要在onLayout结束后才能获取到值

    四、onDraw方法,绘制具体的显示逻辑

    一般容器View不需要实现onDraw方法

    @Override
    protected void onDraw(Canvas canvas) {
    
    }
    

    为了避免在动效类自定义View频繁调用onDraw方法,导致频繁GC,要提前在构造函数中初始化Paint,Path等对象,path可以利用reset方法使用, 不能在onDraw方法重复创建

    canvas.restore可以回到上次canvas.save的状态

    相关文章

      网友评论

          本文标题:[安卓开发日记] 自定义View

          本文链接:https://www.haomeiwen.com/subject/jpsgphtx.html