1. 程式人生 > >Android自定義View-仿華為手機管家病毒查殺類似於雷達掃描動畫效果

Android自定義View-仿華為手機管家病毒查殺類似於雷達掃描動畫效果

最近在使用華為手機管家病毒查殺時有一個類似雷達掃描的動畫,發現該動畫旋轉軸未對準圓心,如下圖:


這樣不仔細看,看不出來,咱們放大看一下,放大後如下:


現在我們可以清楚看到這個問題,於是想自己實現一把,好了,為了便於理解,這裡就按照動畫所見內容依次展開來說。
先簡單的分析一下這裡的元素,主要有3個園,三條直線,一個漸變扇形,一個漸變圓弧,知道這些以後,我們就可以開始進行程式碼編寫了,那麼首先定義一下所需要的畫筆,畫布及一些資料

//初始化畫筆
private void initPaint() {
        mPaintCircleOrLine = new Paint();
        mPaintCircleOrLine.setStrokeWidth(2);
        mPaintCircleOrLine.setAntiAlias(true);
        mPaintCircleOrLine.setStyle(Paint.Style.STROKE);
        mPaintCircleOrLine.setColor(getResources().getColor(R.color.colorCircleOrLine));


        mPaintArc1 = new Paint();
        mPaintArc1.setStyle(Paint.Style.FILL);
        mPaintArc1.setAntiAlias(true);


        mPaintArc2 = new Paint();
        mPaintArc2.setStrokeWidth(4);
        mPaintArc2.setStyle(Paint.Style.STROKE);
        mPaintArc2.setAntiAlias(true);
        mPaintArc2.setColor(getResources().getColor(R.color.colorArcBorder));


        mPaintLine = new Paint();
        mPaintLine.setStrokeWidth(4);
        mPaintLine.setStyle(Paint.Style.STROKE);
        mPaintLine.setAntiAlias(true);
        mPaintLine.setColor(getResources().getColor(R.color.colorLine));


    }

注意這裡畫園和十字直線使用的同一個畫筆的樣式,如果你需要不同的樣式,可以分開定義。初始化操作完成了之後,就是給變數賦值,我選擇在onSizeChange()方法裡面對變數進行賦值,程式碼如下:

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);


        mWidth = getWidth();
        mHeight = getHeight();
        mRectF = new RectF(getPaddingLeft(), getPaddingTop(), mWidth-getPaddingRight(), mHeight-getPaddingBottom());
        SweepGradient sweepGradient1 = new SweepGradient(mWidth / 2, mHeight / 2,
                new int[]{Color.TRANSPARENT, getResources().getColor(R.color.colorArc)}, new float[]{0.8f, 1f});
        mPaintArc1.setShader(sweepGradient1);


        SweepGradient sweepGradient2 = new SweepGradient(mWidth / 2, mHeight / 2,
                Color.TRANSPARENT, getResources().getColor(R.color.colorArcBorder));
        mPaintArc2.setShader(sweepGradient2);
        Log.i("wang", String.format("width:%s, height:%s", mWidth, mHeight));


    }

到這裡我們的準備工作都做完了,接下來就進行繪製了,我重寫了onDraw方法,我們看一下onDraw方法裡面的操作:
@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.i("ScanView", "onDraw()");
        canvasCircle(canvas);
        canvasLine(canvas);
        //根據matrix中設定角度,不斷繪製shader,呈現出一種扇形掃描效果
        if(threadRunning) {
            canvas.concat(matrix);
            canvasArc(canvas);
        }
    }
這裡面有畫園,畫直線,畫扇形,畫圓弧,所以可以發現,我們所看到的效果,都是在onDraw方法裡面實現的,我們具體看看每一個方法:
private void canvasCircle(Canvas canvas) {
        canvas.drawCircle(mWidth / 2, mHeight / 2, (mWidth - getPaddingLeft() - getPaddingRight()) / 6, mPaintCircleOrLine);
        canvas.drawCircle(mWidth / 2, mHeight / 2, (mWidth - getPaddingLeft() - getPaddingRight()) / 3, mPaintCircleOrLine);
        canvas.drawCircle(mWidth / 2, mHeight / 2, (mWidth - getPaddingLeft() - getPaddingRight()) / 2, mPaintCircleOrLine);
    }


    private void canvasLine(Canvas canvas) {
        canvas.drawLine(getPaddingLeft(), mHeight/2, mWidth - getPaddingRight(), mHeight/2, mPaintCircleOrLine);
        canvas.drawLine(mWidth/2, getPaddingTop(), mWidth/2, mHeight - getPaddingBottom(), mPaintCircleOrLine);
    }


    private void canvasArc(Canvas canvas) {
        canvas.drawArc(mRectF, 0, 180, true, mPaintArc1);
        canvas.drawArc(mRectF, 0, 180, false, mPaintArc2);
        canvas.drawLine(getPaddingLeft()-2, mHeight/2, mWidth/2, mHeight/2, mPaintLine);
    }

這樣就繪製了整個UI,接下來實現動畫效果,就可以實現整體的效果。

這裡實現動畫的時候,用到了Matrix這個東西,使用Matrix可以實現強大的圖形動畫,包位移、旋轉、縮放、透明等效果,定義一執行緒,通過Matrix旋轉操作實現動畫,程式碼如下:
protected class ScanThread extends Thread {


        private ScanView view;


        public ScanThread(ScanView view) {
            // TODO Auto-generated constructor stub
            this.view = view;
        }


        @Override
        public void run() {
            Log.i("ScanView", "run()");
            // TODO Auto-generated method stub
            while (threadRunning) {
                view.post(new Runnable() {
                    public void run() {
                        start = start + 1;
                        matrix = new Matrix();
                        matrix.preRotate(start, mWidth/2, mWidth/2);
                        view.invalidate();


                    }
                });
                try {
                    Thread.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

好了,最終整體程式碼如下:
package com.wwj.custom.scan.view;


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.SweepGradient;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;


/**
 * Created by Administrator on 2017/4/25.
 */


public class ScanView extends View {


    private Paint mPaintCircleOrLine, mPaintArc1, mPaintArc2, mPaintLine;
    private float mWidth, mHeight;
    private RectF mRectF;
    private Matrix matrix;
    //旋轉效果起始角度
    private int start = 0;
    private boolean threadRunning = false;
    private ScanThread mThread;


    public ScanView(Context context) {
        super(context);
        initPaint();
    }


    public ScanView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initPaint();
    }


    private void initPaint() {
        mPaintCircleOrLine = new Paint();
        mPaintCircleOrLine.setStrokeWidth(2);
        mPaintCircleOrLine.setAntiAlias(true);
        mPaintCircleOrLine.setStyle(Paint.Style.STROKE);
        mPaintCircleOrLine.setColor(getResources().getColor(R.color.colorCircleOrLine));


        mPaintArc1 = new Paint();
        mPaintArc1.setStyle(Paint.Style.FILL);
        mPaintArc1.setAntiAlias(true);


        mPaintArc2 = new Paint();
        mPaintArc2.setStrokeWidth(4);
        mPaintArc2.setStyle(Paint.Style.STROKE);
        mPaintArc2.setAntiAlias(true);
        mPaintArc2.setColor(getResources().getColor(R.color.colorArcBorder));


        mPaintLine = new Paint();
        mPaintLine.setStrokeWidth(4);
        mPaintLine.setStyle(Paint.Style.STROKE);
        mPaintLine.setAntiAlias(true);
        mPaintLine.setColor(getResources().getColor(R.color.colorLine));


    }


    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);


        mWidth = getWidth();
        mHeight = getHeight();
        mRectF = new RectF(getPaddingLeft(), getPaddingTop(), mWidth-getPaddingRight(), mHeight-getPaddingBottom());
        SweepGradient sweepGradient1 = new SweepGradient(mWidth / 2, mHeight / 2,
                new int[]{Color.TRANSPARENT, getResources().getColor(R.color.colorArc)}, new float[]{0.8f, 1f});
        mPaintArc1.setShader(sweepGradient1);


        SweepGradient sweepGradient2 = new SweepGradient(mWidth / 2, mHeight / 2,
                Color.TRANSPARENT, getResources().getColor(R.color.colorArcBorder));
        mPaintArc2.setShader(sweepGradient2);
        Log.i("wang", String.format("width:%s, height:%s", mWidth, mHeight));


    }


    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Log.i("ScanView", "onDraw()");
        canvasCircle(canvas);
        canvasLine(canvas);
        //根據matrix中設定角度,不斷繪製shader,呈現出一種扇形掃描效果
        if(threadRunning) {
            canvas.concat(matrix);
            canvasArc(canvas);
        }
    }


    private void canvasCircle(Canvas canvas) {
        canvas.drawCircle(mWidth / 2, mHeight / 2, (mWidth - getPaddingLeft() - getPaddingRight()) / 6, mPaintCircleOrLine);
        canvas.drawCircle(mWidth / 2, mHeight / 2, (mWidth - getPaddingLeft() - getPaddingRight()) / 3, mPaintCircleOrLine);
        canvas.drawCircle(mWidth / 2, mHeight / 2, (mWidth - getPaddingLeft() - getPaddingRight()) / 2, mPaintCircleOrLine);
    }


    private void canvasLine(Canvas canvas) {
        canvas.drawLine(getPaddingLeft(), mHeight/2, mWidth - getPaddingRight(), mHeight/2, mPaintCircleOrLine);
        canvas.drawLine(mWidth/2, getPaddingTop(), mWidth/2, mHeight - getPaddingBottom(), mPaintCircleOrLine);
    }


    private void canvasArc(Canvas canvas) {
        canvas.drawArc(mRectF, 0, 180, true, mPaintArc1);
        canvas.drawArc(mRectF, 0, 180, false, mPaintArc2);
        canvas.drawLine(getPaddingLeft()-2, mHeight/2, mWidth/2, mHeight/2, mPaintLine);
    }


    public void start() {
        mThread = new ScanThread(this);
        mThread.start();
        threadRunning = true;
    }


    public void stop() {
        threadRunning = false;
        invalidate();
    }


    protected class ScanThread extends Thread {


        private ScanView view;


        public ScanThread(ScanView view) {
            // TODO Auto-generated constructor stub
            this.view = view;
        }


        @Override
        public void run() {
            Log.i("ScanView", "run()");
            // TODO Auto-generated method stub
            while (threadRunning) {
                view.post(new Runnable() {
                    public void run() {
                        start = start + 1;
                        matrix = new Matrix();
                        matrix.preRotate(start, mWidth/2, mWidth/2);
                        view.invalidate();


                    }
                });
                try {
                    Thread.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
這個SanView使用也很簡單,啟動呼叫start(),需要停止呼叫其stop()即可。

github地址:https://github.com/wjwang0914/CustomScanView

現在才開始寫部落格,歡迎大家一起學習,討論,期待我們一起進步。