从0开始学会自定义View(二)

149 阅读5分钟

1、前言

在👴经历了结肠炎导致的发烧超39度、恶寒等等debuff之后,打赢复活赛重新归来,终于有时间来写第二篇的内容了捏😅。

2、概述

本篇将说明第二个步骤——重写View的onDraw方法。(上篇: 从0开始学会自定义View(一))。

3、重写onDraw

用途:概括一下,onDraw方法目的就是绘制你想要的内容。

但是,在正式了解onDraw方法之前我们还需要了解两个非常重要的类:

  • Paint(画笔🖌️

    很好理解,就是我在画画时手中的画笔,可以决定绘图的颜色(包括背景、文字颜色等)、文字的大小、文字的边距、边框大小等等。

  • Canvas(画布

    这个也很容易懂,就是画笔的颜色确定了之后,我们拿着“这支画笔”来画画,我们可以画一个矩形、圆形、不规则图形、文字等等。

Ok,介绍完这些之后我们正式开始学习onDraw方法。

首先先把方法重写:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }

这里的参数canvas就是我们的画布,我们需要什么就用canvas画什么就行了,里面的内容还是用我们上一期在onDraw里面用到的代码(其中的mSolidColor有过调整,换成其他颜色了):

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //第一部分
        mPaint.setColor(mSolidColor);//背景颜色 
        canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);//绘制背景
        //第二部分
        mPaint.setColor(mTextColor);//文字颜色
        canvas.drawText(mText, getWidth() / 2 - mTextBound.width() / 2, getHeight() / 2 + mTextBound.height() / 2, mPaint);//绘制文字
    }

将这里面的代码拆成两个部分来看:

第一部分:

这部分是在绘制背景图形。

矩形

    mPaint.setColor(mSolidColor);//背景颜色
    //绘制一个矩形
    canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);//绘制背景

步骤就是确定笔刷颜色,使用canvas绘制图形。

效果图:

其中drawRect方法中用到的参数分别是 左侧坐标、顶部坐标、右侧坐标、底部坐标、笔刷对象。 也可以通过创建一个Rect对象来存储矩形边界,调用时直接将该对象作为参数传入也可以达到一样的效果:

Rect rect = new Rect(0,0,getWidth(),getHeight()); //方法外部初始化
canvas.drawRect(rect,mPaint);//方法内部调用

也可以画个带圆角的矩形:

mPaint.setColor(mSolidColor);
canvas.drawRoundRect(0, 0, getWidth(), getHeight(), dp2px(15), dp2px(15), mPaint);

前4个参数不多说了,不同的在后两个参数,表示圆角的椭圆的 x 半径和圆角的椭圆的 y 半径,其中dp2px是将dp值转为px值的方法,我们这里的圆角设为15dp。

protected int dp2px(float dp) {
    //mContext在MyCustomView构造方法中初始化。
    DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
    return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, metrics);
}

效果如下:

  1. 圆形
mPaint.setColor(mSolidColor);
canvas.drawArc(0,0,getWidth(),getHeight(),0,360,false,mPaint);

第三和第四个参数表示开始的角度和扫过的角度,第五个参数表示设置我们的圆弧在绘画的时候,是否经过圆形

mPaint.setColor(mSolidColor);
//这里第五个参数useCenter
canvas.drawArc(0,0,getWidth(),getHeight(),0,270,useCenter,mPaint);
useCenter为trueuseCenter为false

第二部分: 这一部分是在背景之上绘制文字

mPaint.setColor(mTextColor);//文字颜色
canvas.drawText(mText, getWidth() / 2 - mTextBound.width() / 2, getHeight() / 2 + mTextBound.height() / 2, mPaint);//绘制文字

同样的,绘制文字也需要知道文字的坐标,这里的坐标为文字水平方向上的x坐标点,垂直方向上基线的坐标点。

代码中getWidth和getHeight是获取View的宽高,而mTextBound为包含文字边界的Rect对象,如何测量文字并将结果放进这个对象呢?使用Paint对象的getTextBound方法,该方法接收一个字符串类型参数(代表你需要测量的文字)、一个其实位置、和一个结束位置以及一个接收边界的Rect类:

//不为空则测量
if (!TextUtils.isEmpty(mText)) {
    //从0开始,到文字长度结束
    mPaint.getTextBounds(mText, 0, mText.length(), mTextBound);
}

这里要注意的是,View绘制是有层级的,如果我们将两步对调会发现效果和第一张的效果是一样的,因为文字是先绘制的,而后绘制的背景会盖在文字上方。

4、更多

padding值

这个时候我们如果在xml文件上给我们的加上任意padding值会发现根本不会生效,因为在代码中我们的背景大小就是根据我们给的宽高来的,没有将padding算进去,因此我们可以来加入考虑padding值的情况再来看看:

mPaint.setColor(mSolidColor);
canvas.drawRect(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), getHeight() - getPaddingBottom(), mPaint);

xml:

<com.my.mycustomview.MyCustomView
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:layout_centerInParent="true"
    app:solid_color="@color/teal_200"
    app:text_size="25sp"
    app:text="AAA"
    android:padding="10dp"
    app:background_drawable="@drawable/custom_view"/>

效果:

使用Drawable设置背景

如果我们想加入渐变色的背景,分别控制上、下、左、右的圆角角度可以考虑使用Drawable对象,设置改对象并调用setBackGround方法。 attrs:

<!--背景颜色-->
<attr name="solid_color" format="color" />
<!--文字-->
<attr name="text" format="string" />
<!--文字颜色-->
<attr name="text_color" format="color" />
<!--文字大小-->
<attr name="text_size" format="dimension" />
<!--开始颜色-->
<attr name="start_color" format="color" />
<!--中间色-->
<attr name="center_color" format="color" />
<!--结束颜色-->
<attr name="end_color" format="color" />
<!--整体圆角-->
<attr name="corners_radius" format="dimension"/>
<!--左上圆角-->
<attr name="top_left_radius" format="dimension"/>
<!--右上圆角-->
<attr name="top_right_radius" format="dimension"/>
<!--左下圆角-->
<attr name="bottom_left_radius" format="dimension"/>
<!--右下圆角-->
<attr name="bottom_right_radius" format="dimension"/>

<declare-styleable name="MyCustomView">
    <attr name="solid_color" />
    <attr name="text" />
    <attr name="text_color" />
    <attr name="text_size" />
    <attr name="start_color" />
    <attr name="center_color" />
    <attr name="end_color" />
    <attr name="corners_radius"/>
    <attr name="top_left_radius"/>
    <attr name="top_right_radius"/>
    <attr name="bottom_left_radius"/>
    <attr name="bottom_right_radius"/>
    <attr name="background_drawable" format="reference"/>
</declare-styleable>

MyCustomView.java:

mStartColor = a.getColor(R.styleable.MyCustomView_start_color, Color.TRANSPARENT);
mCenterColor = a.getColor(R.styleable.MyCustomView_start_color, Color.TRANSPARENT);
mEndColor = a.getColor(R.styleable.MyCustomView_end_color, Color.TRANSPARENT);
cornersRadius = a.getDimension(R.styleable.MyCustomView_corners_radius, 0.0F);
topLeftRadius = a.getDimension(R.styleable.MyCustomView_top_left_radius, 0.0F);
topRightRadius = a.getDimension(R.styleable.MyCustomView_top_right_radius, 0.0F);
bottomLeftRadius = a.getDimension(R.styleable.MyCustomView_bottom_left_radius, 0.0F);
bottomRightRadius = a.getDimension(R.styleable.MyCustomView_bottom_right_radius, 0.0F);


 @Override
 protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //背景
    setBackGroundColor();
    //文字
    mPaint.setColor(mTextColor);
    if (!TextUtils.isEmpty(mText)) {
    canvas.drawText(mText, (float) getWidth() / 2 - (float) mTextBound.width() / 2, (float) getHeight() / 2 + (float) mTextBound.height() / 2, mPaint);
    }
}


protected void setBackGroundColor() {
    //创建GradientDrawable对象
    GradientDrawable drawable = new GradientDrawable();
    //背景颜色
    if (mSolidColor != 0) {
        drawable.setColor(mSolidColor);
    } else if (mStartColor != 0 && mEndColor != 0) {
        drawable.setColors(new int[]{mStartColor, mEndColor});
    }
    //圆角
    if (topLeftRadius != 0 || topRightRadius != 0 || bottomLeftRadius != 0 || bottomRightRadius != 0) {//设置某个角弧度
        drawable.setCornerRadii(new float[]{topLeftRadius, topLeftRadius, topRightRadius, topRightRadius, bottomRightRadius, bottomRightRadius, bottomLeftRadius, bottomLeftRadius});
    } else if (cornersRadius > 0) {
        drawable.setCornerRadius(cornersRadius);//设置弧度
    }
    setBackground(drawable);
}

xml:

<com.my.mycustomview.MyCustomView
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:layout_centerInParent="true"
    app:start_color="#FF4C81"
    app:end_color="#FF8E61"
    app:text_size="25sp"
    app:text="AAA"
    app:text_color="@color/white"
    app:corners_radius="20dp"
    app:background_drawable="@drawable/custom_view"/>

最终效果:

属性加入Drawable

另一种途径是我们在attrs中加入一个属性来指定背景的drawable。 下面我在MyCustomView的申明中加入background_drawable这个属性,format为reference。 attrs:

<attr name="background_drawable" format="reference"/>

MyCustomView.java:

mDrawable = a.getDrawable(R.styleable.MyCustomView_background_drawable);

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //背景
    if (mDrawable != null) {
        setBackGroundDrawable(mDrawable, canvas);
    }
    //文字
    mPaint.setColor(mTextColor);
    if (!TextUtils.isEmpty(mText)) {
    canvas.drawText(mText, (float) getWidth() / 2 - (float) mTextBound.width() / 2, (float) getHeight() / 2 + (float) mTextBound.height() / 2, mPaint);
    }
}

protected void setBackGroundDrawable(@NonNull Drawable drawable, Canvas canvas) {
    //这里确定引入的drawable对象的边界大小,传入一个Rect对象,大小和View一致
    drawable.setBounds(new Rect(0, 0, getWidth(), getHeight()));
    drawable.draw(canvas);
}

xml:

<com.my.mycustomview.MyCustomView
    android:layout_width="200dp"
    android:layout_height="200dp"
    android:layout_centerInParent="true"
    app:text_size="25sp"
    app:text="AAA"
    app:text_color="@color/white"
    app:background_drawable="@drawable/custom_view"/>

@drawable/custom_view:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">

    <gradient android:startColor="#FF4C81"
        android:endColor="#FF8E61"
        android:angle="270"/>
    <corners android:radius="20dp"/>

</shape>

效果:

5、结尾

那么到这里这篇内容就结束了,在这篇文章中讲述了如何重写onDraw方法,以及加入padding值、在其中使用Drawable对象和属性加入Drawable对象和运用,下一篇将会是我们今天这篇的实战运用,更新时间不定,作者懒狗一条🐶捏,拜拜👋