六邊形戰士—雷達圖實現


    相信大家都看了之前的新聞,世乒賽日本直播版,中二爆表,馬龍的六邊形戰力圖全滿。

  圖是這樣的。

      

    於是乎想實現一個自定義view實現類似的效果。 這種圖正式名稱叫雷達圖(Radar Chart),又可稱為戴布拉圖、蜘蛛網圖(Spider Chart),是財務分析  報表的一種。但是現在已經應用到很多領域,特別是競技體育方面對隊伍或者選手的實力分析。

  整理了一下思路和查詢了一下相關知識,結合前人的代碼,實現了自定義雷達圖。

  下面寫一下實現思路:首先我把雷達圖分為底層蜘蛛網+內容區,底層蜘蛛網的六個屬性和內容區的六個點分別從2個數組去獲取數值,接來下只要依次繪制兩層圖即可。

 

    1.初始化

  

 private int count=6;  //六邊形,數據個數6
    private float angle= (float) (Math.PI/3);  //60度
    private double[] data={50,50,50,50,50,50,50}; //默認數據
    private float maxValue=100;     //默認最大值
    private String[] titles={"a","b","c","d","e","f"};  //默認標題

    private Paint radarPaint;                //蜘蛛網畫筆
    private  Paint valuePaint;               //內容區畫筆
    private Paint textPaint;                 //文字畫筆

    private float radius;                   //網格最大半徑
    private int centerX;                  //中心X
    private int centerY;                  //中心Y


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

    public MyRadar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

 

    private void init() {

    radarPaint=newPaint();

    radarPaint.setAntiAlias(true);

    radarPaint.setColor(Color.GRAY);

    radarPaint.setStyle(Paint.Style.STROKE);

    valuePaint=newPaint();

    valuePaint.setAntiAlias(true);

    valuePaint.setColor(Color.BLUE);

    valuePaint.setStyle(Paint.Style.FILL_AND_STROKE);

    textPaint=newPaint();

    textPaint.setTextSize(sp2px(15));

    textPaint.setAntiAlias(true);

    textPaint.setStyle(Paint.Style.FILL);

    textPaint.setColor(Color.BLACK);

    }
    @Override

    protected voidonSizeChanged(intw,inth,intoldw,intoldh) {

    radius= Math.min(h, w)/2*0.65f;      //得到半徑

    centerX= w/2;                          //得到中心點

    centerY= h/2;

    postInvalidate();

    super.onSizeChanged(w, h, oldw, oldh);

    }

  2,重點的繪圖過程來了,第一步,繪制蜘蛛網圖  ,繪圖之前我們先復習下數學的知識。

  首先,一個正六邊形是圓的內接正六邊形,每個邊對應的圓心角是六十度。

  其次,Android中View的坐標系是我們數學課是不一樣的!(很容易被忽視)

  這里的1,2,3,4代表的是象限,因為y的方向不同,導致了象限與數學書中的不同

  

    

  首先,利用三角函數的知識繪制蜘蛛網圖

  cosX對應映射在X軸上長度,sinX對應映射在Y軸上長度。所以可以通過每次X加上60度(1/3PI)去得到邊角點。

  

 private void drawHexagon(Canvas canvas) {
        Path path=new Path();
        float r=radius/(count-1);
        for (int i = 0; i <count ; i++) {

            float R=r*i;
            path.reset();
            for (int j=0;j<count;j++){
                if(j==0){
                    path.moveTo(centerX+R,centerY);
                }
                else{
                    float x= (float) (centerX+R*Math.cos(angle*j));
                    float y= (float) (centerY+R*Math.sin(angle*j));
                    path.lineTo(x,y);
                }
            }

            path.close();
            canvas.drawPath(path,radarPaint);

        }
        for (int i = 0; i <count ; i++) {
            path.reset();
            path.moveTo(centerX,centerY);
            float x= (float) (centerX+radius*Math.cos(angle*i));
            float y= (float) (centerY+radius*Math.sin(angle*i));
            path.lineTo(x,y);
            canvas.drawPath(path,radarPaint);
        }

}

   效果:

      

  接來下繪制標題,我們想要的效果是這樣的  標題離邊角有一定距離,且呈現對稱效果。

  

   這時的解決方案是將以比半徑稍大的長度作為新的半徑,這樣可以在六個角外面得到相應的六個點,再在這六個點處繪制標題。

   這里能否直接以六個點為坐標依次繪制文字? 答案是否定的。原因如下圖:

    

    沒錯,繪制文字時是將坐標作為文字的左下角,如果不在不同的象限做出處理,文字將無法實現對稱。如下:

    

    代碼:

    

 private void drawText(Canvas canvas) {

        Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
        float fontHeight = fontMetrics.descent - fontMetrics.ascent;
        for (int i = 0; i <count ; i++) {
            float x= (float) (centerX+(radius+fontHeight/2)*Math.cos(angle*i));
            float y= (float) (centerY+(radius+fontHeight/2)*Math.sin(angle*i));

            if(angle*i>=0&&angle*i<=Math.PI/2){
                canvas.drawText(titles[i], x,y+fontHeight/2,textPaint);
            } else if(angle*i>Math.PI/2&&angle*i<=Math.PI){
                float dis = textPaint.measureText(titles[i]);
                canvas.drawText(titles[i], x-dis,y+fontHeight/2,textPaint);
            }
            else if(angle*i>=Math.PI&&angle*i<3*Math.PI/2){
                float dis = textPaint.measureText(titles[i]);
                canvas.drawText(titles[i], x-dis,y,textPaint);
            }else if(angle*i>=3*Math.PI/2&&angle*i<=Math.PI*2){
                canvas.drawText(titles[i], x,y,textPaint);
            }


        }
    }

    最后繪制內容區域也不難:

  

private void drawRegion(Canvas canvas) {
        Path path = new Path();
        valuePaint.setAlpha(255);
        for(int i=0;i<count;i++){
            double percent = data[i]/maxValue;
            float x = (float) (centerX+radius*Math.cos(angle*i)*percent);
            float y = (float) (centerY+radius*Math.sin(angle*i)*percent);
            if(i==0){
                path.moveTo(x, centerY);
            }else{
                path.lineTo(x,y);
            }
            //繪制小圓點
            canvas.drawCircle(x,y,5,valuePaint);
        }
        valuePaint.setStyle(Paint.Style.STROKE);
        canvas.drawPath(path, valuePaint);
        valuePaint.setAlpha(127);
        //繪制填充區域
        valuePaint.setStyle(Paint.Style.FILL_AND_STROKE);
        canvas.drawPath(path, valuePaint);
    }

    完整的draw方法

  

 @Override
    protected void onDraw(Canvas canvas) {
        drawHexagon(canvas);
        drawText(canvas);
        drawRegion(canvas);
    }

   到這里差不多就結束了,后續就是對外暴露一些方法,以及wrapcontent設置默認大小

  

 //設置數值
    public void setData(double[] data) {
        this.data = data;
    }


    public float getMaxValue() {
        return maxValue;
    }

    //設置最大數值
    public void setMaxValue(float maxValue) {
        this.maxValue = maxValue;
    }
    //設置標題顏色
    public void setTextPaintColor(int color){
        textPaint.setColor(color);
    }

    //設置覆蓋局域顏色
    public void setValuePaintColor(int color){
        valuePaint.setColor(color);

    }
    //設置雷達圖顏色
    public void setMainPaintColor(int color){
        radarPaint.setColor(color);
    }

  

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

        if(widthSpecMode==MeasureSpec.AT_MOST&&heightSpecMode==MeasureSpec.AT_MOST){
            setMeasuredDimension(sp2px(300),sp2px(300));
        }
        else if (widthMeasureSpec==MeasureSpec.AT_MOST){
            setMeasuredDimension(sp2px(250),heightSpecSize);
        }else if (heightSpecMode==MeasureSpec.AT_MOST){
            setMeasuredDimension(widthSpecSize,sp2px(250));
        }
    }

  

    最終在acitivty設置數據和標題,最終效果:

public class MainActivity extends AppCompatActivity {
    private MyRadar mRadar;
    double[] data={100,100,100,100,50,100,20};
    String[] titles={"發球","經驗","防守","技巧","速度","力量"};
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mRadar= (MyRadar) findViewById(R.id.radar);

        mRadar.setData(data);
        mRadar.setTitles(titles);

    }
}

        

  源碼在github:https://github.com/xurui1995/Radar


免責聲明!

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



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