Android开发艺术探索笔记(15)- 自定义View

来源:互联网 发布:清华大学材料学院知乎 编辑:程序博客网 时间:2024/05/16 05:42

View的原理学习的差不过了,是时候可以自定义View。记得,3个步骤,测量(onMeasure)- 布局(onLayout)- 绘画(onDraw)。

onMeasure

测量上一节已经学过onMeasure方法的主要参数MeasureSpec,我们主要根据MeasureSpec得到的mode和size,测量View的宽高。

// 定义mWidth和mHeightint mWidth = 200;int mHeight = 100;// 重写onMeasure方法@Overridepublic void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    super.onMeasure(widthMeasureSpec, heightMeasureSpec);    int widthMode = MeasureSpec.getMode(widthMeasureSpec);    int widthSize = MeasureSpec.getSize(widthMeasureSpec);    int heightMode = MeasureSpec.getMode(heightMeasureSpec);    int heightSize = MeasureSpec.getSize(heightMeasureSpec);    /**     * 因为View的LayoutParams是wrap_content,就是AT_MOST模式,如果父容器是match_parent,那么View的模式也会跟着父容器,为match_content,所以wrap_content会不起作用,所以我们自己设置大小。mWidth和mHeight可以根据实际情况而定。     **/    if (widthMode == MeasureSpec.AT_MOST && heightMeasureSpec == MeasureSpec.AT_MOST) {        setMeasuredDimension(mWidth, mHeight);    } else if(widthMode == MeasureSpec.AT_MOST) {        setMeasuredDimension(mWidth, heightSize);    } else if(heightMeasureSpec == MeasureSpec.AT_MOST) {        setMeasuredDimension(widthSize, mHeight);    }}

如果我们需要重新调用View的onMeasure方法,可以调用requestLayout方法即可。

要想在onCreate或者onResume直接调用getWidth/getHeight获取View的大小,貌似是不行的。作者体用了几种常用的方法:

(1)利用onWindowFocusChanged()方法获取

public void onWindowFocusChanged(boolean hadFocus) {    if (hadFocus) {        // 此时View已经初始化完毕了        int width = view.getMeasuredWidth();        int height = view.getMeasureHeight();    }}

(2)利用view.post(runnable)方法获取

// 将消息加到消息队列最后,此时view也已经初始化完毕,也就是在onCreate和onResume之后才获取view的大小view.post(new Runnable() {    int width = view.getMeasuredWidth();    int height = view.getMeasureHeight();});

我认为这种方式是最简单的。

(3)ViewTreeObserver

ViewTreeObserver observer = view.getViewTreeObserver();observer.addOnGlobalLayoutLisetner(new OnGlobalLayoutLisetner(){    public void onGlobalLayout() {        // 一定要移除监听,否则会执行很多遍        view.getViewTreeObserver().removeGlobalLayoutLisetner(this);        int width = view.getMeasuredWidth();        int height = view.getMeasureHeight();    }});

这种方式是作者推荐使用的一种方法。

(4)view.measure(int widthMeasureSpec, int heightMeasureSpec)

之前百度过有这个方法,这里作者指出这个方法可以用,但是要谨慎,分分钟获取不到值,so就不记录了。

onLayout

onLayout方法用于布局,一般自定义Viewgroup时才需要重写该方法,确定子View的位置,子View只有layout方法(确定自己位置)。onLayout会遍历子View,调用其layout方法确定各个子View的方法。

// 这里用伪代码只做演示,请自行修改@Overridepublic void onLayout(boolean isChanged, int l, int t, int r, int b) {    int childCount = getChildCount();    for(int i = 0; i < childCount; i++) {        View childView = getChildAt(i);        if (childView.getVisibility() == GONE) {            continue();        }        // 计算ChildView左上角的x坐标        int left = caculateChildLeft();        // 计算ChildView左上角的y坐标        int top = caculateChildTop();        // 确定ChildView的位置        childView.layout(left, top, left + viewWidth, top + viewHeight);    }}

我在慕课网的Android自定义View学到,应该把比较耗时的操作尽量延迟到onLayout这个方法中,因为onMeasure方法会执行多次,而onLayout方法只会执行一次,比较轻量级。如果想重新调用onLayout方法,也是调用View的requestLayout方法(调用requestLayout方法时,会调用View的onLayout方法和onMeasure方法进行重新测量和布局)。

onDraw

onDraw用于绘画,一般用在自定义View(在自定义ViewGroup时一般不进行绘画)。学会怎么使用Canvas.drawXXX方法进行绘制就行。可以配合translate、rotate等进行一些动画,不过要记得使用save和restore来进行动画状态的操作,还有重要的一点,就是如果Activity销毁时,也就是自定义View被移除时,记得要停止动画,如果不这样的话,会造成内存泄露(特别注意)。在View被移除时或者Activity销毁时,会调用View的onDetachedFromWindow方法,我们重写这个方法,停止动画或者销毁一些线程,回收资源就可以避免内存泄露问题啦。

还有一点,就是我们没有必要在自定义View使用handler。View已经提供了子线程更新View的机制,就是使用postInvalidate方法。如果需要在ui线程更新View,可以使用invalidate方法。

自定义View时的注意事项

(1)让View支持wrap_content

这个问题在上面的onMeasure中已经说的很清楚了。

(2)如果有必要,让你们View支持padding

如果是自定义View,如果需要padding,那么在onDraw方法中处理padding。可以在onDraw方法中利用getPaddingLeft等方法获取到padding的值,然后canvas.drawXXX方法时,计算位置加上一个padding即可。如果是自定义ViewGroup,如果需要padding,在重写onLayout和onMeasure方法计算padding值。

(3)尽量在View中不要使用Handler,没必要

上面onDraw中已经说明白了。

(4)View如果有线程或者动画,需要及时停止和释放资源

在上面onDraw也已经说明白了。

(5)View如果带有滑动嵌套情形时,需要处理好滑动冲突。

具体处理方法参考我之前写的View的滑动冲突处理一节。
0 0