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

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

onMeasure

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

// 定义mWidth和mHeight
int mWidth = 200;
int mHeight = 100;
// 重写onMeasure方法
@Override
public 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的方法。

// 这里用伪代码只做演示,请自行修改
@Override
public 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的滑动冲突处理一节。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值