最近有一个需求,就是做一个扫描的UI,看了很多扫描动画,发现酷狗的扫描动画挺漂亮的,所以就做了一个相似的扫描动画,废话不多说,先看一下最终的效果吧。
首先我们看一下这个效果,它由以下几部分组成:
1.中间一个音符图片
2.在音符图片外面画了三个圆环
3.一个旋转的扇形动画
既然知道它的结构那就来一步一步实现它吧。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
viewSize = 600;
setMeasuredDimension(viewSize, viewSize);
}
/**
* 从资源中解码bitmap
*/
private void initBitmap() {
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.music_note);
}
在这里为了方便直接把视图的长宽设置为600,实际应用中可以自己去定制,但是长宽要一样,然后就是在构造的时候解码一下资源文件中的音符图片。然后就可以开始绘图了。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint mPaint = new Paint();
Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
Rect rectd = new Rect((viewSize / 10) * 4, (viewSize / 10) * 4, viewSize - (viewSize / 10) * 4, viewSize - (viewSize / 10) * 4);
canvas.drawBitmap(bitmap, rect, rectd, mPaint);
circlePaint = new Paint();
circlePaint.setStyle(Paint.Style.STROKE);
circlePaint.setAntiAlias(true);
circlePaint.setStrokeWidth((viewSize / 10));
circlePaint.setColor(Color.parseColor("#DDFA7298"));
canvas.drawCircle(viewSize / 2, viewSize / 2, (viewSize / 10) * 2.5f, circlePaint);
circlePaint.setColor(Color.parseColor("#AAFA7298"));
canvas.drawCircle(viewSize / 2, viewSize / 2, (viewSize / 10) * 3.5f, circlePaint);
circlePaint.setColor(Color.parseColor("#66FA7298"));
canvas.drawCircle(viewSize / 2, viewSize / 2, (viewSize / 10) * 4.5f, circlePaint);
sectorPaint = new Paint();
sectorPaint.setAntiAlias(true);
sectorPaint.setStyle(Paint.Style.STROKE);
sectorPaint.setStrokeWidth((viewSize / 10) * 3);
Shader sectorShader = new SweepGradient(viewSize / 2, viewSize / 2, new int[]{Color.TRANSPARENT, 0x00FA7298, 0xFFFA7298},
new float[]{0, 0.875f, 1f});
sectorPaint.setShader(sectorShader);
if (threadFlag) {
canvas.concat(matrix);
canvas.drawCircle(viewSize / 2, viewSize / 2, (viewSize / 10) * 3.5f, sectorPaint);
}
}
这一段有点长,一个一个看,第一部分是画图片
Paint mPaint = new Paint();
Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
Rect rectd = new Rect((viewSize / 10) * 4, (viewSize / 10) * 4, viewSize - (viewSize / 10) * 4, viewSize - (viewSize / 10) * 4);
canvas.drawBitmap(bitmap, rect, rectd, mPaint);
在这里我们的目的是把整张图画进整张图的最中心,这里使用的是drawBitmap(Bitmap,Rect,Rect,Paint);这个方法。画这张图片需要创建两个Rect,第一个Rect 代表要绘制的bitmap 区域,第二个 Rect 代表的是要将bitmap 绘制在屏幕的什么地方。所以这里第一个Rect我们 取值为整个Bitmap 区域 ,第二个Rect我们取值为view的五等分圆环由内向外第二圆环的外接正方形状,说的这么绕,大家仔细看下应该不难,也就是下图中的蓝色矩形。
circlePaint = new Paint();
circlePaint.setStyle(Paint.Style.STROKE);
circlePaint.setAntiAlias(true);
circlePaint.setStrokeWidth((viewSize / 10));
circlePaint.setColor(Color.parseColor("#DDFA7298"));
canvas.drawCircle(viewSize / 2, viewSize / 2, (viewSize / 10) * 2.5f, circlePaint);
circlePaint.setColor(Color.parseColor("#AAFA7298"));
canvas.drawCircle(viewSize / 2, viewSize / 2, (viewSize / 10) * 3.5f, circlePaint);
circlePaint.setColor(Color.parseColor("#66FA7298"));
canvas.drawCircle(viewSize / 2, viewSize / 2, (viewSize / 10) * 4.5f, circlePaint);
这一部分就是画外面的三个圆环了,在这里,我们给画笔设置描边不填充的属性,然后再设置画笔的宽度则可以实现圆环的绘制了,半径的取值我相信大家稍微看下也能看懂。
sectorPaint = new Paint();
sectorPaint.setAntiAlias(true);
sectorPaint.setStyle(Paint.Style.STROKE);
sectorPaint.setStrokeWidth((viewSize / 10) * 3);
Shader sectorShader = new SweepGradient(viewSize / 2, viewSize / 2, new int[]{Color.TRANSPARENT, 0x00FA7298, 0xFFFA7298},
new float[]{0, 0.875f, 1f});
sectorPaint.setShader(sectorShader);
接下来的这部分用到了SweepGradient扫描梯度渲染,这里简单的介绍一下:
public SweepGradient(float cx, float cy, int colors[], float positions[])
public SweepGradient(float cx, float cy, int color0, int color1)
第一个方法 cx 、cy指圆型坐标; colors[]、渲染颜色数组,至少要两个,positions[]设置相对位置的渲染,如果为null则均匀梯度渲染
第二个方法 cx 、cy指圆型坐标;color0、 color1分别指起始渲染颜色和终止渲染颜色。
我们这里使用的是第一种方法,从代码可以看出我们只渲染了1/8的扇形区域。
if (threadFlag) {
canvas.concat(matrix);
canvas.drawCircle(viewSize / 2, viewSize / 2, (viewSize / 10) * 3.5f, sectorPaint);
}
这部分代码的功能就是只有在线程开启时才会绘制扇形渲染。同时根据在矩阵matrix中改变角度持续绘制达到扇形渲染扫描的效果。
class ScanThread extends Thread {
private ScanView view;
public ScanThread(ScanView view) {
this.view = view;
}
@Override
public void run() {
super.run();
while (threadFlag) {
if (start) {
view.post(new Runnable() {
@Override
public void run() {
angle = angle + 5;
if (angle == 360) {
angle = 0;
}
matrix = new Matrix();
matrix.setRotate(angle, viewSize / 2, viewSize / 2);
view.invalidate();
}
});
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
这里通过在线程中不断对角度进行改变重绘才有了最终的效果
public class ScanView extends View {
private static final String TAG = "ScanView";
/**
* 画圆的笔
*/
private Paint circlePaint;
/**
* 画扇形渲染的笔
*/
private Paint sectorPaint;
/**
* 扫描线程
*/
private ScanThread mThread;
/**
* 线程进行标志
*/
private boolean threadFlag = false;
/**
* 线程启动标志
*/
private boolean start = false;
/**
* 扇形转动的角度
*/
private int angle = 0;
/**
* 当前视图的宽高,这里相同
*/
private int viewSize;
/**
* 画在view中间的图片
*/
private Bitmap bitmap;
/**
* 对图形进行处理的矩阵类
*/
private Matrix matrix;
public ScanView(Context context) {
this(context, null);
}
public ScanView(Context context, AttributeSet attrs) {
super(context, attrs);
initBitmap();
}
/**
* 此处设置viewSize固定值为600
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
viewSize = 600;
setMeasuredDimension(viewSize, viewSize);
}
/**
* 从资源中解码bitmap
*/
private void initBitmap() {
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.music_note);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint mPaint = new Paint();
Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
Rect rectd = new Rect((viewSize / 10) * 4, (viewSize / 10) * 4, viewSize - (viewSize / 10) * 4, viewSize - (viewSize / 10) * 4);
canvas.drawBitmap(bitmap, rect, rectd, mPaint);
circlePaint = new Paint();
circlePaint.setStyle(Paint.Style.STROKE);
circlePaint.setAntiAlias(true);
circlePaint.setStrokeWidth((viewSize / 10));
circlePaint.setColor(Color.parseColor("#DDFA7298"));
canvas.drawCircle(viewSize / 2, viewSize / 2, (viewSize / 10) * 2.5f, circlePaint);
circlePaint.setColor(Color.parseColor("#AAFA7298"));
canvas.drawCircle(viewSize / 2, viewSize / 2, (viewSize / 10) * 3.5f, circlePaint);
circlePaint.setColor(Color.parseColor("#66FA7298"));
canvas.drawCircle(viewSize / 2, viewSize / 2, (viewSize / 10) * 4.5f, circlePaint);
sectorPaint = new Paint();
sectorPaint.setAntiAlias(true);
sectorPaint.setStyle(Paint.Style.STROKE);
sectorPaint.setStrokeWidth((viewSize / 10) * 3);
Shader sectorShader = new SweepGradient(viewSize / 2, viewSize / 2, new int[]{Color.TRANSPARENT, 0x00FA7298, 0xFFFA7298},
new float[]{0, 0.875f, 1f});
sectorPaint.setShader(sectorShader);
if (threadFlag) {
canvas.concat(matrix);
canvas.drawCircle(viewSize / 2, viewSize / 2, (viewSize / 10) * 3.5f, sectorPaint);
}
}
public void start() {
mThread = new ScanThread(this);
mThread.start();
threadFlag = true;
start = true;
}
public void stop() {
if (start) {
threadFlag = false;
start = false;
invalidate();
}
}
class ScanThread extends Thread {
private ScanView view;
public ScanThread(ScanView view) {
this.view = view;
}
@Override
public void run() {
super.run();
while (threadFlag) {
if (start) {
view.post(new Runnable() {
@Override
public void run() {
angle = angle + 5;
if (angle == 360) {
angle = 0;
}
matrix = new Matrix();
matrix.setRotate(angle, viewSize / 2, viewSize / 2);
view.invalidate();
}
});
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
终于实现了,再贴一下效果图
https://github.com/lijunyandev/ScanDemo
这是我写的第一篇博客,前前后后花了几天晚上的时间,所幸今天终于出来了,虽然不难,但是也付出了努力,还是很激动的。希望以后有空能够持续写下去。