Recently I was working on a project, and one of the pages was to do a radar scan effect. So I looked for similar effects of other applications for reference. Just as I used the mobile phone housekeeper in huawei mobile phone — virus check page is a radar scan effect. And look at its style is quite good, just meet my requirements. So I decided to customize a RadarView that looked like it. Here’s the effect of Huawei’s phone butler:

Since I need a radar scan, I draw the percentage in the middle. RadarView can configure the View theme color, scan color, scan speed, circle number, whether to display water droplets and other functions according to their own needs, convenient implementation of various styles. Here is the code for customizing RadarView.

public class RadarView extends View {

    // The default theme color
    private int DEFAULT_COLOR = Color.parseColor("#91D7F4");

    // The color of the circles and crosses
    private int mCircleColor = DEFAULT_COLOR;
    // The number of circles cannot be less than 1
    private int mCircleNum = 3;
    // RadarView will make the gradient transparent to this color
    private int mSweepColor = DEFAULT_COLOR;
    // The color of the water drop
    private int mRaindropColor = DEFAULT_COLOR;
    // The number of droplets is the maximum number of droplets that can occur simultaneously. Because the droplets are randomly generated, the number is uncertain
    private int mRaindropNum = 4;
    // Whether to display cross lines
    private boolean isShowCross = true;
    // Whether to display water droplets
    private boolean isShowRaindrop = true;
    // The speed of the scan, which means a revolution in seconds
    private float mSpeed = 3.0 f;
    // The speed at which the drop displays and disappears
    private float mFlicker = 3.0 f;

    private Paint mCirclePaint;// Round brushes
    private Paint mSweepPaint; // Scan the effect of the brush
    private Paint mRaindropPaint;// Water drop brushes

    private float mDegrees; // Scan rotation Angle during scanning.
    private boolean isScanning = false;// Whether to scan

    // Save the drop data
    private ArrayList<Raindrop> mRaindrops = new ArrayList<>();

    public RadarView(Context context) {
        super(context);
        init();
    }

    public RadarView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        getAttrs(context, attrs);
        init();
    }

    public RadarView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        getAttrs(context, attrs);
        init();
    }

    /** * gets the custom attribute value **@param context
     * @param attrs
     */
    private void getAttrs(Context context, AttributeSet attrs) {
        if(attrs ! =null) {
            TypedArray mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.RadarView);
            mCircleColor = mTypedArray.getColor(R.styleable.RadarView_circleColor, DEFAULT_COLOR);
            mCircleNum = mTypedArray.getInt(R.styleable.RadarView_circleNum, mCircleNum);
            if (mCircleNum < 1) {
                mCircleNum = 3;
            }
            mSweepColor = mTypedArray.getColor(R.styleable.RadarView_sweepColor, DEFAULT_COLOR);
            mRaindropColor = mTypedArray.getColor(R.styleable.RadarView_raindropColor, DEFAULT_COLOR);
            mRaindropNum = mTypedArray.getInt(R.styleable.RadarView_raindropNum, mRaindropNum);
            isShowCross = mTypedArray.getBoolean(R.styleable.RadarView_showCross, true);
            isShowRaindrop = mTypedArray.getBoolean(R.styleable.RadarView_showRaindrop, true);
            mSpeed = mTypedArray.getFloat(R.styleable.RadarView_speed, mSpeed);
            if (mSpeed <= 0) {
                mSpeed = 3;
            }
            mFlicker = mTypedArray.getFloat(R.styleable.RadarView_flicker, mFlicker);
            if (mFlicker <= 0) {
                mFlicker = 3; } mTypedArray.recycle(); }}/** * initializes */
    private void init(a) {
        // Initialize the brush
        mCirclePaint = new Paint();
        mCirclePaint.setColor(mCircleColor);
        mCirclePaint.setStrokeWidth(1);
        mCirclePaint.setStyle(Paint.Style.STROKE);
        mCirclePaint.setAntiAlias(true);

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

        mSweepPaint = new Paint();
        mSweepPaint.setAntiAlias(true);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // Set width and height to 200dp by default
        int defaultSize = dp2px(getContext(), 200);
        setMeasuredDimension(measureWidth(widthMeasureSpec, defaultSize),
                measureHeight(heightMeasureSpec, defaultSize));
    }

    /** * measure width **@param measureSpec
     * @param defaultSize
     * @return* /
    private int measureWidth(int measureSpec, int defaultSize) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            result = defaultSize + getPaddingLeft() + getPaddingRight();
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        result = Math.max(result, getSuggestedMinimumWidth());
        return result;
    }

    /** * measure the height **@param measureSpec
     * @param defaultSize
     * @return* /
    private int measureHeight(int measureSpec, int defaultSize) {
        int result = 0;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            result = defaultSize + getPaddingTop() + getPaddingBottom();
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        result = Math.max(result, getSuggestedMinimumHeight());
        return result;
    }

    @Override
    protected void onDraw(Canvas canvas) {

        // Calculate the radius of the circle
        int width = getWidth() - getPaddingLeft() - getPaddingRight();
        int height = getHeight() - getPaddingTop() - getPaddingBottom();
        int radius = Math.min(width, height) / 2;

        // Calculate the center of a circle
        int cx = getPaddingLeft() + (getWidth() - getPaddingLeft() - getPaddingRight()) / 2;
        int cy = getPaddingTop() + (getHeight() - getPaddingTop() - getPaddingBottom()) / 2;

        drawCircle(canvas, cx, cy, radius);

        if (isShowCross) {
            drawCross(canvas, cx, cy, radius);
        }

		// Scanning
        if (isScanning) {
            if (isShowRaindrop) {
                drawRaindrop(canvas, cx, cy, radius);
            }
            drawSweep(canvas, cx, cy, radius);
            // Calculate the rotation Angle of radar scan
            mDegrees = (mDegrees + (360 / mSpeed / 60)) % 360;

			// Trigger the View to redraw, through continuous drawing to achieve the View scan animation effectinvalidate(); }}/**
     * 画圆
     */
    private void drawCircle(Canvas canvas, int cx, int cy, int radius) {
        // Draw mCircleNum circles of different radius.
        for (int i = 0; i < mCircleNum; i++) { canvas.drawCircle(cx, cy, radius - (radius / mCircleNum * i), mCirclePaint); }}/** ** cross lines */
    private void drawCross(Canvas canvas, int cx, int cy, int radius) {
        / / horizontal line
        canvas.drawLine(cx - radius, cy, cx + radius, cy, mCirclePaint);

        / / vertical line
        canvas.drawLine(cx, cy - radius, cx, cy + radius, mCirclePaint);
    }

    /** * Generates water droplets. Droplet generation is random, and not one droplet is generated for every call. * /
    private void generateRaindrop(int cx, int cy, int radius) {

        // A maximum of mRaindropNum drops can exist simultaneously.
        if (mRaindrops.size() < mRaindropNum) {
            // Random a number up to 20, if the number happens to be 0, generates a drop.
            // Used to control the probability of droplet formation.
            boolean probability = (int) (Math.random() * 20) = =0;
            if (probability) {
                int x = 0;
                int y = 0;
                int xOffset = (int) (Math.random() * (radius - 20));
                int yOffset = (int) (Math.random() * (int) Math.sqrt(1.0 * (radius - 20) * (radius - 20) - xOffset * xOffset));

                if ((int) (Math.random() * 2) = =0) {
                    x = cx - xOffset;
                } else {
                    x = cx + xOffset;
                }

                if ((int) (Math.random() * 2) = =0) {
                    y = cy - yOffset;
                } else {
                    y = cy + yOffset;
                }

                mRaindrops.add(new Raindrop(x, y, 0, mRaindropColor)); }}}/** * drop drop */
    private void removeRaindrop(a) {
        Iterator<Raindrop> iterator = mRaindrops.iterator();

        while (iterator.hasNext()) {
            Raindrop raindrop = iterator.next();
            if (raindrop.radius > 20 || raindrop.alpha < 0) { iterator.remove(); }}}/** * Draw raindrops (random points that appear during the scan). * /
    private void drawRaindrop(Canvas canvas, int cx, int cy, int radius) {
        generateRaindrop(cx, cy, radius);
        for (Raindrop raindrop : mRaindrops) {
            mRaindropPaint.setColor(raindrop.changeAlpha());
            canvas.drawCircle(raindrop.x, raindrop.y, raindrop.radius, mRaindropPaint);
            // Drop diffusion and transparency gradient effect
            raindrop.radius += 1.0 f * 20 / 60 / mFlicker;
            raindrop.alpha -= 1.0 f * 255 / 60 / mFlicker;
        }
        removeRaindrop();
    }

    /** * draw the scan effect */
    private void drawSweep(Canvas canvas, int cx, int cy, int radius) {
        // Fan-shaped transparent gradient effect
        SweepGradient sweepGradient = new SweepGradient(cx, cy,
                new int[]{Color.TRANSPARENT, changeAlpha(mSweepColor, 0), changeAlpha(mSweepColor, 168),
                        changeAlpha(mSweepColor, 255), changeAlpha(mSweepColor, 255)},new float[] {0.0 f.0.6 f.0.99 f.0.998 f.1f});
        mSweepPaint.setShader(sweepGradient);
        // Rotate the canvas first, then draw the color rendering of the scan, to achieve the rotation effect of the scan.
        canvas.rotate(-90 + mDegrees, cx, cy);
        canvas.drawCircle(cx, cy, radius, mSweepPaint);
    }

    /**
     * 开始扫描
     */
    public void start(a) {
        if(! isScanning) { isScanning =true; invalidate(); }}/** * stop scanning */
    public void stop(a) {
        if (isScanning) {
            isScanning = false;
            mRaindrops.clear();
            mDegrees = 0.0 f; }}/** ** Drop data class */
    private static class Raindrop {

        int x;
        int y;
        float radius;
        int color;
        float alpha = 255;

        public Raindrop(int x, int y, float radius, int color) {
            this.x = x;
            this.y = y;
            this.radius = radius;
            this.color = color;
        }

        /** * Gets the color value ** after changing transparency@return* /
        public int changeAlpha(a) {
            return RadarView.changeAlpha(color, (int) alpha); }}/** * dp to px */
    private static int dp2px(Context context, float dpVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dpVal, context.getResources().getDisplayMetrics());
    }

    /** * Change the transparency of the color **@param color
     * @param alpha
     * @return* /
    private static int changeAlpha(int color, int alpha) {
        int red = Color.red(color);
        int green = Color.green(color);
        int blue = Color.blue(color);
        returnColor.argb(alpha, red, green, blue); }}Copy the code

Custom attributes: Create attrs.xml under res/values

<?xml version="1.0" encoding="utf-8"? >
<resources>

    <declare-styleable name="RadarView">
        <! -- Color of circles and crosses -->
        <attr name="circleColor" format="color" />
        <! -- Number of circles -->
        <attr name="circleNum" format="integer" />
        <! RadarView will add gradient transparency to the color -->
        <attr name="sweepColor" format="color" />
        <! -- Color of water drop -->
        <attr name="raindropColor" format="color" />
        <! The number of droplets is the maximum number of droplets that can occur at the same time. Because the droplets are randomly generated, the number is uncertain -->
        <attr name="raindropNum" format="integer" />
        <! -- Whether to display cross lines -->
        <attr name="showCross" format="boolean" />
        <! -- Whether to display water drops -->
        <attr name="showRaindrop" format="boolean" />
        <! -- Speed of scanning, which means one revolution in several seconds -->
        <attr name="speed" format="float" />
        <! -- The speed at which water droplets appear and disappear
        <attr name="flicker" format="float" />
    </declare-styleable>
</resources>
Copy the code

Effect:

Download the source code