学习安卓笔记之自定义控件(二)
------环形的进度条
上文讲了一个带指示器的进度条,实现的方式很简单,小伙伴们可以参考《学习安卓笔记之自定义控件(一)之带三角指示器的ProgressBar》。本章将会作一个如下图效果的圆形进度条(这个图没截好,左边差了一点)。
【准备动手之前】
那吗如何做呢?我们先来分析一下,从图中可以看出该控件是由两部分组成的,一部分是圆环的部分,另一部分就是文本区域。该如何去绘制这些图形呢?我们来看下下图:
图中红色正方形区域控件,也相当于一块画布。之后所有要绘制的图形都会在这个正方形的区域中去绘制。红色圆形部分表示控件中的进度条。而绿色区域则表示文本的区域。接下来将准备将会用到的自定义属性。
【创建自定义属性】
为了这个控件能使用的更加灵活,这里设置了一些将会用到的属性(模仿了一下之前用到的一些类似的控件):
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CircleProgressBar">
<!-- 文字的颜色 -->
<attr name="circle_ring_text_color" format="color"/>
<!-- 数值的颜色 -->
<attr name="circle_ring_values_color" format="color"/>
<!-- 显示的文本 -->
<attr name="circle_ring_text" format="string"/>
<!-- 单位 -->
<attr name="circle_ring_unit" format="string"/>
<!-- 圆环的宽度 -->
<attr name="circle_ring_width" format="dimension"/>
<!-- 默认圆环的颜色 -->
<attr name="circle_ring_background_color" format="color"/>
<!-- 显示进度的圆环的颜色 -->
<attr name="circle_ring_foreground_color" format="color"/>
</declare-styleable>
</resources>
【开始动手】
同样这次代码量也不是很多,所以还是把整个类都贴出来了(包都不用导了直接复制就OK了):
package lyan.circleprogressbar.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.ProgressBar;
import lyan.circleprogressbar.R;
/**
* Author LYJ
* Created on 2016/9/12.
* Time 13:33
*/
public class MyCircleProgressBar extends ProgressBar {
/**
* 默认属性
*/
private static final int CIRCLE_RING_TEXT_COLOR = 0xff000000;//文本默认颜色为黑色
private static final String CIRCLE_RING_TEXT = "当前里程";//默认文本
private static final String CIRCLE_RING_UNIT = "米";//默认单位
private static final int CIRCLE_RING_WIDTH = 30;//圆环的默认宽度
private static final int CIRCLE_RING_BACKGROUND_COLOR = 0xfff2f2f2;//默认背景圆环颜色为灰色
private static final int CIRCLE_RING_FOREGROUND_COLOR = 0xff00ffff;//默认前景圆环颜色为青色
private static final int CIRCLE_RING_VALUES_COLOR = 0xffff0000;//值的颜色默认为红色
/**
* 声明变量
*/
private int circle_ring_Side;//包含圆环容器的边长
private int circle_ring_radius;//圆环的半径
private int draw_offset;//因为画笔的宽度产生的误差
private int text_color;//文本的颜色
private int values_color;//值得颜色
private String text;//内容
private String unit;//单位
private int ring_width;//圆环的宽度
private int ring_back_color;//默认的颜色
private int ring_fore_color;//展示进度的颜色
private Paint textPaint, backPaint, forePaint, testPaint;//画笔
private RectF drawArcRect;//绘制弧形的区域
/**
* @param context
*/
public MyCircleProgressBar(Context context) {
this(context, null);
}
public MyCircleProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyCircleProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
obtainAttributes(attrs);//获取并设置自定义属性
init();//初始化
initPaints();//初始化画笔
}
/**
* 获取自定义属性
*
* @param attrs
*/
private void obtainAttributes(AttributeSet attrs) {
TypedArray typedArray = getContext().obtainStyledAttributes(
attrs, R.styleable.CircleProgressBar);
text = typedArray.getString(R.styleable.CircleProgressBar_circle_ring_text);
text = text == null ? CIRCLE_RING_TEXT : text;
unit = typedArray.getString(R.styleable.CircleProgressBar_circle_ring_unit);
unit = unit == null ? CIRCLE_RING_UNIT : unit;
values_color = typedArray.getColor(
R.styleable.CircleProgressBar_circle_ring_values_color,CIRCLE_RING_VALUES_COLOR);
text_color = typedArray.getColor(
R.styleable.CircleProgressBar_circle_ring_text_color, CIRCLE_RING_TEXT_COLOR);
ring_width = dip2px((int) typedArray.getDimension(
R.styleable.CircleProgressBar_circle_ring_width, CIRCLE_RING_WIDTH));
ring_back_color = typedArray.getColor(
R.styleable.CircleProgressBar_circle_ring_background_color, CIRCLE_RING_BACKGROUND_COLOR);
ring_fore_color = typedArray.getColor(
R.styleable.CircleProgressBar_circle_ring_foreground_color, CIRCLE_RING_FOREGROUND_COLOR);
typedArray.recycle();
}
/**
* 初始化
*/
private void init() {
draw_offset = ring_width / 2 + dip2px(2);//偏移量
drawArcRect = new RectF();//绘制弧形的区域
}
/**
* 初始化画笔
*/
private void initPaints() {
// //测试画笔
// testPaint = new Paint();
// testPaint.setStrokeWidth(2);
// testPaint.setColor(Color.BLACK);
// testPaint.setStyle(Paint.Style.STROKE);
// testPaint.setAntiAlias(true);
//绘制默认圆环的画笔
backPaint = new Paint();//用于绘制默认的圆环
backPaint.setColor(ring_back_color);//画笔颜色
backPaint.setAntiAlias(true);//抗锯齿
backPaint.setDither(true);//防抖动
backPaint.setStrokeWidth(ring_width);//设置笔刷宽度
backPaint.setStyle(Paint.Style.STROKE);//不填充
//绘制进度的画笔
forePaint = new Paint();//用于绘制进度的画笔
forePaint.setColor(ring_fore_color);
forePaint.setStrokeWidth(ring_width);
forePaint.setAntiAlias(true);
forePaint.setDither(true);
forePaint.setStyle(Paint.Style.STROKE);
forePaint.setStrokeCap(Paint.Cap.ROUND);//笔刷边界为圆形
//绘制文本的画笔
textPaint = new Paint();//绘制文字
textPaint.setAntiAlias(true);
textPaint.setDither(true);
textPaint.setStyle(Paint.Style.FILL);//填充
textPaint.setTypeface(Typeface.DEFAULT_BOLD);//文字粗体
textPaint.setTextAlign(Paint.Align.CENTER);//文字居中
}
@Override
protected synchronized void onDraw(Canvas canvas) {
super.onDraw(canvas);
// canvas.drawColor(0xffffffff);
circle_ring_radius = circle_ring_Side / 2 - draw_offset;//半径
//绘制默认的圆环
canvas.drawCircle(circle_ring_Side / 2, circle_ring_Side / 2, circle_ring_radius, backPaint);
//绘制弧形
drawArcRect.left = draw_offset;
drawArcRect.top = draw_offset;
drawArcRect.right = circle_ring_Side - draw_offset;
drawArcRect.bottom = circle_ring_Side - draw_offset;
canvas.drawArc(drawArcRect, 90, getProgress() * 360 / getMax(), false, forePaint);
//绘制文本的区域
int chord_length = (int) Math.sqrt(Math.pow(circle_ring_Side, 2)
+ Math.pow(circle_ring_Side, 2));//获取矩形的对角线长度
int draw_text_left = (int) ((chord_length / 2 - circle_ring_radius) * Math.sin(45));
int draw_text_top = draw_text_left;
int draw_text_right = circle_ring_Side - draw_text_left;
int draw_text_bottom = circle_ring_Side - draw_text_top;
// canvas.drawRect(draw_text_left, draw_text_top, draw_text_right, draw_text_bottom, testPaint);
//绘制文本
int text_4_side = Math.abs(draw_text_bottom - draw_text_top) / 4;//绘制区域的1/4份
//固定提示文本
textPaint.setColor(text_color);
textPaint.setTextSize((float) (text_4_side * 0.6));
canvas.drawText(text, circle_ring_Side / 2, draw_text_top + text_4_side, textPaint);
//固定单位文本
canvas.drawText(unit, circle_ring_Side / 2, draw_text_bottom - text_4_side/2, textPaint);
//变换的数值
textPaint.setColor(values_color);
textPaint.setTextSize((float) (text_4_side)*1.5f);
canvas.drawText(String.valueOf(getProgress()),circle_ring_Side/2,
circle_ring_Side/2 + text_4_side/2,textPaint);
}
/**
* 测量
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);//测量宽度
int heightSize = MeasureSpec.getSize(heightMeasureSpec);//测量高度
circle_ring_Side = Math.min(widthSize, heightSize);//取最小值
setMeasuredDimension(circle_ring_Side, circle_ring_Side);//设置包含圆环的容器为正方形
}
/**
* dp-->px
*
* @param decisionType
* @return
*/
private int dip2px(int decisionType) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
decisionType, getResources().getDisplayMetrics());
}
}
------让我们看一下运行效果
光看着确实有点像那么回事的意思。
【测试一下功能】
刚刚仅仅是看了一下效果图,并没有测试它的功能是否好使,接下来完整的测试一下这个自定义控件的功能是否符合要求吧:
【向布局文件中添加自意义控件】,那个名为lyan的命名空间先不用管!
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:lyan="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.MainActivity">
<lyan.circleprogressbar.view.MyCircleProgressBar
android:id="@+id/test"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
/>
</RelativeLayout>
【向Activity中添加测试代码】随便创建一个就OK。这里一样整类贴出。
package lyan.circleprogressbar.activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import lyan.circleprogressbar.R;
import lyan.circleprogressbar.view.MyCircleProgressBar;
public class MainActivity extends AppCompatActivity {
private MyCircleProgressBar circleProgressBar;
private int count = 0;
private Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message message) {
if (message.what == 1){
++count;
circleProgressBar.setProgress(count);
Log.e("handleMessage: ", count + "");
}
return false;
}
});
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
circleProgressBar = (MyCircleProgressBar) findViewById(R.id.test);
circleProgressBar.setMax(100);
new Thread(new Runnable() {
@Override
public void run() {
while (count < 100){
try {
handler.sendEmptyMessage(1);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
------我们再看一下运行效果
看着还行,就差使用自定义的属性了。
【收工】
更改一下布局文件,将自定义的属性添加上:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:lyan="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.MainActivity">
<lyan.circleprogressbar.view.MyCircleProgressBar
android:id="@+id/test"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true"
lyan:circle_ring_background_color="#545454"
lyan:circle_ring_foreground_color="#4592F3"
lyan:circle_ring_text_color="#4592F3"
lyan:circle_ring_values_color="#FBB900"
lyan:circle_ring_width = "30dp"
lyan:circle_ring_text = "消耗热量"
lyan:circle_ring_unit = "卡路里"
/>
</RelativeLayout>
【最后运行一次】
为了与一开始区分开,在布局里更改了几个字:
至此我们的功能都实现完了*