Android widget - 支持显示距离的雷达扫描控件
Widget部件介绍

Android  widget  -  实现雷达扫描特效,以百分比的方式显示探测到的物体距离,距离越近,点的位置越靠近中心。

软件架构

Android 雷达扫描特效,  两种实现,纯canvas绘制,   或者绘制点图片(以便硬件加速用, 默认)

使用说明及Demo

1.  XML里添加布局

<org.tcshare.widgets.RadarView
        android:id="@+id/radarView"
        android:layout_width="300dp"
        android:layout_height="300dp"/>

2. 代码里启动扫描,并添加雷达反射点的距离,用百分比表示距离。

radarView.setSearching(true);  // 开启扫描模式,扫描线会转动
radarView.addPoint(RandomUtils.getRandomFloat(1)); // 传入一个百分比,用来指示点距离中心点的百分比

完整示例

点击开始按钮,循环添加搜索到的点,这里随机数值演示。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_tcradar_main);

    radarView = findViewById(R.id.radarView);
    radarView.setSearching(true);
    addPoint();


}
private void addPoint(){
    radarView.postDelayed(new Runnable() {
        @Override
        public void run() {
            radarView.addPoint(RandomUtils.getRandomFloat(1));
            addPoint();
        }
    }, 50);

}

RadarView.java 源代码:

package org.tcshare.widgets;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.MaskFilter;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Shader;
import android.graphics.SweepGradient;
import android.util.AttributeSet;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import org.tcshare.androidutils.R;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class RadarView extends View {
    private Bitmap pointImg;
    // 参数调整>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    private final boolean usePointBitmap = true;
    private final float pointRadius = 10f;  // 扫描点的半径
    private final int MAX_POINT_SHOW = 100;// 最多显示100个点
    private final MaskFilter outLightFilter = new BlurMaskFilter(pointRadius, BlurMaskFilter.Blur.SOLID); // 外发光
    private final int showPointAngel = 300; // 最大显示范围, 最大360度
    private final int bgColor = -0x472304; // 背景颜色
    private final int bgRadar = -0xcd874c; // 雷达区域背景色
    private final int colorCircle = -0xce360e; // 内部园环及分割线的颜色
    private final int radarColor = Color.parseColor("#AA0000FF"); // 扫描扇形的颜色
    private final int mNumCicle = 4; // 多少个园环
    private final int mNumLines = 4; // 对角线的个数
    // 参数调整<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<


    private SweepGradient sweepGradient = null;
    private boolean isSearching = false;
    private final Paint mPaint = new Paint();
    private int mCurrentAngel = 0;
    private final List<MyPoint> mPointArray = new ArrayList<MyPoint>();
    private int mWidth = 0;
    private int mHeight = 0;

    private int mOutWidth = 0; // 外圆宽度(w/4/5*2=w/10)
    private int mCx = 0; // x、y轴中心点
    private int mCy = 0;
    private int mOutsideRadius = 0;// 外、内圆半径
    private int mInsideRadius = 0;

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

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

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

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

    private void init(Context context) {
        if (usePointBitmap) {
            pointImg = BitmapFactory.decodeResource(getResources(), R.mipmap.point); // xxhdpi
            // 默认硬件加速
            setLayerType(View.LAYER_TYPE_HARDWARE, (Paint) null);
        } else {
            setLayerType(View.LAYER_TYPE_SOFTWARE, (Paint) null);
        }

    }

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 获取控件区域宽高
        if (mWidth == 0 || mHeight == 0) {
            int minimumWidth = getSuggestedMinimumWidth();
            int minimumHeight = getSuggestedMinimumHeight();
            mWidth = resolveMeasured(widthMeasureSpec, minimumWidth);
            mHeight = resolveMeasured(heightMeasureSpec, minimumHeight);
            // 获取x/y轴中心点
            mCx = mWidth / 2;
            mCy = mHeight / 2;
            // 获取外圆宽度
            mOutWidth = mWidth / 10;
            // 计算内、外半径
            mOutsideRadius = mWidth / 2;// 外圆的半径
            mInsideRadius = (mWidth - mOutWidth) / mNumCicle / 2;// 内圆的半径,除最外层,其它圆的半径=层数*insideRadius
            sweepGradient = new SweepGradient((float) mCx, (float) mCy, 0, radarColor);
        }

    }

    @Override
    protected void onDraw(@NonNull Canvas canvas) {
        super.onDraw(canvas);
        // 1. 绘制圆形背景
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(bgColor);
        mPaint.setShader(null);
        mPaint.setAlpha(255);
        canvas.drawCircle((float) mCx, (float) mCy, (float) mOutsideRadius, mPaint);
        // 2. 绘制雷达区域背景
        mPaint.setColor(bgRadar);
        canvas.drawCircle((float) mCx, (float) mCy, (float) mInsideRadius * (float) mNumCicle, mPaint);
        // 3. 绘制园环
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(colorCircle);

        for (int num = mNumCicle; num >= 1; --num) {
            canvas.drawCircle((float) mCx, (float) mCy, (float) mInsideRadius * (float) num, mPaint);
        }
        // 4. 绘制对角线
        double angle = (double) (180 / mNumLines);
        int lineRadius = mInsideRadius * mNumCicle;
        for (int num = 0; num < mNumLines; ++num) {
            double radianS = Math.toRadians(num * angle); // 将角度转换为弧度
            double radianE = Math.toRadians(num * angle + 180);
            canvas.drawLine(
                    (float) (mCx + lineRadius * Math.cos(radianS)),
                    (float) (mCy + lineRadius * Math.sin(radianS)),
                    (float) (mCx + lineRadius * Math.cos(radianE)),
                    (float) (mCy + lineRadius * Math.sin(radianE)),
                    mPaint
            );
        }
        // 5.绘制扫描扇形图
        if (isSearching) {// 判断是否处于扫描
            canvas.save();
            canvas.rotate(
                    mCurrentAngel,
                    mCx,
                    mCy
            );
            mPaint.setStyle(Paint.Style.FILL);
            mPaint.setShader(sweepGradient);
            mCurrentAngel += 3;
            canvas.drawCircle(
                    mCx,
                    mCy,
                    mInsideRadius * mNumCicle,
                    mPaint
            );
            canvas.restore();


            // 6.开始绘制动态点
            mPaint.setColor(Color.WHITE);
            mPaint.setStyle(Paint.Style.FILL);
            mPaint.setShader(null);
            if (!usePointBitmap) mPaint.setMaskFilter(outLightFilter); // 绘制圆的外发光, 需要关闭硬件加速

            for (int i = mPointArray.size() - 1; i >= 0; i--) { // 倒序,先画最新的,然后更改不透明度
                MyPoint p = mPointArray.get(i);
                if (mCurrentAngel >= showPointAngel && mCurrentAngel - showPointAngel > p.angel) {
                    mPointArray.remove(i); //倒序移除
                } else {
                    int sAngel = (mCurrentAngel - p.angel - 1) % showPointAngel; // 这里减一,是因为浮点型导致最后一个又正常显示了
                    if (sAngel == 0) {
                        mPaint.setAlpha(255);
                    } else {
                        mPaint.setAlpha((int) (255 - (float) sAngel / showPointAngel * 255));
                    }

                    if (usePointBitmap) {
                        canvas.drawBitmap(
                                pointImg,
                                p.pos.x - pointImg.getWidth() / 2f,
                                p.pos.y - pointImg.getHeight() / 2f,
                                mPaint
                        );
                    } else {
                        canvas.drawCircle(
                                p.pos.x,
                                p.pos.y,
                                pointRadius,
                                mPaint
                        );
                    }
                }
            }
            this.invalidate();
        }

    }

    public final void setSearching(boolean status) {
        isSearching = status;
        if (!isSearching) {
            mPointArray.clear();
        }

        invalidate();
    }

    public final boolean isSearching() {
        return isSearching;
    }

    public final void addPoint(float percent) {
        if (!isSearching) return;
        double radian = Math.toRadians(mCurrentAngel);
        float lineRadius = mInsideRadius * mNumCicle * percent;
        float pX = (float) (mCx + lineRadius * Math.cos(radian));
        float pY = (float) (mCy + lineRadius * Math.sin(radian));
        MyPoint point = new MyPoint(percent, new PointF(pX, pY), mCurrentAngel);
        mPointArray.add(point);
        if (mPointArray.size() > MAX_POINT_SHOW) { // 超过允许的点个数,移除最开始的一个
            mPointArray.remove(0); //TODO 测试是否存在重新分配数组的问题
        }
        this.invalidate();
    }

    private int resolveMeasured(int measureSpec, int desired) {
        int specSize = MeasureSpec.getSize(measureSpec);
        int result;
        switch (MeasureSpec.getMode(measureSpec)) {
            case MeasureSpec.UNSPECIFIED:
                result = desired;
                break;
            case MeasureSpec.AT_MOST:
                result = Math.max(specSize, desired);
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
            default:
                result = specSize;
                break;
        }

        return result;
    }


    public static final class MyPoint {
        float percent;
        PointF pos;
        int angel;

        public String toString() {
            return "MyPoint(percent=" + percent + ", pos=" + pos + ", mOffsetArgs=" + angel + ",)";
        }

        public MyPoint(float percent, @NonNull PointF pos, int angel) {
            super();
            this.percent = percent;
            this.pos = pos;
            this.angel = angel;
        }
    }
}

widget 链接集合

 

 

 

评论列表: