Android自定义控件之扫描动画UI

    xiaoxiao2021-03-25  83

    前言

    最近有一个需求,就是做一个扫描的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

    结语

    这是我写的第一篇博客,前前后后花了几天晚上的时间,所幸今天终于出来了,虽然不难,但是也付出了努力,还是很激动的。希望以后有空能够持续写下去。

    转载请注明原文地址: https://ju.6miu.com/read-8453.html

    最新回复(0)