Android雷達圖(蜘蛛網圖)


簡介

最近因為項目的需要,需要實現雷達圖來展示各科目的對題率。

雷達圖的繪制不算復雜,只要按照一定流程來繪制就可以了,其中使用的最多的是path路徑類,使用這個類便於我們繪制出多邊形等效果。

效果圖如下:

使用方式

使用方式很簡單,直接在布局文件里面使用這個控件,記得設置一個合適的大小就可以。

當然也有開放一些public方法,可以進行數據、文本顏色等設置。

    /**
     * 設置數據
     * @param points
     */
    public void setData(ArrayList<LastPoint> points){}

    /**
     * 設置文本
     * @param titles
     */
    public void setTitles(String[] titles){}

    /**
     * 設置圈數
     * @param count
     */
    public void setCount(int count){}

    /**
     * 設置網格線顏色
     * @param color
     */
    public void setLineColor(int color){}

    /**
     * 設置填充區域顏色
     * @param color
     */
    public void setValueColor(int color){}

    /**
     * 設置文本顏色
     * @param color
     */
    public void setTextColor(int color){}

具體實現

一般自定義控件的流程有以下幾個步驟(個人觀點):

* 1、構造函數(初始化)
* 2、onMeasure(測量大小)
* 3、onSizeChanged(確定大小)
* 4、onLayout(子view的位置,如果包含子view的話)
* 5、onDraw(繪制內容)
* 6、暴露給外部的接口

在該控件中,2、4都不用考慮,只要確定了大小(onSizeChanged),就能夠計算出整個布局的中心,雷達圖是以這個中心開始繪制的。

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
        mCenterX = mWidth/2;
        mCenterY = mHeight/2;
        mRadius = (Math.min(mWidth,mHeight)/2 * 0.9f);
        postInvalidate();
    }

繪制蜘蛛網圖

    /**
     * 畫網格
     * @param canvas
     */
    private void drawLine(Canvas canvas){
        Path path = new Path();
        //網格線之間的間距
        float distance =  mRadius / (mCount-1);
        for (int i = 0; i < mCount; i++){//外面的網格圖形
            float currentRadius = i * distance;//當前半徑
            if (i == mCount -1){
                //存儲最后一圈網格的點的坐標
                mLastPoints.add(new LastPoint(currentRadius,0));
                mLastPoints.add(new LastPoint(currentRadius/2,-currentRadius));
                mLastPoints.add(new LastPoint(-currentRadius/2,-currentRadius));
                mLastPoints.add(new LastPoint(-currentRadius,0));
                mLastPoints.add(new LastPoint(-currentRadius/2,currentRadius));
                mLastPoints.add(new LastPoint(currentRadius/2,currentRadius));
            }
            //6個點坐標組成一個網格圖形
            path.lineTo(currentRadius,0);
            //設置上一次操作的坐標點
            path.moveTo(currentRadius,0);
            path.lineTo(currentRadius/2,-currentRadius);
            path.lineTo(-currentRadius/2,-currentRadius);
            path.lineTo(-currentRadius,0);
            path.lineTo(-currentRadius/2,currentRadius);
            path.lineTo(currentRadius/2,currentRadius);
            path.close();
            canvas.drawPath(path,mLinePaint);
        }
    }

繪制從中心到末端的直線

    /**
     * 畫網格對角線
     * @param canvas
     */
    private void drawGridLine(Canvas canvas){
        Path path = new Path();
        for (int i = 0; i < mLastPoints.size(); i++){
            path.reset();
            LastPoint point = mLastPoints.get(i);
            float x = point.x;
            float y = point.y;
            path.lineTo(x, y);
            canvas.drawPath(path, mLinePaint);
        }
    }

繪制末端文本

由於文本與末端有一定的距離,所以需要加上一定的偏移量;當文本在網格左邊顯示的時候,會與網格有重疊,所以需要先計算文本長度,然后再向左邊偏移對應的距離,這樣就可以解決重疊問題。

    /**
     * 畫文本
     * @param canvas
     */
    private void drawText(Canvas canvas){
        for (int i = 0; i < mLastPoints.size(); i++){
            //文本長度
            float dis = mTextPaint.measureText(mTitles[i]);

            LastPoint point = mLastPoints.get(i);
            float x = point.x;
            float y = point.y;
            if (i == 2 || i == 3 || i == 4){
                //左邊繪制文本:文本顯示在坐標左邊
                x = x - dis;
            }
            if (y > 0){
                y+=18;
            }
            canvas.drawText(mTitles[i],x,y,mTextPaint);
        }
    }

繪制填充區域

    /**
     * 畫數據線:填充區域
     * @param canvas
     */
    private void drawDataLine(Canvas canvas){
        if (mDataPoints == null || mDataPoints.size() == 0)
            return;
        Path path = new Path();
        for (int i = 0; i < mDataPoints.size(); i++){
            LastPoint point = mDataPoints.get(i);
            float x = point.x;
            float y = point.y;
            path.lineTo(x, y);
            if (i == 0){//將上一次操作點移到第一個點坐標,保證最后調用close,形成一個封閉的形狀
                path.moveTo(x,y);
            }
            mValuePaint.setAlpha(255);
            //畫小圓點
            canvas.drawCircle(x,y,8,mValuePaint);
        }
        path.close();
        mValuePaint.setAlpha(127);
        canvas.drawPath(path, mValuePaint);

    }

最后,貼上完整的源代碼:

package com.ha.cjy.myproject.view.widget;


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

import java.util.ArrayList;

/**
 * 自定義雷達圖
 * 1、構造函數(初始化)
 * 2、onMeasure(測量大小)
 * 3、onSizeChanged(確定大小)
 * 4、onLayout(子view的位置,如果包含子view的話)
 * 5、onDraw(繪制內容)
 * 6、暴露給外部的接口
 * Created by cjy on 17/8/15.
 */

public class CustomRadarView extends View {
    //寬度
    private int mWidth;
    //高度
    private int mHeight;
    //原點坐標
    private int mCenterX;
    private int mCenterY;
    //網格半徑
    private float mRadius;
    //網格圈數
    private int mCount = 10;
    //Paint
    private Paint mLinePaint;
    private Paint mValuePaint;
    private Paint mTextPaint;
    //顏色值
    private int mLineColor = Color.GRAY;
    private int mValueColor = Color.BLUE;
    private int mTextColor = Color.BLACK;

    //最后一圈網格坐標點集合
    private ArrayList<LastPoint> mLastPoints = new ArrayList<LastPoint>();
    //數據坐標點集合
    private ArrayList<LastPoint> mDataPoints = new ArrayList<LastPoint>();
    //文本集合
    private String[] mTitles = new String[]{"科目A","科目A1","科目1","科目D","科目E","科目F"};

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

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

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

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
        //中心坐標
        mCenterX = mWidth/2;
        mCenterY = mHeight/2;
        mRadius = (Math.min(mWidth,mHeight)/2 * 0.9f);
        postInvalidate();
    }

    private void init(Context context){
        mLinePaint = new Paint();
        mLinePaint.setStyle(Paint.Style.STROKE);
        mLinePaint.setStrokeWidth(2);
        mLinePaint.setColor(mLineColor);

        mValuePaint = new Paint();
        mValuePaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mValuePaint.setStrokeWidth(2);
        mValuePaint.setColor(mValueColor);

        mTextPaint = new Paint();
        mTextPaint.setStyle(Paint.Style.STROKE);
        mTextPaint.setStrokeWidth(2);
        mTextPaint.setColor(mTextColor);
        mTextPaint.setTextSize(28);
        mTextPaint.setAntiAlias(true);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //移動原點坐標
        canvas.translate(mCenterX,mCenterY);
        //畫網格線
        drawLine(canvas);
        //畫網格對角線
        drawGridLine(canvas);
        //畫文本
        drawText(canvas);
        //畫數據線
        drawDataLine(canvas);

    }

    /**
     * 畫網格
     * @param canvas
     */
    private void drawLine(Canvas canvas){
        Path path = new Path();
        //網格線之間的間距
        float distance =  mRadius / (mCount-1);
        for (int i = 0; i < mCount; i++){//外面的網格圖形
            float currentRadius = i * distance;//當前半徑
            if (i == mCount -1){
                //存儲最后一圈網格的點的坐標
                mLastPoints.add(new LastPoint(currentRadius,0));
                mLastPoints.add(new LastPoint(currentRadius/2,-currentRadius));
                mLastPoints.add(new LastPoint(-currentRadius/2,-currentRadius));
                mLastPoints.add(new LastPoint(-currentRadius,0));
                mLastPoints.add(new LastPoint(-currentRadius/2,currentRadius));
                mLastPoints.add(new LastPoint(currentRadius/2,currentRadius));
            }
            //6個點坐標組成一個網格圖形
            path.lineTo(currentRadius,0);
            //設置上一次操作的坐標點
            path.moveTo(currentRadius,0);
            path.lineTo(currentRadius/2,-currentRadius);
            path.lineTo(-currentRadius/2,-currentRadius);
            path.lineTo(-currentRadius,0);
            path.lineTo(-currentRadius/2,currentRadius);
            path.lineTo(currentRadius/2,currentRadius);
            path.close();
            canvas.drawPath(path,mLinePaint);
        }
    }

    /**
     * 畫網格對角線
     * @param canvas
     */
    private void drawGridLine(Canvas canvas){
        Path path = new Path();
        for (int i = 0; i < mLastPoints.size(); i++){
            path.reset();
            LastPoint point = mLastPoints.get(i);
            float x = point.x;
            float y = point.y;
            path.lineTo(x, y);
            canvas.drawPath(path, mLinePaint);
        }
    }

    /**
     * 畫文本
     * @param canvas
     */
    private void drawText(Canvas canvas){
        for (int i = 0; i < mLastPoints.size(); i++){
            //文本長度
            float dis = mTextPaint.measureText(mTitles[i]);

            LastPoint point = mLastPoints.get(i);
            float x = point.x;
            float y = point.y;
            if (i == 2 || i == 3 || i == 4){
                //左邊繪制文本:文本顯示在坐標左邊
                x = x - dis;
            }
            if (y > 0){
                y+=18;
            }
            canvas.drawText(mTitles[i],x,y,mTextPaint);
        }
    }

    /**
     * 畫數據線:填充區域
     * @param canvas
     */
    private void drawDataLine(Canvas canvas){
        if (mDataPoints == null || mDataPoints.size() == 0)
            return;
        Path path = new Path();
        for (int i = 0; i < mDataPoints.size(); i++){
            LastPoint point = mDataPoints.get(i);
            float x = point.x;
            float y = point.y;
            path.lineTo(x, y);
            if (i == 0){//將上一次操作點移到第一個點坐標,保證最后調用close,形成一個封閉的形狀
                path.moveTo(x,y);
            }
            mValuePaint.setAlpha(255);
            //畫小圓點
            canvas.drawCircle(x,y,8,mValuePaint);
        }
        path.close();
        mValuePaint.setAlpha(127);
        canvas.drawPath(path, mValuePaint);

    }

    /**
     * 設置數據
     * @param points
     */
    public void setData(ArrayList<LastPoint> points){
        mDataPoints = points;
        invalidate();
    }

    /**
     * 設置文本
     * @param titles
     */
    public void setTitles(String[] titles){
        mTitles = titles;
        invalidate();
    }

    /**
     * 設置圈數
     * @param count
     */
    public void setCount(int count){
        mCount = count;
        invalidate();
    }

    /**
     * 設置網格線顏色
     * @param color
     */
    public void setLineColor(int color){
        mLineColor = color;
        mLinePaint.setColor(mLineColor);
        invalidate();
    }

    /**
     * 設置填充區域顏色
     * @param color
     */
    public void setValueColor(int color){
        mValueColor = color;
        mValuePaint.setColor(mValueColor);
        invalidate();
    }

    /**
     * 設置文本顏色
     * @param color
     */
    public void setTextColor(int color){
        mTextColor = color;
        mTextPaint.setColor(mTextColor);
        invalidate();
    }


    /**
     * 坐標點
     */
    public static class LastPoint {
        private float x;
        private float y;

        public LastPoint(float x, float y) {
            this.x = x;
            this.y = y;
        }
    }
}

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM