自定义View的基本步骤

版权声明:转载请注明出处 https://blog.csdn.net/QasimCyrus/article/details/52003134

当系统自带的传统布局不满足需求时,我们就需要自定义自己想要的View了。自定义一个View需要四个步骤:

  1. 自定义View的属性;
  2. View的构造方法中获取自定义属性;
  3. 重写onMeasure()方法;(非必要,但大多数时间必要,有时候可以利用onSizeChanged()代替)
  4. 重写onDraw()方法。

一、自定义View的属性:

1.首先需要先新建一个继承自View的类。我们以画一个视图中心为圆心的圆和文字为例,新建CircleView,继承自View并且添加构造方法:
public class CircleView extends View {

    private int mWidth;//视图的宽
    private int mHeight;//视图的高

    private Paint mPaintCircle;//用来画圆的画笔
    private int mCenterX;//圆心在视图中的X轴位置
    private int mCenterY;//圆心在视图中的Y轴位置
    private int mRadius;//圆的半径
    private int mCircleColor;//圆的颜色

    private Paint mPaintText;//用来写字的画笔
    private String mTextStr;//文字的内容
    private float mTextSize;//文字的尺寸
    private int mTextColor;//文字的颜色
    private int mTextWidth;//文字的宽
    private int mTextHeight;//文字的高
    private int mTextX;//文字起始的X轴位置
    private int mTextY;//文字起始的Y轴位置
    private Rect mTextBound;//文字矩形,用于计算文字高和宽

    private float mDegree = 0;//旋转的度数

    public CircleView(Context context) {
        super(context);
    }

    public CircleView(Context context, AttributeSet attrs) {
        super(context, attrs);

        init(context, attrs);
    }
}

2.然后在values文件夹中增加attrs.xml文件,用于添加自定义View的属性,属性可以有若干:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CircleView">
        <attr name="text" format="string"/>
        <attr name="textSize" format="dimension"/>
        <attr name="textColor" format="color"/>
        <attr name="color" format="color"/>
    </declare-styleable>
</resources>
declare-styleable为变量名称,下面自定义了许多attr属性。attr的属性有以下几种:
reference   表示引用,参考某一资源ID
string   表示字符串
color   表示颜色值
dimension   表示尺寸值
boolean   表示布尔值
integer   表示整型值
float   表示浮点值
fraction   表示百分数
enum   表示枚举值
flag   表示位运算

3.有了属性,我们就可以在xml中引用视图并且修改其属性了。
首先添加一句xmlns:app="http://schemas.android.com/apk/res-auto",红色文字的“app”可以自定义为自己想要的前缀名字,“res-auto”可以改成/res加上自己的包名(但是系统建议使用res-auto)。然后利用自定义的属性前缀来使用属性,我在xml中定义了圆的颜色还有文字的内容,大小,颜色:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.cyrus.mycircleview.MainActivity">

    <com.cyrus.mycircleview.CircleView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:color="#FF3F51B5"
        app:text="Some text"
        app:textColor="#FFFFFFFF"
        app:textSize="40sp"/>

</LinearLayout>

二、View的构造方法中获取自定义属性:

1.单单设置第一步的属性值还不够,需要在java文件中设置默认值,否则xml中的属性设置不会生效。在构造函数中得到TypedArray实例,其方法可以得到各类属性值并且设置默认值,如getColor()、getDimension()、getString()等。这些属性值可以利用Paint类的setColor()、setTextSize()等方法添加到画笔Paint上。
注意:调用完TyepdArray之后一定要执行其recycle()方法,否则会这次的设定会对下次使用造成影响。    
    private void init(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleView);

        mCircleColor = typedArray.getColor(R.styleable.CircleView_color, 0xffffffff);
        mPaintCircle = new Paint(Paint.ANTI_ALIAS_FLAG);//创建圆的画笔实例
        mPaintCircle.setColor(mCircleColor);

        mTextStr = typedArray.getString(R.styleable.CircleView_text);
        if (mTextStr == null) {
            //如果直接让内容为空会出错
            mTextStr = "";
        }
        mTextSize = typedArray.getDimension(R.styleable.CircleView_textSize, 30f);
        mTextColor = typedArray.getColor(R.styleable.CircleView_textColor, 0xffffffff);
        mPaintText = new Paint(Paint.ANTI_ALIAS_FLAG);//创建文字的画笔实例
        mPaintText.setTextSize(mTextSize);
        mPaintText.setColor(mTextColor);

        //利用文字矩阵来得到文字的宽和高
        mTextBound = new Rect();
        mPaintText.getTextBounds(mTextStr, 0, mTextStr.length(), mTextBound);
        mTextWidth = mTextBound.width();
        mTextHeight = mTextBound.height();

        typedArray.recycle();//调用结束后务必调用recycle()方法,否则这次的设定会对下次的使用造成影响
    }

三、重写onMeasure()方法:

1.该方法有两个形参:widthMeasureSpec、heightMeasureSpec,是从ViewGroup传入的。这两个形参都可以分为高32位的specMode和低16位的specSize。specMode和specSize分别可以使用Measure.getMode()和Measure.getSize()获得。
specMode有三个模式:分别为:
AT_MOST,specSize 代表的是最大可获得的空间; 
EXACTLY,specSize 代表的是精确的尺寸; 
UNSPECIFIED,对于控件尺寸来说,没有任何参考意义。

更多关于onMeasure()的解释,请参考文章:Android View.onMeasure方法的理解

2.我的重写是按照上面的文章修改的:
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        mWidth = measureWidth(widthMeasureSpec);//得到视图宽度
        mHeight = measureHeight(heightMeasureSpec);//得到视图高度
        mCenterX = mWidth / 2;//得到圆心的X轴位置
        mCenterY = mHeight / 2;//得到圆心的Y轴位置
        mRadius = Math.min(mWidth, mHeight) / 2;//取高和宽之间的较小值,折半即为半径

        mTextX = (mWidth - mTextWidth) / 2;//得到文字起始的X轴位置
        mTextY = (mHeight + mTextHeight) / 2;//得到文字起始的Y轴位置
    }
    private int measureWidth(int widthMeasureSpec) {
        int specMode = MeasureSpec.getMode(widthMeasureSpec);
        int specSize = MeasureSpec.getSize(widthMeasureSpec);

        int result = 400;

        if (specMode == MeasureSpec.AT_MOST) {
            result = specSize;
        } else if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        }

        return result;
    }

    private int measureHeight(int heightMeasureSpec) {
        int specMode = MeasureSpec.getMode(heightMeasureSpec);
        int specSize = MeasureSpec.getSize(heightMeasureSpec);

        int result = 400;

        if (specMode == MeasureSpec.AT_MOST) {
            result = specSize;
        } else if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        }

        return result;
    }

四、重写onDraw()方法:

利用onDraw()的参数Canvas来绘制图形,这时候就需要用到Paint画笔了:
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.save();
        canvas.rotate(mDegree, mWidth / 2, mHeight / 2);
        if (mDegree++ > 360) {
            mDegree = 0;
        }

        //四个参数分别为圆心的x轴位置、圆心的y轴位置、半径、画笔
        canvas.drawCircle(mCenterX, mCenterY, mRadius, mPaintCircle);

        //四个参数分别为文字内容、文字起始的X轴位置,文字起始的Y轴位置、画笔
        canvas.drawText(mTextStr, mTextX, mTextY, mPaintText);

        canvas.restore();

        invalidate();//使画布无效,将重新执行onDraw()方法
    }
绘制圆和文字的两句代码分别是canvas.drawCircle(mCenterX, mCenterY, mRadius, mPaintCircle);以及canvas.drawText(mTextStr, mTextX, mTextY, mPaintText);,其他的是用来产生旋转的动画效果的,可有可无。

至此我们已经可以产生一个放置在视图中心的圆加上文字了。

猜你喜欢

转载自blog.csdn.net/QasimCyrus/article/details/52003134