啥都不说,先上效果图:
1.总共三个小demo,先说图1
一、自定义曲线与柱状图的类
(1).BarChartView 柱状
public class BarChartView extends View { private Context mContext; private Paint mPaintBar; private Paint mPaintLline; private Paint mPaintText; //柱状条对应的颜色数组 private int[] colors; private int keduTextSpace = 10;//刻度与文字之间的间距 private int keduWidth = 20; //坐标轴上横向标识线宽度(X、Y轴交叉底部延伸的宽度) private int keduSpace = 100; //每个刻度之间的间距 px(Y轴上刻度的距离) private int itemSpace = 20;//柱状条之间的间距 private int itemWidth = 60;//柱状条的宽度 //刻度递增的值 private int valueSpace = 5; //绘制柱形图的坐标起点 private int startX; private int startY; private int mTextSize = 30; private int mMaxTextWidth; private int mMaxTextHeight; private Rect mXMaxTextRect; private Rect mYMaxTextRect; //是否要展示柱状条对应的值 private boolean isShowValueText = true; //数据值 private List<Integer> mData = new ArrayList<>(); private List<Integer> yAxisList = new ArrayList<>(); private List<String> xAxisList = new ArrayList<>(); public BarChartView(Context context) { this(context, null); } public BarChartView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public BarChartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.mContext = context; // colors = new int[]{ContextCompat.getColor(context, R.color.color_07f2ab), // ContextCompat.getColor(context, R.color.color_79d4d8), // ContextCompat.getColor(context, R.color.color_4388bc), // ContextCompat.getColor(context, R.color.color_07f2ab), // ContextCompat.getColor(context, R.color.color_4388bc)}; init(context, false); } private void init(Context context, boolean isUpdate) { if (!isUpdate) { initData(); } //设置边缘特殊效果 BlurMaskFilter PaintBGBlur = new BlurMaskFilter( 1, BlurMaskFilter.Blur.INNER); //绘制柱状图的画笔 mPaintBar = new Paint(); mPaintBar.setStyle(Paint.Style.FILL); mPaintBar.setStrokeWidth(4); mPaintBar.setMaskFilter(PaintBGBlur); //绘制直线的画笔 mPaintLline = new Paint(); mPaintLline.setColor(ContextCompat.getColor(context, R.color.color_xy)); mPaintLline.setAntiAlias(true); mPaintLline.setStrokeWidth(2); //绘制文字的画笔 mPaintText = new Paint(); mPaintText.setTextSize(mTextSize); mPaintText.setColor(ContextCompat.getColor(context, R.color.color_xy)); mPaintText.setAntiAlias(true); mPaintText.setStrokeWidth(1); mYMaxTextRect = new Rect(); mXMaxTextRect = new Rect(); mPaintText.getTextBounds(Integer.toString(yAxisList.get(yAxisList.size() - 1)), 0, Integer.toString(yAxisList.get(yAxisList.size() - 1)).length(), mYMaxTextRect); mPaintText.getTextBounds(xAxisList.get(xAxisList.size() - 1), 0, xAxisList.get(xAxisList.size() - 1).length(), mXMaxTextRect); //绘制的刻度文字的最大值所占的宽高 mMaxTextWidth = mYMaxTextRect.width() > mXMaxTextRect.width() ? mYMaxTextRect.width() : mXMaxTextRect.width(); mMaxTextHeight = mYMaxTextRect.height() > mXMaxTextRect.height() ? mYMaxTextRect.height() : mXMaxTextRect.height(); if (yAxisList.size() >= 2) { valueSpace = yAxisList.get(1) - yAxisList.get(0); } //文字+刻度宽度+文字与刻度之间间距 startX = mMaxTextWidth + keduWidth + keduTextSpace; //坐标原点 y轴起点 startY = keduSpace * (yAxisList.size() - 1) + mMaxTextHeight + (isShowValueText ? keduTextSpace : 0); } /** * 初始化数据 */ private void initData() { int[] dataY = {0, 4, 8, 12, 16, 20, 24, 28}; for (int i = 0; i < dataY.length; i++) { yAxisList.add(dataY[i]); } int[] data = {28, 24, 20, 16, 12}; for (int i = 0; i < data.length; i++) { mData.add(data[i]); } String[] xAxis = {"1月", "2月", "3月", "4月", "5月"}; for (int i = 0; i < mData.size(); i++) { xAxisList.add(xAxis[i]); } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); Log.e("TAG", "onMeasure()"); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); if (heightMode == MeasureSpec.AT_MOST) { if (keduWidth > mMaxTextHeight + keduTextSpace) { heightSize = (yAxisList.size() - 1) * keduSpace + keduWidth + mMaxTextHeight; } else { heightSize = (yAxisList.size() - 1) * keduSpace + (mMaxTextHeight + keduTextSpace) + mMaxTextHeight; } heightSize = heightSize + keduTextSpace + (isShowValueText ? keduTextSpace : 0);//x轴刻度对应的文字距离底部的padding:keduTextSpace } if (widthMode == MeasureSpec.AT_MOST) { widthSize = startX + mData.size() * itemWidth + (mData.size() + 1) * itemSpace; } Log.e("TAG", "heightSize=" + heightSize + "widthSize=" + widthSize); //保存测量结果 setMeasuredDimension(widthSize, heightSize); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Log.e("TAG", "onDraw()"); //从下往上绘制Y 轴 canvas.drawLine(startX, startY + keduWidth, startX, startY - (yAxisList.size() - 1) * keduSpace, mPaintLline); for (int i = 0; i < yAxisList.size(); i++) { //绘制Y轴的文字 Rect textRect = new Rect(); mPaintText.getTextBounds(Integer.toString(yAxisList.get(i)), 0, Integer.toString(yAxisList.get(i)).length(), textRect); canvas.drawText(Integer.toString(yAxisList.get(i)), (startX - keduWidth) - textRect.width() - keduTextSpace, startY - (i + 1) * keduSpace + keduSpace, mPaintText); //画X轴及上方横向的刻度线 canvas.drawLine(startX - keduWidth, startY - keduSpace * i, startX + mData.size() * itemWidth + itemSpace * (mData.size() + 1), startY - keduSpace * i, mPaintLline); } for (int j = 0; j < xAxisList.size(); j++) { //绘制X轴的文字 Rect rect = new Rect(); mPaintText.getTextBounds(xAxisList.get(j), 0, xAxisList.get(j).length(), rect); canvas.drawText(xAxisList.get(j), startX + itemSpace * (j + 1) + itemWidth * j + itemWidth / 2 - rect.width() / 2, startY + rect.height() + keduTextSpace, mPaintText); if (isShowValueText) { Rect rectText = new Rect(); mPaintText.getTextBounds(mData.get(j) + "", 0, (mData.get(j) + "").length(), rectText); //绘制柱状条上的值 canvas.drawText(mData.get(j) + "", startX + itemSpace * (j + 1) + itemWidth * j + itemWidth / 2 - rectText.width() / 2, (float) (startY - keduTextSpace - (mData.get(j) * (keduSpace * 1.0 / valueSpace))), mPaintText); } //绘制柱状条 // mPaintBar.setColor(colors[j]); //可以每个柱状图颜色不同 mPaintBar.setColor(ContextCompat.getColor(mContext, R.color.color_true)); //(mData.get(j) * (keduSpace * 1.0 / valueSpace)):为每个柱状条所占的高度值px int initx = startX + itemSpace * (j + 1) + j * itemWidth; canvas.drawRect(initx, (float) (startY - (mData.get(j) * (keduSpace * 1.0 / valueSpace))), initx + itemWidth, startY, mPaintBar); } } /** * 根据真实的数据刷新界面 * * @param datas * @param xList * @param yList * private int itemSpace = 20;//柱状条之间的间距 * private int itemWidth = 60;//柱状条的宽度 */ public void updateValueData( List<Integer> datas, List<String> xList, List<Integer> yList , int itemSpace ,int itemWidth) { this.itemSpace = itemSpace; this.itemWidth = itemWidth; this.mData = datas; this.xAxisList = xList; this.yAxisList = yList; init(mContext, true); invalidate(); } public void updateValueData( List<Integer> datas, List<String> xList, List<Integer> yList) { this.mData = datas; this.xAxisList = xList; this.yAxisList = yList; init(mContext, true); invalidate(); } } (2).LineChartView 曲线
public class LineChartView extends View { private Context mContext; //绘制坐标轴的画笔 private Paint mAxisPaint; //绘制曲线的画笔 private Paint mPaint; private Paint mPaint2; //绘制X轴上方的画笔 private Paint mXAxisLinePaint; private Paint mPaintText; //向上的曲线图的绘制起点(px) private int startX; private int startY; //向下的曲线图的绘制起点(px) private int downStartX; private int downStartY; //上方Y轴每单位刻度所占的像素值 private float YAxisUpUnitValue; //下方Y轴每单位刻度所占的像素值 private float YAxisDownUnitValue; //根据具体传入的数据,在坐标轴上绘制点 private Point[] mPoints; private Point[] mPoints2; //传入的数据,决定绘制的纵坐标值 private List<Integer> mDatas = new ArrayList<>(); private List<Integer> mDatas2 = new ArrayList<>(); //Y轴刻度集合 private List<Integer> mYAxisList = new ArrayList<>(); //X轴刻度集合 private List<String> mXAxisList = new ArrayList<>(); //X轴的绘制距离 private int mXAxisMaxValue; //Y轴的绘制距离 private int mYAxisMaxValue; //Y轴刻度间距(px) private int yAxisSpace = 120; //X轴刻度间距(px) private int xAxisSpace = 100; //Y轴刻度线宽度 private int mKeduWidth = 20; //X Y轴字体的大小 private float keduTextSize = 30; //刻度值距离坐标的padding距离 private int textPadinng = 10; //Y轴递增的实际值 private int yIncreaseValue; //true:绘制曲线 false:折线 private boolean isCurve = true; private Rect mYMaxTextRect; private Rect mXMaxTextRect; private int mMaxTextHeight; private int mMaxTextWidth; public LineChartView(Context context) { this(context, null); } public LineChartView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public LineChartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.mContext = context; initData(); initView(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); if (heightMode == MeasureSpec.AT_MOST) { heightSize = (mYAxisList.size() - 1) * yAxisSpace + mMaxTextHeight * 2 + textPadinng * 2; } if (widthMode == MeasureSpec.AT_MOST) { widthSize = startX + (mDatas.size() - 1) * xAxisSpace + mMaxTextWidth; } //保存测量结果 setMeasuredDimension(widthSize, heightSize); } private void initView() { //初始化画笔 mPaint = new Paint(); mPaint.setColor(ContextCompat.getColor(mContext, R.color.color_true)); mPaint.setStrokeWidth(5); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.STROKE); mPaint2 = new Paint(); mPaint2.setColor(ContextCompat.getColor(mContext, R.color.color_yuyue)); mPaint2.setStrokeWidth(5); mPaint2.setAntiAlias(true); mPaint2.setStyle(Paint.Style.STROKE); //绘制X,Y轴坐标的画笔 mAxisPaint = new Paint(); mAxisPaint.setColor(ContextCompat.getColor(mContext, R.color.color_xy)); mAxisPaint.setStrokeWidth(2); mAxisPaint.setAntiAlias(true); mAxisPaint.setStyle(Paint.Style.STROKE); //绘制坐标轴上方的横线的画笔 mXAxisLinePaint = new Paint(); mXAxisLinePaint.setColor(ContextCompat.getColor(mContext, R.color.color_xy)); mXAxisLinePaint.setStrokeWidth(1); mXAxisLinePaint.setAntiAlias(true); mXAxisLinePaint.setStyle(Paint.Style.STROKE); //绘制刻度值文字的画笔 mPaintText = new Paint(); mPaintText.setTextSize(keduTextSize); mPaintText.setColor(ContextCompat.getColor(mContext, R.color.color_black)); mPaintText.setAntiAlias(true); mPaintText.setStrokeWidth(5); mYMaxTextRect = new Rect(); mXMaxTextRect = new Rect(); mPaintText.getTextBounds(Integer.toString(mYAxisList.get(mYAxisList.size() - 1)), 0, Integer.toString(mYAxisList.get(mYAxisList.size() - 1)).length(), mYMaxTextRect); mPaintText.getTextBounds(mXAxisList.get(mXAxisList.size() - 1), 0, mXAxisList.get(mXAxisList.size() - 1).length(), mXMaxTextRect); //绘制的刻度文字的最大值所占的宽高 mMaxTextWidth = mYMaxTextRect.width() > mXMaxTextRect.width() ? mYMaxTextRect.width() : mXMaxTextRect.width(); mMaxTextHeight = mYMaxTextRect.height() > mXMaxTextRect.height() ? mYMaxTextRect.height() : mXMaxTextRect.height(); //指定绘制的起始位置 startX = mMaxTextWidth + textPadinng + mKeduWidth; //坐标原点Y的位置(+1的原因:X轴画笔的宽度为2 ; +DP2PX.dip2px(mContext, 5)原因:为刻度文字所占的超出的高度 )——>解决曲线画到最大刻度值时,显示高度不够,曲线显示扁扁的问题 startY = yAxisSpace * (mYAxisList.size() - 1) + mMaxTextHeight; if (mYAxisList.size() >= 2) { yIncreaseValue = mYAxisList.get(1) - mYAxisList.get(0); } //X轴绘制距离 mXAxisMaxValue = (mDatas.size() - 1) * xAxisSpace; //Y轴绘制距离 mYAxisMaxValue = (mYAxisList.size() - 1) * yAxisSpace; //坐标起始点Y轴高度=(startY+mKeduWidth) 下方文字所占高度= DP2PX.dip2px(mContext, keduTextSize) // int viewHeight = startY + 2 * mKeduWidth + DP2PX.dip2px(mContext, keduTextSize); int viewHeight = startY + 2 * mKeduWidth + (int)keduTextSize; //viewHeight=121 Log.e("TAG", "viewHeight=" + viewHeight); } /** * 根据传入的数据,确定绘制的点 * * @return */ private Point[] initPoint() { Point[] points = new Point[mDatas.size()]; for (int i = 0; i < mDatas.size(); i++) { Integer ybean = mDatas.get(i); int drawHeight = (int) (startY * 1.0 - (ybean * yAxisSpace * 1.0 / yIncreaseValue)); int startx = startX + xAxisSpace * i; points[i] = new Point(startx, drawHeight); } Log.e("TAG", "startX=" + startX + "---startY=" + startY); return points; } private Point[] initPoint2() { Point[] points = new Point[mDatas2.size()]; for (int i = 0; i < mDatas2.size(); i++) { Integer ybean = mDatas2.get(i); int drawHeight = (int) (startY * 1.0 - (ybean * yAxisSpace * 1.0 / yIncreaseValue)); int startx = startX + xAxisSpace * i; points[i] = new Point(startx, drawHeight); } Log.e("TAG", "startX=" + startX + "---startY=" + startY); return points; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPoints = initPoint(); mPoints2 = initPoint2(); for (int i = 0; i < mYAxisList.size(); i++) { //Y轴方向递增的高度 int yAxisHeight = startY - yAxisSpace * i; //绘制X轴和上方横线 canvas.drawLine(startX - mKeduWidth, yAxisHeight, startX + (mDatas.size() - 1) * xAxisSpace, yAxisHeight, mXAxisLinePaint); //绘制左边Y轴刻度线 // canvas.drawLine(startX, yAxisHeight, startX - mKeduWidth, yAxisHeight, mAxisPaint); //绘制文字时,Y轴方向递增的高度 int yTextHeight = startY - yAxisSpace * i; //绘制Y轴刻度旁边的刻度文字值,10为刻度线与文字的间距 mPaintText.setTextAlign(Paint.Align.RIGHT); if (i == ( mYAxisList.size()-1)){ canvas.drawText(mYAxisList.get(i) + " ", (startX - mKeduWidth) - textPadinng, yTextHeight, mPaintText); }else { canvas.drawText(mYAxisList.get(i) + "", (startX - mKeduWidth) - textPadinng, yTextHeight, mPaintText); } } //绘制Y轴 canvas.drawLine(startX, startY, startX, startY - mYAxisMaxValue, mAxisPaint); //连接所有的数据点,画曲线 if (isCurve) { //画曲线 drawScrollLine(canvas); drawScrollLine2(canvas); } else { //画折线 drawLine(canvas); } //绘制X轴下面显示的文字 for (int i = 0; i < mXAxisList.size(); i++) { int xTextWidth = startX + xAxisSpace * i - mKeduWidth; //设置从起点位置的左边对齐绘制文字 mPaintText.setTextAlign(Paint.Align.LEFT); Rect rect = new Rect(); mPaintText.getTextBounds(mXAxisList.get(i), 0, mXAxisList.get(i).length(), rect); canvas.drawText(mXAxisList.get(i), startX - rect.width() / 2 + xAxisSpace * i, startY + rect.height() + textPadinng, mPaintText); } } /** * 绘制曲线-曲线图 * * @param canvas */ private void drawScrollLine(Canvas canvas) { Point startp; Point endp; for (int i = 0; i < mPoints.length - 1; i++) { startp = mPoints[i]; endp = mPoints[i + 1]; int wt = (startp.x + endp.x) / 2; Point p3 = new Point(); Point p4 = new Point(); p3.y = startp.y; p3.x = wt; p4.y = endp.y; p4.x = wt; Path path = new Path(); path.moveTo(startp.x, startp.y); path.cubicTo(p3.x, p3.y, p4.x, p4.y, endp.x, endp.y); canvas.drawPath(path, mPaint); } } private void drawScrollLine2(Canvas canvas) { Point startp; Point endp; for (int i = 0; i < mPoints2.length - 1; i++) { startp = mPoints2[i]; endp = mPoints2[i + 1]; int wt = (startp.x + endp.x) / 2; Point p3 = new Point(); Point p4 = new Point(); p3.y = startp.y; p3.x = wt; p4.y = endp.y; p4.x = wt; Path path = new Path(); path.moveTo(startp.x, startp.y); path.cubicTo(p3.x, p3.y, p4.x, p4.y, endp.x, endp.y); canvas.drawPath(path, mPaint2); } } /** * 绘制直线-折线图 * * @param canvas */ private void drawLine(Canvas canvas) { Point startp; Point endp; for (int i = 0; i < mPoints.length - 1; i++) { startp = mPoints[i]; endp = mPoints[i + 1]; canvas.drawLine(startp.x, startp.y, endp.x, endp.y, mPaint); } } private void initData() { //外界传入的数据,即为绘制曲线的每个点 mDatas.add(0); mDatas.add(10); mDatas.add(5); mDatas.add(20); mDatas.add(15); mDatas2.add(10); mDatas2.add(5); mDatas2.add(0); mDatas2.add(10); mDatas2.add(20); int[] mYAxisData = new int[]{0, 5, 10, 15, 20 ,25 ,30}; for (int i = 0; i < mYAxisData.length; i++) { mYAxisList.add(mYAxisData[i]); } //X轴数据 mXAxisList.add("06-01"); mXAxisList.add("06-02"); mXAxisList.add("06-03"); mXAxisList.add("06-04"); mXAxisList.add("06-05"); } /** * 传入数据,重新绘制图表 * * @param datas * @param yAxisData */ public void updateData(List<Integer> datas,List<Integer> datas2, List<String> xAxisData, List<Integer> yAxisData ,int xAxisSpace) { this.xAxisSpace = xAxisSpace; this.mDatas = datas; this.mDatas2 = datas2; this.mXAxisList = xAxisData; this.mYAxisList = yAxisData; initView(); postInvalidate(); } }
3.布局 activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:layout_marginRight="10dp" android:orientation="vertical"> <com.zdmtech.tongjiquxiandemo.LineChartView android:id="@+id/lineChartView" android:layout_width="match_parent" android:layout_height="wrap_content"/> <com.zdmtech.tongjiquxiandemo.BarChartView android:id="@+id/barChartView" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp"/> </LinearLayout> </RelativeLayout>
4.Activity 代码
public class MainActivity extends AppCompatActivity { private Activity activity; private LineChartView lineChartView ; private BarChartView barChartView ; /** * 线形图 * */ //传入的数据,决定绘制的纵坐标值 private List<Integer> mDatas = new ArrayList<>(); private List<Integer> mDatas2 = new ArrayList<>(); //Y轴刻度集合 private List<Integer> mYAxisList = new ArrayList<>(); //X轴刻度集合 private List<String> mXAxisList = new ArrayList<>(); /** * 柱状图 * */ //柱状体数据 集合 private List<Integer> mData = new ArrayList<>(); //Y轴刻度集合(相差大小一定) private List<Integer> yAxisList = new ArrayList<>(); //X轴刻度集合 private List<String> xAxisList = new ArrayList<>(); private int width_bar = 0 ; private int width_chart = 0 ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); activity = this; initData(); initDataBar(); lineChartView = findViewById(R.id.lineChartView); barChartView = findViewById(R.id.barChartView); /** * 获取控件宽高 * 线性 * */ ViewTreeObserver vto = lineChartView.getViewTreeObserver(); vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { lineChartView.getViewTreeObserver().removeGlobalOnLayoutListener(this); /** * 线形图更新数据 * */ width_chart = lineChartView.getWidth(); Log.e("++++++" ,"width_chart = "+width_chart); int chartWidth = (width_chart-80)/mDatas.size(); Log.e("++++++" ,"chartWidth = "+chartWidth); lineChartView.updateData(mDatas ,mDatas2 ,mXAxisList ,mYAxisList ,chartWidth); } }); /** * 获取控件宽高 * 柱状 * */ ViewTreeObserver vto2 = barChartView.getViewTreeObserver(); vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { barChartView.getViewTreeObserver().removeGlobalOnLayoutListener(this); /** * 柱状图更新数据 * */ width_bar = barChartView.getWidth(); Log.e("++++++" ,"width_bar = "+width_bar); int barWidth = width_bar/(mData.size()+1); int itemSpace = (barWidth*3)/10; int itemWidth = (barWidth*7)/10; Log.e("++++++" ,"itemSpace = "+itemSpace); Log.e("++++++" ,"itemWidth = "+itemWidth); Log.e("++++++" ,"xAxisList = "+xAxisList.size()); barChartView.updateValueData(mData, xAxisList , yAxisList ,itemSpace ,itemWidth); // barChartView.updateValueData(mData, xAxisList , yAxisList); } }); } private void initData() { //外界传入的数据,即为绘制曲线的每个点 mDatas.add(30); mDatas.add(20); mDatas.add(10); mDatas.add(5); mDatas.add(0); mDatas.add(25); mDatas.add(10); mDatas.add(30); mDatas.add(10); mDatas.add(5); mDatas2.add(0); mDatas2.add(30); mDatas2.add(10); mDatas2.add(5); mDatas2.add(0); mDatas2.add(10); mDatas2.add(15); mDatas2.add(20); mDatas2.add(25); mDatas2.add(30); int[] mYAxisData = new int[]{0, 5, 10, 15, 20 ,25 ,30}; for (int i = 0; i < mYAxisData.length; i++) { mYAxisList.add(mYAxisData[i]); } //X轴数据 mXAxisList.add("06-01"); mXAxisList.add("06-02"); mXAxisList.add("06-03"); mXAxisList.add("06-04"); mXAxisList.add("06-05"); mXAxisList.add("06-06"); mXAxisList.add("06-07"); mXAxisList.add("06-08"); mXAxisList.add("06-09"); mXAxisList.add("06-10"); } /** * 初始化数据 */ private void initDataBar() { int[] dataY = {0, 4, 8, 12, 16, 20, 24, 28}; for (int i = 0; i < dataY.length; i++) { yAxisList.add(dataY[i]); } int[] data = {28, 24, 20, 16, 12, 10, 8, 6, 4, 2}; for (int i = 0; i < data.length; i++) { mData.add(data[i]); } String[] xAxis = {"1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月"}; for (int i = 0; i < mData.size(); i++) { xAxisList.add(xAxis[i]); } } }
5.Demo下载地址 https://download.youkuaiyun.com/download/yyxhzdm/15365958
后期会添加XY轴根据数据的长短与大小来自动适配XY轴数据
二、利用RecycleView来实现柱状统计图
1.布局类 bar_chart_layout
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/chart" android:layout_width="match_parent" android:layout_height="200dp"> <View android:id="@+id/view_y" android:layout_width="0.5dp" android:layout_height="match_parent" android:layout_marginLeft="40dp" android:layout_marginTop="30dp" android:background="#666" /> <View android:id="@+id/view_x" android:layout_width="match_parent" android:layout_height="0.5dp" android:layout_alignBottom="@+id/view_y" android:layout_marginLeft="40dp" android:layout_marginBottom="20dp" android:background="#666" /> <TextView android:id="@+id/text_low" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_marginLeft="5dp" android:layout_marginBottom="30dp" android:drawablePadding="2dp" android:gravity="right" android:minWidth="20dp" android:text="低" android:textSize="10sp" /> <TextView android:id="@+id/text_mid" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_marginLeft="5dp" android:layout_marginBottom="95dp" android:drawablePadding="2dp" android:gravity="right" android:minWidth="20dp" android:text="中" android:textSize="10sp" /> <TextView android:id="@+id/text_hi" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_marginLeft="5dp" android:layout_marginBottom="150dp" android:drawablePadding="2dp" android:gravity="right" android:minWidth="20dp" android:text="高" android:textSize="10sp" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rcv" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_alignParentBottom="true" android:layout_marginLeft="45dp"/> </RelativeLayout>
(2).item 的布局bar_chart_item
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical" android:layout_width="50dp" android:id="@+id/rl_chart_item" android:layout_height="match_parent"> <TextView android:id="@+id/name" android:layout_width="wrap_content" android:layout_height="20dp" android:textSize="11sp" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" android:singleLine="true" android:maxLines="1" android:ellipsize="end" android:text="" android:textColor="#999" tools:text = "name"/> <ProgressBar android:id="@+id/pb_vertical" android:layout_width="20dp" android:layout_height="150dp" android:indeterminateOnly="false" android:layout_above="@+id/name" android:max="100" android:progress="60" android:layout_centerHorizontal="true" android:layout_marginBottom="2dp" android:progressDrawable="@drawable/progress_normal" /> </RelativeLayout>
2.adapter
public class BarAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private final LayoutInflater mLayoutInflater; private List<ChartData> mDatas; private float mLowStandard; private float mHighStandard; private Context mContext; private int indexSelected = -1; public BarAdapter(Context context, float lowStandard, float highStandard,List<ChartData> mDatas) { mLayoutInflater = LayoutInflater.from(context); mContext = context; mLowStandard = lowStandard; mHighStandard = highStandard; this.mDatas=mDatas; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { ViewHolder holder = new ViewHolder(mLayoutInflater.inflate(R.layout.bar_chart_item, parent, false)); return holder; } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { final ChartData data = mDatas.get(position); TextView tv_name = ((ViewHolder) holder).getView(R.id.name); if(position == indexSelected){ //当用户选中时改变文字颜色 tv_name.setTextColor(Color.RED); } else { tv_name.setTextColor(Color.GRAY); } ProgressBar progressBar = ((ViewHolder) holder).getView(R.id.pb_vertical); float pro = data.getProgress(); progressBar.setVisibility(View.VISIBLE); if (pro > mHighStandard) {//大于“高”时使用一种颜色的Drawable progressBar.setProgressDrawable(ContextCompat.getDrawable(mContext, R.drawable.progress_high)); } else if (pro < mLowStandard) { progressBar.setProgressDrawable(ContextCompat.getDrawable(mContext, R.drawable.progress_low)); } else {//小于“低”时使用一种颜色的Drawable progressBar.setProgressDrawable(ContextCompat.getDrawable(mContext, R.drawable.progress_normal)); } progressBar.setProgress(Math.round(pro)); tv_name.setText(data.getName()); } @Override public int getItemCount() { if (mDatas == null) { return 0; } return mDatas.size(); } public void setSelected(int position) { if(indexSelected == -1){ indexSelected = position; notifyItemChanged(indexSelected); } else { int a = indexSelected; indexSelected = position; notifyItemChanged(indexSelected); notifyItemChanged(a); } } public class ViewHolder extends RecyclerView.ViewHolder { public ViewHolder(View view) { super(view); } public void setText(int viewId, String text){ TextView tv = (TextView) itemView.findViewById(viewId); tv.setText(text); } public <T extends View> T getView(int viewId){ return (T) itemView.findViewById(viewId); } } }
3.Activity
public class MainActivity extends AppCompatActivity { protected View viewY; protected View viewX; protected TextView textLow; protected TextView textMid; protected TextView textHi; protected RecyclerView rcv; protected RelativeLayout chart; BarAdapter mdapter; List<ChartData> mDatas=new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); super.setContentView(R.layout.activity_main); initView(); initData(); initAdapter(); } private void initView() { viewY = (View) findViewById(R.id.view_y); viewX = (View) findViewById(R.id.view_x); textLow = (TextView) findViewById(R.id.text_low); textMid = (TextView) findViewById(R.id.text_mid); textHi = (TextView) findViewById(R.id.text_hi); rcv = (RecyclerView) findViewById(R.id.rcv); LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this); linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); rcv.setHasFixedSize(true); rcv.setLayoutManager(linearLayoutManager); chart = (RelativeLayout) findViewById(R.id.chart); } private void initData(){ for (int i=1;i<=25;i++){ ChartData item=new ChartData(); item.setName("第"+i+"个"); item.setProgress(i*4); mDatas.add(item); } } private void initAdapter(){ mdapter = new BarAdapter(this, 25, 75,mDatas); rcv.setAdapter(mdapter); mdapter.notifyDataSetChanged(); } }
4.demo下载地址 https://download.youkuaiyun.com/download/yyxhzdm/15365449
三、自定义底部斜体字的柱状图
1.自定义柱状图类 : BarChartView
package com.example.mybarchartrecycleviewtwo; import android.app.Activity; 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.Path; import android.graphics.Rect; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; import java.util.List; /** * 自定义组件:条形统计图 * Created by hanj on 14-12-30. */ public class BarChartView extends View { private int screenW, screenH; private List<BarChartItemBean> mItems; //max value in mItems. private float maxValue; //max height of the bar private int maxHeight; private int[] mBarColors = new int[]{Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW, Color.MAGENTA, Color.CYAN}; private Paint barPaint, linePaint, textPaint; private Rect barRect, leftWhiteRect, rightWhiteRect; private Path textPath; private int leftMargin, topMargin, smallMargin; //the width of one bar item private int barItemWidth; //the spacing between two bar items. private int barSpace; //the width of the line. private int lineStrokeWidth; /** * The x-position of y-index and the y-position of the x-index.. */ private float x_index_startY, y_index_startX; private Bitmap arrowBmp; private Rect x_index_arrowRect, y_index_arrowRect; private static final int BG_COLOR = Color.parseColor("#FFFFFF"); //整体背景 public BarChartView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } private void init(Context context) { screenW = ScreenUtils.getScreenW(context); // screenH = (int) (ScreenUtils.getScreenH(context)*2/3); // Log.e("OkHttp" ,"screenH2222 = "+screenH); screenH = 1080; leftMargin = ScreenUtils.dp2px(context, 16); topMargin = ScreenUtils.dp2px(context, 15); smallMargin = ScreenUtils.dp2px(context, 6); barPaint = new Paint(); barPaint.setColor(mBarColors[0]); linePaint = new Paint(); lineStrokeWidth = ScreenUtils.dp2px(context, 1); linePaint.setStrokeWidth(lineStrokeWidth); textPaint = new Paint(); textPaint.setAntiAlias(true); barRect = new Rect(0, 0, 0, 0); textPath = new Path(); leftWhiteRect = new Rect(0, 0, 0, screenH); rightWhiteRect = new Rect(screenW - leftMargin, 0, screenW, screenH); arrowBmp = BitmapFactory.decodeResource(context.getResources(), R.drawable.arrow_up); } //标记是否已经获取过状态拉的高度 private boolean statusHeightHasGet; @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (!statusHeightHasGet) { subStatusBarHeight(); statusHeightHasGet = true; } //draw background canvas.drawColor(BG_COLOR); //bounds checkLeftMoving(); textPaint.setTextSize(ScreenUtils.dp2px(getContext(), 13)); for (int i = 0; i < mItems.size(); i++) { //draw bar rect barRect.left = (int) y_index_startX + barItemWidth * i + barSpace * (i + 1) - (int) leftMoving; barRect.top = topMargin * 2 + (int) (maxHeight * (1.0f - mItems.get(i).getItemValue() / maxValue)); barRect.right = barRect.left + barItemWidth; barPaint.setColor(mBarColors[i % mBarColors.length]); canvas.drawRect(barRect, barPaint); //draw type text String typeText = mItems.get(i).getItemType(); float textPathStartX = barRect.left + barItemWidth / 2 - (float) (Math.sin(Math.PI / 6)) * textPaint.measureText("好") / 2; float textPathStartY = barRect.bottom; textPath.reset(); textPath.moveTo(textPathStartX, textPathStartY); textPath.lineTo(textPathStartX + (float) (1000 * Math.tan(Math.PI / 6)), textPathStartY + 1000); canvas.drawTextOnPath(typeText, textPath, smallMargin * 1.5f, smallMargin * 2, textPaint); //draw value text String valueText = String.valueOf(mItems.get(i).getItemValue()); canvas.drawText(valueText, barRect.left - (textPaint.measureText(valueText) - barItemWidth) / 2, barRect.top - smallMargin, textPaint); } //draw left white space and right white space int c = barPaint.getColor(); barPaint.setColor(BG_COLOR); leftWhiteRect.right = (int) y_index_startX; canvas.drawRect(leftWhiteRect, barPaint); canvas.drawRect(rightWhiteRect, barPaint); barPaint.setColor(c); //draw x-index line. canvas.drawLine( y_index_startX - lineStrokeWidth / 2, x_index_startY, screenW - leftMargin, x_index_startY, linePaint); //draw y-index line. canvas.drawLine( y_index_startX, x_index_startY + lineStrokeWidth / 2, y_index_startX, topMargin / 2, linePaint); canvas.drawBitmap(arrowBmp, null, y_index_arrowRect, null); canvas.save(); canvas.rotate(90, (x_index_arrowRect.left + x_index_arrowRect.right) / 2, (x_index_arrowRect.top + x_index_arrowRect.bottom) / 2); canvas.drawBitmap(arrowBmp, null, x_index_arrowRect, null); canvas.restore(); //draw division value int maxDivisionValueHeight = (int) (maxHeight * 1.0f / maxValue * maxDivisionValue); textPaint.setTextSize(ScreenUtils.dp2px(getContext(), 12)); //Y轴字体大小 for (int i = 1; i <= 10; i++) { float startY = barRect.bottom - maxDivisionValueHeight * 0.1f * i; if (startY < topMargin / 2) { break; } canvas.drawLine(y_index_startX, startY, y_index_startX + 10, startY, linePaint); String text = String.valueOf((int) (maxDivisionValue * 0.1 * i)); canvas.drawText(text, y_index_startX - textPaint.measureText(text) - 5, startY + textPaint.measureText("0") / 2, textPaint); } } private float leftMoving; private float lastPointX; private float movingLeftThisTime = 0.0f; @Override public boolean onTouchEvent(@NonNull MotionEvent event) { int type = event.getAction(); switch (type) { case MotionEvent.ACTION_DOWN: lastPointX = event.getRawX(); break; case MotionEvent.ACTION_MOVE: float x = event.getRawX(); movingLeftThisTime = lastPointX - x; leftMoving += movingLeftThisTime; lastPointX = x; invalidate(); break; case MotionEvent.ACTION_UP: //smooth scroll new Thread(new SmoothScrollThread(movingLeftThisTime)).start(); break; default: return super.onTouchEvent(event); } return true; } /** * Check the value of leftMoving to ensure that the view is not out of the screen. */ private void checkLeftMoving() { if (leftMoving < 0) { leftMoving = 0; } if (leftMoving > (maxRight - minRight)) { leftMoving = maxRight - minRight; } } public List<BarChartItemBean> getItems() { return mItems; } public void setItems(List<BarChartItemBean> items) { if (items == null) { throw new RuntimeException("BarChartView.setItems(): the param items cannot be null."); } if (items.size() == 0) { return; } this.mItems = items; //Calculate the max value. maxValue = mItems.get(0).getItemValue(); for (BarChartItemBean bean : items) { if (bean.getItemValue() > maxValue) { maxValue = bean.getItemValue(); } } //Calculate the max division value. getRange(maxValue, 0); //Get the width of each bar. getBarItemWidth(screenW, items.size()); //Refresh the view. invalidate(); } private int maxRight, minRight; /** * Get the width of each bar which is depended on the screenW and item count. */ private void getBarItemWidth(int screenW, int itemCount) { //The min width of the bar is 50dp. int minBarWidth = ScreenUtils.dp2px(getContext(), 40); //The min width of spacing. int minBarSpacing = ScreenUtils.dp2px(getContext(), 30); barItemWidth = (screenW - leftMargin * 2) / (itemCount + 3); barSpace = (screenW - leftMargin * 2 - barItemWidth * itemCount) / (itemCount + 1); if (barItemWidth < minBarWidth || barSpace < minBarSpacing) { barItemWidth = minBarWidth; barSpace = minBarSpacing; } maxRight = (int) y_index_startX + lineStrokeWidth + (barSpace + barItemWidth) * mItems.size(); minRight = screenW - leftMargin - barSpace; } /** * Sub the height of status bar and action bar to get the accurate height of screen. */ private void subStatusBarHeight() { //The height of the status bar int statusHeight = ScreenUtils.getStatusBarHeight((Activity) getContext()); //The height of the actionBar ActionBar ab = ((MainActivity) getContext()).getSupportActionBar(); int abHeight = ab == null ? 0 : ab.getHeight(); screenH -= (statusHeight + abHeight); barRect.top = topMargin * 2; barRect.bottom = screenH - topMargin * 3; maxHeight = barRect.bottom - barRect.top; x_index_startY = barRect.bottom; x_index_arrowRect = new Rect(screenW - leftMargin, (int) (x_index_startY - 10), screenW - leftMargin + 10, (int) (x_index_startY + 10)); } //The max and min division value. private float maxDivisionValue, minDivisionValue; //Get the max and min division value by the max and min value in mItems. private void getRange(float maxValue, float minValue) { //max int scale = Utility.getScale(maxValue); float unscaledValue = (float) (maxValue / Math.pow(10, scale)); maxDivisionValue = (float) (getRangeTop(unscaledValue) * Math.pow(10, scale)); y_index_startX = getDivisionTextMaxWidth(maxDivisionValue) + 10; y_index_arrowRect = new Rect((int) (y_index_startX - 5), topMargin / 2 - 20, (int) (y_index_startX + 5), topMargin / 2); } private float getRangeTop(float value) { //value: [1,10) if (value < 1.2) { return 1.2f; } if (value < 1.5) { return 1.5f; } if (value < 2.0) { return 2.0f; } if (value < 3.0) { return 3.0f; } if (value < 4.0) { return 4.0f; } if (value < 5.0) { return 5.0f; } if (value < 6.0) { return 6.0f; } if (value < 8.0) { return 8.0f; } return 10.0f; } /** * Get the max width of the division value text. */ private float getDivisionTextMaxWidth(float maxDivisionValue) { Paint textPaint = new Paint(); textPaint.setTextSize(ScreenUtils.dp2px(getContext(), 10)); float max = textPaint.measureText(String.valueOf(maxDivisionValue * 0.1f)); for (int i = 2; i <= 10; i++) { float w = textPaint.measureText(String.valueOf(maxDivisionValue * 0.1f * i)); if (w > max) { max = w; } } return max; } /** * Use this thread to create a smooth scroll after ACTION_UP. */ private class SmoothScrollThread implements Runnable { float lastMoving; boolean scrolling = true; private SmoothScrollThread(float lastMoving) { this.lastMoving = lastMoving; scrolling = true; } @Override public void run() { while (scrolling) { long start = System.currentTimeMillis(); lastMoving = (int) (0.9f * lastMoving); leftMoving += lastMoving; checkLeftMoving(); postInvalidate(); if (Math.abs(lastMoving) < 5) { scrolling = false; } long end = System.currentTimeMillis(); if (end - start < 20) { try { Thread.sleep(20 - (end - start)); } catch (InterruptedException e) { e.printStackTrace(); } } } } } }
2.布局xml activity_main
<com.example.mybarchartrecycleviewtwo.BarChartView android:layout_marginTop="10dp" android:id="@+id/bar_chart" android:layout_width="match_parent" android:layout_height="400dp" android:background="#FFFFFF"/>
3.下载地址:https://download.youkuaiyun.com/download/yyxhzdm/15365417