《Android 群英传》读书笔记:自定义 View -- 弧线展示图

《Android 群英传》读书笔记:自定义 View – 弧线展示图

参考:
《Android 群英传》
《Android 群英传》读书笔记:自定义 View – 弧线展示图

最终效果图:

这里写图片描述

分析:

很明显,这个自定义View其实分为三个部分,分别为中间的圆形,中间显示的文字和外圈的弧线。

新建项目 :

打开 Android Studio,新建 ViewTest 项目。
新建一个CircleViewTest.java 继承 View 并实现两个构造方法。代码如下:

package com.example.pc.viewtest;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by PC on 2017/11/4.
 */
public class CircleViewTest extends View{
    public CircleViewTest(Context context) {
        super(context);
    }

    public CircleViewTest(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

}

1.绘制圆

我们先把圆绘制出来。代码如下:

package com.example.pc.viewtest;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by PC on 2017/11/4.
 */
public class CircleViewTest extends View{

    //中心圆画笔
    private Paint mCirclePaint;

    private Context mContext;

    //画布宽
    private int length;
    //中心圆的XY轴坐标
    private int mCircleXY;
    //中心圆的半径
    private float mRadius;

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

    public CircleViewTest(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        // w  h分别是宽 高  oldw/oldh 之前的width height
        length = w;
        //中心圆的圆心坐标
        mCircleXY = length / 2;
        // 中心圆半径(宽的四分之一为半径)
        mRadius = (float) (length * 0.5 / 2);

        initPaint();
    }

    private void initPaint() {

        //绘制中心圆画笔
        mCirclePaint = new Paint();
        //设置画笔颜色
        mCirclePaint.setColor(ContextCompat.getColor(mContext, R.color.colorAccent));
    }

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

        canvas.drawCircle(mCircleXY,
                mCircleXY,
                mRadius,
                mCirclePaint);
    }
}

由于圆的半径,圆心坐标都和屏幕宽度有关,所以我们在onSizeChanged()方法来获取屏幕宽度。我们就可以给圆的半径,圆心坐标赋值了。

接着我们初始化了画笔,给画笔设置了颜色。
注意 :getResources().getColor() 过时,用ContextCompat.getColor(Context context, int id) 代替。同时兼容高、低版本。
圆的属性,画笔,都已经准备就绪了,接下来,我们就在 onDraw() 方法中画圆。
画圆使用的是 canvas.drawCircle() 方法,这里解释下 drawCircle() 的几个参数:

/**
 * 绘制圆
 * 
 * @param cx 圆心的 x 坐标
 * @param cy 圆心的 y 坐标
 * @param radius 圆的半径
 * @param paint  画笔
 */
public void drawCircle (float cx, float cy, float radius, Paint paint)

这几个参数很好理解,就不多说了。
好了,圆已经画好了,我们把自定义的 View 放在布局文件activity_main.xml 中。代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <!--使用自定义控件时,要引用包名-->
    <com.example.pc.viewtest.CircleViewTest
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

可以看到,圆已经成功的显示出来了。

这里写图片描述

2.绘制文字

圆已经成功的绘制出来,那么接下来我们就来绘制文字,在绘制文字之前,先要给大家普及一个概念:基线。

什么是基线?

引用下 自定义控件之绘图篇( 五):drawText()详解 的内容

小时候,我们在刚开始学习写字母时,用的本子是四线格的,我们必须把字母按照规则写在四线格内。
比如:

这里写图片描述

那么问题来了,在canvas在利用drawText绘制文字时,也是有规则的,这个规则就是基线!
我们先来看一下什么是基线:

这里写图片描述

基线就是四线格中的第三条线!

canvas.drawText() 方法与基线

绘制文字,需要调用 canvas.drawText() 这个方法,那我们来看canvas.drawText() 这个方法:

/**
 * 绘制文字
 * 
 * @param text 要绘制的文字内容
 * @param x 要绘制文字的原点的 x 坐标
 * @param y 要绘制文字的原点的 y 坐标
 * @param paint  画笔
 */
public void drawText(String text, float x, float y, Paint paint)

上面这个构造函数是最常用的 drawText 方法,
传进去一个 String 对象就能画出对应的文字。但这里有两个参数需要非常注意,
文字原点的 x 坐标和 y 坐标 。很多同学可能会认为,这里传进去的原点参数 (x,y) 是绘制文字所在矩形的左上角的点。但实际上并不是!
比如,我们上面如果要画 “harvic’s blog” 这几个字,这个原点坐标应当是下图中绿色小点的位置:

这里写图片描述

一般而言,(x,y) 所代表的位置是所画图形对应的矩形的左上角点。但在 drawText() 中是非常例外的,y 所代表的是基线的位置!

嗯,相信文字原点的概念,大家已经清楚了。

清楚了原点的概念,后面也就好办了。修改 CircleViewTest.java,代码如下:

package com.example.pc.viewtest;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by PC on 2017/11/4.
 */
public class CircleViewTest extends View{

    //中心圆画笔
    private Paint mCirclePaint;

    private Context mContext;

    //画布宽
    private int length;
    //中心圆的XY轴坐标
    private int mCircleXY;
    //中心圆的半径
    private float mRadius;


    //文字画笔
    private Paint mTextPaint;
    //绘制的文字
    private String mShowText = "Android studio";
    //文字大小
    private int mShowTextSize = 50;


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

    public CircleViewTest(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        // w  h分别是宽 高  oldw/oldh 之前的width height
        length = w;
        //中心圆的圆心坐标
        mCircleXY = length / 2;
        // 中心圆半径(宽的四分之一为半径)
        mRadius = (float) (length * 0.5 / 2);

        initPaint();
    }

    private void initPaint() {

        //绘制中心圆画笔
        mCirclePaint = new Paint();
        //设置画笔颜色
        mCirclePaint.setColor(ContextCompat.getColor(mContext, R.color.colorAccent));


        //文字画笔
        mTextPaint = new Paint();
        mTextPaint.setColor(ContextCompat.getColor(mContext,R.color.colorPrimaryDark));

        //文字大小
        mTextPaint.setTextSize(mShowTextSize);

        // 文本对齐
        mTextPaint.setTextAlign(Paint.Align.CENTER);

    }

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

        canvas.drawCircle(mCircleXY,
                mCircleXY,
                mRadius,
                mCirclePaint);

        // 绘制文字
        canvas.drawText(
                mShowText,
                0,
                mShowText.length(),
                mCircleXY,
                mCircleXY + mShowTextSize / 4,
                mTextPaint);
    }
}

我们初始化了文字的画笔,并设置了一些属性,比如画笔颜色,文字大小,文字内容,文本对齐方式。然后调用了 canvas.drawText() 来绘制文字。这里,我们来看下这里我们调用的 canvas.drawText() 方法的各个参数是什么意思:

/**
 * 绘制文字
 * 
 * @param text 要绘制的文字内容
 * @param start 要绘制的文字中第一个字符的索引
 * @param end 要绘制的文字中最后一个字符的索引是 (end - 1)
 * @param x 要绘制文字的原点的 x 坐标
 * @param y 要绘制文字的原点的 y 坐标
 * @param paint 画笔
 */
public void drawText (String text, int start, int end, float x, float y, Paint paint)

参数意思很好懂,就不多说了。

好了,运行一下程序:
这里写图片描述

关于 setTextAlign() 方法

不知道大家注意到没有,刚才设置文本对齐的时候,我们调用了 setTextAlign() 方法,并传入了 Paint.Align.CENTER ,让文字的显示效果是水平居中。

那么,我想问一下,如果我传入 Align.LEFT ,文字会怎样放置?有的同学可能会想,这还不简单,当然是和屏幕左边缘对齐了。

真的是这样吗?

我们修改下代码:

// mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setTextAlign(Paint.Align.LEFT);

运行一下程序:

这里写图片描述

咦?什么情况?怎么跑到右边去了,也没有和屏幕右边缘对齐。

我们查看下 setTextAlign 的 api:

Set the paint’s text alignment. This controls how the text is positioned relative to its origin. LEFT align means that all of the text will be drawn to the right of its origin (i.e. the origin specifieds the LEFT edge of the text) and so on.

大意是:设置绘制文字的对齐方式,控制绘制文字如何相对于文字原点的位置定位,如果是 LEFT,表示文字的左边缘和原点对齐。

原来,这里 Align.LEFT 的意思并不是和屏幕左边缘对齐,而是文字的左边缘和原点对齐,也就是原点在文字的左边(原点即上面图片中的绿色小点),我们设置的原点的 x 坐标是在屏幕中间,原点又在文字的左边,那文字自然就跑到屏幕右边了。

那这样的话,如果设置成 Align.RIGHT,那么原点就会在文字的右边。文字就会跑到屏幕左边。为了验证我们的猜想,修改下代码:

// mTextPaint.setTextAlign(Paint.Align.CENTER);
// mTextPaint.setTextAlign(Paint.Align.LEFT);
mTextPaint.setTextAlign(Paint.Align.RIGHT);

运行一下程序:

这里写图片描述

果然,文字跑到屏幕左边了。

3.绘制弧形

好了,圆和文字我们都绘制出来了,只剩弧形了。

Android 给我们提供了 canvas.drawArc() 来绘制弧形。我们来看下 canvas.drawArc() 方法的各个参数意思:

/**
 * 绘制圆弧
 *
 * @param oval 椭圆的边界,用来定义弧形的形状和大小
 * @param startAngle 圆弧的起始角度
 * @param sweepAngle 圆弧扫过的角度
 * @param useCenter 这个参数如果为 true,就把椭圆的中心包括在圆弧中
 * @param paint 画笔
 */
public void drawArc (RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)

这个方法参数有点不好理解,所以详细解释下:

第一个参数 oval

看上去我们要传入一个椭圆参数。

我们发现,这个椭圆参数居然是矩形类型。换句话说,我们传进去一个矩形,然后系统会使用矩形的内切椭圆来定义弧形的形状和大小。

内切椭圆。。。

估计很多人的数学知识都早已还给数学老师了,没事,这里我准备了二张图,来帮助大家理解:

这里写图片描述

内切椭圆就是矩形里面的那个椭圆。我们可以看出,只要一个矩形的形状和大小确定下来了,那么它的内切椭圆也就确定下来了。

所以呢,我们创建一个矩形传入 canvas.drawArc() 方法,系统就会使用这个矩形的内切椭圆的边界来确定弧形的形状和大小。

第二个参数 startAngle 和第三个参数 sweepAngle :

圆弧的起始角度 startAngle 和圆弧扫过的角度 sweepAngle

这这。。又用到数学知识了:平面直角坐标系。这里我也准备了一张图,来解释坐标系的角度:

这里写图片描述

第四个参数 useCenter

这个参数如果为 true,就把椭圆的中心包括在圆弧中。

听不懂是吧?听不懂就对了,我特么自己都听不懂,所以我们直接看图

这里写图片描述

最后一个参数 paint

不解释。

好了,接下来我们就来绘制圆弧。修改 CircleProgressView.java,代码如下:

package com.example.pc.viewtest;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by PC on 2017/11/4.
 */
public class CircleViewTest extends View{

    //中心圆画笔
    private Paint mCirclePaint;

    private Context mContext;

    //画布宽
    private int length;
    //中心圆的XY轴坐标
    private int mCircleXY;
    //中心圆的半径
    private float mRadius;


    //文字画笔
    private Paint mTextPaint;
    //绘制的文字
    private String mShowText = "Android studio";
    //文字大小
    private int mShowTextSize = 50;


    //弧线的画笔
    private Paint mArcPaint;
    //弧线扫过的角度
    private float mSweepAngle;
    //辅助矩形
    private RectF mArcRectF;


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

    public CircleViewTest(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        // w  h分别是宽 高  oldw/oldh 之前的width height
        length = w;
        //中心圆的圆心坐标
        mCircleXY = length / 2;
        // 中心圆半径(宽的四分之一为半径)
        mRadius = (float) (length * 0.5 / 2);

        // 圆弧扫过的角度,360 度的 66%
        mSweepAngle = 66 / 100f * 360f;

        initPaint();
    }

    private void initPaint() {

        //绘制中心圆画笔
        mCirclePaint = new Paint();
        //设置画笔颜色
        mCirclePaint.setColor(ContextCompat.getColor(mContext, R.color.colorAccent));


        //文字画笔
        mTextPaint = new Paint();
        mTextPaint.setColor(ContextCompat.getColor(mContext,R.color.colorPrimaryDark));

        //文字大小
        mTextPaint.setTextSize(mShowTextSize);

        // 文本对齐
        mTextPaint.setTextAlign(Paint.Align.RIGHT);


        // 弧形的画笔
        mArcPaint = new Paint();
        // 颜色
        mArcPaint.setColor(ContextCompat.getColor(mContext,R.color.colorAccent));
        // 描边
        mArcPaint.setStyle(Paint.Style.STROKE);
        // 描边宽度
        mArcPaint.setStrokeWidth(length * 0.1f);

    }

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

        canvas.drawCircle(mCircleXY,
                mCircleXY,
                mRadius,
                mCirclePaint);

        // 绘制文字
        canvas.drawText(
                mShowText,
                0,
                mShowText.length(),
                mCircleXY,
                mCircleXY + mShowTextSize / 4,
                mTextPaint);

        //绘制一个矩形,用来辅助绘制圆弧
        //RectF是使用float类型作为数值
        mArcRectF = new RectF(
                (float) (length * 0.1),
                (float) (length * 0.1),
                (float) (length * 0.9),
                (float) (length * 0.9));

        //绘制圆弧
        canvas.drawArc(
                mArcRectF,
                270,
                mSweepAngle,
                false,
                mArcPaint
        );
    }
}

代码没什么好说的,嗯,再解释下关于创建矩形。矩形 RectF 的构造方法有四个参数,含义如下:

/**
 * 创建矩形
 * 
 * @param left 矩形左边缘的 x 坐标
 * @param top 矩形上边缘的 y 坐标
 * @param right 矩形右边缘的 x 坐标
 * @param bottom 矩形下边缘的 y 坐标
 */
public RectF (float left, float top, float right, float bottom)

也很好懂,不多说。

运行一下程序:

这里写图片描述

好了,我们已经成功的把比例图绘制出来了。

绘制顺序

最后需要注意一下的是:在 onDraw() 方法中,canvas 绘制的图案是依次覆盖的。也就是说,如果你先画文字,再画圆,那么圆就会把文字覆盖掉,导致你在屏幕上看不到绘制的文字。

最后

贴上 CircleViewTest.java 的全部源码:

package com.example.pc.viewtest;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.View;

/**
 * Created by PC on 2017/11/4.
 */
public class CircleViewTest extends View{

    //中心圆画笔
    private Paint mCirclePaint;

    private Context mContext;

    //画布宽
    private int length;
    //中心圆的XY轴坐标
    private int mCircleXY;
    //中心圆的半径
    private float mRadius;


    //文字画笔
    private Paint mTextPaint;
    //绘制的文字
    private String mShowText = "Android studio";
    //文字大小
    private int mShowTextSize = 50;


    //弧线的画笔
    private Paint mArcPaint;
    //弧线扫过的角度
    private float mSweepAngle;
    //辅助矩形
    private RectF mArcRectF;


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

    public CircleViewTest(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        // w  h分别是宽 高  oldw/oldh 之前的width height
        length = w;
        //中心圆的圆心坐标
        mCircleXY = length / 2;
        // 中心圆半径(宽的四分之一为半径)
        mRadius = (float) (length * 0.5 / 2);

        // 圆弧扫过的角度,360 度的 66%
        mSweepAngle = 66 / 100f * 360f;

        initPaint();
    }

    private void initPaint() {

        //绘制中心圆画笔
        mCirclePaint = new Paint();
        //设置画笔颜色
        mCirclePaint.setColor(ContextCompat.getColor(mContext, R.color.colorAccent));


        //文字画笔
        mTextPaint = new Paint();
        mTextPaint.setColor(ContextCompat.getColor(mContext,R.color.colorPrimaryDark));

        //文字大小
        mTextPaint.setTextSize(mShowTextSize);

        // 文本对齐
        mTextPaint.setTextAlign(Paint.Align.CENTER);


        // 弧形的画笔
        mArcPaint = new Paint();
        // 颜色
        mArcPaint.setColor(ContextCompat.getColor(mContext,R.color.colorAccent));
        // 描边
        mArcPaint.setStyle(Paint.Style.STROKE);
        // 描边宽度
        mArcPaint.setStrokeWidth(length * 0.1f);

    }

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

        canvas.drawCircle(mCircleXY,
                mCircleXY,
                mRadius,
                mCirclePaint);

        // 绘制文字
        canvas.drawText(
                mShowText,
                0,
                mShowText.length(),
                mCircleXY,
                mCircleXY + mShowTextSize / 4,
                mTextPaint);

        //绘制一个矩形,用来辅助绘制圆弧
        //RectF是使用float类型作为数值
        mArcRectF = new RectF(
                (float) (length * 0.1),
                (float) (length * 0.1),
                (float) (length * 0.9),
                (float) (length * 0.9));

        //绘制圆弧
        canvas.drawArc(
                mArcRectF,
                270,
                mSweepAngle,
                false,
                mArcPaint
        );
    }
}

感谢阅读,下次再见。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值