Android 自定义 圆环,Android自定义View之酷炫圆环(二)

先看下最终的效果

静态:

4a6abf86f765fe5f504d3c5116968a44.png

动态:

f4c0d5102305d4f7e270f75eb40a27c5.gif

一、开始实现

新建一个DoughnutProgress继承View

public class DoughnutProgress extends View {

}

先给出一些常量、变量以及公共方法的代码,方便理解后面的代码

private static final int DEFAULT_MIN_WIDTH = 400; //View默认最小宽度

private static final int RED = 230, GREEN = 85, BLUE = 35; //基础颜色,这里是橙红色

private static final int MIN_ALPHA = 30; //最小不透明度

private static final int MAX_ALPHA = 255; //最大不透明度

private static final float doughnutRaduisPercent = 0.65f; //圆环外圆半径占View最大半径的百分比

private static final float doughnutWidthPercent = 0.12f; //圆环宽度占View最大半径的百分比

private static final float MIDDLE_WAVE_RADUIS_PERCENT = 0.9f; //第二个圆出现时,第一个圆的半径百分比

private static final float WAVE_WIDTH = 5f; //波纹圆环宽度

//圆环颜色

private static int[] doughnutColors = new int[]{

Color.argb(MAX_ALPHA, RED, GREEN, BLUE),

Color.argb(MIN_ALPHA, RED, GREEN, BLUE),

Color.argb(MIN_ALPHA, RED, GREEN, BLUE)};

private Paint paint = new Paint(); //画笔

private float width; //自定义view的宽度

private float height; //自定义view的高度

private float currentAngle = 0f; //当前旋转角度

private float raduis; //自定义view的最大半径

private float firstWaveRaduis;

private float secondWaveRaduis;

//

private void resetParams() {

width = getWidth();

height = getHeight();

raduis = Math.min(width, height)/2;

}

private void initPaint() {

paint.reset();

paint.setAntiAlias(true);

}

重写onMeasure方法,为什么要重写onMeasure方法可以看我的上一篇文章,点这里

/**

* 当布局为wrap_content时设置默认长宽

*

* @param widthMeasureSpec

* @param heightMeasureSpec

*/

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

setMeasuredDimension(measure(widthMeasureSpec), measure(heightMeasureSpec));

}

private int measure(int origin) {

int result = DEFAULT_MIN_WIDTH;

int specMode = MeasureSpec.getMode(origin);

int specSize = MeasureSpec.getSize(origin);

if (specMode == MeasureSpec.EXACTLY) {

result = specSize;

} else {

if (specMode == MeasureSpec.AT_MOST) {

result = Math.min(result, specSize);

}

}

return result;

}

下面就是最重要的重写onDraw方法,大致流程如下

在开始绘制之前,先初始化width、height、raduis, 以及将View的中心作为原点

resetParams();

//将画布中心设为原点(0,0), 方便后面计算坐标

canvas.translate(width / 2, height / 2);

实现静态的渐变圆环

1、画渐变圆环

float doughnutWidth = raduis * doughnutWidthPercent;//圆环宽度

//圆环外接矩形

RectF rectF = new RectF(

-raduis * doughnutRaduisPercent,

-raduis * doughnutRaduisPercent,

raduis * doughnutRaduisPercent,

raduis * doughnutRaduisPercent);

initPaint();

paint.setStrokeWidth(doughnutWidth);

paint.setStyle(Paint.Style.STROKE);

paint.setShader(new SweepGradient(0, 0, doughnutColors, null));

canvas.drawArc(rectF, 0, 360, false, paint);

通过修改doughnutColors可以实现不同的渐变效果

2、画圆环旋转头部的圆

//画旋转头部圆

initPaint();

paint.setStyle(Paint.Style.FILL);

paint.setColor(Color.argb(MAX_ALPHA, RED, GREEN, BLUE));

canvas.drawCircle(raduis * doughnutRaduisPercent, 0, doughnutWidth / 2, paint);

此时运行代码得到效果如下图:

468bf712973aa5900693fe9c177b4da6.png

我们还可以在绘制圆环之前通过旋转画布得到不同初始状态

canvas.rotate(-45, 0, 0);

e2fce21ff57953824f9944264f6f2b4c.png

canvas.rotate(-180, 0, 0);

087672234213706c3d71f0bc937ae2ec.png

此时聪明的你应该已经想到怎么让这个圆环旋转起来了吧^_^

对!正如你所想的,就是通过canvas.rotate方法不停地旋转画布(这个“地”是这么用的吧o(╯□╰)o)

让圆环旋转起来

在绘制圆环之前加上下面的代码:

//转起来

canvas.rotate(-currentAngle, 0, 0);

if (currentAngle >= 360f){

currentAngle = currentAngle - 360f;

} else{

currentAngle = currentAngle + 2f;

}

然后再让一个线程循环刷新就好了

private Thread thread = new Thread(){

@Override

public void run() {

while(true){

try {

Thread.sleep(10);

} catch (InterruptedException e) {

e.printStackTrace();

}

postInvalidate();

}

}

};

试试!转起来了吗O(∩_∩)O~

下面是比较有意思的部分,实现类似水波涟漪的效果

分析水波涟漪效果的实现原理(画了张草图方便理解):

32d03a1208468e4c1457c2fe354c8fd1.png

假设淡黄色背景区域为整个View的大小

黑色圆圈为View内的最大圆(半径为R3)

橙色圆环代表渐变圆环

红色圆圈代表圆环的外圆(半径为R1)

紫色圆圈是干啥子的,待会儿再介绍~(半径为R2)

通过观察实现的最终效果,可以发现有个圆的半径从R1逐渐增大R3,不透明度逐渐减小到0。

那是不是这样周而复始就可以实现最终的效果了呢?

没那么简单。。。

仔细观察发现,第二个圆不是等到第一个圆的半径增大到R3才开始出现的,而是在将要消失的时候就出现了,有一段时间是两个圆同时存在的。

那么我们就假设当第一个圆的半径增大到R2,第二个圆开始出现。

开始想象两个圆的循环运行模型~~~

我的方案是:

绘制两个圆,每个圆的半径都从R1增大到R1+2x(R2-R1),不透明度还是从R1到R3的过程中逐渐变为0,也就是当圆的半径大于R3时,不透明度就为0了(不可见了),将第一个圆半径初始值设为R1,第二个圆半径初始值设为R2。这样两个圆半径同时逐渐增大,当半径大于 R1+2x(R2-R1)时又重新回到R1大小继续增大,就实现了类似水波涟漪的效果了。

//实现类似水波涟漪效果

initPaint();

paint.setStyle(Paint.Style.STROKE);

paint.setStrokeWidth(5);

secondWaveRaduis = calculateWaveRaduis(secondWaveRaduis);

firstWaveRaduis = calculateWaveRaduis(secondWaveRaduis + raduis*(MIDDLE_WAVE_RADUIS_PERCENT - doughnutRaduisPercent) - raduis*doughnutWidthPercent/2);

paint.setColor(Color.argb(calculateWaveAlpha(secondWaveRaduis), RED, GREEN, BLUE));

canvas.drawCircle(0, 0, secondWaveRaduis, paint); //画第二个圆(初始半径较小的)

initPaint();

paint.setStyle(Paint.Style.STROKE);

paint.setStrokeWidth(5);

paint.setColor(Color.argb(calculateWaveAlpha(firstWaveRaduis), RED, GREEN, BLUE));

canvas.drawCircle(0, 0, firstWaveRaduis, paint); //画第一个圆(初始半径较大的)

/**

* 计算波纹圆的半径

* @param waveRaduis

* @return

*/

private float calculateWaveRaduis(float waveRaduis){

if(waveRaduis < raduis*doughnutRaduisPercent + raduis*doughnutWidthPercent/2){

waveRaduis = raduis*doughnutRaduisPercent + raduis*doughnutWidthPercent/2;

}

if(waveRaduis > raduis*MIDDLE_WAVE_RADUIS_PERCENT + raduis*(MIDDLE_WAVE_RADUIS_PERCENT - doughnutRaduisPercent) - raduis*doughnutWidthPercent/2){

waveRaduis = waveRaduis - (raduis*MIDDLE_WAVE_RADUIS_PERCENT + raduis*(MIDDLE_WAVE_RADUIS_PERCENT - doughnutRaduisPercent) - raduis*doughnutWidthPercent/2) + raduis*doughnutWidthPercent/2 + raduis*doughnutRaduisPercent;

}

waveRaduis += 0.6f;

return waveRaduis;

}

/**

* 根据波纹圆的半径计算不透明度

* @param waveRaduis

* @return

*/

private int calculateWaveAlpha(float waveRaduis){

float percent = (waveRaduis-raduis*doughnutRaduisPercent-raduis*doughnutWidthPercent/2)/(raduis-raduis*doughnutRaduisPercent-raduis*doughnutWidthPercent/2);

if(percent >= 1f){

return 0;

}else{

return (int) (MIN_ALPHA*(1f-percent));

}

}

以上就是本文的全部内容,希望对大家的学习Android软件编程有所帮助。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值