自定义View入门

来源:互联网 发布:riot.js 编辑:程序博客网 时间:2024/06/07 18:03

一、自定义View步骤

  • 在res/values/目录下新建attrs.xml文件,在该文件中定义和我们自定义View相关的属性。
  • 在构造函数中获取自定义的属性
  • 重写onMeasure()方法获测量自定义View的大小
  • 重写onDraw()方法绘制自定义View显示在界面上


二、下面一步一步的演示上面的步骤

先上效果图:

             

1、自定义属性

  • 自定义属性时,declare-styleable 后的name可以任意定义,只是为了区分,默认情况下该name为自定义View的类名,当然,这里也最好使用类名,这样容易辨别。
<resources>            <declare-styleable name="CustomProgressBar">                <!--圆圈内的文本-->                <attr name="progressText" format="string"/>                <!--圆圈内文本大小-->                <attr name="progressTextSize" format="dimension"/>                <!--圆圈内文本颜色-->                <attr name="progressTextColor" format="color"/>                <!--圆圈底层颜色-->                <attr name="firstColor" format="color"/>                <!--圆圈第二层颜色-->                <attr name="secondColor" format="color"/>                <!--圆圈宽度-->                <attr name="progressWidth" format="float"/>                <!--圆圈的内径-->                <attr name="progressInnerRadius" format="float"/>                <!--最大进度-->                <attr name="max" format="integer"/>                <!--当前进度-->                <attr name="progress" format="integer"/>            </declare-styleable>        </resources>

2、获取自定义属性

  • 在自定义View的构造函数中获取自定义属性,一定要有AttributeSet attrs的构造函数,因为获取自定义属性全靠它了,如果仅仅是一个只有context的构造函数是不能获取得到的,所以这里一定要注意了。
public CustomProgressBar(Context context) {            this(context, null);        }        public CustomProgressBar(Context context, AttributeSet attrs) {            this(context, attrs, 0);        }        public CustomProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {            super(context, attrs, defStyleAttr);            initAttributes(context, attrs, defStyleAttr);            mPaint = new Paint();        }        /**         * 初始化自定义属性         *         * @param context      上下文         * @param attrs        属性集合         * @param defStyleAttr 自定义属性样式         */        private void initAttributes(Context context, AttributeSet attrs, int defStyleAttr) {            if (attrs != null) {                TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomProgressBar, defStyleAttr, 0);                try {                    //第一层颜色                    mFirstColor = a.getColor(R.styleable.CustomProgressBar_firstColor, mDefaultFirstColor);                    //第二层颜色                    mSecondColor = a.getColor(R.styleable.CustomProgressBar_secondColor, mDefaultSecondColor);                    //圆圈内部显示的文本                    mProgressText = a.getString(R.styleable.CustomProgressBar_progressText);                    //圆圈宽度                    mProgressWidth = a.getFloat(R.styleable.CustomProgressBar_progressWidth, mDefaultProgressWidth);                    //圆圈内径                    mProgressInnerRadius = a.getFloat(R.styleable.CustomProgressBar_progressInnerRadius, mDefaultInnerRadius);                    //圆圈内文本颜色                    mProgressTextColor = a.getColor(R.styleable.CustomProgressBar_progressTextColor, mDefaultProgressTextColor);                    //圆圈内文本字体大小                    mProgressTextSize = a.getDimension(R.styleable.CustomProgressBar_progressTextSize, mDefaultProgressTextSize);                    //最大进度                    max = a.getInt(R.styleable.CustomProgressBar_max, 0);                    //当前进度                    progress = a.getInt(R.styleable.CustomProgressBar_progress, 0);                } finally {                    a.recycle();                }            }        }

3、重写onMeasure()方法

  • 其实这一步不是必须的,因为如果在xml文件中给定的layout_width和layout_height为确定的值的话,如固定的dp或者match_parent,那么onMeasure是不需要重写的,如果是wrap_content的话,默认为父容器剩余空间的最大值,除非是当没有给该View一个确定的大小的时候,重写该方法是有意义的,当用户使用该View时是用的是wrap_content时可以计算一个默认的大小出来。建议还是重写该方法,将多种情况都考虑进来。
    /**         * 测量组件的大小,设置的组件大小为wrapcontent的时候,组件的大小由半径决定         * @param widthMeasureSpec         * @param heightMeasureSpec         */        @Override        protected 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);            //测量组件的宽度            if (widthMode == MeasureSpec.EXACTLY) {                //如果是精准测量,即在使用的时候给的大小为match_parent或者是一个固定大小                mWidth = widthSize;            } else {                int desire = (int) (mProgressInnerRadius * 2 //圆圈的内部直径                        + mProgressWidth * 2); // 圆圈的左边宽度和右边的宽度的和                if (widthMode == MeasureSpec.AT_MOST) { //wrap_content                    mWidth = Math.min(desire, widthSize);                }            }            //测量组件的高度            if (heightMode == MeasureSpec.EXACTLY) {                mHeight = heightSize;            } else {                int desire = (int) ( mProgressInnerRadius * 2 //圆圈的内部直径                        + mProgressWidth * 2); // 圆圈的左边宽度和右边的宽度的和                if (heightMode == MeasureSpec.AT_MOST) { //wrap_content                    mHeight = Math.min(desire, heightSize);                }            }            //保存测量的大小            setMeasuredDimension(mWidth, mHeight);        }

4、重写ondraw()方法

  • 该方法是用来绘制View在界面上显示的,绘图当然和Canvas这个类密不可分了。后面会单独出一篇文章来写canvas这个类的用法。
@Override        protected void onDraw(Canvas canvas) {            super.onDraw(canvas);            //画第一层圆            int length = Math.min(mWidth,mHeight);            mPaint.setColor(mFirstColor);            mPaint.setStyle(Paint.Style.STROKE);            mPaint.setStrokeWidth(mProgressWidth);            mPaint.setAntiAlias(true);            mPaint.setFilterBitmap(true);            canvas.drawCircle(mWidth / 2, mHeight / 2, length / 2 - mProgressWidth / 2, mPaint);            //画第二层圆弧            mPaint.setColor(mSecondColor);            RectF oval = new RectF();            oval.left = mWidth/2 - length/2 + mProgressWidth/2;            oval.top = mHeight/2 - length/2 + mProgressWidth/2;            oval.right = mWidth/2 + length/2 - mProgressWidth/2;            oval.bottom = mHeight/2 + length/2 - mProgressWidth/2;            int angle;            if (max <= 0 || progress < 0) {                angle = 0;            }else {                angle = (int) (progress / max * 360);            }            canvas.drawArc(oval, -90, angle, false, mPaint);           //画圆圈内的文本            mPaint.setTextAlign(Paint.Align.CENTER);            mPaint.setColor(mProgressTextColor);            mPaint.setTextSize(mProgressTextSize);            mPaint.setStyle(Paint.Style.FILL);            if (!TextUtils.isEmpty(mProgressText)) {                //如果有文本                canvas.drawText(mProgressText, mWidth / 2, mHeight / 2 + mTextRect.height() / 2, mPaint);            }        }


  • 在画圆弧的时候使用到类RectF这个类,该类表示一个方形,可以确定一个图形的范围,在android所有的图形都是在一个方形中确定的。

通过以上4个步骤,我们的这个自定义View就定义完成了,下面来演示如何使用。


三、使用自定义View

  • 使用自定义View时和正常使用android内置View一样,但是值得注意的是这里需要写类的全名,即需要加上包名才可以,否则会报错。如果需要使用到自定义属性,那么就需要定义一个命名空间,不能使用android这个命名空间,因为这个是android自己定义的命名空间,我们自定义的属性没有在该命名空间中,需要重新定义一个命名空间。如何定义一个命名空间呢?复制”xmlns:android=”http://schemas.android.com/apk/res/android”这一个话,将xmln:android中的”android”修改为任意一个名称,不能是android,此处我修改为custom,这里custom就是我自定义的一个命名空间,当然还需要将末尾的res/android修改为res-auto。
<?xml version="1.0" encoding="utf-8"?>        <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"                xmlns:custom="http://schemas.android.com/apk/res-auto"                android:layout_width="match_parent"                android:layout_height="match_parent">            <com.taiyang.commonui.view.CustomProgressBar                android:id="@+id/progressbar"                android:layout_width="200dp"                android:layout_height="300dp"                android:layout_centerInParent="true"                custom:firstColor="#000088"                custom:max="100"                custom:progressTextColor="#567890"                custom:progressTextSize="30sp"                custom:progressWidth="40"                custom:secondColor="#ff0000"/>        </RelativeLayout>
  • 以上使用我们自定义的View就完成了,后面也会逐步深入的讲解如何自定义View和自定义ViewGroup
  • 下面将会附上该自定义View的完整源码
package com.taiyang.commonui.view;import android.content.Context;import android.content.res.TypedArray;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Rect;import android.graphics.RectF;import android.text.TextUtils;import android.util.AttributeSet;import android.util.TypedValue;import android.view.View;import com.taiyang.commonui.R;/** * Created by taiyang5946 on 2016/1/10. * 这是一个圆形的进度条 */public class CustomProgressBar extends View {    private final String TAG = "CustomProgressBar";    /**圆圈第一层默认颜色*/    private int mDefaultFirstColor = Color.BLACK;    /**圆圈第二层默认颜色*/    private int mDefaultSecondColor = Color.CYAN;    /**圆圈内径默认半径为30,单位为:dp*/    private float mDefaultInnerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30,            getResources().getDisplayMetrics());    /** 圆圈默认宽度为4,单位为:dp*/    private float mDefaultProgressWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4,            getResources().getDisplayMetrics());    /**圆圈内文本默认颜色*/    private int mDefaultProgressTextColor = Color.BLUE;    /** 圆圈内文本默认字体大小*/    private float mDefaultProgressTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 14,            getResources().getDisplayMetrics());    /**圆圈第一层颜色*/    private int mFirstColor;    /**圆圈第二层颜色*/    private int mSecondColor;    /**圆圈内部显示文字*/    private String mProgressText;    /**圆圈宽度*/    private float mProgressWidth;    /**圆圈内径*/    private float mProgressInnerRadius;    /**圆圈内文本的颜色*/    private int mProgressTextColor;    /**圆圈内文本的字体大小*/    private float mProgressTextSize;    /**该组件的宽度 */    private int mWidth;    /**该组件的高度*/    private int mHeight;    /**画笔*/    private Paint mPaint;    /**圆圈内的文本约束*/    private Rect mTextRect;    /**最大进度*/    private float max;    /**当前进度 */    private int progress;    public CustomProgressBar(Context context) {        this(context, null);    }    public CustomProgressBar(Context context, AttributeSet attrs) {        this(context, attrs, 0);    }    public CustomProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        initAttributes(context, attrs, defStyleAttr);        mPaint = new Paint();    }    /**     * 测量圆圈内文本的约束,即宽和高     */    private void initProgressTextSize() {        //如果有文本就进行测量        if (!TextUtils.isEmpty(mProgressText)) {            mPaint.setTextSize(mProgressTextSize);            mTextRect = new Rect();            //文本的宽度约束就在textRect中            mPaint.getTextBounds(mProgressText, 0, mProgressText.length(), mTextRect);        }    }    /**     * 初始化自定义属性     *     * @param context      上下文     * @param attrs        属性集合     * @param defStyleAttr 自定义属性样式     */    private void initAttributes(Context context, AttributeSet attrs, int defStyleAttr) {        if (attrs != null) {            TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CustomProgressBar, defStyleAttr, 0);            try {                //第一层颜色                mFirstColor = a.getColor(R.styleable.CustomProgressBar_firstColor, mDefaultFirstColor);                //第二层颜色                mSecondColor = a.getColor(R.styleable.CustomProgressBar_secondColor, mDefaultSecondColor);                //圆圈内部显示的文本                mProgressText = a.getString(R.styleable.CustomProgressBar_progressText);                //圆圈宽度                mProgressWidth = a.getFloat(R.styleable.CustomProgressBar_progressWidth, mDefaultProgressWidth);                //圆圈内径                mProgressInnerRadius = a.getFloat(R.styleable.CustomProgressBar_progressInnerRadius, mDefaultInnerRadius);                //圆圈内文本颜色                mProgressTextColor = a.getColor(R.styleable.CustomProgressBar_progressTextColor, mDefaultProgressTextColor);                //圆圈内文本字体大小                mProgressTextSize = a.getDimension(R.styleable.CustomProgressBar_progressTextSize, mDefaultProgressTextSize);                //最大进度                max = a.getInt(R.styleable.CustomProgressBar_max, 0);                //当前进度                progress = a.getInt(R.styleable.CustomProgressBar_progress, 0);            } finally {                a.recycle();            }        }    }    /**     * 测量组件的大小,设置的组件大小为wrapcontent的时候,组件的大小由半径决定     * @param widthMeasureSpec     * @param heightMeasureSpec     */    @Override    protected 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);        //测量组件的宽度        if (widthMode == MeasureSpec.EXACTLY) {            //如果是精准测量,即在使用的时候给的大小为match_parent或者是一个固定大小            mWidth = widthSize;        } else {            int desire = (int) (mProgressInnerRadius * 2 //圆圈的内部直径                    + mProgressWidth * 2); // 圆圈的左边宽度和右边的宽度的和            if (widthMode == MeasureSpec.AT_MOST) { //wrap_content                mWidth = Math.min(desire, widthSize);            }        }        //测量组件的高度        if (heightMode == MeasureSpec.EXACTLY) {            mHeight = heightSize;        } else {            int desire = (int) ( mProgressInnerRadius * 2 //圆圈的内部直径                    + mProgressWidth * 2); // 圆圈的左边宽度和右边的宽度的和            if (heightMode == MeasureSpec.AT_MOST) { //wrap_content                mHeight = Math.min(desire, heightSize);            }        }        //保存测量的大小        setMeasuredDimension(mWidth, mHeight);    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        //画第一层圆        int length = Math.min(mWidth,mHeight);        mPaint.setColor(mFirstColor);        mPaint.setStyle(Paint.Style.STROKE);        mPaint.setStrokeWidth(mProgressWidth);        mPaint.setAntiAlias(true);        mPaint.setFilterBitmap(true);        canvas.drawCircle(mWidth / 2, mHeight / 2, length / 2 - mProgressWidth / 2, mPaint);        //画第二层圆弧        mPaint.setColor(mSecondColor);        RectF oval = new RectF();        oval.left = mWidth/2 - length/2 + mProgressWidth/2;        oval.top = mHeight/2 - length/2 + mProgressWidth/2;        oval.right = mWidth/2 + length/2 - mProgressWidth/2;        oval.bottom = mHeight/2 + length/2 - mProgressWidth/2;        int angle;        if (max <= 0 || progress < 0) {            angle = 0;        }else {            angle = (int) (progress / max * 360);        }        canvas.drawArc(oval, -90, angle, false, mPaint);       //画圆圈内的文本        mPaint.setTextAlign(Paint.Align.CENTER);        mPaint.setColor(mProgressTextColor);        mPaint.setTextSize(mProgressTextSize);        mPaint.setStyle(Paint.Style.FILL);        if (!TextUtils.isEmpty(mProgressText)) {            //如果有文本            canvas.drawText(mProgressText, mWidth / 2, mHeight / 2 + mTextRect.height() / 2, mPaint);           /* if ((mTextRect.width() * mTextRect.width() + mTextRect.height() * mTextRect.height())                    <  //勾股定理                    length * length) {                //如果文本被约在圆圈内                canvas.drawText(mProgressText, mWidth / 2, mHeight / 2, mPaint);            } else {                TextPaint textPaint = new TextPaint();                String text = TextUtils.ellipsize(mProgressText,                        textPaint,                        mProgressInnerRadius * 2,                        TextUtils.TruncateAt.MARQUEE).toString();                canvas.drawText(text, mWidth / 2, mHeight / 2, mPaint);            }*/        }       }    public String getProgressText() {        return mProgressText;    }    /**     * 设置圆圈内的文本     * @param progressText     */    public void setProgressText(String progressText) {        mProgressText = progressText;        //测量圆圈内文本的大小,即宽和高        initProgressTextSize();        postInvalidate();    }    public void setMax(int max) {        this.max = max;        postInvalidate();    }    public void setProgress(int progress) {        this.progress = progress;        postInvalidate();    }}
1 0
原创粉丝点击