Android自定义View:时钟和圆百分比图--(2)
- 自定义时钟
/**
* 绘制一个钟表的View
* 1.时分秒的转动 画完后 需要执行
* canvas.save();
* canvas.restore(); 才能重新把画布旋转回来
* @author fanshenxia
*
*/
public class MyClockView extends View {
private Context mContext;
private Paint mPaint;
private int mScreenWidth;
private int mHour = 0;
private int mMinute = 0;
private int mSecond = 0;
private int[] mNums = null;
public MyClockView(Context context) {
super(context);
this.mContext = context;
init();
}
public MyClockView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
init();
}
public MyClockView(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
init();
}
private void init() {
mPaint = new Paint();
mPaint.setColor(Color.BLACK);
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(3);
mPaint.setStyle(Style.STROKE);
mPaint.setTextSize(12);
mPaint.setTextAlign(Align.CENTER);
//ScreenUtil.getScreenWidth(mContext)获取屏幕宽度
mScreenWidth = ScreenUtil.getScreenWidth(mContext);
mNums = new int[] { 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setStyle(Style.STROKE);
// 绘制中间的点
canvas.drawCircle(mScreenWidth / 2, mScreenWidth / 4 + 100, 3, mPaint);
// 绘制外层圆
canvas.drawCircle(mScreenWidth / 2, mScreenWidth / 4 + 100, mScreenWidth / 4, mPaint);
mPaint.setStyle(Style.FILL);
// 绘制刻度
for (int i = 0; i < 12; i++) {
canvas.save();
//这是长点的刻度
if (i == 0 || i == 3 || i == 6 || i == 9) {
//每次绘制前需要重新设置宽度
mPaint.setStrokeWidth(5);
canvas.drawLine(mScreenWidth / 2, 100, mScreenWidth / 2, 130, mPaint);
} else {
mPaint.setStrokeWidth(3);
canvas.drawLine(mScreenWidth / 2, 100, mScreenWidth / 2, 120, mPaint);
}
canvas.drawText("" + mNums[i], mScreenWidth / 2, 70, mPaint);
// 旋转画布,每次旋转15度,以mScreenWidth / 2, mScreenWidth / 4为中心点旋转
canvas.rotate(30, mScreenWidth / 2, mScreenWidth / 4 + 100);
}
canvas.save();
// 恢复画布
canvas.restore();
// 绘制时钟和分钟 这里将画布平移
// canvas.translate(mScreenWidth / 2, mScreenWidth / 4);
// 获取当前时间
getSystemTime();
canvas.rotate(6 * mSecond, mScreenWidth / 2, mScreenWidth / 4 + 100);
// 秒
canvas.drawLine(mScreenWidth / 2, mScreenWidth / 4 + 100, mScreenWidth / 2, 150, mPaint);
canvas.save();
canvas.restore();
canvas.rotate(-6 * mSecond, mScreenWidth / 2, mScreenWidth / 4 + 100);
canvas.rotate(6 * mMinute + mSecond / 10, mScreenWidth / 2, mScreenWidth / 4 + 100);
// 分
canvas.drawLine(mScreenWidth / 2, mScreenWidth / 4 + 100, mScreenWidth / 2, 180, mPaint);
canvas.save();
canvas.restore();
canvas.rotate(-6 * mMinute - mSecond / 10, mScreenWidth / 2, mScreenWidth / 4 + 100);
canvas.rotate(mHour * 30 + mMinute / 2, mScreenWidth / 2, mScreenWidth / 4 + 100);
// 时
canvas.drawLine(mScreenWidth / 2, mScreenWidth / 4 + 100, mScreenWidth / 2, 200, mPaint);
canvas.save();
canvas.restore();
canvas.rotate(-(mHour * 30 + mMinute / 2), mScreenWidth / 2, mScreenWidth / 4 + 100);
//绘制后延时1s刷新
postInvalidateDelayed(1 * 1000);
}
/**
* 获取系统时间时分秒
*/
@SuppressLint("NewApi")
private void getSystemTime() {
Calendar calendar = Calendar.getInstance();
mHour = calendar.get(Calendar.HOUR_OF_DAY);
mMinute = calendar.get(Calendar.MINUTE);
mSecond = calendar.get(Calendar.SECOND);
// SimpleDateFormat format = new SimpleDateFormat("HH-mm-ss");
// String time = format.format(new Date(System.currentTimeMillis()));
// String[] split = time.split("-");
// mHour = Integer.parseInt(split[0]);
// mMinute = Integer.parseInt(split[1]);
// mSecond = Integer.parseInt(split[2]);
}
}
运行效果
- 自定义百分比图
/**
* 自定义一个控件 写这些是一步步来的
* 自定义显示百分比的控件 RectF是绘制一个矩形 左上右下的各个位置
* 1.本来画了两个弧线,改为只画一个,把
* mPaint.setStyle(Style.STROKE);
* mPaint.setStrokeWidth(60);设置,就不用两个弧了
* 2.关于想改变圆弧值,但是它的背景颜色和是否穿过圆心会改变的问题。 这个是画笔的问题,需要设置画笔颜色
* 3.drawArc
* 和RectF中的数值单位不同的问题,这个还没有解决
* 4.实现动画效果:利用属性动画ValueAnimation
* 5.实现渐变效果 利用LinearGradient渲染效果
* 6.attr 资源文件中设置数据值
* @author fanshenxia
*
*/
public class MyPercentView extends View {
private Context mContext;
private Paint mPaint, mPaint2;
private RectF mRectF;
private int percent= 0;
private Canvas mCanvas;
private ValueAnimator animator;
private float nowPro = 0;
private float x, y;
//实现渐变
private LinearGradient mGradient;
//自定义属性
private TypedArray mTypedArray;
public MyPercentView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mContext = context;
initAttr(attrs);
init();
}
public MyPercentView(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
initAttr(attrs);
init();
}
//上篇文章说了,在xml中写布局会调用2个参数的构造函数,那么那些我们设置的属性是从哪里设置进我们的布局中的呢,就是这里了
private void initAttr(AttributeSet attrs) {
//获取属性
mTypedArray = mContext.obtainStyledAttributes(attrs,com.fsx.customviewpro.R.styleable.PercentView);
//从TypedArray中获取对应的值来为要设置的属性赋值,对应着在res-values-attrs.xml中定义的属性名
this.percent = mTypedArray.getInteger(com.fsx.customviewpro.R.styleable.PercentView_percent,0);
}
public MyPercentView(Context context) {
super(context);
this.mContext = context;
init();
}
/**
* 初始化
*/
private void init() {
x = ScreenUtil.getScreenWidth(mContext);
y = ScreenUtil.getScreenHeight(mContext);
mPaint = new Paint();
mPaint.setColor(Color.RED);
mPaint.setStyle(Style.STROKE);
//设置圆角
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeWidth(60);
//设置渐变 参数五必须两个值以上,不然直接崩掉
// mGradient = new LinearGradient(0, 0, 30, 30, new int[]{Color.BLUE}, null, TileMode.MIRROR);
//还有种设置方法
mGradient = new LinearGradient(60, (y - x) / 2 + 60, x - 60, x + (y - x) / 2 - 60, new int[]{mPaint.getColor(),Color.CYAN}, null,
Shader.TileMode.REPEAT);
mPaint.setShader(mGradient);
mRectF = new RectF(60, (y - x) / 2 + 60, x - 60, x + (y - x) / 2 - 60);
mPaint2 = new Paint();
mPaint2.setColor(Color.CYAN);
mPaint2.setTextSize(25);
mPaint2.setStrokeWidth(15);
// 设置文字时文字设置在哪里 CENTER表示水平点在文字的中心
mPaint2.setTextAlign(Align.CENTER);
mPaint2.setStyle(Style.FILL);
startMyAnimation();
}
/**
* 开启动画
*/
private void startMyAnimation() {
//动画 1.定义animation
animator = ValueAnimator.ofFloat(0, percent);
//设置属性动画市场
animator.setDuration(1800);
//插值器,这是设置动画效果设置动画类型 如加速,减速,先加后减,弹回等效果
/**
* 1、AccelerateInterpolator:动画从开始到结束,变化率是一个加速的过程。
2、DecelerateInterpolator:动画从开始到结束,变化率是一个减速的过程。
3、CycleInterpolator:动画从开始到结束,变化率是循环给定次数的正弦曲线。
4、AccelerateDecelerateInterpolator:动画从开始到结束,变化率是先加速后减速的过程。
5、LinearInterpolator:动画从开始到结束,变化率是线性变化。
6.OvershootInterpolator:先超过定义的数值,再弹回
*/
// animator.setInterpolator(new OvershootInterpolator());
animator.setInterpolator(new DecelerateInterpolator());
//添加更新监听事件
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
nowPro = (Float) animation.getAnimatedValue();
postInvalidate();
}
});
//添加对动画的监听
// animator.addListener(listener);
//开始属性动画
animator.start();
}
private void changeColor() {
mPaint2.setColor(Color.BLACK);
// mPaint.setTextSize(25);
// mPaint.setStrokeWidth(15);
// // 设置文字时文字设置在哪里 CENTER表示水平点在文字的中心
// mPaint.setTextAlign(Align.CENTER);
// mPaint.setStyle(Style.FILL);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
this.mCanvas = canvas;
// mPaint.setColor(Color.WHITE);
// mRectF.set(x / 4 - 20, y / 2 - x / 4 - 20, x / 4 * 3 + 20, y / 2 + x
// / 4 + 20);
// 画里层的弧
// canvas.drawArc(mRectF, -90, 90, true, mPaint);
mPaint2.setColor(Color.CYAN);
// 画中间的那个圆
canvas.drawCircle(x / 2, (y - x) / 2 + x / 2, x / 4, mPaint2);
changeColor();
// 画中间的百分比
canvas.drawText("百分比:" + (int)nowPro + "%", x / 2, y / 2, mPaint2);
mPaint2.setColor(Color.BLACK);
// 画外层的圈圈
canvas.drawArc(mRectF, -90, (int) (3.6 * nowPro), false, mPaint);
//本想用这种方法实现动画效果,不行
// for(int i = 1;i<=percent;i++){
// canvas.drawArc(mRectF, -90, (int) (3.6 * i), false, mPaint);
// canvas.drawText("百分比:" + i + "%", x / 2, y / 2, mPaint2);
// postInvalidateDelayed(300);
// }
}
public void setPercent(int percent) {
this.percent = percent;
// 这个方法是重新绘制
// invalidate();
startMyAnimation();
}
}
布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<custom.MyPercentView
android:id="@+id/mypercentview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
custom:percent="80"
android:visibility="visible"
/>
</LinearLayout>
实现效果:
上述的代码都没有详细说,就拿这个详细说吧。
步骤,我是一点一点写的:
1.先绘制最基本的圆,圆弧,中间的文字
2.添加自定义属性,设置百分比
3.实现用户输入数字,重新绘制比例图
4.实现百分比和圆弧的动画效果
5.给圆弧添加上动画效果
先说第一个步骤:绘制最基本的圆,圆弧,中间的文字
1.先给画笔设置颜色
mPaint2.setColor(Color.CYAN);
// 画中间的那个圆
canvas.drawCircle(x / 2, (y - x) / 2 + x / 2, x / 4, mPaint2);
//改变颜色
changeColor();
// 画中间的百分比
canvas.drawText("百分比:" + (int)nowPro + "%", x / 2, y / 2, mPaint2);
mPaint2.setColor(Color.BLACK);
// 画外层的圈圈
canvas.drawArc(mRectF, -90, (int) (3.6 * nowPro), false, mPaint);
注意:先画相对底层的,如圆,再画添加在它上边的文字,不然那会被覆盖。
画圆弧用的canvas.drawArc(mRectF, -90, (int) (3.6 * nowPro), false, mPaint);
各参数意思:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2013/0111/798.html
第二个步骤:添加自定义属性
我只添加了一个属性,就是比例值
a.在res–values–attrs.xml中添加代码 ,如果在 values包下没有这个文件,自己新建一个就好了(attrs.xml)
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="PercentView">
<attr name="percent" format="integer" />
<attr name="percentColor" format="color|reference"
</declare-styleable>
</resources>
我们在代码中通过declare-styleable标签声明了使用自定义属性,通过name来确定引用的名称。通过attr标签来申明具体的自定义属性,这里定义了百分比的数值percent,通过format属性来指定属性的类型。有些属性可以是颜色,也可以是引用属性(reference),就像背景,所以我们用|分割开。
format可取的值和意思:
reference 表示引用,参考某一资源ID
string 表示字符串
color 表示颜色值
dimension 表示尺寸值
boolean 表示布尔值
integer 表示整型值
float 表示浮点值
fraction 表示百分数
enum 表示枚举值
flag 表示位运算
b.在逻辑代码中取值,系统提供了TypedArray来获取自定义属性集
//这里的PercentView对应attrs中定义时的name
mTypedArray = mContext.obtainStyledAttributes(attrs,com.fsx.customviewpro.R.styleable.PercentView);
//从TypedArray中获取对应的值来为要设置的属性赋值,对应着在res-values-attrs.xml中定义的属性名,如percent
this.percent = Integer.valueOf(mTypedArray.getString(com.fsx.customviewpro.R.styleable.PercentView_percent));
c.在xml中引用并设置属性值(引用《Android群英传中内容》)
引用UI模板,在引用前,需要指定引用第三方空间的控件名字。在布局代码中,始终有 xmlns:android="http://schemas.android.com/apk/res/android"
这就是在指定引用的名字控件xmls,即xml namespace。这里指定了名字控件为“android”,因此在控件中才可以使用系统属性”android:”。同样,如果要使用自定义属性,就需要自己创建自己的名字空间,在AS中,第三方控件都使用如下代码来引用名字空间。
xmlns:custom="http://schemas.android.com/apk/res-auto"
这里我们将引用的第三方控件名字取为custom,之后在xml中使用自定义属性时,就可以通过这个名字空间来引用,如下:
custom:percent="80"
第三个步骤:实现用户输入数字,重新绘制比例图
这里直接设置一个方法,将percent值设置进来,然后重新调用绘制的方法即可,特别注意,重新调用invalidate相关方法不会重新设置值,会用原来Paint设置过的那些属性,所以调用重绘后需要在对应的地方重新设置颜色等属性。
public void setPercent(int percent) {
this.percent = percent;
// 这个方法是重新绘制
//invalidate();
//重新开动动画进行绘制
startMyAnimation();
}
第四个步骤:实现百分比和圆弧的动画效果
为了实现这个效果,我首先想到的是利用for循坏,每次绘制一点点,在休眠一会,重新绘制,这个方法不可行,没啥效果。
后来找到了属性动画,ValueAnimator,实现的效果很理想。
/**
* 开启动画
*/
private void startMyAnimation() {
//动画 1.定义animation
animator = ValueAnimator.ofFloat(0, percent);
//设置属性动画时长
animator.setDuration(1800);
//插值器,这是设置动画效果设置动画类型 如加速,减速,先加后减,弹回等效果
/**
* 1、AccelerateInterpolator:动画从开始到结束,变化率是一个加速的过程。
2、DecelerateInterpolator:动画从开始到结束,变化率是一个减速的过程。
3、CycleInterpolator:动画从开始到结束,变化率是循环给定次数的正弦曲线。
4、AccelerateDecelerateInterpolator:动画从开始到结束,变化率是先加速后减速的过程。
5、LinearInterpolator:动画从开始到结束,变化率是线性变化。
6.OvershootInterpolator:先超过定义的数值,再弹回
*/
// animator.setInterpolator(new OvershootInterpolator());
animator.setInterpolator(new DecelerateInterpolator());
//添加更新监听事件
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//获取当前value
nowPro = (Float) animation.getAnimatedValue();
//刷新
postInvalidate();
}
});
//添加对动画的监听
// animator.addListener(listener);
//开始属性动画
animator.start();
}
第五个步骤:给圆弧添加上动画效果
//实现渐变
private LinearGradient mGradient;
//参数5:颜色该怎样变化,从什么颜色到什么颜色 参数7,:模式REPEAT重复 CLAMP:重复最后一个像素 MIRROR翻转像素(这个参数不是一下子能说清楚的,具体之后会写到)
mGradient = new LinearGradient(60, (y - x) / 2 + 60, x - 60, x + (y - x) / 2 - 60, new int[]{mPaint.getColor(),Color.CYAN}, null,
Shader.TileMode.REPEAT);
//设置Shader
mPaint.setShader(mGradient);
就这样一步一步实现的啦。OK了。控件是我想实现什么功能就实现了什么功能,没在意搭配,丑就丑啦。