Android 自定义 View 总结

##ViewRoot,WindowManager 和 DecorView 的关系
ViewRoot 对应于 ViewRootImpl 类,它是连接 WindowManager 和 DecorView 的纽带,View 的三大流程是通过 ViewRoot 来完成的;

##View 的绘制过程

performTraversals ->

performMeasure -> measure -> onMeasure ->

performLayout -> layout -> onLayout ->

performDraw -> draw -> onDraw

在没有重写的情况下,上述加粗方法的作用:

\ViewViewGroup
measurefinal,测量准备工作final,测量准备工作
onMeasure测量自身,没有处理 wrap_content测量自身,没有处理 wrap_content(继承自 View)
layout定位自身定位自身
onLayout没有实现没有实现

而我们需要实现的效果如下:

对于 View:
onMeasure 测量自身(同时处理 wrap_content),layout 定位自身;

对于 ViewGroup:
onMeasure 测量自身(同时处理 wrap_content)及所有子 View,layout 定位自身,onLayout 定位所有子 View;

因此,我们需要…

##自定义 View 需要重写的方法

View 的 onMeasure:需要处理 wrap_content 情况;
ViewGroup 的 onMeasure:需要测量所有子 View 的大小,同时需要处理 wrap_content 情况;
ViewGroup 的 onLayout:需要确定所有子 View 的位置;
(View 的 onLayout 虽然本身没有实现,但是因为 onLayout 负责测量所有子 View 的位置,而 View 没有子 View,因此无需重写)

##何时可以拿到 View 的信息

measure 过程之后,可以拿到测量宽 / 高:
· getMeasuredWidth
· getMeasuredHeight

layout 过程之后,可以拿到顶点位置和最终宽 / 高:
· getTop
· getBottom
· getLeft
· getRight
· getWidth
· getHeight

##MeasureSpec

· 自身大小的一个参考,由 mode(UNSPECIFIED、EXACTLY、AT_MOST)和 size 组成;
· 在 onMeasure 中传进去;
· 由 View 的 LayoutParams 和父容器的 MeasureSpec(SpecMode)共同决定:

childLayoutParams \ parentSpecModeEXACTLYAT_MOSTUNSPECIFIED
dp/pxEXACTLY childSizeEXACTLY childSizeEXACTLY childSize
match_parentEXACTLY parentSizeAT_MOST parentSizeUNSPECIFIED 0
wrap_contentAT_MOST parentSizeAT_MOST parentSizeUNSPECIFIED 0

子 View 的 mode 是 AT_MOST,子 View 要么是 wrap_content,要么是 match_parent 的第二种。
如果是 wrap_content,按照 wrap_content 处理;
如果是 match_parent 的第二种,说明它父 View 的 mode 是 AT_MOST,那么它的父 View 要么是 wrap_content,要么是 match_parent 的第二种,形成一个循环。
最终在 xml 中会形成一种 wrap_content -> match_parent -> match_parent … -> match_parent 的结构,match_parent 可能有一个,也可能有很多个。
这时,一个 wrap_content -> match_parent 结构,后面的 match_parent 可以转换成 wrap_contnet,即上面的结构可以转换成 wrap_contnet -> wrap_content … -> wrap_contnet。
因此,所有 match_parent 的第二种都可以按照 wrap_content 处理。

注意逻辑:a -> b =\= b -> a

##onMeasure 处理 wrap_content 情况

private int mWidth = 200;
private int mHeight = 200;

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
    if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(mWidth, mHeight);
    } else if (widthSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(mWidth, heightSpecSize);
    } else if (heightSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(widthSpecSize, mHeight);
    }
}

##关于测量宽 / 高的取值

在 onCreate,onStart 和 onResume 中无法取到测量宽 / 高的值,这是因为 View 的 measure 过程和 Activity 的生命周期不是同步执行的:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Log.d("Guu", "onCreate mCustomView: width->" + mCustomView.getWidth()
            + " height->" + mCustomView.getHeight());
}

@Override
protected void onStart() {
    super.onStart();
    Log.d("Guu", "onStart mCustomView: width->" + mCustomView.getWidth()
            + " height->" + mCustomView.getHeight());
}

@Override
protected void onResume() {
    super.onResume();
    Log.d("Guu", "onResume mCustomView: width->" + mCustomView.getWidth()
            + " height->" + mCustomView.getHeight());
}

/* 输出结果:

12-24 23:38:21.928 13829-13829/com.example.customview D/Guu: onCreate mCustomView: width->0 height->0
12-24 23:38:21.929 13829-13829/com.example.customview D/Guu: onStart mCustomView: width->0 height->0
12-24 23:38:21.931 13829-13829/com.example.customview D/Guu: onResume mCustomView: width->0 height->0

*/

以下三种方法可以取到测量宽 / 高的值:

// 1. 通过 onWindowFocusChanged
@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    if (hasFocus) {
        Log.d("Guu", "onWindowFocusChanged mCustomView: width->" + mCustomView.getWidth()
                + " height->" + mCustomView.getHeight());
    }
}

@Override
protected void onStart() {
    super.onStart();

	// 2. 通过消息队列
    mCustomView.post(new Runnable() {
        @Override
        public void run() {
            Log.d("Guu", "loop mCustomView: width->" + mCustomView.getWidth()
                    + " height->" + mCustomView.getHeight());
        }
    });

	// 3. 通过 ViewTreeObserver
    ViewTreeObserver observer = mCustomView.getViewTreeObserver();
    observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @TargetApi(16)
        @Override
        public void onGlobalLayout() {
            mCustomView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            Log.d("Guu", "onGlobalLayout mCustomView: width->" + mCustomView.getWidth()
                    + " height->" + mCustomView.getHeight());
        }
    });
}

/* 输出结果:

12-24 23:38:21.984 13829-13829/com.example.customview D/Guu: onGlobalLayout mCustomView: width->200 height->200
12-24 23:38:22.016 13829-13829/com.example.customview D/Guu: loop mCustomView: width->200 height->200
12-24 23:38:22.016 13829-13829/com.example.customview D/Guu: onWindowFocusChanged mCustomView: width->200 height->200

*/

##九宫格(ViewGroup)示例代码

PS:本例没有在 onMeasure 中处理 wrap_content 情况;

public class NineSquareViewGroup extends ViewGroup {

    private static final int PADDING = 100;
    private static final int NUM_PER_ROW = 3;

    public NineSquareViewGroup(Context context) {
        super(context);
    }

    public NineSquareViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public NineSquareViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);

            LayoutParams lp = child.getLayoutParams();

            int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
            int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);

//            // 可以自定义子 View 的大小
//            int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);
//            int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);

            if (child.getVisibility() != GONE) {
                child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
            }
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int count = getChildCount();
        for (int i = 0; i < count; i++) {
            View child = getChildAt(i);

            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();

            int left = (childWidth + PADDING) * (i % NUM_PER_ROW) + PADDING;
            int top = (childHeight + PADDING) * (i / NUM_PER_ROW) + PADDING;

            if (child.getVisibility() != GONE) {
                child.layout(left, top, left + childWidth, top + childHeight);
            }
        }
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值