Android 雷达扫描效果实现(自定义控件)

雷达的样式是,后面一个圆形表盘,前面有一个光线绕中心进行旋转扫描;
总体实现思路是:

  1. 定义自定义控件RadarView继承自View;
  2. 在自定义控件中绘制出后面的圆形表盘;
  3. 将一张圆形光线的Bitmap绘制在圆形表盘前面,绕表盘中心旋转;
    4.在表盘范围内随机生成坐标点,将亮点的Bitmap绘制上;

自定义控件RadarView的代码如下:

package com.changhong.settings.diagnosis;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

import com.changhong.settings.R;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;


/**
 * @author auth
 * Created by auth on 2018/1/17 0017.
 * 思路是:
 * 1. 绘制出来雷达的表盘,五个同心圆和四条对角线
 * 2. 一个雷达圆形图片radar_scan_img.png
 *    图片中有一道高亮竖线
 *    让这个图片旋转起来,高亮的竖线会出现扫描的效果
 * 3. 可以添加高亮的点,制造效果
 */


public class RadarView extends View {

    private Context mcontext;
    /**
     * mPaint:画笔
     * radarBitmap:执行雷达扫描的图片;
     * normalPointBitmap:低亮扫描点;
     * lightPointBitmap:高亮扫描点;
     * mPointCount:亮点的总个数;
     * mPointArray:存储已经添加的亮点的位置;
     * scanAngle:每次扫描动作的偏转角度;
     */
    private Paint mPaint;
    private Bitmap radarBitmap;
    private Bitmap normalPointBitmap;
    private Bitmap lightPointBitmap;
    private int mPointCount = 0;
    private List<String> mPointArray = new ArrayList<String>();
    private int scanAngle = 0;

    private int mWidth,mHeight;
    private Random random = new Random();
    /**
     * mOutWidth:外圆高度;
     * mCenterX:中心点X轴位置;
     * mCenterY:中心点Y轴位置;
     * mInsideRadius:内圆半径;
     * mOutsideRadius:外圆半径;
     */
    private int mOutWidth;
    private int mCenterX,mCenterY;
    private int mInsideRadius,mOutsideRadius;


    private boolean isSearching = false;

    /**
     * 构造函数
     * @param context
     */
    public RadarView(Context context) {
        super(context);
        mcontext = context;
        initView();

    }
    public RadarView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mcontext = context;
        initView();
    }
    public RadarView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mcontext = context;
        initView();
    }

    /**
     * 初始化 画笔和图片资源
     */
    private void initView(){
        mPaint = new Paint();
        normalPointBitmap = Bitmap.createBitmap(BitmapFactory.decodeResource
                (mcontext.getResources(), R.drawable.radar_default_point_ico));
        lightPointBitmap = Bitmap.createBitmap(BitmapFactory.decodeResource
                (mcontext.getResources(),R.drawable.radar_light_point_ico));



    }

    /**
     * 测量视图及内容,确定其在父控件中的长度和宽度;
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if(mWidth == 0 || mHeight ==0){
            final int MinimumWidth = getSuggestedMinimumWidth();
            final int MinimumHeight = getSuggestedMinimumHeight();
            mWidth = resolveMeasure(widthMeasureSpec,MinimumWidth);
            mHeight = resolveMeasure(heightMeasureSpec,MinimumHeight);

            radarBitmap = Bitmap.createScaledBitmap(BitmapFactory.decodeResource
                    (mcontext.getResources(),R.drawable.radar_scan_img),
                    mWidth-mOutWidth,mWidth-mOutWidth,false);

            //获取X轴Y轴中心点
            mCenterX = mWidth /2;
            mCenterY = mHeight /2;

            //获取外圆环的厚度的两倍(即圆环左厚度右厚度的和)
            mOutWidth = mWidth /10;

            //外圆半径
            mOutsideRadius = mWidth /2;
            // 内圆的半径,除最外层,其它圆的半径=层数*insideRadius
            mInsideRadius = (mWidth - mOutWidth)/4/2;
        }



    }

    /**
     * 视图的绘制
     * 从外部向内部绘制
     * Android在用画笔的时候有三种Style,分别是
     * Paint.Style.STROKE 只绘制图形轮廓(描边)
     * Paint.Style.FILL 只绘制图形内容
     * Paint.Style.FILL_AND_STROKE 既绘制轮廓也绘制内容
     * @param canvas
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //设置画笔:抗锯齿,填充样式,画笔颜色;
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(0xffB8DCFC);
        //第一步:绘制外圆
        canvas.drawCircle(mCenterX,mCenterY,mOutsideRadius,mPaint);
        //第二步:绘制第一个内圆(最外层内圆)
        mPaint.setColor(0xff3278B4);
        canvas.drawCircle(mCenterX,mCenterY,mInsideRadius*4,mPaint);

        //第三部:绘制第二个内圆(倒数第二外层内圆)
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(0xff31C9F2);
        canvas.drawCircle(mCenterX,mCenterY,mInsideRadius*3,mPaint);
        //第四步:绘制第三个内圆(倒数第三外层内圆)
        canvas.drawCircle(mCenterX,mCenterY,mInsideRadius*2,mPaint);
        //第五步:绘制第四个内圆(最里面的内圆)
        canvas.drawCircle(mCenterX,mCenterY,mInsideRadius*1,mPaint);

        //第六步:绘制0°和180°对角线
        canvas.drawLine(mOutWidth/2,mCenterY,mWidth-mOutWidth/2,mCenterY,mPaint);
        //第七步:绘制90°和270°对角线
        canvas.drawLine(mWidth/2,mOutWidth/2,mWidth/2,mHeight-mOutWidth/2,mPaint);
        // 根据角度绘制对角线
        int startX, startY, endX, endY;
        double radian;
        // 第八步:绘制45°~225°对角线
        // 计算开始位置x/y坐标点
        // 将角度转换为弧度
        radian = Math.toRadians((double) 45);
        // 通过圆心坐标、半径和当前角度计算当前圆周的某点横坐标
        // 通过圆心坐标、半径和当前角度计算当前圆周的某点纵坐标
        startX = (int) (mCenterX + mInsideRadius * 4 * Math.cos(radian));
        startY = (int) (mCenterY + mInsideRadius * 4 * Math.sin(radian));
        // 计算结束位置x/y坐标点
        radian = Math.toRadians((double) 45 + 180);
        endX = (int) (mCenterX + mInsideRadius * 4 * Math.cos(radian));
        endY = (int) (mCenterY + mInsideRadius * 4 * Math.sin(radian));
        canvas.drawLine(startX, startY, endX, endY, mPaint);
        // 第九步:绘制135°~315°对角线
        // 计算开始位置x/y坐标点
        radian = Math.toRadians((double) 135);
        startX = (int) (mCenterX + mInsideRadius * 4 * Math.cos(radian));
        startY = (int) (mCenterY + mInsideRadius * 4 * Math.sin(radian));
        // 计算结束位置x/y坐标点
        radian = Math.toRadians((double) 135 + 180);
        endX = (int) (mCenterX + mInsideRadius * 4 * Math.cos(radian));
        endY = (int) (mCenterY + mInsideRadius * 4 * Math.sin(radian));
        canvas.drawLine(startX, startY, endX, endY, mPaint);

        //第十步:绘制扫描的扇形
        // 用来保存Canvas的状态.save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作.
        canvas.save();
        if(isSearching){
            // 绘制旋转角度,参数一:角度;参数二:x中心;参数三:y中心.
            canvas.rotate(scanAngle,mCenterX,mCenterY);
            canvas.drawBitmap(radarBitmap,mCenterX-radarBitmap.getWidth()/2,
                    mCenterY-radarBitmap.getHeight()/2,null);
            //扫描动作每次角度加3
            scanAngle += 3;
        }else{
            canvas.drawBitmap(radarBitmap,mCenterX-radarBitmap.getWidth()/2,
                    mCenterY-radarBitmap.getHeight()/2,null);
        }

        //第十一步:绘制扫描出来的动态点
        //重置canvas
        canvas.restore();
        if(mPointCount>0){
            //当前亮点个数比已经存储的亮点个数大时;
            //证明新增加了一个亮点,那么就随机生成一个亮点的坐标;
            //并将新增的坐标位置保存到List中;
            if(mPointCount > mPointArray.size()){
                int x = mOutWidth/2+random.nextInt(mInsideRadius*8);
                int y = mOutWidth/2+random.nextInt(mInsideRadius*8);
                mPointArray.add(x +"/"+ y);
            }

            //绘制已经存储了坐标的亮点
            for(int i =0;i<mPointArray.size();i++){
                String [] point = mPointArray.get(i).split("/");

                //最后添加的那个亮点要高亮
                //else,其他的亮点都低亮
                if(i == mPointArray.size()-1){
                    canvas.drawBitmap(lightPointBitmap,
                            Integer.getInteger(point[0]),Integer.getInteger(point[1]),null);
                }else{
                    canvas.drawBitmap(normalPointBitmap,
                            Integer.getInteger(point[0]),Integer.getInteger(point[1]),null);
                }

            }

        }

        if(isSearching){
            this.invalidate();
        }

    }

    /**
     *开始扫描
     */
    public void startSearching(){
        this.isSearching = true;
        this.invalidate();
    }

    /**
     * 停止扫描
     */
    public void stopSearching(){
        this.isSearching = false;
        this.invalidate();
    }
    /**
     * 添加亮点个数
     */
    public void addPoint(){
        this.mPointCount ++;
        this.invalidate();
    }

    /**
     * 解析数据,获取控件的宽和高
     * @param measureSpec
     * @param minimumWidth
     * @return
     */
    private int resolveMeasure(int measureSpec,int minimumWidth){

        int result =0;
        int specSize = View.MeasureSpec.getSize(measureSpec);
        switch (View.MeasureSpec.getMode(measureSpec)){
            case View.MeasureSpec.AT_MOST:
                result = Math.min(specSize,minimumWidth);
                 break;
            case View.MeasureSpec.UNSPECIFIED:
                result = minimumWidth;
                break;

            case View.MeasureSpec.EXACTLY:
            default:
                result = specSize;
                break;
        }

        return result;
    }


}

在Activity中只需要获取到RadarView后,调用其startSearching()函数即可开始扫描

public class NetDiagnosisActivity extends Activity implements View.OnClickListener{

    private RadarView radarView;
    private ImageButton start;


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


    }

    private void initView(){
        radarView = (RadarView) findViewById(R.id.Radar);
        start = (ImageButton) findViewById(R.id.start_diagnosis);
        start.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if(v.getId()==R.id.start_diagnosis){
            radarView.startSearching();
        }
    }

    @Override
    protected void onDestroy() {
        radarView.stopSearching();
    }
}

下面来看布局文件
布局比较简单,只要将RadarView放在布局中,定义好Width和Height就好;
布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">

    <LinearLayout
        android:layout_width="500dp"
        android:layout_height="match_parent"
        android:layout_marginLeft="100dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:gravity="center_horizontal"
            android:orientation="vertical">

            <com.changhong.settings.diagnosis.RadarView
                android:id="@+id/Radar"
                android:layout_width="300dp"
                android:layout_height="300dp" />

            <ImageButton
                android:id="@+id/start_diagnosis"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="50dp"
                android:src="@drawable/button_background" />
            
        </LinearLayout>

    </LinearLayout>

</LinearLayout>


附图
扫描光线图:


radar_scan_img.png

随机亮点(暗)图:


radar_default_point_ico.png

随机亮点(亮)图:


radar_light_point_ico.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269