Android自定义统计图(柱状图,折线图,饼状图)

来源:互联网 发布:上海 数据 恢复 编辑:程序博客网 时间:2024/06/06 00:53

最近由于项目需要,研究了一些统计图的做法,开始时,看了很多博文,大部分都是引用第三方的库,虽然简单,

易上手,但是功能太死板,有很多要求都是不能满足的,所以经过研究,自己使用View中的canvas重新绘图制作

统计图。首先上几张的效果图吧。





点击这里下载(0分下载)


一、demo的结构

一个activity中嵌套了三个fragment(v4),是用viewpager对页面进行滑动,下面是整个项目的结构:


二、核心代码

首先是MainActivity,这个demo中只使用到一个activity,现在一个activity中镶嵌多个fragment很火,

很多Android应用都在使用这个布局,例如微信,QQ等。这样做节省了空间上的浪费。

package com.example.statisticalchart;import android.support.v4.app.Fragment;import android.support.v4.app.FragmentActivity;import android.support.v4.app.FragmentPagerAdapter;import android.support.v4.view.ViewPager;import android.os.Bundle;import java.util.ArrayList;import java.util.List;public class MainActivity extends FragmentActivity {private ViewPager viewPager;private List<Fragment> fragments;private FragmentPagerAdapter adapter;// 设置是否显示动画,为了防止在创建时就开启动画,用以下三个参数做了判断,只有当看到视图后才会显示动画public static int flag1 = 2;public static int flag2 = 1;public static int flag3 = 1;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();}private void initView() {viewPager = (ViewPager) findViewById(R.id.record_viewpager);fragments = new ArrayList<Fragment>();RecordPager1 recordPager1 = new RecordPager1();RecordPager2 recordPager2 = new RecordPager2();RecordPager3 recordPager3 = new RecordPager3();fragments.add(recordPager1);fragments.add(recordPager2);fragments.add(recordPager3);adapter = new FragmentPagerAdapter(getSupportFragmentManager()) {@Overridepublic Fragment getItem(int position) {return fragments.get(position);}@Overridepublic int getCount() {return fragments.size();}};viewPager.setAdapter(adapter);viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {@Overridepublic void onPageScrolled(int position, float positionOffset,int positionOffsetPixels) {}@Overridepublic void onPageSelected(int position) {if (position == 0 && flag1 == 1) {flag1 = 2;fragments.get(0).onResume();flag1 = 3;}if (position == 1 && flag2 == 1) {flag2 = 2;fragments.get(1).onResume();flag2 = 3;}if (position == 2 && flag3 == 1) {flag3 = 2;fragments.get(2).onResume();flag3 = 3;}}@Overridepublic void onPageScrollStateChanged(int state) {}});}}

接下来是这个项目中最主要的三个类:HistogramView、LineChartView、PinChart。这三个类继承View类,重新构图,分别画成了柱状图,折线图,饼状图,然后给出三个类的代码:

package com.example.statisticalchart;import android.annotation.SuppressLint;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Paint.Align;import android.graphics.Rect;import android.os.Looper;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import android.view.animation.Animation;import android.view.animation.Transformation;public class HistogramView extends View {private Paint xLinePaint;// 坐标轴 轴线 画笔:private Paint hLinePaint;// 坐标轴水平内部 虚线画笔private Paint titlePaint;// 绘制文本的画笔private Paint paint;// 矩形画笔 柱状图的样式信息private int[] progress = { 2000, 5000, 6000, 8000, 500, 6000, 9000 };// 7// 条,显示各个柱状的数据private int[] aniProgress;// 实现动画的值private final int TRUE = 1;// 在柱状图上显示数字private int[] text;// 设置点击事件,显示哪一条柱状的信息private Bitmap bitmap;// 坐标轴左侧的数标private String[] ySteps;// 坐标轴底部的星期数private String[] xWeeks;private int flag;// 是否使用动画private HistogramAnimation ani;public HistogramView(Context context) {super(context);init();}public HistogramView(Context context, AttributeSet attrs) {super(context, attrs);init();}private void init() {ySteps = new String[] { "10k", "7.5k", "5k", "2.5k", "0" };xWeeks = new String[] { "周一", "周二", "周三", "周四", "周五", "周六", "周日" };text = new int[] { 0, 0, 0, 0, 0, 0, 0 };aniProgress = new int[] { 0, 0, 0, 0, 0, 0, 0 };ani = new HistogramAnimation();ani.setDuration(2000);xLinePaint = new Paint();hLinePaint = new Paint();titlePaint = new Paint();paint = new Paint();// 给画笔设置颜色xLinePaint.setColor(Color.DKGRAY);hLinePaint.setColor(Color.LTGRAY);titlePaint.setColor(Color.BLACK);// 加载画图bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.column);}public void start(int flag) {this.flag = flag;this.startAnimation(ani);}@SuppressLint("DrawAllocation")@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);int width = getWidth();int height = getHeight() - dp2px(50);// 绘制底部的线条canvas.drawLine(dp2px(30), height + dp2px(3), width - dp2px(30), height+ dp2px(3), xLinePaint);int leftHeight = height - dp2px(5);// 左侧外周的 需要划分的高度:int hPerHeight = leftHeight / 4;// 分成四部分hLinePaint.setTextAlign(Align.CENTER);// 设置四条虚线for (int i = 0; i < 4; i++) {canvas.drawLine(dp2px(30), dp2px(10) + i * hPerHeight, width- dp2px(30), dp2px(10) + i * hPerHeight, hLinePaint);}// 绘制 Y 周坐标titlePaint.setTextAlign(Align.RIGHT);titlePaint.setTextSize(sp2px(12));titlePaint.setAntiAlias(true);titlePaint.setStyle(Paint.Style.FILL);// 设置左部的数字for (int i = 0; i < ySteps.length; i++) {canvas.drawText(ySteps[i], dp2px(25), dp2px(13) + i * hPerHeight,titlePaint);}// 绘制 X 周 做坐标int xAxisLength = width - dp2px(30);int columCount = xWeeks.length + 1;int step = xAxisLength / columCount;// 设置底部的数字for (int i = 0; i < columCount - 1; i++) {// text, baseX, baseY, textPaintcanvas.drawText(xWeeks[i], dp2px(25) + step * (i + 1), height+ dp2px(20), titlePaint);}// 绘制矩形if (aniProgress != null && aniProgress.length > 0) {for (int i = 0; i < aniProgress.length; i++) {// 循环遍历将7条柱状图形画出来int value = aniProgress[i];paint.setAntiAlias(true);// 抗锯齿效果paint.setStyle(Paint.Style.FILL);paint.setTextSize(sp2px(15));// 字体大小paint.setColor(Color.parseColor("#6DCAEC"));// 字体颜色Rect rect = new Rect();// 柱状图的形状rect.left = step * (i + 1);rect.right = dp2px(30) + step * (i + 1);int rh = (int) (leftHeight - leftHeight * (value / 10000.0));rect.top = rh + dp2px(10);rect.bottom = height;canvas.drawBitmap(bitmap, null, rect, paint);// 是否显示柱状图上方的数字if (this.text[i] == TRUE) {canvas.drawText(value + "", dp2px(15) + step * (i + 1)- dp2px(15), rh + dp2px(5), paint);}}}}private int dp2px(int value) {float v = getContext().getResources().getDisplayMetrics().density;return (int) (v * value + 0.5f);}private int sp2px(int value) {float v = getContext().getResources().getDisplayMetrics().scaledDensity;return (int) (v * value + 0.5f);}/** * 设置点击事件,是否显示数字 */public boolean onTouchEvent(MotionEvent event) {int step = (getWidth() - dp2px(30)) / 8;int x = (int) event.getX();for (int i = 0; i < 7; i++) {if (x > (dp2px(15) + step * (i + 1) - dp2px(15))&& x < (dp2px(15) + step * (i + 1) + dp2px(15))) {text[i] = 1;for (int j = 0; j < 7; j++) {if (i != j) {text[j] = 0;}}if (Looper.getMainLooper() == Looper.myLooper()) {invalidate();} else {postInvalidate();}}}return super.onTouchEvent(event);}/** * 集成animation的一个动画类 *  * @author 李垭超 */private class HistogramAnimation extends Animation {protected void applyTransformation(float interpolatedTime,Transformation t) {super.applyTransformation(interpolatedTime, t);if (interpolatedTime < 1.0f && flag == 2) {for (int i = 0; i < aniProgress.length; i++) {aniProgress[i] = (int) (progress[i] * interpolatedTime);}} else {for (int i = 0; i < aniProgress.length; i++) {aniProgress[i] = progress[i];}}invalidate();}}}


package com.example.statisticalchart;import android.annotation.SuppressLint;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Paint.Align;import android.graphics.Path;import android.graphics.PorterDuff;import android.graphics.PorterDuffXfermode;import android.graphics.Rect;import android.util.AttributeSet;import android.util.Log;import android.view.View;import android.view.animation.Animation;import android.view.animation.Transformation;public class LineChartView extends View {private Paint rectPaint;// 设置左侧为白色,显示数表private Paint hLinePaint;// 坐标轴水平内部 虚线画笔private Paint titlePaint;// 绘制文本的画笔private Paint linePaint;private Paint paint;// 矩形画笔 柱状图的样式信息private int[] text;// 折线的转折点int x, y, preX, preY;// 坐标轴左侧的数标private Bitmap mBitmap;// 坐标轴底部的星期数private String[] str = { "62", "72", "82", "92", "102", "112", "122","132", "142" };private HistogramAnimation ani;private int flag;public LineChartView(Context context) {super(context);init(context, null);}public LineChartView(Context context, AttributeSet attrs) {super(context, attrs);// TODO Auto-generated constructor stubinit(context, attrs);}private void init(Context context, AttributeSet attrs) {text = new int[] { 6, 5, 5, 4, 5, 3, 2, 3, 1, 1 };ani = new HistogramAnimation();ani.setDuration(4000);rectPaint = new Paint();titlePaint = new Paint();}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);linePaint = new Paint();//titlePaint.setAntiAlias(true);Rect bundle1 = new Rect();Rect bundle2 = new Rect();hLinePaint = new Paint();int perWidth = getWidth() / 10;// 将宽度分为10部分int hPerHeight = getHeight() / 10;// 将高度分为10部分rectPaint.setColor(Color.WHITE);canvas.drawRect(0, 0, dp2px(30), getHeight(), rectPaint);// 画一块白色区域Path path = new Path();// 折线图的路径mBitmap = Bitmap.createBitmap(getWidth(), getHeight(),Bitmap.Config.ARGB_8888);Canvas mCanvas = new Canvas(mBitmap);for (int i = 0; i < 10; i++) {// 画x线,并在左侧显示相应的数值hLinePaint.setTextAlign(Align.CENTER);hLinePaint.setColor(Color.WHITE);y = i * hPerHeight;if (i == 2) {hLinePaint.setStrokeWidth(4);for (int j = 0; j < 10; j++) {canvas.drawLine(dp2px(30) + j * perWidth, y, dp2px(28)+ (j + 1) * perWidth, y, hLinePaint);}titlePaint.setTextSize(sp2px(20));titlePaint.getTextBounds(str[i - 1], 0, str[i - 1].length(),bundle1);canvas.drawText(str[i - 1], dp2px(25) - bundle1.width(), i* hPerHeight + (bundle1.height() / 2), titlePaint);} else {hLinePaint.setStrokeWidth(1);canvas.drawLine(dp2px(30), y, getWidth(), y, hLinePaint);if (i != 0) {titlePaint.setTextSize(sp2px(15));titlePaint.getTextBounds(str[i - 1], 0,str[i - 1].length(), bundle2);canvas.drawText(str[i - 1], dp2px(25) - bundle2.width(), i* hPerHeight + (bundle2.height() / 2), titlePaint);}}x = i * perWidth + dp2px(30);if (i == 0) {path.moveTo(x, text[i] * hPerHeight);} else {path.lineTo(x, text[i] * hPerHeight);}linePaint.setColor(Color.parseColor("#bb2222"));linePaint.setAntiAlias(true);paint = new Paint();paint.setColor(Color.RED);paint.setStyle(Paint.Style.STROKE);paint.setStrokeWidth(dp2px(1));if (i != 0) {mCanvas.drawCircle(x, text[i] * hPerHeight, dp2px(3), linePaint);}paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER));mCanvas.drawPath(path, paint);}paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));paint.setStyle(Paint.Style.FILL);mCanvas.drawRect(preX + dp2px(30), 0, getWidth(), getHeight(), paint);canvas.drawBitmap(mBitmap, 0, 0, null);// Log.i("tag", "onDraw()1111");}private int dp2px(int value) {float v = getContext().getResources().getDisplayMetrics().density;return (int) (v * value + 0.5f);}private int sp2px(int value) {float v = getContext().getResources().getDisplayMetrics().scaledDensity;return (int) (v * value + 0.5f);}public void start(int flag) {startAnimation(ani);this.flag = flag;}/** * 集成animation的一个动画类 *  * @author  */private class HistogramAnimation extends Animation {@Overrideprotected void applyTransformation(float interpolatedTime,Transformation t) {super.applyTransformation(interpolatedTime, t);if (interpolatedTime < 1.0f && flag == 2) {preX = (int) ((getWidth() - dp2px(30)) * interpolatedTime);} else {preX = getWidth();}invalidate();}}}

package com.example.statisticalchart;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.PorterDuff;import android.graphics.PorterDuffXfermode;import android.graphics.Rect;import android.graphics.RectF;import android.util.AttributeSet;import android.util.Log;import android.view.View;import android.view.animation.Animation;import android.view.animation.Transformation;public class PinChart extends View {static Canvas c;private Paint[] mPaints;private RectF mBigOval;float[] mSweep = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };private int preWidth;private mAnimation ani;private int flag;private int centerX;private int centerY;int valueX;int valueY;public static float[] humidity = { 110, 60, 50, 50, 40, 30, 10, 10 };private String str[] = { "数据24%", "数据19%", "数据21%", "其他18%", "数据3%","数据3%", "数据4%", "数据6%" };private final String color[] = { "#2cbae7", "#ffa500", "#ff5b3b","#9fa0a4", "#6a71e5", "#f83f5d", "#64a300", "#64ef85" };public PinChart(Context context) {super(context);initView();}public PinChart(Context context, AttributeSet atr) {super(context, atr);initView();}private void initView() {ani = new mAnimation();ani.setDuration(2000);}@Overrideprotected void onDraw(Canvas canvas) {canvas.drawColor(Color.TRANSPARENT);// 设置背景颜色(透明)mPaints = new Paint[humidity.length];for (int i = 0; i < humidity.length; i++) {mPaints[i] = new Paint();mPaints[i].setAntiAlias(true);mPaints[i].setStyle(Paint.Style.FILL);mPaints[i].setColor(Color.parseColor(color[i]));}int cicleWidth = getWidth() - dp2px(60);centerX = getWidth() / 2;centerY = dp2px(10) + cicleWidth / 2;preWidth = (getWidth() - dp2px(40)) / 4;int half = getWidth() / 2;mBigOval = new RectF();// 饼图的四周边界mBigOval.top = dp2px(10);mBigOval.left = half - cicleWidth / 2;mBigOval.bottom = dp2px(10) + cicleWidth;mBigOval.right = half + cicleWidth / 2;float start = -180;Rect bounds = new Rect();for (int i = 0; i < humidity.length; i++) {canvas.drawArc(mBigOval, start, mSweep[i], true, mPaints[i]);if (humidity[i] > 45) {mPaints[i].setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));mPaints[i].setAntiAlias(true);mPaints[i].setColor(Color.WHITE);mPaints[i].getTextBounds(str[i], 0, str[i].length(), bounds);mPaints[i].setTextSize(sp2px(15));measureText(start + 180, humidity[i], cicleWidth / 3, i);canvas.drawText(str[i], valueX - mPaints[i].measureText(str[i])/ 2, valueY + bounds.height() / 2, mPaints[i]);}start += humidity[i];int j = 1;int k;if (i < 4) {j = 0;k = i;} else {j = 1;k = i - 4;}mPaints[i] = new Paint();mPaints[i].setAntiAlias(true);mPaints[i].setStyle(Paint.Style.FILL);mPaints[i].setColor(Color.parseColor(color[i]));canvas.drawRect(new RectF(dp2px(20) + preWidth * k, cicleWidth+ dp2px(j * 30 + 20), dp2px(20) + preWidth * (k + 1),cicleWidth + dp2px(50 + j * 30)), mPaints[i]);mPaints[i].setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER));mPaints[i].setAntiAlias(true);mPaints[i].setColor(Color.WHITE);mPaints[i].getTextBounds(str[i], 0, str[i].length(), bounds);mPaints[i].setTextSize(sp2px(15));canvas.drawText(str[i], dp2px(20) + preWidth * k + preWidth / 2- mPaints[i].measureText(str[i]) / 2, cicleWidth+ dp2px(j * 30 + 20)+ (dp2px(30) / 2 + bounds.height() / 2), mPaints[i]);}}/** * 显示相应区域字开始的x,y坐标 *  * @param start * @param angle * @param radius * @param i */private void measureText(float start, float angle, int radius, int i) {float temp = start + (angle / 2);if (temp < 90) {valueX = (int) (centerX - Math.abs(radius* Math.sin((temp / 180) * Math.PI)));valueY = (int) (centerY - Math.abs(radius* Math.cos((temp / 180) * Math.PI)));} else if (temp > 90 && temp < 180) {temp = 180 - temp;valueX = centerX+ (int) Math.abs((radius * Math.cos((temp / 180) * Math.PI)));valueY = centerY- (int) Math.abs((radius * Math.sin((temp / 180) * Math.PI)));} else if (temp > 180 && temp < 270) {temp = temp - 180;valueX = centerX+ (int) Math.abs((radius * Math.cos((temp / 180) * Math.PI)));valueY = centerY+ (int) Math.abs((radius * Math.sin((temp / 180) * Math.PI)));} else {temp = 360 - temp;valueX = centerX- (int) Math.abs((radius * Math.cos((temp / 180) * Math.PI)));valueY = centerY+ (int) Math.abs((radius * Math.sin((temp / 180) * Math.PI)));}}private int sp2px(int value) {float v = getResources().getDisplayMetrics().scaledDensity;return (int) (value * v + 0.5f);}private int dp2px(int value) {float v = getResources().getDisplayMetrics().density;return (int) (value * v + 0.5f);}public void start(int flag) {startAnimation(ani);this.flag = flag;}class mAnimation extends Animation {@Overrideprotected void applyTransformation(float interpolatedTime,Transformation t) {super.applyTransformation(interpolatedTime, t);if (interpolatedTime < 1.0f && flag == 2) {for (int i = 0; i < humidity.length; i++) {mSweep[i] = humidity[i] * interpolatedTime;}} else if (flag == 1) {for (int i = 0; i < humidity.length; i++) {mSweep[i] = 0;}} else {for (int i = 0; i < humidity.length; i++) {mSweep[i] = humidity[i];}}invalidate();}}}

以上都是核心代码,并不是全部的代码,起他部分的代码就不贴出来了,感兴趣的朋友可以下载来研究研究。


4 0