首页 > 代码库 > 自定义View--一个简单地圆形Progress效果
自定义View--一个简单地圆形Progress效果
先看效果图吧
我们要实现一个自定义的再一个圆形中绘制一个弧形的自定义View,思路是这样的:
先要创建一个类ProgressView,继承自View类,然后重写其中的两个构造方法,一个是一个参数的,一个是两个参数的,因为我们要在xml文件中使用该自定义控件,所以必须要定义这个两个参数的构造函数。创建完了这个类后,我们先不去管它,先考虑我们实现的这个自定义View,我们想让它的哪些部分可以由使用者自己指定,比如说这个Demo中我们让他的外面圆的外边框颜色和宽度,还有扇形部分的颜色,扇形增长的速度等等属性,这时候我们要在项目工程目录的res/values目录下创建一个资源文件命名为attrs(注意,名字随意,只是大多数情况下都这么叫而已),然后我们在这个资源文件中添加我们想要的属性,如下:
1 <?xml version="1.0" encoding="utf-8"?> 2 <resources> 3 <declare-styleable name="ProgressView"> 4 <!--circleColor 设置圆形边框的颜色 sweepColor设置扇形变换的颜色 5 startAngle 设置起始角度 sweepStep 设置变换的步长--> 6 <attr name="circleColor" format="color|reference"></attr> 7 <attr name="sweepColor" format="color|reference"></attr> 8 <attr name="startAngle" format="integer"></attr> 9 <attr name="sweepStep" format="integer"></attr>10 <attr name="padding" format="integer"></attr>11 </declare-styleable>12 </resources>
可以看到,<declare-styleable>标签中的name属性是为了方便我们获取AttributeSet时候使用,而<attr>标签中name,则是我们希望控件可以自定义的属性部分,类似于xml文件中的android:name=""等标签的使用。format属性是设置该属性可以接受什么类型的参数。
定义好了自定义资源类,我们开始写ProgressView中的主要代码:
1 package com.yztc.customprogressview; 2 3 import android.content.Context; 4 import android.content.res.TypedArray; 5 import android.graphics.Canvas; 6 import android.graphics.Color; 7 import android.graphics.Paint; 8 import android.graphics.RectF; 9 import android.util.AttributeSet; 10 import android.view.View; 11 12 /** 13 * 自定义的 14 */ 15 public class ProgressView extends View { 16 private int sweepStep = 10;//扇形变换的步长(就是角度) 17 private int padding = 40;//外边框距离扇形的距离 填充 18 private int circleColor = Color.GRAY;//边框的颜色 19 private int sweepColor = Color.BLUE;//扇形颜色 20 private int startAngle = 90;//起始角度 21 //设置外边框圆的边框粗细 22 private int storke = 20; 23 24 private int sweepAngle = 0;//扫过的角度 25 26 private static final int DEFAULT_WIDTH = 200; 27 private static final int DEFAULT_HEIGHT = 200; 28 29 public ProgressView(Context context) { 30 super(context); 31 } 32 33 //如果要在xml文件中使用该自定义控件,则必须重写两个参数的构造函数 34 //因为我们使用自定义的属性的时候,会默认传递过来一个AttributeSet集合 35 public ProgressView(Context context, AttributeSet attrs) { 36 super(context, attrs); 37 TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ProgressView); 38 if (array != null) { 39 //获取我们在xml中设置的各个自定义属性 40 sweepStep = array.getInteger(R.styleable.ProgressView_sweepStep, sweepStep); 41 padding = array.getInteger(R.styleable.ProgressView_padding, padding); 42 circleColor = array.getColor(R.styleable.ProgressView_circleColor, circleColor); 43 sweepColor = array.getColor(R.styleable.ProgressView_sweepColor, sweepColor); 44 startAngle = array.getInteger(R.styleable.ProgressView_startAngle, startAngle); 45 46 //回收TypeArray资源 47 array.recycle(); 48 } 49 } 50 51 /** 52 * 先绘制外边框 --内部扇形 53 * 54 * @param canvas 55 */ 56 @Override 57 protected void onDraw(Canvas canvas) { 58 Paint mPaint = new Paint(); 59 mPaint.setAntiAlias(true); //设置抗锯齿 60 //绘制外层的圆框 61 mPaint.setColor(circleColor); 62 mPaint.setStrokeWidth(storke); 63 mPaint.setStyle(Paint.Style.STROKE);//设置圆形为空心的圆 64 //这里我们得到控件的Height和Width,根据Heigh和Width来确定圆心的位置,来绘制外层圆 65 canvas.drawCircle(getWidth() / 2, getWidth() / 2, getWidth() / 2 - storke / 2, mPaint); 66 // Log.d("tag", "onDraw: "+getWidth()); 67 invalidate();//请求重新绘制view 68 69 //绘制内部的扇形 70 mPaint.setStyle(Paint.Style.FILL_AND_STROKE); 71 mPaint.setColor(sweepColor); 72 /* 73 drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter,Paint paint) 74 RectF oval 指定扇形的矩形容器对象 指定圆弧的外轮廓的矩形 75 float startAngle 表示圆弧的起始角度 76 float sweepAngle 表示圆弧走过扫过的角度 顺时针方向 77 boolean useCenter 如果设置为true 在绘制圆弧时将圆心包括在内,是指以一个固定的圆心来绘制弧形(扇形), 78 如果指定为false,则不规则绘制扇形 79 Paint paint 画笔 颜色 填充 80 81 public RectF(float left, float top, float right, float bottom) 82 float left 矩形的左边点(左切点)的x坐标 83 float top 矩形上边点(上切点)的y轴坐标 84 float right, 矩形的右边点(右切点)的x坐标 85 float bottom 矩形的下边点(下切点)的y坐标 86 */ 87 //RectF中的四个参数,分别对应其内切圆的四个切点的坐标 88 RectF rectF = new RectF(padding + storke, padding + storke, getWidth() - padding - storke, getWidth() - padding - storke); 89 canvas.drawArc(rectF, startAngle, sweepAngle, true, mPaint); 90 91 sweepAngle += sweepStep;//根据步长更新扫过的角度 92 sweepAngle = sweepAngle > 360 ? 0 : sweepAngle; 93 invalidate();//重绘view 94 } 95 96 //因为我们是继承的View来自定义的View,所以onMeasure()方法要重写 97 @Override 98 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 99 int wMode = MeasureSpec.getMode(widthMeasureSpec);100 int hMode = MeasureSpec.getMode(heightMeasureSpec);101 int wSize = MeasureSpec.getSize(widthMeasureSpec);102 int hSize = MeasureSpec.getSize(heightMeasureSpec);103 104 //因为绘制的是圆,所以判断一下高度或者宽度中的一个就可以。105 switch (wMode) {106 case MeasureSpec.AT_MOST://android:layout_width="warp_content"107 //获取屏幕像素108 float density = getResources().getDisplayMetrics().density;109 wSize = (int) (DEFAULT_WIDTH * density);110 hSize = (int) (DEFAULT_HEIGHT * density);111 break;112 //当在xml中指定控件的宽高为match_parent或者指定数值的宽高时,回调以下代码113 case MeasureSpec.EXACTLY://android:layout_width="match_parent" android:layout_width="40dp"114 wSize = hSize = Math.min(wSize, hSize);115 break;116 }117 //只要重写onMeasure()方法,一定要调用以下方法,不然会报错118 setMeasuredDimension(wSize, hSize);119 }120 }
我们先画一个外部的圆,也就是都用drawCircle()方法,这里我们调用getWidth(),getHeight()表示得到布局中设置的控件的尺寸,因为是圆,所以可以使用getWidth()/2来表示圆心位置。而画内部弧形的时候,确定其圆心位置,左部点的坐标是外面圆的边框加上弧形与原的间距padding来确定,右侧点的x坐标则是getWidth()得到总长度,减去边框宽度,再减去padding得到,上边距和下边距同样,不明白的画一个图立马明白。大部分不好理解的地方都有注释,只要认真看不会看不明白的~
然后就是在布局中引入我们的自定义View了:
1 <?xml version="1.0" encoding="utf-8"?> 2 <RelativeLayout 3 xmlns:android="http://schemas.android.com/apk/res/android" 4 xmlns:tools="http://schemas.android.com/tools" 5 xmlns:app="http://schemas.android.com/apk/res-auto" 6 android:layout_width="match_parent" 7 android:layout_height="match_parent" 8 android:paddingBottom="@dimen/activity_vertical_margin" 9 android:paddingLeft="@dimen/activity_horizontal_margin"10 android:paddingRight="@dimen/activity_horizontal_margin"11 android:paddingTop="@dimen/activity_vertical_margin"12 tools:context="com.yztc.customprogressview.MainActivity">13 14 <com.yztc.customprogressview.ProgressView15 android:id="@+id/pv"16 android:layout_width="match_parent"17 android:layout_height="match_parent"18 android:background="#0000ff"19 app:padding="20"20 app:circleColor="#aa0000"21 app:sweepColor="#00aa00"22 app:sweepStep="1"23 app:startAngle="180"24 />25 </RelativeLayout>
需要注意的是我们需要在布局的跟标签下面加上这么一段代码:xmlns:app="http://schemas.android.com/apk/res-auto"
用来指定我们可以使用自己再attrs中自定义的属性啦~,使用的形式就是app:你定义的属性。当然,这个app也不是固定的写法,只要跟上面你加的那段代码的xmlns后面的字段一致就可以了~
还有需要注意的是:
默认使用Canvas类的drawCircle()方法来画圆的时候,圆的半径是你指定的半径,再加上一半的边长,比如你的边框size=10,radius=50,则实际空心部分的半径为55.注意这点,否则画出来的圆的四个切点位置会与其他位置有些不一样,类似出现锯齿的效果。具体请看drawCircle()的Rect边框问题。
自定义View--一个简单地圆形Progress效果