Android繪制柱狀圖、折線圖、餅狀圖

當項目要做圖標功能時,大家基本就是去引用開源庫來使用了,其中最著名的圖標庫是MPAndroidChart了。但是,如果能夠自己手動擼出各種圖標,那是不是很酷呢?常用圖表類型包括柱狀圖,折線圖,餅狀圖,雷達圖,股票圖,這里是做的前三者圖表。

演示效果:

戳這里Github代碼

實現步驟

  • 柱狀圖

1.以等差高度畫Y軸文字和橫線
2.畫X軸文字
3.依據數值畫不等高度的矩形柱
4.點擊各個柱子顯示頂部數值

  • 折線圖

1.以等差高度畫Y軸文字和橫線
2.畫X軸文字
3.根據數值繪制path

  • 餅狀圖

1.根據數值繪制不同起始角度和弧度的圓環
2.依據弧度并利用三角函數知識繪制各版塊文字,并調節位置至板塊中心
3.繪制內部同心圓環

繪制圓環上文字知識點:


圓心為o(x,y),半徑r,圓上各個弧度點上的坐標,求值公式為
xA = x+rsinα,yA = y-rcosα,α大小隨著頂點變化而變化。

代碼實現:
  • 柱狀圖View
/**
 * create by libo
 * create on 2020/7/26
 * description 柱狀圖View
 */
public class BarChartView extends View {
    /** 文字Paint */
    private Paint textPaint;
    /** Y軸基準線Paint */
    private Paint linePaint;
    /** 柱型paint */
    private Paint rectPaint;
    /** Y軸每單元數量高度 */
    private float unitHeight;
    /** Y軸數據數組 */
    private int[] unitHeightNum = new int[] {100, 200, 300, 400, 500};
    /** 各個階段數據數組 */
    private int[] stageNum = new int[] {125, 230, 323, 253, 398, 410};
    private String[] stageStr = new String[] {"Jan", "Feb", "Mar", "Apr", "May", "Jun"};
    private int[] colors = new int[] {R.color.green, R.color.blue, R.color.yellow, R.color.red};
    /** X軸單元寬度 */
    private float unitWidth;
    /** 橫線左邊距大小 */
    private float lineLeftPadding;
    /** 柱狀圖左右間距 */
    private int rectPadding = 12;
    /** 每個柱子集合 */
    private ArrayList<Bar> bars = new ArrayList<>();
    /** 當前顯示值的位置 */
    private int showValuePos = -1;

    public BarChartView(Context context) {
        super(context);
        initPaint();
    }

    public BarChartView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initPaint();
    }

    private void initPaint() {
        textPaint = new Paint();
        textPaint.setColor(getResources().getColor(R.color.textcolor));
        textPaint.setTextSize(40);
        textPaint.setAntiAlias(true);

        linePaint = new Paint();
        linePaint.setColor(getResources().getColor(R.color.linecolor));
        linePaint.setAntiAlias(true);
        linePaint.setStyle(Paint.Style.FILL);
        linePaint.setStrokeWidth(2);

        rectPaint = new Paint();
        rectPaint.setAntiAlias(true);
        rectPaint.setStyle(Paint.Style.FILL);
    }

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

        drawYText(canvas);
        drawXText(canvas);
        drawBars(canvas);
    }

    /**
     * 繪制Y軸文字及基準線
     */
    private void drawYText(Canvas canvas) {
        int top = getHeight() - 80;  //給底部文字留下高度
        unitHeight = getHeight()/unitHeightNum.length - 20;

        for (int num : unitHeightNum) {
            Rect rect = new Rect();
            String text = num + "萬";
            textPaint.getTextBounds(text, 0, text.length(), rect);
            canvas.drawText(text, 0, top, textPaint);  //畫文字

            lineLeftPadding = rect.width() + 20;
            canvas.drawLine(lineLeftPadding, top, getWidth(), top, linePaint);  //畫橫線
            top -= unitHeight;
        }
    }

    /**
     * 繪制X軸文字
     */
    private void drawXText(Canvas canvas) {
        float left = lineLeftPadding;
        unitWidth = getWidth()/stageNum.length - 20;

        Bar bar;
        for (int i=0;i<stageNum.length;i++) {
            canvas.drawText(stageStr[i], left + unitWidth/4, getHeight()-20, textPaint);  //畫文字

            float top = getHeight() - (float)stageNum[i]/100*unitHeight;
            int color = getResources().getColor(colors[i%colors.length]);
            bar = new Bar(stageNum[i], left+rectPadding, top, left+unitWidth-rectPadding, getHeight()- 80, color);
            bars.add(bar);
            left += unitWidth;
        }
    }

    /**
     * 繪制柱形
     */
    private void drawBars(Canvas canvas) {
        //畫矩形,并左右設置間距
        //根據該項數值獲取實際的柱形高度
        //Y軸每格單元高度為100數值
        for (int i=0;i<bars.size();i++) {
            Bar bar = bars.get(i);
            rectPaint.setColor(bar.color);
            canvas.drawRect(bar.left, bar.top, bars.get(i).right, bar.bootom, rectPaint);

            //繪制柱形上數值
            if (showValuePos == i) {
                String value = String.valueOf(bar.value);
                Rect rect = new Rect();
                textPaint.getTextBounds(value, 0, value.length(), rect);
                float textLeft = bar.left + (bar.right-bar.left-rect.width())/2;  //計算使文字在柱形居中位置
                canvas.drawText(value, textLeft, bar.top-20, textPaint);  //繪制柱頂部數值
            }
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            for (int i=0;i<bars.size();i++) {
                if (event.getX() > bars.get(i).left && event.getX() < bars.get(i).right) {
                    //按下事件在當前柱形內
                    showValuePos = i;
                    invalidate();
                }
            }
        }

        return true;
    }

    /**
     * 柱形類
     */
    class Bar {
        private int value;
        private float left;
        private float top;
        private float right;
        private float bootom;
        private int color;

        public Bar(int value, float left, float top, float right, float bootom, int color) {
            this.value = value;
            this.left = left;
            this.top = top;
            this.right = right;
            this.bootom = bootom;
            this.color = color;
        }
    }

}

  • 折線圖View
/**
 * create by libo
 * create on 2020/7/26
 * description 折線圖View
 */
public class LineChartView extends View {
    /** 文字Paint */
    private Paint textPaint;
    /** 基準線Paint */
    private Paint linePaint;
    /** 折線paint */
    private Paint charLinePaint;
    /** Y軸每單元數量高度 */
    private float unitHeight;
    /** Y軸數據數組 */
    private int[] unitHeightNum = new int[] {0, 20, 40, 60, 80, 100};
    private String[] stageStr = new String[] {"Jan", "Feb", "Mar", "Apr", "May", "Jun"};
    /** 橫線左邊距大小 */
    private float lineLeftPadding;
    /** X軸單元寬度 */
    private float unitWidth;
    /** 各個階段數據數組 */
    private int[] stageNum = new int[] {56, 40, 82, 74, 60, 92};
    private Path linePath;

    public LineChartView(Context context) {
        super(context);
        initPaint();
    }

    public LineChartView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initPaint();
    }

    private void initPaint() {
        textPaint = new Paint();
        textPaint.setColor(getResources().getColor(R.color.textcolor));
        textPaint.setTextSize(40);
        textPaint.setAntiAlias(true);

        linePaint = new Paint();
        linePaint.setColor(getResources().getColor(R.color.linecolor));
        linePaint.setAntiAlias(true);
        linePaint.setStyle(Paint.Style.STROKE);
        linePaint.setStrokeWidth(2);

        charLinePaint = new Paint();
        charLinePaint.setColor(getResources().getColor(R.color.orange));
        charLinePaint.setAntiAlias(true);
        charLinePaint.setStyle(Paint.Style.FILL);

        linePath = new Path();
    }

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

        drawYText(canvas);
        drawXText(canvas);
        drawLinePath(canvas);
    }

    /**
     * 繪制Y軸文字及基準線
     */
    private void drawYText(Canvas canvas) {
        int top = getHeight() - 80;  //給底部文字留下高度
        unitHeight = getHeight()/unitHeightNum.length - 20;

        Rect rect = new Rect();
        String longText = unitHeightNum[unitHeightNum.length-1]+"萬";  //以最長文字對齊
        textPaint.getTextBounds(longText, 0, longText.length(), rect);

        for (int num : unitHeightNum) {

            canvas.drawText(num + "萬", 0, top, textPaint);  //畫文字

            lineLeftPadding = rect.width() + 20;
            canvas.drawLine(lineLeftPadding, top, getWidth(), top, linePaint);  //畫橫線
            top -= unitHeight;
        }
    }

    /**
     * 繪制X軸文字
     */
    private void drawXText(Canvas canvas) {
        float left = lineLeftPadding;
        unitWidth = getWidth()/stageNum.length - 20;

        for (int i=0;i<stageNum.length;i++) {
            canvas.drawText(stageStr[i], left + unitWidth/4, getHeight()-20, textPaint);  //畫文字

            canvas.drawLine(left, getHeight()-80, left, 80, linePaint);

            left += unitWidth;
        }
    }

    /**
     * 繪制折線
     */
    private void drawLinePath(Canvas canvas) {
        float left = lineLeftPadding;

        for (int i=0;i<stageNum.length;i++) {
            float topX = left + unitWidth/2;
            float topY = getHeight() - (float)stageNum[i]/20*unitHeight;

            if (i == 0) {
                linePath.moveTo(topX, topY);
            } else {
                linePath.lineTo(topX, topY);
            }

            charLinePaint.setStyle(Paint.Style.FILL);
            canvas.drawCircle(topX, topY, 10, charLinePaint);  //繪制拐點小圓

            left += unitWidth;
        }

        charLinePaint.setStyle(Paint.Style.STROKE);
        charLinePaint.setStrokeWidth(4);
        canvas.drawPath(linePath, charLinePaint);
    }
}

  • 餅狀圖View
/**
 * create by libo
 * create on 2020/7/26
 * description 餅狀圖
 */
public class PieChartView extends View {
    private Paint piePaint;
    private Paint innerPiePaint;
    /** 圓環寬度 */
    private int ringWidth;
    private int[] colors = new int[] {R.color.green, R.color.blue, R.color.yellow, R.color.red, R.color.orange};
    private int[] values = new int[] {21, 12, 30, 23, 14};
    private String[] titles = new String[] {"醫院", "學校", "酒店", "商場", "商業建筑"};
    private Paint textPaint;
    private Paint centerTextPaint;
    private String title = "設施占比";

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

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

    private void init() {
        piePaint = new Paint();
        piePaint.setAntiAlias(true);
        piePaint.setStyle(Paint.Style.STROKE);
        piePaint.setAlpha(190);

        innerPiePaint = new Paint();
        innerPiePaint.setAntiAlias(true);
        innerPiePaint.setStyle(Paint.Style.STROKE);

        textPaint = new Paint();
        textPaint.setAntiAlias(true);
        textPaint.setColor(getResources().getColor(R.color.white));
        textPaint.setTextSize(45);

        centerTextPaint = new Paint();
        centerTextPaint.setAntiAlias(true);
        centerTextPaint.setColor(getResources().getColor(R.color.colorPrimary));
        centerTextPaint.setTextSize(55);
    }

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

        drawRing(canvas);
        drawText(canvas);
        drawCenterText(canvas);
    }

    /**
     * 畫分塊圓環
     */
    private void drawRing(Canvas canvas) {
        ringWidth = getMeasuredWidth()/4;
        piePaint.setStrokeWidth(ringWidth);
        innerPiePaint.setStrokeWidth(60);
        RectF rectF = new RectF(ringWidth/2, ringWidth/2, getWidth()-ringWidth/2, getHeight()-ringWidth/2);
        RectF innerRectF = new RectF(ringWidth, ringWidth, getWidth()-ringWidth, getHeight()-ringWidth);

        int startAngle = -90;  //12點鐘方向起始
        for (int i=0;i<values.length;i++) {
            float sweepAngle = (float) values[i]/100*360;
            piePaint.setColor(getResources().getColor(colors[i]));
            innerPiePaint.setColor(getResources().getColor(colors[i]));

            canvas.drawArc(rectF, startAngle, sweepAngle, false, piePaint);
            innerPiePaint.setAlpha(120);
            canvas.drawArc(innerRectF, startAngle, sweepAngle, false, innerPiePaint);

            startAngle += sweepAngle;
        }
    }

    /**
     * 畫每分塊文字
     */
    private void drawText(Canvas canvas) {
        int startAngle = 0;  //12點鐘方向起始
        int radius = getWidth()/2-ringWidth/2;
        for (int i=0;i<titles.length;i++) {
            float sweepAngle = (float) values[i]/100*360;

            double angle = Math.toRadians(startAngle + sweepAngle/2);
            float x = (float) (getWidth()/2 + radius*Math.sin(angle));
            float y = (float) (getHeight()/2 - radius*Math.cos(angle));

            //橫坐標需要全部左移文字寬度的一半
            Rect rect = new Rect();
            textPaint.getTextBounds(titles[i], 0, titles[i].length(), rect);
            canvas.drawText(titles[i], x-rect.width()/2, y, textPaint);

            startAngle += sweepAngle;
        }
    }

    /**
     * 中心文字
     */
    private void drawCenterText(Canvas canvas) {
        Rect rect = new Rect();
        centerTextPaint.getTextBounds(title, 0, title.length(), rect);
        canvas.drawText(title, getWidth()/2-rect.width()/2, getHeight()/2+rect.height()/2, centerTextPaint);
    }

}
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內容