自定義View 實現多塔機監控類似星軌雷達掃描

feichong888發表於2019-01-04
效果

最近公司業務要實現塔機監控功能 自己寫了一個自定義view
它會根據你設定 的資料大小自動畫圓 資料list. size是1畫一個圓 這裡我傳了4組資料
裡面用到了弧度計算 和角度計算都在程式碼裡
它的基本原理是根據ValueAnimator 動畫動態繪製圖形 廢話不多說 直接上程式碼 程式碼不多直接複製下面的view就可以了

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
import android.view.animation.LinearInterpolator;

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

/**
 * @since 1.0
 * <p>自定義View
 * chendu  2018/6/1
 */
public class DemoTowerView extends View {

    /**
     * 邊框的寬度 字型大小和顏色
     */
    private int mBorderWidth = 2;
    private int mTextColor = Color.GRAY;
    private int mTextSize = 12;

    /**
     * 畫筆
     */
    private Paint mPaint;

    /**
     * 區域的寬度和高度
     */
    private int mWidth;
    private int mHeight;

    private float[] currentAngle = new float[6];//線動態移動角度 最多同時展示6個塔機
    private int[] greenx = new int[6];//綠點動態距離x
    private int[] greeny = new int[6];//綠點動態距離y

    //下面這三個引數是為了第二次傳參過來時 圖形能接著上一次結束的狀態在移動 而不是從0開始
    private float[] currentAnglelast = new float[6];//記錄線動態移動角度結束後最後的角度
    private int[] greenxlast = new int[6];//記錄綠點移動結束後最後的距離x
    private int[] greenylast = new int[6];//記錄綠點移動結束後最後的距離y
    private int[] mwidthlast = new int[6];//記錄上一個塔吊移動的距離
    private int[] prevmovewidth = new int[6];//記錄上一個塔吊移動的距離

    List<Towerbean> list = new ArrayList<>();

    private int cx, cy, leve;
    private int width = this.getResources().getDisplayMetrics().widthPixels / 6;   //getMeasuredWidth()/6;根據控制元件大小取寬 或者直接獲取螢幕大小

    public DemoTowerView(Context context) {
        this(context, null);
    }

    public DemoTowerView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public DemoTowerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        mPaint = new Paint();
        
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        if (widthMode == MeasureSpec.EXACTLY) {
            mWidth = widthSize;
        } else {
            int desire = getPaddingLeft() + getPaddingRight() + (int) TypedValue.applyDimension(
                    TypedValue.COMPLEX_UNIT_DIP, 200, getResources().getDisplayMetrics());
            mWidth = Math.min(desire, widthSize);
        }
        if (heightMode == MeasureSpec.EXACTLY) {
            mHeight = heightSize;
        } else {
            int desire = getPaddingTop() + getPaddingBottom() + (int) TypedValue.applyDimension(
                    TypedValue.COMPLEX_UNIT_DIP, 200, getResources().getDisplayMetrics());
            mHeight = Math.min(desire, heightSize);
        }
        mWidth = Math.min(mWidth, mHeight);//取最小值  防止繪製內容出錯   以最小的邊來為基準進行相關的繪製
        setMeasuredDimension(mWidth, mWidth);
    }


    @Override
    protected void onDraw(final Canvas canvas) {

        /**
         * 監控的塔機圓心的xy和圓環的寬度
         */
        cx = getPaddingLeft() + (getMeasuredWidth() - getPaddingLeft() - getPaddingRight()) / 2;
        cy = getPaddingTop() + (getMeasuredHeight() - getPaddingTop() - getPaddingBottom()) / 2;
        width = Math.min(getWidth() / 6, getHeight() / 6);//半徑
        //畫十字座標
        mPaint.setColor(Color.LTGRAY);
        canvas.drawLine(cx, 0, cx, mHeight, mPaint);
        canvas.drawLine(0, cy, mWidth, cy, mPaint);
        mPaint.setTextAlign(Paint.Align.CENTER);
        canvas.drawText("0°", cx, 15, mPaint);
        canvas.drawText("90°", mWidth - 15, cy, mPaint);
        canvas.drawText("180°", cx, mWidth - 15, mPaint);
        canvas.drawText("270°", 15, cy, mPaint);

        mPaint.setAntiAlias(true);//去除邊緣鋸齒,優化繪製效果

        if (list != null && list.size() > 0) {

            for (int i = 0; i < list.size(); i++) {

                leve = leve != 0 ? list.get(0).twlong : width;//獲取檢測物件臂長 按比例設定其他塔機臂長
                int twocx = cx + (int) (width * list.get(i).distance / leve * Math.sin(Math.PI * list.get(i).angle / 180));//圓心x
                int twocy = cy - (int) (width * list.get(i).distance / leve * Math.cos(Math.PI * list.get(i).angle / 180));//圓心y

                if (i == 0) {
                    //畫檢測塔機內外圓
                    mPaint.setColor(Color.BLACK);
                    mPaint.setStyle(Paint.Style.STROKE); //設定空心
                    mPaint.setStrokeWidth(mBorderWidth); //設定圓環的寬度
                    canvas.drawCircle(cx, cy, width, mPaint);//外圓  黑色
                    //內圓 綠色
                    mPaint.setStyle(Paint.Style.FILL); //設定實心
                    mPaint.setColor(Color.parseColor("#B2C8F9D2"));
                    canvas.drawCircle(cx, cy, width - mBorderWidth, mPaint);
                } else {

                    //基於圓心*度的外圍塔機外圓
                    mPaint.setColor(Color.BLACK);
                    mPaint.setStyle(Paint.Style.STROKE); //設定空心
                    mPaint.setStrokeWidth(mBorderWidth); //設定圓環的寬度
                    canvas.drawCircle(twocx, twocy, width * list.get(i).twlong / leve, mPaint);//外圓  黑色
                    mPaint.setStyle(Paint.Style.FILL); //設定實心
                }


                //畫塔臂線2
                mPaint.setColor(Color.BLACK);
                canvas.save();//儲存後面的狀態
                canvas.rotate(currentAngle[i], twocx, twocy);
                canvas.drawRect(twocx - TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics()),
                        twocy - width * list.get(i).twlong / leve + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics()),
                        twocx + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics()),
                        twocy + TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics()), mPaint);
                canvas.restore();//撤銷儲存的狀態

                //圓心,紅色2
                mPaint.setColor(Color.RED);
                canvas.drawCircle(twocx, twocy, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getResources().getDisplayMetrics()), mPaint);

                //塔吊綠點
                mPaint.setColor(Color.GREEN);
                canvas.drawCircle(twocx + greenx[i], twocy - greeny[i], TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 3, getResources().getDisplayMetrics()), mPaint);//圓,綠色色


                // 塔機字型
                mPaint.setColor(mTextColor);
                mPaint.setTextSize(mTextSize);
                mPaint.setTextAlign(Paint.Align.CENTER);
                canvas.drawText(list.get(i).name, twocx, twocy - (width * list.get(i).twlong / (2 * leve)), mPaint);


            }

        }


    }


    /**
     * 設定資料
     *
     * @param list
     */
    public void setDate(List<Towerbean> list) {
        this.list = list;
        if (list != null && list.size() > 0)
            for (int i = 0; i < list.size(); i++) {
                currentAnglelast[i] = currentAngle[i];
                greenxlast[i] = greenx[i];
                greenylast[i] = greeny[i];
                mwidthlast[i] = prevmovewidth[i];
                leve = list.get(0).twlong;
                int mmovewidth = width * list.get(i).movewidth / leve;//實際值轉換為等比例寬度 第一個塔吊的臂長為整個螢幕的1/6
                startCirMotion(list.get(i).twturnAngle, Math.abs(mmovewidth - mwidthlast[i]), i);

            }


    }

    /**
     * 設定塔機字型顏色 要在資料設定之前
     *
     * @param color
     */
    public void setmTextColor(int color) {
        mTextColor = color;
    }

    /**
     * 設定塔機字型大小
     *
     * @param size
     */
    public void setmTextSize(int size) {
        mTextSize = size;
    }

    /**
     * 旋轉動畫
     */
    private void startCirMotion(final float Angle, final int mwidth, final int i) {
        ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
        animator.setDuration((long) 3000);//動畫時間3S
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Float t = (Float) animation.getAnimatedValue();
                currentAngle[i] = t * Angle + currentAnglelast[i];
                float r = t * mwidth + mwidthlast[i];
                greenx[i] = (int) (r * Math.sin(Math.PI * currentAngle[i] / 180));
                greeny[i] = (int) (r * Math.cos(Math.PI * currentAngle[i] / 180));
//                Log.e("CD", "debug:(x,y) = " + greenx[i] + "," + greeny[i]);
                invalidate();
                prevmovewidth[i] = (int) (t * mwidth);
            }
        });
        animator.setInterpolator(new LinearInterpolator());// 勻速旋轉
        animator.start();
    }


}

複製程式碼

資料物件類如下

public class Towerbean {

    float angle;//相對檢測物件塔機角度
    int twlong;//臂長
    float twturnAngle;//轉動角度
    int movewidth;//塔機移動距離
    int distance;//兩個塔機相對距離
    String name;//塔機名字

}
複製程式碼

在佈局中引用

<?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="vertical">

    <tower.DemoTowerView
        android:id="@+id/view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="10dp"
        android:text="塔吊動畫"/>

</LinearLayout>
複製程式碼

Activity中使用

view = findViewById(R.id.view);

 List<Towerbean> list = new ArrayList<>();
//自己造的資料物件 你要幾個圓就造幾個這樣的資料 
        Towerbean bean = new Towerbean();
        bean.angle = 0;
        bean.twlong = 100;
        bean.twturnAngle = -30;
        bean.movewidth = 80;
        bean.distance = 0;
        bean.name = "塔機卡薩粉紅色發揮1";
        list.add(bean);
        
        view.setDate(list);
複製程式碼

最後感想
! 這個控制元件修改掉原點可以實現類似雷達掃描 去掉槓桿 多新增幾個點可以實現類似 星圖環繞 自己想到的就這麼多 希望上面的程式碼對你有用!

相關文章