千古八方的博客

  • 程序开发
    • Android
    • Spring Boot
  • 数据私有化
    • NAS
    • 私有云
    • 内网穿透
    • 服务器
  • 留言
  • 搞事
进学
为学无间断,如流水行云,日进而不已也
  1. 首页
  2. Android
  3. 正文

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

2022年6月21日 182点热度 0人点赞 0条评论
目录
Widget部件介绍
使用说明及Demo
完整示例
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 链接集合

  • Android widget - 完全可配的炫酷仪表盘
  • Android widget - 支持实际距离的雷达扫描控件
  • Android widget - 直播右下角点击刷礼物特效
标签: Android Widgets
最后更新:2022年6月22日

千古八方

物格而后知至,知至而后意诚,意诚而后心正

点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

千古八方

物格而后知至,知至而后意诚,意诚而后心正

最新 热点 随机
最新 热点 随机
火狐下远离内容农场的方法! 中国城市生活成本查询 Debian11 系统备份与灾备盘制作 Debian logwatch 自定义配置文件,根据规则忽略掉某些日志内容 Ampache 5.0 报错 The root Ampache folder has changed to ./public socat 正反向传输文件
中国城市生活成本查询 自建NAS上,使用ssmtp向外部邮箱发送邮件 mysql 定时每天备份数据库 命令行手动升级 Nextcloud NAS是什么?如何DIY一台NAS 存储? 使用AutoSSH 建立TCP/IP隧道,实现内网穿透
分类
  • Android
  • Linux
  • NAS
  • Spring Boot
  • Windows
  • 内网穿透
  • 回忆
  • 基于NEAT的瞎几把寻思算法笔记
  • 散篇
  • 普通人的致富之路在哪里
  • 服务器
  • 未分类
  • 私有云
标签聚合
Android Widgets ubuntu Nextcloud 服务器 Spring Boot 散篇 Android Utils 内网穿透 linux Android系统编译 安全维护 TrueNAS NAS 群晖NAS Windows 私有云 我开源的APP NPS
工具/友链

资源导航 – NAS、私有云存储
在线工具 – MD5、SHA、BASE64、URL编解码
在线工具 – 贷款计算器
在线工具 – IPv4/IPv6地址查询

千古八方 在 B站

千古八方 在 知乎

我的微信小程序:
微信小程序-城市生活成本查询
城市生活成本查询
库房物资流水记账簿
最近评论
Avatar photo
千古八方 发布于 1 个月前(12月28日) 重置命令是Nextcloud提供的,和哪个平台没关系。 如果报错,一般是路径或权限的问题导致的。 ...
Avatar photo
aron 发布于 1 个月前(12月24日) 博主你好 我的nextcloud管理员密码过期了 用了occ命令重置但是报错,能请教下是什么原因...
Avatar photo
千古八方 发布于 2 个月前(12月15日) 贝塞尔这个类在这里 https://gitee.com/aityu/AndroidUtils/blo...
Avatar photo
disco 发布于 2 个月前(12月05日) 有demo链接吗?贝塞尔曲线这个关键类没有写出来~
Avatar photo
HJM 发布于 3 个月前(10月20日) 必须点赞!很有效!

COPYRIGHT © 2022 千古八方的博客. ALL RIGHTS RESERVED.

京ICP备14020471号