Android 自定义View实例之进度圆环
来源:互联网 发布:linux echo $ 编辑:程序博客网 时间:2024/05/18 00:29
自定义View的相关文章:
- Android 实现一个简单的自定义View
- Android 自定义View步骤
- Android Paint详解
- Android 自定义View之Canvas相关方法说明
- Android 自定义View实例之 “京东跑”
- Android 自定义View实例之进度圆环
- Android 源码分析(TextView)
- Android 自定义View分发流程
- Android 自定义View 需要注意的事项
看下效果:
实际效果呢要比这个gif好,大家运行看看吧。
我们可以把这个View分为几个部分:
- 底部灰色的圆环
- 渐变、动态的圆弧,
- 圆弧上的箭头
- 中间的文字
我们就一个一个的来实现,我本来想直接把最终的代码给大家的,可是我做的过程中遇到了好多细节问题,所以我详细的把我实现的步骤也分享给大家。
底部灰色的圆环
底部灰色的圆环宽度和颜色可以通过属性设置,所以在res/values下新建attrs.xml:
<?xml version="1.0" encoding="utf-8"?><resources> <declare-styleable name="RoundView"> <!--圆环半径--> <attr name="radius" format="dimension"/> <!--圆环宽度--> <attr name="ring_width" format="dimension"/> <!--圆环颜色--> <attr name="ring_color" format="color"/> </declare-styleable></resources>
后面还会有其他属性,我们直接添加这里,不在具体说明。
RoundView(此自定义View)的代码:
/** * 自定义进度圆环 */public class RoundView extends View{ /** * 圆环宽度,默认半径的1/5 */ private float mRingWidth = 0; /** * 圆环颜色,默认 #CBCBCB */ private int mRingColor = 0; /** * 圆环半径,默认:Math.min(getHeight()/2,getWidth()/2) */ private float mRadius = 0; /** * 底环画笔 */ private Paint mRingPaint; public RoundView(Context context) { this(context, null); } public RoundView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RoundView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public RoundView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RoundView); mRingWidth = typedArray.getDimension(R.styleable.RoundView_ring_width, 0); mRingColor = typedArray.getColor(R.styleable.RoundView_ring_color, Color.parseColor("#CBCBCB")); mRadius = typedArray.getDimension(R.styleable.RoundView_android_radius, 0); init(); } /** * 初始化 */ private void init() { mRingPaint = new Paint(); mRingPaint.setAntiAlias(true);// 抗锯齿效果 mRingPaint.setStyle(Paint.Style.STROKE); mRingPaint.setColor(mRingColor);// 背景 } @Override public void onDraw(Canvas canvas) { // 圆心坐标,当前View的中心 float x = getWidth() / 2; float y = getHeight() / 2; //如果未设置半径,则半径的值为view的宽、高一半的较小值 mRadius = mRadius == 0 ? Math.min(getWidth() / 2, getHeight() / 2) : mRadius; //圆环的宽度默认为半径的1/5 mRingWidth = mRingWidth == 0 ? mRadius / 5 : mRingWidth; // 底环 canvas.drawCircle(x, y, mRadius, mRingPaint); }
效果:
我们发现这个环不全啊,因为圆的宽度是在当前半径向2边展开的。
红线就是我们的半径,一部分超出的view的边界了。解决的办法就是将我们的半径减去圆环宽度的一半:
加入图中方框内部分。在运行就会出现我们想要的效果了,不过这里还有一个问题,就是onDraw方法会一直重绘,所以这段代码(就是我们解决上面问题的代码)
//由于圆环本身有宽度,所以半径要减去圆环宽度的一半,不然一部分圆会在view外面 mRadius = mRadius - mRingWidth / 2;
会越来越小,所以修改为:
@Override public void onDraw(Canvas canvas) { // 圆心坐标,当前View的中心 float x = getWidth() / 2; float y = getHeight() / 2; //如果未设置半径,则半径的值为view的宽、高一半的较小值 float radius = mRadius == 0 ? Math.min(getWidth() / 2, getHeight() / 2) : mRadius; //圆环的宽度默认为半径的1/5 float ringWidth = mRingWidth == 0 ? radius / 5 : mRingWidth; //由于圆环本身有宽度,所以半径要减去圆环宽度的一半,不然一部分圆会在view外面 radius = radius - ringWidth / 2; mRingPaint.setStrokeWidth(ringWidth); // 底环 canvas.drawCircle(x, y, radius, mRingPaint); }
没有设置任何属性的效果:
设置属性:
<qiwei.com.frameworknote.ui.RoundView android:layout_width="300dp" android:layout_height="300dp" app:ring_width="10dp" app:ring_color="#ff0000" android:radius="100"/>
效果:
渐变、动态的圆弧
我们先在圆环上画一个静态进度圆环,属性有宽度,由于进度圆环有渐变效果,所以设置属性开始颜色、结束颜色,还有一点要说明这个进度圆环和底部圆环的半径是一样的。
添加如下属性:
<!--进度圆环宽度--> <attr name="progress_ring_width" format="dimension"/> <!--进度圆环开始颜色--> <attr name="progress_ring_start_color" format="color"/> <!--进度圆环结束颜色--> <attr name="progress_ring_end_color" format="color"/>
在RoundView.java中添加属性:
/** * 进度条圆环宽度 */ private float mProgressRingWidth = 0; /** * 进度条圆环开始颜色,进度条圆环是渐变的,默认 */ private int mProgressRingStartColor = 0; /** * 进度条圆环结束颜色,进度条圆环是渐变的,默认 */ private int mProgressRingEndColor = 0; /** * 进度条圆环Paint */ private Paint mProgressRingPaint; /** * 进度条圆环渐变shader */ private Shader mProgressRingShader;
TypeArray解析:
mProgressRingWidth = typedArray.getDimension(R.styleable.RoundView_progress_ring_width, 0); mProgressRingStartColor = typedArray.getColor(R.styleable.RoundView_progress_ring_start_color, Color.parseColor("#f90aa9")); mProgressRingEndColor = typedArray.getColor(R.styleable.RoundView_progress_ring_end_color, Color.parseColor("#931c80"));
在init方法中初始化进度圆滑的Paint和渐变数组
// 圆环紫色渐变色 arcColors = new int[]{mProgressRingStartColor, mProgressRingEndColor}; mProgressRingPaint = new Paint(); mProgressRingPaint.setAntiAlias(true);// 抗锯齿效果 mProgressRingPaint.setStyle(Paint.Style.STROKE); mProgressRingPaint.setStrokeCap(Cap.ROUND);// 圆形笔头 mProgressRingPaint.setStrokeWidth(mProgressRingWidth);
注意进度圆环应该小于等于底部圆环的宽度,如果小于,那么进度圆环应该在底部圆环的中间。
在onDraw方法中绘制完底部圆环后绘制静态的进度圆环:
// 设置渐变色 if (mProgressRingShader == null) { mProgressRingShader = new SweepGradient(x, y, arcColors,null); mProgressRingPaint.setShader(mProgressRingShader); } canvas.drawArc(new RectF(0,0,getWidth(),getHeight()), 0, 180, false, mProgressRingPaint);
看下效果:
出现几个问题:
- 一部分超出view边界
- 进度圆环没有在底部圆环里
- 我们想要的效果是从顶部开始的
其实1、2都是由半径引发的问题,3问题是代码中我们设置的是从0处开始,解决的方法就是设置为-90。
修改后:
//----绘制进度圆环------ // 设置渐变色 if (mProgressRingShader == null) { mProgressRingShader = new SweepGradient(x, y, arcColors, null); mProgressRingPaint.setShader(mProgressRingShader); } //计算进度圆环宽度,默认和底部圆滑一样大 float progressRingWidth = mProgressRingWidth == 0 ? ringWidth : mProgressRingWidth; mProgressRingPaint.setStrokeWidth(progressRingWidth); canvas.drawArc(new RectF(progressRingWidth/2,progressRingWidth/2,getWidth()-progressRingWidth/2,getHeight()-progressRingWidth/2), -90, 180, false, mProgressRingPaint);
我们设置底部圆环的宽度和进度条的宽度:
<qiwei.com.frameworknote.ui.RoundView android:layout_width="300dp" android:layout_height="300dp" app:ring_width="28dp" app:progress_ring_width="14dp"/>
效果:
问题又出现了:
- 进度圆环应该在底部圆环的中间
渐变色的效果不对
第一个问题是我们的RectF(矩形区域)设置的不对,下面这张图告诉我们怎么算
修改后:
// 设置渐变色 if (mProgressRingShader == null) { mProgressRingShader = new SweepGradient(x, y, arcColors, null); mProgressRingPaint.setShader(mProgressRingShader); } //计算进度圆环宽度,默认和底部圆滑一样大 float progressRingWidth = mProgressRingWidth == 0 ? ringWidth : mProgressRingWidth; mProgressRingPaint.setStrokeWidth(progressRingWidth); float left = x - radius; float top = y - radius; float right = x + radius; float bottom = y + radius; canvas.drawArc(new RectF(left, top, right, bottom), -90, 180, false, mProgressRingPaint);
效果:
我们看下渐变的问题,我们使用的是SweepGradient,也是所说的雷达渐变,它也是从水平方向开始顺时针渐变,所以就出现了上面的情况,怎么解决呢?我想到的办法是旋转画布:
//计算进度圆环宽度,默认和底部圆滑一样大 float progressRingWidth = mProgressRingWidth == 0 ? ringWidth : mProgressRingWidth; mProgressRingPaint.setStrokeWidth(progressRingWidth); float left = x - radius; float top = y - radius; float right = x + radius; float bottom = y + radius; //保存当前的状态 canvas.save(); //逆时针旋转90度 canvas.rotate(-90, x, y); canvas.drawArc(new RectF(left, top, right, bottom), 0, 180, false, mProgressRingPaint);
效果:
无限接近中啊,可是最上面的圆头有问题啊,看下图:
我们旋转90度只旋转到了蓝线处,而实际我们应该旋转到红线处,所以我们要计算出红蓝线间的夹角然后加上90度就是我们要旋转的角度。
修改如下:
/** * 已知圆半径和切线长求弧长公式 */ private double radianToAngle(float radios) { double aa = mProgressRingWidth / 2 / radios; double asin = Math.asin(aa); double radian = Math.toDegrees(asin); return radian; } @Override public void onDraw(Canvas canvas) { // 圆心坐标,当前View的中心 float x = getWidth() / 2; float y = getHeight() / 2; //如果未设置半径,则半径的值为view的宽、高一半的较小值 float radius = mRadius == 0 ? Math.min(getWidth() / 2, getHeight() / 2) : mRadius; //圆环的宽度默认为半径的1/5 float ringWidth = mRingWidth == 0 ? radius / 5 : mRingWidth; //由于圆环本身有宽度,所以半径要减去圆环宽度的一半,不然一部分圆会在view外面 radius = radius - ringWidth / 2; mRingPaint.setStrokeWidth(ringWidth); // 底环 canvas.drawCircle(x, y, radius, mRingPaint); //----绘制圆环(最底部)结束------ //----绘制进度圆环------ // 设置渐变色 if (mProgressRingShader == null) { mProgressRingShader = new SweepGradient(x, y, arcColors, null); mProgressRingPaint.setShader(mProgressRingShader); } //计算进度圆环宽度,默认和底部圆滑一样大 float progressRingWidth = mProgressRingWidth == 0 ? ringWidth : mProgressRingWidth; mProgressRingPaint.setStrokeWidth(progressRingWidth); float left = x - radius; float top = y - radius; float right = x + radius; float bottom = y + radius; //保存当前的状态 canvas.save(); // 旋转画布90度+笔头半径转过的角度 double radian = radianToAngle(radius); double degrees = Math.toDegrees(-2 * Math.PI / 360 * (90 + radian));// 90度+ //逆时针旋转90度 canvas.rotate((float)-degrees, x, y); canvas.drawArc(new RectF(left, top, right, bottom), (float)radian, 180, false, mProgressRingPaint);}
注意绘制弧的开始角度为 radian
效果:
如何让我们的圆环动起来呢?就是每次绘制的时候修改圆弧的角度,另外圆弧旋转的速度我们如何控制呢?我们可以使用动画和差值器,动画和差值器我们就不详细讲了。
/** * 自定义进度圆环 */public class RoundView extends View implements AnimationListener { /** * 圆环转过的角度 */ private float mSweepAngle = 1; /** * 之前的角度 */ private float mOldAngle = 0; /** * 新的角度 */ private float mNewAngle = 0; /** * 圆环宽度,默认半径的1/5 */ private float mRingWidth = 0; /** * 圆环颜色,默认 #CBCBCB */ private int mRingColor = 0; /** * 进度条圆环宽度 */ private float mProgressRingWidth = 0; /** * 进度条圆环开始颜色,进度条圆环是渐变的,默认 */ private int mProgressRingStartColor = 0; /** * 进度条圆环结束颜色,进度条圆环是渐变的,默认 */ private int mProgressRingEndColor = 0; /** * 圆环半径,默认:Math.min(getHeight()/2,getWidth()/2) */ private float mRadius = 0; /** * 进度条圆环Paint */ private Paint mProgressRingPaint; /** * 进度条圆环渐变shader */ private Shader mProgressRingShader; /** * 底环画笔 */ private Paint mRingPaint; private int[] arcColors = {};// 渐变色 /** * 进度动画 */ private BarAnimation barAnimation; /** * 抖动(缩放)动画 */ private ScaleAnimation scaleAnimation; /** * 是否正在改变 */ private boolean isAnimation = false; public RoundView(Context context) { this(context, null); } public RoundView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RoundView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public RoundView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RoundView); mRingWidth = typedArray.getDimension(R.styleable.RoundView_ring_width, 0); mRingColor = typedArray.getColor(R.styleable.RoundView_ring_color, Color.parseColor("#CBCBCB")); mProgressRingWidth = typedArray.getDimension(R.styleable.RoundView_progress_ring_width, 0); mProgressRingStartColor = typedArray.getColor(R.styleable.RoundView_progress_ring_start_color, Color.parseColor("#f90aa9")); mProgressRingEndColor = typedArray.getColor(R.styleable.RoundView_progress_ring_end_color, Color.parseColor("#931c80")); mRadius = typedArray.getDimension(R.styleable.RoundView_android_radius, 0); typedArray.recycle(); init(); } /** * 初始化 */ private void init() { mRingPaint = new Paint(); mRingPaint.setAntiAlias(true);// 抗锯齿效果 mRingPaint.setStyle(Paint.Style.STROKE); mRingPaint.setColor(mRingColor);// 背景灰 // 圆环紫色渐变色 arcColors = new int[]{mProgressRingStartColor, mProgressRingEndColor}; mProgressRingPaint = new Paint(); mProgressRingPaint.setAntiAlias(true);// 抗锯齿效果 mProgressRingPaint.setStyle(Paint.Style.STROKE); mProgressRingPaint.setStrokeCap(Cap.ROUND);// 圆形笔头 mProgressRingPaint.setStrokeWidth(mProgressRingWidth); barAnimation = new BarAnimation(); barAnimation.setAnimationListener(this); scaleAnimation = new ScaleAnimation(); scaleAnimation.setDuration(100); } /** * 设置新的角度 */ public void setAngle(final float newAngle,boolean isScale) { if (!isAnimation) { if(isScale){ scaleAnimation.setAnimationListener(new AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { changeAngle(newAngle); } @Override public void onAnimationRepeat(Animation animation) { } }); startAnimation(scaleAnimation); }else{ changeAngle(newAngle); } } } private void changeAngle(float newAngle){ mOldAngle = mNewAngle; mNewAngle = newAngle; int duration = (int) (Math.abs(mNewAngle - mOldAngle) * 15); barAnimation.setDuration(duration); barAnimation.setInterpolator(new DecelerateInterpolator()); startAnimation(barAnimation); } @SuppressLint("DrawAllocation") @Override public void onDraw(Canvas canvas) { // 圆心坐标,当前View的中心 float x = getWidth() / 2; float y = getHeight() / 2; //如果未设置半径,则半径的值为view的宽、高一半的较小值 float radius = mRadius == 0 ? Math.min(getWidth() / 2, getHeight() / 2) : mRadius; //圆环的宽度默认为半径的1/5 float ringWidth = mRingWidth == 0 ? radius / 5 : mRingWidth; //由于圆环本身有宽度,所以半径要减去圆环宽度的一半,不然一部分圆会在view外面 radius = radius - ringWidth / 2; mRingPaint.setStrokeWidth(ringWidth); // 底环 canvas.drawCircle(x, y, radius, mRingPaint); //----绘制圆环(最底部)结束------ //----绘制进度圆环------ // 设置渐变色 if (mProgressRingShader == null) { mProgressRingShader = new SweepGradient(x, y, arcColors, null); mProgressRingPaint.setShader(mProgressRingShader); } //计算进度圆环宽度,默认和底部圆滑一样大 float progressRingWidth = mProgressRingWidth == 0 ? ringWidth : mProgressRingWidth; mProgressRingPaint.setStrokeWidth(progressRingWidth); float left = x - radius; float top = y - radius; float right = x + radius; float bottom = y + radius; // 旋转画布90度+笔头半径转过的角度 double radian = radianToAngle(radius); double degrees = Math.toDegrees(-2 * Math.PI / 360 * (90 + radian));// 90度+ canvas.save(); canvas.rotate((float) degrees, x, y); canvas.drawArc(new RectF(left, top, right, bottom), (float) radian, mOldAngle + mSweepAngle, false, mProgressRingPaint); //----绘制进度圆环结束------ super.onDraw(canvas); } /** * 已知圆半径和切线长求弧长公式 * * @param radios * @return */ private double radianToAngle(float radios) { double aa = mProgressRingWidth / 2 / radios; double asin = Math.asin(aa); double radian = Math.toDegrees(asin); return radian; } /** * 抖动(缩放动画) */ public class ScaleAnimation extends Animation { private int centerX; private int centerY; public ScaleAnimation() { } @Override public void initialize(int width, int height, int parentWidth, int parentHeight) { super.initialize(width, height, parentWidth, parentHeight); centerX = width / 2; centerY = height / 2; } @Override protected void applyTransformation(float interpolatedTime, Transformation t) { super.applyTransformation(interpolatedTime, t); if (interpolatedTime < 0.25f) { // 1-1.1 float ss = interpolatedTime * 0.4f + 1f; Matrix matrix = t.getMatrix(); matrix.setScale(ss, ss, centerX, centerY); } else if (interpolatedTime >= 0.25f && interpolatedTime < 0.5f) { // 1.1-1 float ss = (0.5f - interpolatedTime) * 0.4f + 1f; Matrix matrix = t.getMatrix(); matrix.setScale(ss, ss, centerX, centerY); } else if (interpolatedTime >= 0.5f && interpolatedTime < 0.75f) { // 1-0.9 float ss = (0.75f - interpolatedTime) * 0.4f + 0.9f; Matrix matrix = t.getMatrix(); matrix.setScale(ss, ss, centerX, centerY); } else if (interpolatedTime >= 0.75f && interpolatedTime <= 1f) { // 0.9-1 float ss = interpolatedTime * 0.4f + 0.6f; Matrix matrix = t.getMatrix(); matrix.setScale(ss, ss, centerX, centerY); } } } /** * 进度条动画 */ public class BarAnimation extends Animation { public BarAnimation() { } /** * 然后调用postInvalidate()不停的绘制view。 */ @Override protected void applyTransformation(float interpolatedTime, Transformation t) { super.applyTransformation(interpolatedTime, t); if (mNewAngle - mOldAngle >= 0) { // 正向 mSweepAngle = interpolatedTime * (mNewAngle - mOldAngle); } else { // 逆向 mSweepAngle = interpolatedTime * (mNewAngle - mOldAngle); } postInvalidate(); } } @Override public void onAnimationStart(Animation animation) { isAnimation = true; } @Override public void onAnimationEnd(Animation animation) { isAnimation = false; } @Override public void onAnimationRepeat(Animation animation) { // TODO Auto-generated method stub }}
这里面主要涉及到了动画和差值器,重写了动画的 applyTransformation 方法。
抖动的效果在gif上没有展示出来,运行看看吧。
下面我们绘制下箭头,其实箭头呢就是3条线,重点是计算线的坐标:
由图中计算1、2、3、4点到坐标,我们定义1、2点之间长度的一半为箭头的大小(arrowSize)。r代表半径所以:
1点坐标:(x-arrowSize,y-r)
2点坐标:(x+arrowSize,y-r)
3点坐标:(x, y - r - arrowSize)
4点坐标:(x, y - r + arrowSize)
所以绘制箭头的代码:
//----绘制箭头开始------ //画布恢复到上一次save到状态,也就是旋转的画布 //将我们要画箭头的位置旋转到顶部,方便我们计算箭头的坐标,绘制完箭头在将画布恢复 canvas.save(); double hudu = 2 * Math.PI / 360 * (mOldAngle + mSweepAngle); double radians = Math.toDegrees(hudu); // 旋转画布 canvas.rotate((float) radians, x, y); //绘制箭头 canvas.drawLine(x - arrowSize, y - radius, x + arrowSize, y - radius, mArrowPaint); canvas.drawLine(x + arrowSize, y - radius, x, y - radius - arrowSize, mArrowPaint); canvas.drawLine(x + arrowSize, y - radius, x, y - radius + arrowSize, mArrowPaint); canvas.restore(); //----绘制箭头结束------
完整代码:
/** * 自定义进度圆环 */public class RoundView extends View implements AnimationListener { /** * 圆环转过的角度 */ private float mSweepAngle = 1; /** * 之前的角度 */ private float mOldAngle = 0; /** * 新的角度 */ private float mNewAngle = 0; /** * 圆环宽度,默认半径的1/5 */ private float mRingWidth = 0; /** * 圆环颜色,默认 #CBCBCB */ private int mRingColor = 0; /** * 进度条圆环宽度 */ private float mProgressRingWidth = 0; /** * 进度条圆环开始颜色,进度条圆环是渐变的,默认 */ private int mProgressRingStartColor = 0; /** * 进度条圆环结束颜色,进度条圆环是渐变的,默认 */ private int mProgressRingEndColor = 0; /** * 圆环半径,默认:Math.min(getHeight()/2,getWidth()/2) */ private float mRadius = 0; /** * 进度条圆环Paint */ private Paint mProgressRingPaint; /** * 进度条圆环渐变shader */ private Shader mProgressRingShader; /** * 底环画笔 */ private Paint mRingPaint; /** * 箭头画笔 */ private Paint mArrowPaint; /** * 箭头大小 */ private int arrowSize = 0; /** * 文字画笔 */ private Paint mTextPaint; /** * 文字颜色 */ private int mTextColor; /** * 文字大小 */ private float mTextSize; private int[] arcColors = {};// 渐变色 /** * 进度动画 */ private BarAnimation barAnimation; /** * 抖动(缩放)动画 */ private ScaleAnimation scaleAnimation; /** * 是否正在改变 */ private boolean isAnimation = false; public RoundView(Context context) { this(context, null); } public RoundView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RoundView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } public RoundView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RoundView); mRingWidth = typedArray.getDimension(R.styleable.RoundView_ring_width, 0); mRingColor = typedArray.getColor(R.styleable.RoundView_ring_color, Color.parseColor("#CBCBCB")); mProgressRingWidth = typedArray.getDimension(R.styleable.RoundView_progress_ring_width, 0); mProgressRingStartColor = typedArray.getColor(R.styleable.RoundView_progress_ring_start_color, Color.parseColor("#f90aa9")); mProgressRingEndColor = typedArray.getColor(R.styleable.RoundView_progress_ring_end_color, Color.parseColor("#931c80")); mRadius = typedArray.getDimension(R.styleable.RoundView_android_radius, 0); mTextColor = typedArray.getColor(R.styleable.RoundView_text_color, Color.parseColor("#f90aa9")); mTextSize = typedArray.getDimension(R.styleable.RoundView_text_size, dp2px(context, 40)); arrowSize = dp2px(context, 7); typedArray.recycle(); init(); } /** * 初始化 */ private void init() { mRingPaint = new Paint(); mRingPaint.setAntiAlias(true);// 抗锯齿效果 mRingPaint.setStyle(Paint.Style.STROKE); mRingPaint.setColor(mRingColor);// 背景灰 // 圆环紫色渐变色 arcColors = new int[]{mProgressRingStartColor, mProgressRingEndColor}; mProgressRingPaint = new Paint(); mProgressRingPaint.setAntiAlias(true);// 抗锯齿效果 mProgressRingPaint.setStyle(Paint.Style.STROKE); mProgressRingPaint.setStrokeCap(Cap.ROUND);// 圆形笔头 mProgressRingPaint.setStrokeWidth(mProgressRingWidth); mTextPaint = new Paint(); mTextPaint.setAntiAlias(true);// 抗锯齿效果 mTextPaint.setStyle(Paint.Style.FILL); mTextPaint.setTextAlign(Align.CENTER); mTextPaint.setColor(mTextColor);// 字体颜色 mTextPaint.setTextSize(mTextSize); mArrowPaint = new Paint(); mArrowPaint.setAntiAlias(true);// 抗锯齿效果 mArrowPaint.setStyle(Paint.Style.FILL); mArrowPaint.setColor(Color.parseColor("#000000")); mArrowPaint.setStrokeCap(Cap.ROUND); mArrowPaint.setStrokeWidth(5); barAnimation = new BarAnimation(); barAnimation.setAnimationListener(this); scaleAnimation = new ScaleAnimation(); scaleAnimation.setDuration(100); } /** * 设置新的角度 * newAngle:角度 */ public void setAngle(final float newAngle) { setAngle(newAngle, true); } /** * 设置新的角度 * newAngle:角度 * isScale:是否抖动 */ public void setAngle(final float newAngle, boolean isScale) { if (!isAnimation) { if (isScale) { scaleAnimation.setAnimationListener(new AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { changeAngle(newAngle); } @Override public void onAnimationRepeat(Animation animation) { } }); startAnimation(scaleAnimation); } else { changeAngle(newAngle); } } } private void changeAngle(float newAngle) { mOldAngle = mNewAngle; mNewAngle = newAngle; //计算动画时间 int duration = (int) (Math.abs(mNewAngle - mOldAngle) * 15); barAnimation.setDuration(duration); //动画差值器 barAnimation.setInterpolator(new DecelerateInterpolator()); startAnimation(barAnimation); } @SuppressLint("DrawAllocation") @Override public void onDraw(Canvas canvas) { // 圆心坐标,当前View的中心 float x = getWidth() / 2; float y = getHeight() / 2; //如果未设置半径,则半径的值为view的宽、高一半的较小值 float radius = mRadius == 0 ? Math.min(getWidth() / 2, getHeight() / 2) : mRadius; //圆环的宽度默认为半径的1/5 float ringWidth = mRingWidth == 0 ? radius / 5 : mRingWidth; //由于圆环本身有宽度,所以半径要减去圆环宽度的一半,不然一部分圆会在view外面 radius = radius - ringWidth / 2; mRingPaint.setStrokeWidth(ringWidth); // 底环 canvas.drawCircle(x, y, radius, mRingPaint); //----绘制圆环(最底部)结束------ //----绘制进度圆环------ // 设置渐变色 if (mProgressRingShader == null) { mProgressRingShader = new SweepGradient(x, y, arcColors, null); mProgressRingPaint.setShader(mProgressRingShader); } //计算进度圆环宽度,默认和底部圆滑一样大 float progressRingWidth = mProgressRingWidth == 0 ? ringWidth : mProgressRingWidth; mProgressRingPaint.setStrokeWidth(progressRingWidth); float left = x - radius; float top = y - radius; float right = x + radius; float bottom = y + radius; // 旋转画布90度+笔头半径转过的角度 double radian = radianToAngle(radius); double degrees = Math.toDegrees(-2 * Math.PI / 360 * (90 + radian));// 90度+ canvas.save(); canvas.rotate((float) degrees, x, y); canvas.drawArc(new RectF(left, top, right, bottom), (float) radian, mOldAngle + mSweepAngle, false, mProgressRingPaint); canvas.restore(); //----绘制进度圆环结束------ //----绘制箭头开始------ //画布恢复到上一次save到状态,也就是旋转的画布 //将我们要画箭头的位置旋转到顶部,方便我们计算箭头的坐标,绘制完箭头在将画布恢复 canvas.save(); double hudu = 2 * Math.PI / 360 * (mOldAngle + mSweepAngle); double radians = Math.toDegrees(hudu); // 旋转画布 canvas.rotate((float) radians, x, y); //绘制箭头 canvas.drawLine(x - arrowSize, y - radius, x + arrowSize, y - radius, mArrowPaint); canvas.drawLine(x + arrowSize, y - radius, x, y - radius - arrowSize, mArrowPaint); canvas.drawLine(x + arrowSize, y - radius, x, y - radius + arrowSize, mArrowPaint); canvas.restore(); //----绘制箭头结束------ //----绘制百分比字体开始------ int percentage = (int) ((mOldAngle + mSweepAngle) / 360 * 100); float sizeHeight = getFontHeight(); canvas.drawText(percentage + "%", x, y + sizeHeight / 2, mTextPaint); //----绘制百分比字体结束------ super.onDraw(canvas); } public float getFontHeight() { FontMetrics fm = mTextPaint.getFontMetrics(); return fm.descent - fm.ascent; } /** * 已知圆半径和切线长求弧长公式 * * @param radios * @return */ private double radianToAngle(float radios) { double aa = mProgressRingWidth / 2 / radios; double asin = Math.asin(aa); double radian = Math.toDegrees(asin); return radian; } /** * 抖动(缩放动画) */ public class ScaleAnimation extends Animation { private int centerX; private int centerY; public ScaleAnimation() { } @Override public void initialize(int width, int height, int parentWidth, int parentHeight) { super.initialize(width, height, parentWidth, parentHeight); centerX = width / 2; centerY = height / 2; } @Override protected void applyTransformation(float interpolatedTime, Transformation t) { super.applyTransformation(interpolatedTime, t); if (interpolatedTime < 0.25f) { // 1-1.1 float ss = interpolatedTime * 0.4f + 1f; Matrix matrix = t.getMatrix(); matrix.setScale(ss, ss, centerX, centerY); } else if (interpolatedTime >= 0.25f && interpolatedTime < 0.5f) { // 1.1-1 float ss = (0.5f - interpolatedTime) * 0.4f + 1f; Matrix matrix = t.getMatrix(); matrix.setScale(ss, ss, centerX, centerY); } else if (interpolatedTime >= 0.5f && interpolatedTime < 0.75f) { // 1-0.9 float ss = (0.75f - interpolatedTime) * 0.4f + 0.9f; Matrix matrix = t.getMatrix(); matrix.setScale(ss, ss, centerX, centerY); } else if (interpolatedTime >= 0.75f && interpolatedTime <= 1f) { // 0.9-1 float ss = interpolatedTime * 0.4f + 0.6f; Matrix matrix = t.getMatrix(); matrix.setScale(ss, ss, centerX, centerY); } } } /** * 进度条动画 */ public class BarAnimation extends Animation { public BarAnimation() { } /** * 然后调用postInvalidate()不停的绘制view。 */ @Override protected void applyTransformation(float interpolatedTime, Transformation t) { super.applyTransformation(interpolatedTime, t); if (mNewAngle - mOldAngle >= 0) { // 正向 mSweepAngle = interpolatedTime * (mNewAngle - mOldAngle); } else { // 逆向 mSweepAngle = interpolatedTime * (mNewAngle - mOldAngle); } postInvalidate(); } } @Override public void onAnimationStart(Animation animation) { isAnimation = true; } @Override public void onAnimationEnd(Animation animation) { isAnimation = false; } @Override public void onAnimationRepeat(Animation animation) { // TODO Auto-generated method stub } private int dp2px(Context context, float dpVal) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpVal, context.getResources().getDisplayMetrics()); }}
- Android 自定义View实例之进度圆环
- 一个自定义圆环进度View
- 自定义view,实现圆环进度
- 自定义View实现进度圆环
- Android自定义View之酷炫吊炸天的圆环
- Android学习之自定义控件-圆环进度条加内圆填充进度
- 自定义view之自定义圆环
- 自定义View之绘制圆环
- 自定义View之交替圆环
- 自定义View之圆环进度条
- 自定义View之圆环刷新
- Android自定义View --- 绘制圆环
- Android--view自定义--圆环等待
- Android自定义view 圆环进度条
- Android自定义view三圆环
- android 自定义pm2.5进度显示圆环
- Android开发 自定义动态进度圆环
- Android自定义View和属性动画完美结合,创造出酷炫圆环动画,带标尺和进度
- Ubuntu 虚拟机 映射网络到 winddows
- hdu2181 哈密顿绕行世界问题【dfs】
- Sublime Text 2.0.2 注册码
- 前端常见的性能优化手段
- HTML总结
- Android 自定义View实例之进度圆环
- python sorted函数按value值对字典排序
- Java内存区域划分
- LeetCode 217. Contains Duplicate
- DevOps介绍
- 清除浮动的方法整理
- eclispe或者myeclispe maven jar包不能部署到tomcat下
- 基于SpringCloud的微服务架构实战案例项目,以一个简单的购物流程为示例
- Android dp和px的转换