Android 自绘控件开发探索1

Android 自绘控件,听起来是高大上的对吧,然鹅,真正开发起来是一件非常苦逼的事情。对于初学者来说,总是有这样或者那样的坑等着去踩,为了以后少踩些坑,特奉上自绘控件的一些心得,有不对的地方欢迎大神批评指正0.0

首先,你需要知道屏幕坐标系是个什么鬼,不知道坐标系很容易被各种坐标值搞晕:


坐标系统以左上角为原点,右方向为x正向,下方向为y正向。获取左上右下的坐标,是显示区域左上右下边界的坐标。相对坐标是相对于父控件的坐标,绝对坐标是相对于屏幕坐标系的坐标。

控件,说白了就是一块有特定功能的显示区域。那么问题来了,从你的代码开始,它是如何显示在屏幕上的呢?

拢共分三步:1测量,2布局,3绘制。


1:measure 测量大小,你首先需要知道这个控件要多大的显示区域。对于不包含子view的自绘控件(也就是原始view)来说,measure过程是测量自身大小;对于ViewGroup,除测量自身,还需调用子view的measure方法。so,measure是一个递归的过程,只有所有子View的大小知道了,父控件的大小才能确定。

measure 方法是一个final类型的方法,其内部会调用onMeasure方法, onMeasure中会设置view宽高的测量值。xml中对控件大小有三种设定,精确值,wrap_Content,match_Parent, 对于wrapContent和match_Parent,安卓采用AT_MOST即最大值模式;对于精确值采用EXACTLY即精确值模式。当自定义控件使用wrap_Content,其测量模式为ATMOST,在这种测量模式下,它的宽高为parentSize。所以当自绘控件使用wrap_Content时,等同于使用了match_Parent。

那么如何解决呢?我的view就不能用wrap_Content了吗 ,当然可以,附上方案:

重写onMeasure方法,判断测量模式是啥,如果是ATMOST,说明用户采用了wrapContent,这个时候设置你的宽高就行。


 

/**
 * 使view能支持wrapcontent
 *
 * @param widthMeasureSpec
 * @param heightMeasureSpec
 */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(mWidth, mHeight);
    } else if (widthSpecMode == MeasureSpec.AT_MOST)
        setMeasuredDimension(mWidth, heightMeasureSpec);
    else if (heightSpecMode == MeasureSpec.AT_MOST)
        setMeasuredDimension(widthMeasureSpec, mHeight);


}


上面的mHeight和mWidth就是wrap_Content时的测量大小。
刚才我一直再说测量大小而非最终大小,因为最终大小是在layout阶段确定的,但几乎所有情况下测量大小就是最终大小。有好事儿的问了,什么情况下不等于嘞?。。。这个我先
挖个坑,等谈layout的时候再讲。
测量完毕之后,就可以获得测量后的宽高值。
2:layout过程将确定在屏幕上显示的位置。layout方法会确定自身四个顶点的位置,确定了四个顶点,就确定了自身的位置。接着会调用onlayout方法确定子view的位置。so,与
测量过程相反,布局过程是从上而下的。



 回答上面好事的人的问题,啥时候测量大小不等于最终大小,就是我们强行找事的时候:


布局结束后,就可以获得最终显示的宽高值。

3: 绘制过程,绘制过程将把view绘制到屏幕上。

步骤: 绘制背景(background.draw)----> 绘制自身(onDraw)----->绘制子视图(dispatchDraw)----->绘制装饰(onDrawScrollBars)

绘制过程告诉我们,view会通过dispatchDraw方法将绘制任务一层一层的下发。

在这里有个坑,view的ondraw方法中啥也没写,这意味着如果你xml中定义了padding,就得自己让他们有效。具体做法是在ondraw方法中获取padding,获取测量宽高或最终宽高相减,得到显示区域的宽高。

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //使view支持padding
        final int paddingLeft = getPaddingLeft();
        final int paddingRight = getPaddingRight();
        final int paddingTop = getPaddingTop();
        final int paddingButtom = getPaddingBottom();

        this.__Left = paddingLeft;
        this.__Top = paddingTop;
        this.__Right = getWidth() - paddingRight;
        this.__Bottom = getHeight() - paddingButtom;

        this.mHeight = __Bottom - __Top;
        this.mWidth = __Right - __Left;
    }



这样,view就支持了padding._left _right _top _buttom是padding之后的绘制区域边界,如果你的view还需要留白,自己去设置。

还有view常用到的自定义属性:

1:在attrs.xml中新建,name与自定义view相同

<declare-styleable name="MyView_ArcScale">  
        <attr name="inCircleColor" format="reference" />  
        <attr name="outArcColor" format="reference" />  
        <attr name="testColor" format="reference" />  
        <attr name="outArcWidth" format="dimension" />  
        <attr name="testSize" format="dimension" />  
        <attr name="degreeStart" format="integer" />  
  
    </declare-styleable>
2:在布局文件中引用,需使用全路径
<com.example.administrator.view.mywidgit.myview.MyView_ArcScale  
        android:layout_width="wrap_content"  
        android:layout_height="wrap_content"  
        MyView_ArcScale:inCircleColor="@android:color/holo_purple"  
        MyView_ArcScale:outArcColor="@android:color/holo_blue_light"  
3:在自绘控件中获取
final TypedArray typedArray = context.obtainStyledAttributes(attrs,  
                R.styleable.MyView_ArcScale);  
        this.inCricleColor = typedArray.getColor(R.styleable.MyView_ArcScale_inCircleColor, inCricleColor);  
        this.outArcColor = typedArray.getColor(R.styleable.MyView_ArcScale_outArcColor, outArcColor);  
        this.textColor = typedArray.getColor(R.styleable.MyView_ArcScale_testColor, textColor);  
        this.outArcWidth = typedArray.getDimension(R.styleable.MyView_ArcScale_outArcWidth, outArcWidth);  
        this.startDegree = typedArray.getInt(R.styleable.MyView_ArcScale_degreeStart, startDegree);  
        this.testSize = typedArray.getDimension(R.styleable.MyView_ArcScale_testSize, testSize);  
        typedArray.recycle();//回收资源  


这样,view支持padding,wrapconent,还支持自定义属性,应该能满足基本要求了。

附上MyStandardView:

public abstract class MyStandardView extends View {


    private int mHeight = 100;
    private int mWidth = 100;

    private int __Top;
    private int __Left;
    private int __Right;
    private int __Bottom;


    public MyStandardView(Context context) {

        this(context, null, 0);
    }

    public MyStandardView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }


    public MyStandardView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mHeight = getDefaultHeight();
        mWidth = getDefaultWidth();


    }


    /**
     * 绘制区域的上边界
     * @return
     */
    protected final int get__Top() {
        return __Top;
    }
    /**
     * 绘制区域的左边界
     * @return
     */
    protected final int get__Left() {
        return __Left;
    }
    /**
     * 绘制区域的右边界
     * @return
     */
    protected final int get__Right() {
        return __Right;
    }
    /**
     * 绘制区域的下边界
     * @return
     */
    protected final int get__Bottom() {
        return __Bottom;

    }

    /**
     * 获取wrapContent时的高度
     *
     * @return
     */
    protected abstract int getDefaultHeight();

    /**
     * 获取wrapContent时的宽度
     *
     * @return
     */
    protected abstract int getDefaultWidth();

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //使view支持padding
        final int paddingLeft = getPaddingLeft();
        final int paddingRight = getPaddingRight();
        final int paddingTop = getPaddingTop();
        final int paddingButtom = getPaddingBottom();
 
        this.__Left = paddingLeft;
        this.__Top = paddingTop;
        this.__Right = getWidth() - paddingRight;
        this.__Bottom = getHeight() - paddingButtom;

        this.mHeight = __Bottom - __Top;
        this.mWidth = __Right - __Left;
    }


    /**
     * 获取layout过程之后控件的高度,即最终高度
     * 仅在layout之后调用有效
     *
     * @return
     */
    protected final int getFinalHeight() {
        return this.mHeight;
    }

    /**
     * 获取layout过程之后控件的宽度,即最终宽度
     * 仅在layout之后调用有效
     *
     * @return
     */
    protected final int getFinalWidth() {
        return this.mWidth;
    }

    /**
     * 使view能支持wrapcontent
     *
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(mWidth, mHeight);
        } else if (widthSpecMode == MeasureSpec.AT_MOST)
            setMeasuredDimension(mWidth, heightMeasureSpec);
        else if (heightSpecMode == MeasureSpec.AT_MOST)
            setMeasuredDimension(widthMeasureSpec, mHeight);


    }


}

























  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值