Android 最簡(jiǎn)單的餅狀圖(轉(zhuǎn))

原文:android 最簡(jiǎn)單的餅狀圖

要做這么一個(gè)效果,我們應(yīng)該分幾步來(lái)寫(xiě),
1.先做一個(gè)靜態(tài)的餅狀圖
2.然后加上屬性動(dòng)畫(huà),有一個(gè)繪制的過(guò)程(這里有個(gè)難點(diǎn))
3.加上選中效果

第一步:繪制一個(gè)餅狀圖,用canvas的drawArc來(lái)繪制圓弧,代碼如下

private void drawColor(Canvas canvas, int color, float startAngle, float sweepAngle) {
        mChartPaint.setColor(color);
        mChartPaint.setAlpha(255);
        canvas.drawArc(mRectF, startAngle, sweepAngle, true, mChartPaint);
    }

這里是封裝出來(lái)的方法,mRectF是繪制的矩形區(qū)域,startAngle是開(kāi)始角度,sweepAngle是掃過(guò)的角度也就是你要繪制多少度,true為useCenter,這是你是否采用填充的形式,這里可以自己設(shè)為為false看看效果,mChartPaint是你的畫(huà)筆,需要繪制什么顏色啊采用什么繪制樣式啊等。

第二步:加上屬性動(dòng)畫(huà),有一個(gè)繪制的過(guò)程,代碼如下

public void startAnima() {
        final ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0f, 360f);
        mValueAnimator.setDuration(3 * 1000);
 
        mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
 
            @Override public void onAnimationUpdate(ValueAnimator animation) {
                mAnimaAngle = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        mValueAnimator.start();
    }
這里的mAnimaAngle是執(zhí)行動(dòng)畫(huà)的時(shí)候當(dāng)前的角度在0到360之間取值。動(dòng)畫(huà)是加上了,怎么加到繪制餅狀圖的過(guò)程中呢?怎么才能有一個(gè)繪制的動(dòng)畫(huà)過(guò)程呢?看下邊代碼

@Override protected void onDraw(Canvas canvas) {
        if (mPieModelList == null || mPieModelList.isEmpty()) {
            return;
        }
        for (int i = 0; i < mPieModelList.size(); i++) {
            if (mPieModelList.get(i).percent > 0) {
                if (mAnimaAngle >= mPieModelList.get(i).startAngle && mAnimaAngle <= (mPieModelList.get(i).startAngle + mPieModelList.get(i).sweepAngle)) {
                    drawColor(canvas, mPieModelList.get(i).color, mPieModelList.get(i).startAngle, mAnimaAngle - mPieModelList.get(i).startAngle);
                } else if (mAnimaAngle >= (mPieModelList.get(i).startAngle + mPieModelList.get(i).sweepAngle)) {
                    drawColor(canvas, mPieModelList.get(i).color, mPieModelList.get(i).startAngle, mPieModelList.get(i).sweepAngle);
                }
                if (mPieModelList.get(i).selected) {
                    drawSelectedView(canvas, mPieModelList.get(i).color, mPieModelList.get(i).startAngle, mPieModelList.get(i).sweepAngle);
                }
            }
        }
        canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredWidth() / 2, padding, mCirclePaint);
    }
 
    private void drawColor(Canvas canvas, int color, float startAngle, float sweepAngle) {
        mChartPaint.setColor(color);
        mChartPaint.setAlpha(255);
        canvas.drawArc(mRectF, startAngle, sweepAngle, true, mChartPaint);
    }
 
    private void drawSelectedView(Canvas canvas, int color, float startAngle, float sweepAngle) {
        mChartPaint.setColor(color);
        mChartPaint.setAlpha(150);
        canvas.drawArc(mSelectedRectF, startAngle, sweepAngle, true, mChartPaint);
    }

drawSelectedView這個(gè)方法暫時(shí)忽略,canvas.drawCircle這是繪制中心的一個(gè)白色的圓,你也可以去掉看效果,mPieModeList這個(gè)集合是裝載的弧形對(duì)象的,我們把每一個(gè)弧形寫(xiě)成一個(gè)model這樣有利于做選中和切換顏色的效果,model的代碼后邊貼,我們可以看到在draw方法里,有一個(gè)for循環(huán),這個(gè)for循環(huán)繪制每一個(gè)弧形(每一個(gè)弧形都有不同的顏色和開(kāi)始角度和掃過(guò)的角度),但是在for循環(huán)里有兩個(gè)判斷,第一個(gè)判斷是:mAnimaAngle>=當(dāng)前model的startAngle并且<=當(dāng)前model的終點(diǎn)角度,也就是說(shuō)只能在當(dāng)前model的開(kāi)始角度和終點(diǎn)角度之間繪制它自己特有的顏色。第二個(gè)判斷是mAnimaAngle>=當(dāng)前model的終點(diǎn)角度,如果動(dòng)畫(huà)執(zhí)行的mAnimaAngle大于當(dāng)前model的終點(diǎn)角度,那么該是什么顏色就繪制什么顏色,如果把這個(gè)判斷去掉的話,會(huì)導(dǎo)致只會(huì)繪制最后一個(gè)model的弧形,前邊的都會(huì)消失。

第三步:繪制選中的區(qū)域,并且讓選中的弧形突出。代碼如下

private void drawSelectedView(Canvas canvas, int color, float startAngle, float sweepAngle) {
        mChartPaint.setColor(color);
        mChartPaint.setAlpha(150);
        canvas.drawArc(mSelectedRectF, startAngle, sweepAngle, true, mChartPaint);
    }

這個(gè)效果我想了半天,怎么讓選中的弧形突出呢?最后還是借鑒了MPAndroidChart庫(kù)的實(shí)現(xiàn)方式,給弧形的繪制區(qū)域多加30或者50不就突出了么,而且選中不是有一個(gè)透明度的效果么,就直接設(shè)置畫(huà)筆的透明度值就好。那么在哪里設(shè)置選中的區(qū)域位置呢?

@Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        padding = w / 6;
        mRectF = new RectF(padding, padding, w - padding, w - padding);
        mSelectedRectF.set(mRectF);
        mSelectedRectF.inset(-30, -30);
    }

這里有個(gè)mSelectedRectF.inset(-30,-30)這里為什么要設(shè)置為負(fù)數(shù),因?yàn)樵O(shè)置的參數(shù)如果為正數(shù),則矩形會(huì)向內(nèi)移動(dòng),使矩形變窄,而設(shè)置為負(fù)數(shù),則矩形向外移動(dòng),矩形會(huì)變寬。

OK,餅狀圖全部分析完畢,最后是全部代碼

package cms.chart.demo.view;
 
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
 
import java.util.List;
 
import cms.chart.demo.bean.PieModel;
 
/**
 * 自定義餅狀圖 第一:可能需要繪制多個(gè)顏色的圖
 * <p>
 * Created by 54966 on 2018/2/27.
 */
 
public class PieChartView extends View {
 
    private Paint           mChartPaint;
 
    private Paint           mCirclePaint;                   // 中心圓
 
    private RectF           mRectF;
 
    private int             padding;
 
    private List<PieModel>  mPieModelList;
 
    private float           mAnimaAngle;
 
    private RectF           mSelectedRectF  = new RectF();
 
    public PieChartView(Context context) {
        this(context, null);
    }
 
    public PieChartView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }
 
    public PieChartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }
 
    private void init() {
        mChartPaint = new Paint();
        mChartPaint.setAntiAlias(true);
        mChartPaint.setDither(true);
        mChartPaint.setStrokeWidth(100);
        mChartPaint.setStyle(Paint.Style.FILL);
 
        mCirclePaint = new Paint();
        mCirclePaint.setAntiAlias(true);
        mCirclePaint.setStyle(Paint.Style.FILL);
        mCirclePaint.setColor(Color.WHITE);
    }
 
    @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
 
    @Override protected void onDraw(Canvas canvas) {
        if (mPieModelList == null || mPieModelList.isEmpty()) {
            return;
        }
        for (int i = 0; i < mPieModelList.size(); i++) {
            if (mPieModelList.get(i).percent > 0) {
                if (mAnimaAngle >= mPieModelList.get(i).startAngle &&
                        mAnimaAngle <= (mPieModelList.get(i).startAngle + mPieModelList.get(i).sweepAngle)) {
 
                    drawColor(canvas, mPieModelList.get(i).color, mPieModelList.get(i).startAngle, mAnimaAngle - mPieModelList.get(i).startAngle);
 
                } else if (mAnimaAngle >= (mPieModelList.get(i).startAngle + mPieModelList.get(i).sweepAngle)) {
                    drawColor(canvas, mPieModelList.get(i).color, mPieModelList.get(i).startAngle, mPieModelList.get(i).sweepAngle);
                }
                if (mPieModelList.get(i).selected) {
                    drawSelectedView(canvas, mPieModelList.get(i).color, mPieModelList.get(i).startAngle, mPieModelList.get(i).sweepAngle);
                }
            }
        }
        canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredWidth() / 2, padding, mCirclePaint);
    }
 
    private void drawColor(Canvas canvas, int color, float startAngle, float sweepAngle) {
        mChartPaint.setColor(color);
        mChartPaint.setAlpha(255);
        canvas.drawArc(mRectF, startAngle, sweepAngle, true, mChartPaint);
    }
 
    private void drawSelectedView(Canvas canvas, int color, float startAngle, float sweepAngle) {
        mChartPaint.setColor(color);
        mChartPaint.setAlpha(150);
        canvas.drawArc(mSelectedRectF, startAngle, sweepAngle, true, mChartPaint);
    }
 
    public void startAnima() {
        final ValueAnimator mValueAnimator = ValueAnimator.ofFloat(0f, 360f);
        mValueAnimator.setDuration(3 * 1000);
 
        mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
 
            @Override public void onAnimationUpdate(ValueAnimator animation) {
                mAnimaAngle = (float) animation.getAnimatedValue();
                invalidate();
            }
        });
        mValueAnimator.start();
    }
 
    public void setData(List<PieModel> pieModelList) {
        this.mPieModelList = pieModelList;
        for (int i = 0; i < mPieModelList.size(); i++) {
            PieModel model = mPieModelList.get(i);
            if (i == 0) {
                model.startAngle = 0;
            } else {
                model.startAngle = mPieModelList.get(i - 1).startAngle + mPieModelList.get(i - 1).sweepAngle;
            }
            model.sweepAngle = (model.percent * 360);
        }
    }
 
    @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        padding = w / 6;
        mRectF = new RectF(padding, padding, w - padding, w - padding);
        mSelectedRectF.set(mRectF);
        mSelectedRectF.inset(-30, -30);
    }
 
}
package cms.chart.demo;
 
import java.util.ArrayList;
import java.util.List;
 
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;
 
import cms.chart.demo.bean.PieModel;
import cms.chart.demo.utils.ColorRandom;
import cms.chart.demo.view.PieChartView;
 
public class MainActivity extends AppCompatActivity {
 
    private PieChartView    id_pie_chart;
 
    private TextView        id_tv_1, id_tv_2, id_tv_3, id_tv_4;
 
    private List<PieModel>  pieModelList    = new ArrayList<>();
 
    private List<Integer>   colorList       = new ArrayList<>();
 
    @Override protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        id_tv_1 = findViewById(R.id.id_tv_1);
        id_tv_2 = findViewById(R.id.id_tv_2);
        id_tv_3 = findViewById(R.id.id_tv_3);
        id_tv_4 = findViewById(R.id.id_tv_4);
        id_pie_chart = findViewById(R.id.id_pie_chart);
 
        ColorRandom colorRandom = new ColorRandom(10);
        for (int i = 0; i < 5; i++) {
            int colors = (int) colorRandom.getColors().get(i);
            if (i == 0) {
                pieModelList.add(new PieModel(colors, 0.1f));
            } else {
                pieModelList.add(new PieModel(colors, 0.3f));
            }
        }
 
        id_pie_chart.setData(pieModelList);
        id_pie_chart.startAnima();
 
        List<Double> mList = new ArrayList<>();
        mList.add(0.25);
        mList.add(0.35);
        mList.add(0.15);
        mList.add(0.05);
        mList.add(0.20);
 
        id_tv_1.setOnClickListener(new View.OnClickListener() {
 
            @Override public void onClick(View v) {
                if (pieModelList.get(0).selected) {
                    pieModelList.get(0).selected = false;
                } else {
                    pieModelList.get(0).selected = true;
                }
                id_pie_chart.setData(pieModelList);
                id_pie_chart.invalidate();
            }
        });
 
        id_tv_2.setOnClickListener(new View.OnClickListener() {
 
            @Override public void onClick(View v) {
                if (pieModelList.get(1).selected) {
                    pieModelList.get(1).selected = false;
                } else {
                    pieModelList.get(1).selected = true;
                }
                id_pie_chart.setData(pieModelList);
                id_pie_chart.invalidate();
            }
        });
 
        id_tv_3.setOnClickListener(new View.OnClickListener() {
 
            @Override public void onClick(View v) {
                if (pieModelList.get(2).selected) {
                    pieModelList.get(2).selected = false;
                } else {
                    pieModelList.get(2).selected = true;
                }
                id_pie_chart.setData(pieModelList);
                id_pie_chart.invalidate();
            }
        });
 
        id_tv_4.setOnClickListener(new View.OnClickListener() {
 
            @Override public void onClick(View v) {
                if (pieModelList.get(3).selected) {
                    pieModelList.get(3).selected = false;
                } else {
                    pieModelList.get(3).selected = true;
                }
                id_pie_chart.setData(pieModelList);
                id_pie_chart.invalidate();
            }
        });
 
    }
 
}
package cms.chart.demo.bean;
 
/**
 * Created by 54966 on 2018/2/28.
 */
 
public class PieModel {
 
    public float    startAngle; // 開(kāi)始繪制的角度
 
    public float    sweepAngle; // 掃過(guò)的角度
 
    public int      color;      // 顯示的顏色
 
    public float    percent;    // 所占百分比
 
    public boolean  selected;   // true為選中
 
    public PieModel(int color, float percent) {
        this.color = color;
        this.percent = percent;
    }
 
    public PieModel(int color, float percent, boolean selected) {
        this.color = color;
        this.percent = percent;
        this.selected = selected;
    }
 
}
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#ffffff"
    tools:context="cms.chart.demo.MainActivity">
 
    <cms.chart.demo.view.PieChartView
        android:id="@+id/id_pie_chart"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_centerHorizontal="true" />
 
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/id_pie_chart"
        android:orientation="horizontal">
 
        <TextView
            android:id="@+id/id_tv_1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
 
            android:padding="10dp"
            android:text="選中1" />
 
        <TextView
            android:id="@+id/id_tv_2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="選中2" />
 
        <TextView
            android:id="@+id/id_tv_3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="選中3" />
 
        <TextView
            android:id="@+id/id_tv_4"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
 
            android:padding="10dp"
            android:text="選中4" />
 
    </LinearLayout>
 
 
</RelativeLayout>
package com.qfdqc.views.demo.utils;
 
import java.util.ArrayList;
import java.util.Random;
 
import android.graphics.Color;
 
/**
 * Created by 54966 on 2018/3/8.
 */
 
public class ColorRandom {
 
    private ArrayList<Integer> colorArrays;
 
    private int                 count;
 
    public ColorRandom(int count) {
        colorArrays = new ArrayList<>(count);
        this.count = count;
        setColor();
    }
 
    private void setColor() {
        for (int i = 0; i < count; i++) {
            int color = getColor();
            colorArrays.add(color);
        }
    }
 
    private Integer getColor() {
        int color = Color.parseColor("#FFA500");
        while (colorArrays.contains(color) || "#FFFFFF".equals(color)) {
            color = getRandColorCode();
            if (!colorArrays.contains(color)) {
                break;
            }
        }
        return color;
    }
 
    public ArrayList getColors() {
        return colorArrays;
    }
 
    private Integer getRandColorCode() {
        String r, g, b;
        Random random = new Random();
        r = Integer.toHexString(random.nextInt(256)).toUpperCase();
        g = Integer.toHexString(random.nextInt(256)).toUpperCase();
        b = Integer.toHexString(random.nextInt(256)).toUpperCase();
 
        r = r.length() == 1 ? "0" + r : r;
        g = g.length() == 1 ? "0" + g : g;
        b = b.length() == 1 ? "0" + b : b;
 
        return Color.parseColor("#" + r + g + b);
    }
 
}

其實(shí)多研究別人的開(kāi)源框架能學(xué)到不少封裝的思想。MPAndroidChart可以去github上搜索

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容