您的位置:首页 > 移动开发 > Android开发

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

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

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

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







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

一、demo的结构

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



二、核心代码

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

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

[java] view
plaincopy

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;

@Override

protected 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()) {

@Override

public Fragment getItem(int position) {

return fragments.get(position);

}

@Override

public int getCount() {

return fragments.size();

}

};

viewPager.setAdapter(adapter);

viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {

@Override

public void onPageScrolled(int position, float positionOffset,

int positionOffsetPixels) {

}

@Override

public 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;

}

}

@Override

public void onPageScrollStateChanged(int state) {

}

});

}

}

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

[java] view
plaincopy

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")

@Override

protected 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, textPaint

canvas.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();

}

}

}

[java] view
plaincopy

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 stub

init(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();

}

@Override

protected 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 {

@Override

protected 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();

}

}

}

[java] view
plaincopy

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

}

@Override

protected 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 {

@Override

protected 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();

}

}

}

以上都是核心代码,并不是全部的代码,起他部分的代码就不贴出来了,感兴趣的朋友可以下载来研究研究。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: