[[191018]]
效果图
可以看到这个自定义控件结合了颜色渐变、动态绘制刻度、动态水球效果。接下来我们就来看看这个效果是如何一步一步实现的。
开始自定义控件
和很多自定义控件方式一样需要去基础某种View或者某种ViewGroup
我这里选择的是View,如下所示:
- public class HuaWeiView extends View {
- /**
- * 用来初始化画笔等
- * @param context
- * @param attrs
- */
- public HuaWeiView(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- }
- /**
- * 用来测量限制view为正方形
- * @param widthMeasureSpec
- * @param heightMeasureSpec
- */
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- }
- /**
- * 实现各种绘制功能
- * @param canvas
- */
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- }
- }
其中构造方法用来布局中使用。
onMeasure()方法用来测量和限定view大小
onDraw()方法用来进行具体的绘制功能
1.使用onMeasure()方法将View限制为一个正方形
只有确定了一个矩形才能够去画椭圆,如果这个矩形是正方形,椭圆也就随之变成了圆形。
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int width=MeasureSpec.getSize(widthMeasureSpec);
- int height = MeasureSpec.getSize(heightMeasureSpec);
- //以最小值为正方形的长
- len=Math.min(width,height);
- //设置测量高度和宽度(必须要调用,不然无效果)
- setMeasuredDimension(len,len);
- }
分别通过MeasureSpec取得用户设置的宽和高,然后取出最小值,设置给我们的view,这样我们就做好了一个矩形
现在使用在布局中:
- <?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"
- android:background="@color/colorPrimary"
- android:padding="20dp"
- tools:context="com.example.huaweiview.MainActivity">
- <com.example.huaweiview.HuaWeiView
- android:layout_gravity="center"
- android:background="@color/colorAccent"
- android:layout_width="200dp"
- android:layout_height="300dp"
- />
- </LinearLayout>
父布局背景为蓝色背景,控件背景为粉色背景,而且设置的宽高不同,但是控件的显示效果还是一个正方形,而且以小值为准。我们的onMeasure()生效了
接下来就是如何在确定一个圆形区域了
2.onDraw()绘制圆形区域
绘制之前我们需要对Android中的坐标系有个了解
我们都知道手机屏幕左上角为坐标原点,往右为X正轴,往下为Y正轴。其实手机页面就是activity的展示界面,也是一个View。那可不可以说所有的View在绘制图形的时候都有自己的这么一个坐标系呢(个人想法。。。)
也就是所每个View都有自己的一个坐标系,比如现在的自定义View:
现在我们需要在我们自定义的view中绘制一个圆弧,那么这个圆弧的半径就是我们自定义view的长度的一半,即:
radius=len/2;
那么圆心的坐标刚好是(radius,radius)
接下来开始绘制
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- //画圆弧的方法
- canvas.drawArc(oval, startAngle, sweepAngle, useCenter,paint);
- }
介绍一下绘制圆弧的方法:
- 参数一oval是一个RectF对象为一个矩形
- 参数二startAngle为圆弧的起始角度
- 参数三sweepAngle为圆弧的经过角度(扫过角度)
- 参数四useCenter为圆弧是一个boolean值,为true时画的是圆弧,为false时画的是割弧
- 参数五paint为一个画笔对象
也就是说只要确定了一个矩形,在确定他起始和经过的角度就能够画出一个圆弧(这点大家可以用画板测试)
下来就是初始化这些参数
初始化矩形
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int width = MeasureSpec.getSize(widthMeasureSpec);
- int height = MeasureSpec.getSize(heightMeasureSpec);
- //以最小值为正方形的长
- len = Math.min(width, height);
- //实例化矩形
- oval=new RectF(0,0,len,len);
- //设置测量高度和宽度(必须要调用,不然无效果)
- setMeasuredDimension(len, len);
- }
画矩形需要确定左上角和右下角的坐标(通过画板可以测试),通过上面的分析坐标原点就是我们view的左上角,右下角的坐标当然就是len了。
接下来就是初始化起始和经过角度
- private float startAngle=120;
- private float sweepAngle=300;
需要搞清楚往下为Y轴正轴,刚好和上学时候学的相反,也就是说90度在下方,-90度在上方
初始化画笔
- public HuaWeiView(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- paint =new Paint();
- //设置画笔颜色
- paint.setColor(Color.WHITE);
- //设置画笔抗锯齿
- paint.setAntiAlias(true);
- //让画出的图形是空心的(不填充)
- paint.setStyle(Paint.Style.STROKE);
- }useCenter=false
到这里真不容易呀,然而发现只画个圆弧没用呀,我要的是刻度线呀,canvas里面又没用给我们提供画刻度线的方法,这个时候就需要我们自己去写一个画刻度线的方法了。
通过观察图片我们可以看出,所有的线都是从圆弧上的点为起点向某个方向画一条直线,那么该如何确定这两个点呢,需要我们做两件事:
移动坐标系
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- //画圆弧的方法
- canvas.drawArc(oval, startAngle, sweepAngle, useCenter,paint);
- //画刻度线的方法
- drawViewLine(canvas);
- }
- private void drawViewLine(Canvas canvas) {
- //先保存之前canvas的内容
- canvas.save();
- //移动canvas(X轴移动距离,Y轴移动距离)
- canvas.translate(radius,radius);
- //操作完成后恢复状态
- canvas.restore();
- }
我们自己写了一个绘制刻度线的方法并在onDraw()方法中调用。移动坐标系之前需要保存之前的canvas状态,然后X和Y轴分别移动圆弧半径的距离,如下图:
canvas.translate(radius,radius);方法移动的是坐标系(通过实际效果和查资料所得)
canvas.save()和canvas.restore()要成对出现,就好像流用完要关闭一样。
***件事情完成后,开始第二件事情,旋转坐标系
只通过移动坐标系,仍然很难确定圆弧点上的坐标,和另外一点的坐标,如果这两个点都在坐标轴上该多好呀,下面实现:
- private void drawViewLine(Canvas canvas) {
- //先保存之前canvas的内容
- canvas.save();
- //移动canvas(X轴移动距离,Y轴移动距离)
- canvas.translate(radius,radius);
- //旋转坐标系
- canvas.rotate(30);
- //操作完成后恢复状态
- canvas.restore();
画刻度线的方法了增加了一个旋转30度的代码,旋转后的坐标系应该怎么样呢;
因为起始点和90度相差30,旋转之后,起始点刚好落在了Y轴上,那么这个点的坐标就很好确定了吧,没错就是(0,radius);如果我们在Y轴上在找一点不就可以画出一条刻度线了吗,那么它的坐标是多少呢?对,应该是(0,radius-y),因为我们要往内部化刻度线,因此是减去一个值,赶快去试试吧,代码如下:
- private void drawViewLine(Canvas canvas) {
- //先保存之前canvas的内容
- canvas.save();
- //移动canvas(X轴移动距离,Y轴移动距离)
- canvas.translate(radius,radius);
- //旋转坐标系
- canvas.rotate(30);
- Paint linePatin=new Paint();
- //设置画笔颜色
- linePatin.setColor(Color.WHITE);
- //线宽
- linePatin.setStrokeWidth(2);
- //设置画笔抗锯齿
- linePatin.setAntiAlias(true);
- //画一条刻度线
- canvas.drawLine(0,radius,0,radius-40,linePatin);
- //操作完成后恢复状态
- canvas.restore();
- }
根据得到的两个点的坐标,画出来一条白线,如图:
当然这些点都是移动后的坐标系在旋转30度得到的,这里画好了一条线,如果画多条呢,还是刚才的思路每次都让它旋转一个小角度然后画条直线不就好了吗,那么旋转多少度呢,比如这里:总共扫过的角度sweepAngle=300;需要100条刻度,那么每次需要旋转的角度rotateAngle=sweepAngle/100,具体代码如下:
- private void drawViewLine(Canvas canvas) {
- //先保存之前canvas的内容
- canvas.save();
- //移动canvas(X轴移动距离,Y轴移动距离)
- canvas.translate(radius,radius);
- //旋转坐标系
- canvas.rotate(30);
- Paint linePatin=new Paint();
- //设置画笔颜色
- linePatin.setColor(Color.WHITE);
- //线宽
- linePatin.setStrokeWidth(2);
- //设置画笔抗锯齿
- linePatin.setAntiAlias(true);
- //确定每次旋转的角度
- float rotateAngle=sweepAngle/99;
- for(int i=0;i<100;i++){
- //画一条刻度线
- canvas.drawLine(0,radius,0,radius-40,linePatin);
- canvas.rotate(rotateAngle);
- }
- //操作完成后恢复状态
- canvas.restore();
- }
100个刻度,需要101次循环画线(请看你的手表),画完线就旋转。依次循环,如图
经过这么久的时间总于完成了刻度盘了,接下来就是去确定不同角度显示什么样的颜色,***我们需要确定要绘制的范围targetAngle:
绘制有色部分
- private void drawViewLine(Canvas canvas) {
- //先保存之前canvas的内容
- canvas.save();
- //移动canvas(X轴移动距离,Y轴移动距离)
- canvas.translate(radius,radius);
- //旋转坐标系 </l