自定义View初级

来源:互联网 发布:日程提醒软件 编辑:程序博客网 时间:2024/06/10 19:37

对于自定义View,一开始用的都是别人的,当时刚学Android的时候,想到自定义View,觉得是挺难的,也比较抵触,不愿意去看,到后来觉得,不可能一直用别人的,得学会自己开发,然后,就试着学了。我学习的历程,首先肯定是在网上查阅资料,看博客,看视频,跟着敲代码,然后,就自己试着写了。

下面来写一个比较简单易懂的例子,实现一个类似TextView的控件:

  • 自定义属性:

    为一个View提供可自定义的属性非常简单,只需要在res资源目录的values目录下创建一个attr.xml属性定义文件,并在该文件中通过如下定义相应的属性即可。

<declare-styleable name="MyTextView">    <attr name="title_text" format="string" />    <attr name="title_color" format="color" />    <attr name="title_size" format="dimension" />    <attr name="background_color" format="color" /></declare-styleable>

在自定义属性的时候,我一开始犯了一个错误,这里得注意一下,在attr标签中定义的属性名不能是已经存在的,不然会报找不到这个属性的错误,这里一定要谨记。

  • 继承View,实现其构造方法:
public MyTextView(Context context) {    this(context, null);}public MyTextView(Context context, AttributeSet attrs) {    this(context, attrs, 0);}public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {    super(context, attrs, defStyleAttr);    obtainStyledAttrs(context, attrs);   //初始化,获取自定义属性 }

这儿有一个小技巧,在写构造方法的时候,一个参数的使用this调用两个参数的构造方法,以此类推,最后的初始化在三个参数的构造方法中完成,这样,只需把初始化的代码放入三个参数的构造方法中,就可以统一在其他构造方法调用,这样写的好处在于比较简洁,避免在每一个构造方法中都要单独的写上初始化的代码。
还有一点需要特别注意,上面叙述中的是使用this进行构造方法的调用,千万不要用super,一定要注意!!!后面会有讲到。

  • 获取自定义属性值:

    在获取自定义属性值之前,还需要做一件事,就是为属性值设置默认值以及参数的声明,如下:

private static final int DEFAULT_TITLE_SIZE = 10;private static final int DEFAULT_TITLE_COLOR = 0x000000;private static final int DEFAULT_BACKGROUND_COLOR = 0xffffff;private int titleSize = sp2px(DEFAULT_TITLE_SIZE);private int titleColor = DEFAULT_TITLE_COLOR;private int background_color = DEFAULT_BACKGROUND_COLOR;private String titleText = "";

获取属性值的代码如下:

/** - 获取自定义属性 -  * @param attrs */private void obtainStyledAttrs(Context context, AttributeSet attrs) {    TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyTextView);    titleSize = (int) array.getDimension(R.styleable.MyTextView_title_size, titleSize);    titleColor = array.getColor(R.styleable.MyTextView_title_color, titleColor);    titleText = array.getString(R.styleable.MyTextView_title_text);    background_color = array.getColor(R.styleable.MyTextView_background_color, background_color);    array.recycle();}
  • 接下来是View的测量,相信大家都知道,在自定义View中需要完成几件事,分别是onMeasure、onDraw(尤为重要),下面,进入onMeasure,也就是测量View。

    那么问题来了,如何测量呢?在Android中方为我们提供了一个设计短小精悍却功能强大的类——MeasureSpec类,通过它来帮助我们测量View。MeasureSpec是一个32位的int值,其中高2位为测量的模式,低30位为测量的大小,在计算中使用位运算的原因是为了提高并优化效率。
    测量模式分为三种:

    • EXACTLY:即精确模式,当我们将控件的layout_width属性或layout_height属性指定为具体数值时,比如android:layout_width=”100dp”,或者指定为match_parent时(占据父View的大小),系统使用的是EXACTLY模式。

    • AL_MOST:即最大模式,当控件的layout_width属性或layout_height属性指定为wrap_content时,控件大小一般随着控件的子空间或内容的变化而变化,此时控件的尺寸只要不超过父控件允许的最大尺寸即可。

    • UPSPECIFIED:这个属性比较奇怪——它不指定其大小测量模式,View想多大就多大。

简单的介绍完了MeasureSpec的三种测量模式之后,下面上代码:

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    super.onMeasure(widthMeasureSpec, heightMeasureSpec);    setMeasuredDimension(measureDimension(200, widthMeasureSpec), measureDimension(100, heightMeasureSpec));}public int measureDimension(int defaultSize, int measureSpec) {    int result;    //获取测量模式    int specMode = MeasureSpec.getMode(measureSpec);    //获取值    int specSize = MeasureSpec.getSize(measureSpec);    if (specMode == MeasureSpec.EXACTLY) {        result = specSize;    } else {        result = defaultSize;   //UNSPECIFIED        if (specMode == MeasureSpec.AT_MOST) {            result = Math.min(result, specSize);        }    }    return result;}
  • 最后一步便是onDraw,代码如下:
@Overrideprotected void onDraw(Canvas canvas) {    canvas.save();   //保存绘制状态    Paint paint = new Paint();     //画笔    paint.setColor(background_color);    //设置绘制的颜色    canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), paint);    canvas.save();    //此处绘制矩形    paint.setColor(titleColor);    paint.setTextSize(titleSize);    Rect rect = new Rect();    //将文本内容放入Rect中,以便获取文本的宽高    paint.getTextBounds(titleText, 0, titleText.length(), rect);    canvas.drawText(titleText, getWidth() / 2 - rect.width() / 2, getHeight() / 2 + rect.height() / 2, paint);    //绘制文本    canvas.restore(); }

到此,自定义View已完成,此处需特别注意一个问题,在写构造函数的时候,我一开始没注意,使用了系统默认的super,后来把代码基本写完之后,发现使用的自定义View没有任何效果,屏幕上一片空白,之后发现了问题所在,因为使用super调用的是父类的方法,要想调用本类的构造方法,必须改为this,切记。

完整的代码如下:

public class MyTextView extends View {    private static final int DEFAULT_TITLE_SIZE = 10;    private static final int DEFAULT_TITLE_COLOR = 0x000000;    private static final int DEFAULT_BACKGROUND_COLOR = 0xffffff;    private int titleSize = sp2px(DEFAULT_TITLE_SIZE);    private int titleColor = DEFAULT_TITLE_COLOR;    private int background_color = DEFAULT_BACKGROUND_COLOR;    private String titleText = "";    public MyTextView(Context context) {        this(context, null);    }    public MyTextView(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        obtainStyledAttrs(context, attrs);    }    /**     * 获取自定义属性     *     * @param attrs     */    private void obtainStyledAttrs(Context context, AttributeSet attrs) {        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyTextView);        titleSize = (int) array.getDimension(R.styleable.MyTextView_title_size, titleSize);        titleColor = array.getColor(R.styleable.MyTextView_title_color, titleColor);        titleText = array.getString(R.styleable.MyTextView_title_text);        background_color = array.getColor(R.styleable.MyTextView_background_color, background_color);        array.recycle();    }    public int getTitleSize() {        return titleSize;    }    public int getTitleColor() {        return titleColor;    }    public String getTitleText() {        return titleText;    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        setMeasuredDimension(measureDimension(200, widthMeasureSpec), measureDimension(100, heightMeasureSpec));    }    public int measureDimension(int defaultSize, int measureSpec) {        int result;        int specMode = MeasureSpec.getMode(measureSpec);        int specSize = MeasureSpec.getSize(measureSpec);        if (specMode == MeasureSpec.EXACTLY) {            result = specSize;        } else {            result = defaultSize;   //UNSPECIFIED            if (specMode == MeasureSpec.AT_MOST) {                result = Math.min(result, specSize);            }        }        return result;    }    @Override    protected void onDraw(Canvas canvas) {        //save方法是将当前的结果推到栈里,相当于保存多个历史记录        canvas.save();        Paint paint = new Paint();        paint.setColor(background_color);        canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), paint);        canvas.save();        paint.setColor(titleColor);        paint.setTextSize(titleSize);        Rect rect = new Rect();        //getTextBounds方法一定要在setColor...之后        //此方法是将text文本放入已新建的Rect容器中,由此得到文本内容的宽高        paint.getTextBounds(titleText, 0, titleText.length(), rect);        //此处不能用getMeasuredWidth()替换getWidth()        canvas.drawText(titleText, getWidth() / 2 - rect.width() / 2, getHeight() / 2 + rect.height() / 2, paint);        //restore方法是将最后一个结果弹出栈,相当于只留下最后一个修改的状态作为最终结果,而之前的状态全部被删除        canvas.restore();    }    /**     * dp转换为px     *     * @param dpVal     * @return     */    protected int dp2px(int dpVal) {        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, getResources().getDisplayMetrics());    }    /**     * sp转换为px     *     * @param spVal     * @return     */    protected int sp2px(int spVal) {        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spVal, getResources().getDisplayMetrics());    }}
1 0
原创粉丝点击