前言
最近有一个需求,就是做一个扫描的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();
}
}
}
}
}
}
终于实现了,再贴一下效果图
完整的代码demo
https://github.com/lijunyandev/ScanDemo
结语
这是我写的第一篇博客,前前后后花了几天晚上的时间,所幸今天终于出来了,虽然不难,但是也付出了努力,还是很激动的。希望以后有空能够持续写下去。