学习安卓笔记之自定义控件(三)
------自定义CircleBar
前两天写了个环形进度条弄得有点像CircleBar,今天就来写一个自定义的CircleBar。(最近也是突然开了点窍,稍微明白了一点自定义控件,怕以后忘了先做点笔记。)
首先来看下效果,感觉还算可以,跟上一张内容其实差不了多少。基本是一样的!
第一步:创建自定义属性
按照惯例,这里还是用到了自定义属性,代码如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyCircleBar">
<!-- 绘制角度类型 -->
<attr name="circle_ring_angle_type">
<enum name="angle_270" value="1"/>
<enum name="angle_360" value="2"/>
</attr>
<!-- 圆环的宽度 -->
<attr name="circle_ring_width" format="dimension"/>
<!-- 显示默认的颜色 -->
<attr name="circle_ring_un_reached" format="color"/>
<!-- 显示进度的颜色 -->
<attr name="circle_ring_reached" format="color"/>
<!-- 设置最大进度 -->
<attr name="circle_max_progress" format="integer"/>
<!-- 设置显示的进度 -->
<attr name="circle_show_progress" format="integer"/>
<!-- 设置渐变颜色 -->
<attr name="circle_start_color" format="color"/>
<attr name="circle_center_color" format="color"/>
<attr name="circle_end_color" format="color"/>
<!-- 是否开渐变 -->
<attr name="circle_gradient_on" format="boolean"/>
<!-- 提示文字的颜色 -->
<attr name="circle_hint_text_color" format="color"/>
<!-- 显示文字的颜色 -->
<attr name="circle_show_text_color" format="color"/>
<!-- 刻度的颜色 -->
<attr name="circle_show_scale_color" format="color"/>
<!-- 是否显示刻度 -->
<attr name="circle_show_scale_on" format="boolean"/>
</declare-styleable>
</resources>
第二步:创建MyCircleBar
整类贴出,直接Copy就能用:
package lyan.circlebar.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.RectF;
import android.graphics.SweepGradient;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.Transformation;
import java.text.DecimalFormat;
import lyan.circlebar.R;
/**
* Author LYJ
* Created on 2016/9/12.
* Time 21:56
*/
public class MyCircleBar extends View {
/**
* 属性常量
*/
private static final int INIT_270_ANGLE = 270;//270度
private static final int INIT_360_ANGLE = 360;//360度
private static final int START_POINT_270 = 135;//圆环起始点角度
private static final int START_POINT_360 = 90;//圆环起始点角度
private static final int DEFAULT_CIRCLE_SIDE = 200;//控件而的默认边长
private static final int DEFAULT_ANGLE_TYPE = 2;//默认类型-->360
private static final int CHANGE_ANGLE_TYPE = 1;//默认类型-->270
private static final int DEFAULT_RING_WIDTH = 30;//默认宽度
private static final int DEFAULT_RING_UN_REACHED_COLOR = 0xff545454;//默认颜色
private static final int DEFAULT_RING_REACHED_COLOR = 0xff4592f3;//默认进度颜色
private static final int DEFAULT_PROGRESS = 0;//默认进度是0
private static final int DEFAULT_MAX_PROGRESS = 100;//默认最大进度值
private static final boolean DEFAULT_GRADIENT_ON = false;//默认是关闭的
private static final int DEFAULT_CIRCLE_START_COLOR = 0xff00ff00;//绿色
private static final int DEFAULT_CIRCLE_CENTER_COLOR = 0xffffff00;//黄色
private static final int DEFAULT_CIRCLE_END_COLOR = 0xffff0000;//红色
private static final int DEFAULT_HINT_TEXT_COLOR = 0xff000000;//黑色
private static final int DEFAULT_SHOW_TEXT_COLOR = 0xff00ffff;//黑色
private static final int DEFAULT_DRAW_SCALE_COLOR = 0xffff00ff;//紫红色
private static final boolean DEFAULT_DRAW_SCALE_ON = false;//默认不绘制刻度
/**
* 绘制变量
*/
private int circle_diameter;//圆环的直径
private int circle_type;//圆环类型
private int circle_width;//宽度
private int unReachedColor;//颜色
private int reachedColor;//颜色
private int drawArcStartAngle;//绘制圆弧的起始点
private int drawMaxValues;//绘制的最大值
private Paint unReachedPaint,reachedPaint;//画笔
private RectF drawArcRect;//绘制弧形的区域
private int drawCircleRadius;//实际绘制半径
private int drawOffset;//绘制的偏移量
private int circlePointX;//中心X
private int circlePointY;//中心Y
private int nowProgress;//当前进度
private int maxProgress;//最大进度值
private boolean drawSingleColor;//是否绘制单一的颜色
private int startColor;//起始颜色
private int centerColor;//中间颜色
private int endColor;//结束颜色
private SweepGradient gradientColors;//渐变颜色
private PaintFlagsDrawFilter mDrawFilter;//图形抗锯齿
private BarAnimation circleAnimation;//进度动画
private float unit;//角度值
private Paint drawHintText;//绘制提示
private Paint drawShowText;//绘制文字
private RectF drawTextRect;//绘制文字的区域
private int hintTextColor;//提示文字颜色
private int showTextColor;//显示文字颜色
private double values;//显示的值
private int drawScaleHeightStartPoint;//绘制刻度的起点
private Paint drawScalePaint;//绘制刻度
private int drawScaleColor;//绘制刻度的颜色
private boolean isShowScale;//是否显示刻度
/**
* 构造
*
* @param context
*/
public MyCircleBar(Context context) {
this(context, null);
}
public MyCircleBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyCircleBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
obtainAttributes(attrs);//获取自定义属性
init();//初始化
initPaint();//初始化画笔
}
/**
* 初始化
*/
private void init() {
drawArcStartAngle = circle_type == DEFAULT_ANGLE_TYPE ? START_POINT_360 : START_POINT_270;//绘制圆弧的开始角度
drawMaxValues = circle_type == DEFAULT_ANGLE_TYPE ? INIT_360_ANGLE : INIT_270_ANGLE;//绘制出的图形
drawArcRect = new RectF();//创建这个对象
drawOffset = (circle_width>>1) + dip2px(2);//为了美观
drawScaleHeightStartPoint = circle_width + dip2px(2);
circleAnimation = new BarAnimation();
drawTextRect = new RectF();
}
/**
* 初始化画笔
*/
private void initPaint() {
//绘制默认圆环
unReachedPaint = new Paint();
unReachedPaint.setAntiAlias(true);
unReachedPaint.setDither(true);
unReachedPaint.setStrokeWidth(circle_width);
unReachedPaint.setColor(unReachedColor);
unReachedPaint.setStyle(Paint.Style.STROKE);
unReachedPaint.setStrokeCap(Paint.Cap.ROUND);
//绘制进度
reachedPaint = new Paint();
reachedPaint.setAntiAlias(true);
reachedPaint.setDither(true);
reachedPaint.setStrokeWidth(circle_width);
reachedPaint.setStyle(Paint.Style.STROKE);
reachedPaint.setStrokeCap(Paint.Cap.ROUND);
//绘制提示文字
drawHintText = new Paint();
drawHintText.setAntiAlias(true);
drawHintText.setColor(hintTextColor);
drawHintText.setTextAlign(Paint.Align.CENTER);
drawHintText.setStyle(Paint.Style.FILL);
//绘制显示文字
drawShowText = new Paint();
drawShowText.setAntiAlias(true);
drawShowText.setColor(showTextColor);
drawShowText.setTextAlign(Paint.Align.CENTER);
drawShowText.setStyle(Paint.Style.FILL);
//绘制刻度
drawScalePaint = new Paint();
drawScalePaint.setAntiAlias(true);
drawScalePaint.setDither(true);
drawScalePaint.setColor(drawScaleColor);
//设置抗锯齿
mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
}
/**
* 绘制
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制圆弧的区域
drawArcRect.left = drawOffset;
drawArcRect.top = drawOffset;
drawArcRect.right = circle_diameter - drawOffset;
drawArcRect.bottom = circle_diameter - drawOffset;
//实际绘制的半径长度
drawCircleRadius = (circle_diameter>>1) - drawOffset;
//绘制文字的区域
int chord_length = (int) Math.sqrt(Math.pow(circle_diameter, 2)
+ Math.pow(circle_diameter, 2));//获取矩形的对角线长度
drawTextRect.left = (int) ((chord_length / 2 - drawCircleRadius) * Math.sin(45));
drawTextRect.top = drawTextRect.left;
drawTextRect.right = circle_diameter - drawTextRect.left;
drawTextRect.bottom = circle_diameter - drawTextRect.top;
int placeValues = (int)(drawTextRect.bottom - drawTextRect.top)/5;
//绘制文本
drawHintText.setTextSize(placeValues);
drawHintText.setTypeface(Typeface.DEFAULT_BOLD);
canvas.drawText("已完成",circle_diameter/2,(float)(placeValues/2)*3 + drawTextRect.top,drawHintText);
drawShowText.setTextSize(placeValues);
drawShowText.setTypeface(Typeface.DEFAULT_BOLD);
canvas.drawText(getStringValues(values) + "%",circle_diameter/2,placeValues*4 + drawTextRect.top,drawShowText);
//中心点
circlePointX = circle_diameter>>1;
circlePointY = circlePointX;
//绘制默认圆环
switch (circle_type){
case DEFAULT_ANGLE_TYPE://绘制圆形
canvas.drawCircle(circlePointX,circlePointY,drawCircleRadius,unReachedPaint);
break;
case CHANGE_ANGLE_TYPE://绘制弧形
canvas.drawArc(drawArcRect,drawArcStartAngle,drawMaxValues,false,unReachedPaint);
break;
}
//绘制进度
if (drawSingleColor){
reachedPaint.setShader(gradientColors());
}else {
reachedPaint.setColor(reachedColor);
}
if (values <= maxProgress){
//当设置进度小于最大进度就进行绘制
canvas.drawArc(drawArcRect,drawArcStartAngle,
unit == 0 ?(float) nowProgress/(float) maxProgress*drawMaxValues:unit,
false,reachedPaint);
}else {
canvas.drawArc(drawArcRect,drawArcStartAngle,drawMaxValues,false,reachedPaint);
}
//绘制刻度
if (isShowScale){
int scaleLength = (int) (drawTextRect.top - drawScaleHeightStartPoint)/2;//刻度的长度
float scaleValues = drawMaxValues*1.0f/100;
canvas.save();
int drawCounts;
if (circle_type == DEFAULT_ANGLE_TYPE){
canvas.rotate(-180,circlePointX,circlePointY);
drawCounts = 100;
}else {
canvas.rotate(-135,circlePointX,circlePointY);
drawCounts = 101;
}
canvas.translate(circlePointX,drawScaleHeightStartPoint);
for (int i = 0;i < drawCounts;i++){
if (i % 10 == 0){
canvas.drawLine(0,0,0,scaleLength,drawScalePaint);
}else{
canvas.drawLine(0,0,0,scaleLength/2,drawScalePaint);
}
canvas.rotate(scaleValues,0,circlePointY - drawScaleHeightStartPoint);
}
canvas.restore();
}
canvas.setDrawFilter(mDrawFilter);
}
/**
* 格式化
*/
private String getStringValues(double values){
DecimalFormat decimalFormat = new DecimalFormat("0.00");
return decimalFormat.format(values);
}
/**
* 颜色渐变
*/
private SweepGradient gradientColors(){
if (gradientColors == null){
switch (circle_type){
case DEFAULT_ANGLE_TYPE:
gradientColors = new SweepGradient(circlePointX,circlePointY,
new int[]{startColor,centerColor,endColor,centerColor,startColor},null);
break;
case CHANGE_ANGLE_TYPE:
gradientColors = new SweepGradient(circlePointX,circlePointY,
new int[]{startColor,centerColor,endColor,startColor},null);
break;
}
Matrix matrix = new Matrix();
matrix.setRotate(drawArcStartAngle,circlePointX,circlePointY);
gradientColors.setLocalMatrix(matrix);
}
return gradientColors;
}
/**
* 设置进度值
*/
public void updateProgress(int progress){
this.nowProgress = progress;
unit = (float) nowProgress/(float) maxProgress*drawMaxValues;
values = (double) nowProgress;
invalidate();
}
/**
* 显示进度
*/
public void showProgress(int progress,long time){
this.nowProgress = progress;
circleAnimation.setDuration(time);
circleAnimation.setInterpolator(new LinearInterpolator());
startAnimation(circleAnimation);
}
/**
* 设置最大进度值
*/
public void setMaxProgress(int maxProgress){
this.maxProgress = maxProgress;
}
/**
* 获取自定义属性
*/
private void obtainAttributes(AttributeSet attrs) {
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.MyCircleBar);
circle_type = typedArray.getInt(R.styleable.MyCircleBar_circle_ring_angle_type, DEFAULT_ANGLE_TYPE);
circle_width = dip2px((int) typedArray.getDimension(R.styleable.MyCircleBar_circle_ring_width, DEFAULT_RING_WIDTH));
unReachedColor = typedArray.getColor(R.styleable.MyCircleBar_circle_ring_un_reached, DEFAULT_RING_UN_REACHED_COLOR);
reachedColor = typedArray.getColor(R.styleable.MyCircleBar_circle_ring_reached, DEFAULT_RING_REACHED_COLOR);
nowProgress = typedArray.getInt(R.styleable.MyCircleBar_circle_show_progress,DEFAULT_PROGRESS);
maxProgress = typedArray.getInt(R.styleable.MyCircleBar_circle_max_progress,DEFAULT_MAX_PROGRESS);
drawSingleColor = typedArray.getBoolean(R.styleable.MyCircleBar_circle_gradient_on,DEFAULT_GRADIENT_ON);
startColor = typedArray.getColor(R.styleable.MyCircleBar_circle_start_color,DEFAULT_CIRCLE_START_COLOR);
centerColor = typedArray.getColor(R.styleable.MyCircleBar_circle_center_color,DEFAULT_CIRCLE_CENTER_COLOR);
endColor = typedArray.getColor(R.styleable.MyCircleBar_circle_end_color,DEFAULT_CIRCLE_END_COLOR);
hintTextColor = typedArray.getColor(R.styleable.MyCircleBar_circle_hint_text_color,DEFAULT_HINT_TEXT_COLOR);
showTextColor = typedArray.getColor(R.styleable.MyCircleBar_circle_show_text_color,DEFAULT_SHOW_TEXT_COLOR);
drawScaleColor = typedArray.getColor(R.styleable.MyCircleBar_circle_show_scale_color,DEFAULT_DRAW_SCALE_COLOR);
isShowScale = typedArray.getBoolean(R.styleable.MyCircleBar_circle_show_scale_on,DEFAULT_DRAW_SCALE_ON);
typedArray.recycle();
}
/**
* 测量
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取宽度和高度的测量模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);//宽度的测量模式
int heightMode = MeasureSpec.getMode(heightMeasureSpec);//高度的测量模式
//获取宽度和高度的测量值
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//获取最小值
circle_diameter = Math.min(opinionSide(widthMode, widthSize),
opinionSide(heightMode, heightSize));
//获取宽和高的最小值最为边长(直径),设置控件的宽和高
setMeasuredDimension(circle_diameter, circle_diameter);
}
/**
* 测量
*/
private int opinionSide(int mode, int size) {
int result = 0;
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else {
// 设置默认边长
int defaultSize = DEFAULT_CIRCLE_SIDE;
if (mode == MeasureSpec.AT_MOST) {
result = Math.min(size, defaultSize);
}
}
return result;
}
/**
* dp转px
*/
private int dip2px(int dipValues) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dipValues, getResources().getDisplayMetrics());
}
/**
* 进度条动画
*/
public class BarAnimation extends Animation {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
unit = (float) nowProgress/(float) maxProgress*drawMaxValues*interpolatedTime;
values = nowProgress * interpolatedTime;
postInvalidate();
}
}
}
第三步:测试效果
布局如下(circle_ring_angle_type这个属性只有两个值一个是angle_270另一个是angle_360作用就是一个画弧一个画圆):
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:lyan = "http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="lyan.circlebar.MyCircleBarActivity">
<lyan.circlebar.view.MyCircleBar
android:id="@+id/my_circle_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
lyan:circle_ring_angle_type="angle_270"
lyan:circle_max_progress="100"
lyan:circle_start_color="#ff00ff"
lyan:circle_center_color="#00ffff"
lyan:circle_end_color="#0000ff"
lyan:circle_gradient_on="true"
lyan:circle_show_scale_on="true"
lyan:circle_ring_un_reached="#e8122831"
lyan:circle_show_scale_color="#ed4613"/>
</RelativeLayout>
Activity中的代码如下:我们用一个线程去模拟更新进度:
public class MyCircleBarActivity extends AppCompatActivity {
private int count = 0;
private MyCircleBar myCircleBar;//自定义的CircleBar
private Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message message) {
if (message.what == 1){
count++;
if (count > 100){
return false;
}
myCircleBar.updateProgress(count);//更新进度
Log.e("count -- >" , count + "");
}
return false;
}
});
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_circle_bar);
myCircleBar = (MyCircleBar) findViewById(R.id.my_circle_bar);
new Thread(new Runnable() {
@Override
public void run() {
while (count < 100){
try {
Thread.sleep(100);
handler.sendEmptyMessage(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
【运行一下】看看效果
第四步:部分属性介绍
- **circle_ring_angle_type:**该属性是用来设置CircleBar显示类型,不设置该属性的话,默认是360°的圆环。
- **circle_show_scale_on:**该属性是用来判断是否绘制刻度的,默认是false不绘制刻度。
- **circle_show_scale_color:该属性是用来设置刻度的颜色,当circle_show_scale_on = “true”**的时候生效。
- **circle_gradient_on:**该属性是用来判断是否绘制渐变颜色的圆环,默认是false绘制颜色为单一颜色。
- **circle_start_color、circle_center_color和circle_end_color:是用来设置渐变颜色的,当circle_gradient_on = “true”**时生效。
- circle_ring_reached:当circle_gradient_on设置为false的时候则用该属性去设置显示进度的圆环的颜色。
第五步:效果展示
一、首先更改下Activity里的代码:
这里我们将不再用线程去模拟更新进度,而是使用showProgress去设置想要显示的进度。Activity中的代码如下:
public class MyCircleBarActivity extends AppCompatActivity {
private MyCircleBar myCircleBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my_circle_bar);
myCircleBar = (MyCircleBar) findViewById(R.id.my_circle_bar);
myCircleBar.showProgress(100,5000);//设置显示的进度为100,动画时间5秒
}
}
二、将【第三步】中的CirlceBar的效果改成圆环:
首先更改布局,将circle_ring_angle_type:设置为angle_360(为了做对比,之后的布局整体不变,只对属性的值进行更改)。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:lyan = "http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="lyan.circlebar.MyCircleBarActivity">
<lyan.circlebar.view.MyCircleBar
android:id="@+id/my_circle_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
lyan:circle_ring_angle_type="angle_360"
lyan:circle_max_progress="100"
lyan:circle_start_color="#ff00ff"
lyan:circle_center_color="#00ffff"
lyan:circle_end_color="#0000ff"
lyan:circle_gradient_on="false"
lyan:circle_show_scale_on="true"
lyan:circle_ring_un_reached="#e8122831"
lyan:circle_show_scale_color="#ed4613"/>
</RelativeLayout>
**【运行效果】**如图所示:形状已经发生改变。
三、继续更改让CircleBar不显示刻度:
按**【第四步】中所说,我们只需要将circle_show_scale_on**属性设为false或者删除就可以达到目的。这里为了做比较,将它设为false。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:lyan = "http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="lyan.circlebar.MyCircleBarActivity">
<lyan.circlebar.view.MyCircleBar
android:id="@+id/my_circle_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
lyan:circle_ring_angle_type="angle_360"
lyan:circle_max_progress="100"
lyan:circle_start_color="#ff00ff"
lyan:circle_center_color="#00ffff"
lyan:circle_end_color="#0000ff"
lyan:circle_gradient_on="true"
lyan:circle_show_scale_on="false"
lyan:circle_ring_un_reached="#e8122831"
lyan:circle_show_scale_color="#ed4613"/>
</RelativeLayout>
**【运行效果】**刻度已经不显示了。
四、继续更改让CircleBar显示单一颜色:
在这里将circle_gradient_on属性设为false。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:lyan = "http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="lyan.circlebar.MyCircleBarActivity">
<lyan.circlebar.view.MyCircleBar
android:id="@+id/my_circle_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
lyan:circle_ring_angle_type="angle_360"
lyan:circle_max_progress="100"
lyan:circle_start_color="#ff00ff"
lyan:circle_center_color="#00ffff"
lyan:circle_end_color="#0000ff"
lyan:circle_gradient_on="false"
lyan:circle_show_scale_on="false"
lyan:circle_ring_un_reached="#e8122831"
lyan:circle_show_scale_color="#ed4613"/>
</RelativeLayout>
**【运行效果】**从图可以看出,设置的渐变色已经失效,显示的则是默认的颜色。
总结
这个自定义CircleBar基本就算完成了。其实跟上一篇内容没什么区别,只是将它绘制的更好看一些。为什么写这个控件,其实是为了更好的去了解自定义控件,虽然还是不太了解,但至少比实现这个例子之前了解的深刻一些。