自定义控件学习第一课

来源:互联网 发布:js窗口大小改变事件 编辑:程序博客网 时间:2024/06/05 19:21

自定义控件学习第一课

Android中的自定义控件是成为中高级程序员必须去克服的一个课题。学好自定义控件,需要你对View绘制的原理,触摸手势的处理,动画的运用等基础重要的知识点有较为深刻的认识和见解。
本人打算认真系统的学习下自定义控件,学习资料来自于大牛“鸿洋”(http://blog.csdn.net/lmj623565791),我用重新实现其中的实例,并把自己碰到的问题疑问和大家分享。进入正题:
Lesson 1:
第一步:
先定义自定义控件的属性(当然不是所有自定义控件都需要定义自己的属性)

本Markdown编辑器使用[StackEdit][6]修改而来,用它写博客,将会带来全新的体验哦:

<?xml version="1.0" encoding="utf-8"?><resources>    <attr name="titleText" format="string" /><!-- 参数是字符串的属性 -->    <attr name="titleColor" format="color" /><!-- 参数是颜色类型的属性 -->    <attr name="titleSize" format="dimension" /><!-- 参数是尺寸类型的属性 -->    <declare-styleable name="CustomTitleView"><!-- 一定和自定义控件的名字是一致的 -->        <attr name="titleText" />        <attr name="titleColor" />        <attr name="titleSize" />    </declare-styleable></resources>

第二步:
继承View,重写onMeasure,onDraw这个方法,其中onMeasure主要是做了测量控件需要的大小的工作,onDraw就是绘制view的工作,当然还需要解析自定义的一些属性

public class CustomTitleView extends View {    private String mTitleText;// 显示的文字    private int mTitleColor;// 文字的颜色    private int mTitleSize;// 文字的尺寸    private Paint mPaint;// 画笔    /**     * 绘制时控制文本绘制的范围     */    private Rect mBound;    public CustomTitleView(Context context) {        this(context, null);        // TODO Auto-generated constructor stub    }    /**     * 默认的布局文件调用的是两个参数的构造方法     *      * @param context     * @param attrs     */    public CustomTitleView(Context context, AttributeSet attrs) {        this(context, attrs, 0);        // TODO Auto-generated constructor stub    }    /**     * 在三个参数的构造中获得自定义属性     *      * @param context     * @param attrs     * @param defStyle     */    public CustomTitleView(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        // TODO Auto-generated constructor stub        // 获取我们自定义的属性        TypedArray a = context.getTheme().obtainStyledAttributes(attrs,                R.styleable.CustomTitleView, defStyle, 0);        int n = a.getIndexCount();// 属性的个数        // 循环处理每个属性        for (int i = 0; i < n; i++) {            int attr = a.getIndex(i);            switch (attr) {            case R.styleable.CustomTitleView_titleText:                mTitleText = a.getString(attr);                break;            case R.styleable.CustomTitleView_titleColor:                mTitleColor = a.getColor(attr, android.R.color.black);// 默认颜色设置为黑色                break;            case R.styleable.CustomTitleView_titleSize:                mTitleSize = a.getDimensionPixelSize(attr, (int) TypedValue                        .applyDimension(TypedValue.COMPLEX_UNIT_SP, 16,                                getResources().getDisplayMetrics()));// 默认设置为16sp,TypeValue也可以把sp转化为px                break;            }        }        a.recycle();// 释放属性        // 初始化画笔        mPaint = new Paint();        mPaint.setTextSize(mTitleSize);        /**         * 获得绘制文本的宽和高         */        mBound = new Rect();        mPaint.getTextBounds(mTitleText, 0, mTitleText.length(), mBound);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        // TODO Auto-generated method stub        int width;        int height;        int widthMode = MeasureSpec.getMode(widthMeasureSpec);        int widthSize = MeasureSpec.getSize(widthMeasureSpec);        int heightMode = MeasureSpec.getMode(heightMeasureSpec);        int heightSize = MeasureSpec.getSize(heightMeasureSpec);        if(widthMode == MeasureSpec.EXACTLY){            width = widthSize;        }else{            float textWidth = mBound.width();            width = (int)(textWidth+getPaddingLeft()+getPaddingRight());        }        if(heightMode == MeasureSpec.EXACTLY){            height = heightSize;        }else{            float textHeight = mBound.height();            height = (int)(textHeight+getPaddingTop()+getPaddingBottom());        }        setMeasuredDimension(width, height);    }    @Override    protected void onDraw(Canvas canvas) {        // TODO Auto-generated method stub        Log.i("info", "getMeasuredWidth()="+getMeasuredWidth()+",getMeasuredHeight()="+getMeasuredHeight());        Log.i("info", "getWidth()="+getWidth()+",getHeight()="+getHeight());        // 画背景        mPaint.setColor(Color.YELLOW);        canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);        // 画字符串        mPaint.setColor(mTitleColor);        canvas.drawText(mTitleText, getWidth() / 2 - mBound.width() / 2,                getHeight() / 2 + mBound.height() / 2, mPaint);    }}

重写onMeasure先要了解的知识点:
了解MeasureSpec的specMode,一共三种类型:
EXACTLY:一般是设置了明确的值或者是MATCH_PARENT
AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT
UNSPECIFIED:表示子布局想要多大就多大,很少使用

学习时还遇到一个小疑问:
getWidth()和getMeasuredWidth()这两个方法有什么区别呢?从字面上看getMeasuredWidth()是测量的宽度,getWidth()就是宽度,所以一般情况下两者是相同的,比如本例中就是相同的,但是也有一些不一样的情况,比如一些可以滚动的视图,getMeasuredWidth()往往比getWidth()要大。
总结:
getWidth(): View在设定好布局后整个View的宽度。
getMeasuredWidth(): 对View上的内容进行测量后得到的View内容占据的宽度,前提是你必须在父布局的onLayout()方法或者此View的onDraw()方法里调用measure(0,0);(measure中的参数的值你自己可以定义),否则你得到的结果和getWidth()得到的结果是一样的。

第三步:
在布局文件中运用:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    xmlns:app="http://schemas.android.com/apk/res/com.loubf.customview">   <com.loubf.customview.CustomTitleView       android:layout_width="wrap_content"       android:layout_height="wrap_content"       app:titleText="12530"       app:titleColor="#FF0000"       app:titleSize="20sp"       android:padding="10dp"       android:layout_centerInParent="true"       >   </com.loubf.customview.CustomTitleView></RelativeLayout>

这样一个简单的自定义控件就算完成了

0 0