Android繪制柱狀圖将饺、折線圖贡避、餅狀圖

當(dāng)項目要做圖標(biāo)功能時痛黎,大家基本就是去引用開源庫來使用了,其中最著名的圖標(biāo)庫是MPAndroidChart了贸桶。但是舅逸,如果能夠自己手動擼出各種圖標(biāo),那是不是很酷呢皇筛?常用圖表類型包括柱狀圖琉历,折線圖,餅狀圖水醋,雷達(dá)圖旗笔,股票圖,這里是做的前三者圖表拄踪。

演示效果:

戳這里Github代碼

實現(xiàn)步驟

  • 柱狀圖

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

  • 折線圖

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

  • 餅狀圖

1.根據(jù)數(shù)值繪制不同起始角度和弧度的圓環(huán)
2.依據(jù)弧度并利用三角函數(shù)知識繪制各版塊文字蝇恶,并調(diào)節(jié)位置至板塊中心
3.繪制內(nèi)部同心圓環(huán)

繪制圓環(huán)上文字知識點:


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

代碼實現(xiàn):
  • 柱狀圖View
/**
 * create by libo
 * create on 2020/7/26
 * description 柱狀圖View
 */
public class BarChartView extends View {
    /** 文字Paint */
    private Paint textPaint;
    /** Y軸基準(zhǔn)線Paint */
    private Paint linePaint;
    /** 柱型paint */
    private Paint rectPaint;
    /** Y軸每單元數(shù)量高度 */
    private float unitHeight;
    /** Y軸數(shù)據(jù)數(shù)組 */
    private int[] unitHeightNum = new int[] {100, 200, 300, 400, 500};
    /** 各個階段數(shù)據(jù)數(shù)組 */
    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<>();
    /** 當(dāng)前顯示值的位置 */
    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軸文字及基準(zhǔn)線
     */
    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) {
        //畫矩形,并左右設(shè)置間距
        //根據(jù)該項數(shù)值獲取實際的柱形高度
        //Y軸每格單元高度為100數(shù)值
        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);

            //繪制柱形上數(shù)值
            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);  //繪制柱頂部數(shù)值
            }
        }
    }

    @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) {
                    //按下事件在當(dāng)前柱形內(nèi)
                    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;
    /** 基準(zhǔn)線Paint */
    private Paint linePaint;
    /** 折線paint */
    private Paint charLinePaint;
    /** Y軸每單元數(shù)量高度 */
    private float unitHeight;
    /** Y軸數(shù)據(jù)數(shù)組 */
    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;
    /** 各個階段數(shù)據(jù)數(shù)組 */
    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軸文字及基準(zhǔn)線
     */
    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;
    /** 圓環(huán)寬度 */
    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[] {"醫(yī)院", "學(xué)校", "酒店", "商場", "商業(yè)建筑"};
    private Paint textPaint;
    private Paint centerTextPaint;
    private String title = "設(shè)施占比";

    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);
    }

    /**
     * 畫分塊圓環(huán)
     */
    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));

            //橫坐標(biāo)需要全部左移文字寬度的一半
            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);
    }

}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末救恨,一起剝皮案震驚了整個濱河市贸辈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌肠槽,老刑警劉巖擎淤,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異秸仙,居然都是意外死亡嘴拢,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進(jìn)店門寂纪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來席吴,“玉大人,你說我怎么就攤上這事弊攘∏栏” “怎么了姑曙?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵襟交,是天一觀的道長。 經(jīng)常有香客問我伤靠,道長捣域,這世上最難降的妖魔是什么啼染? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮焕梅,結(jié)果婚禮上迹鹅,老公的妹妹穿的比我還像新娘。我一直安慰自己贞言,他們只是感情好斜棚,可當(dāng)我...
    茶點故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著该窗,像睡著了一般弟蚀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上酗失,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天义钉,我揣著相機(jī)與錄音,去河邊找鬼规肴。 笑死捶闸,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的拖刃。 我是一名探鬼主播删壮,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼序调!你這毒婦竟也來了醉锅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤发绢,失蹤者是張志新(化名)和其女友劉穎硬耍,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體边酒,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡经柴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了墩朦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坯认。...
    茶點故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖氓涣,靈堂內(nèi)的尸體忽然破棺而出牛哺,到底是詐尸還是另有隱情,我是刑警寧澤劳吠,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布引润,位于F島的核電站,受9級特大地震影響痒玩,放射性物質(zhì)發(fā)生泄漏淳附。R本人自食惡果不足惜议慰,卻給世界環(huán)境...
    茶點故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望奴曙。 院中可真熱鬧别凹,春花似錦、人聲如沸洽糟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坤溃。三九已至颁督,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間浇雹,已是汗流浹背沉御。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留昭灵,地道東北人吠裆。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像烂完,于是被迫代替她去往敵國和親试疙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,654評論 2 354

推薦閱讀更多精彩內(nèi)容