Android 曲线图的绘制示例代码

时间:2021-05-21

本文介绍了Android 曲线图的绘制示例代码,分享给大家,具体如下:

效果展示

效果展示.gif

使用方式

// 初始化数据表格相关with(mTableView) { // 配置坐标系 setupCoordinator("日", "人", 0f, 5f, 10f, 15f, 20f, 25f, 30f) // 添加曲线, 确保纵坐标的数值位数相等 addWave(ContextCompat.getColor(this@MainActivity, R.color.colorYellow), false, 0f, 10f, 30f, 54f, 30f, 100f, 10f) addWave(ContextCompat.getColor(this@MainActivity, R.color.colorGreen), false, 0f, 30f, 20f, 20f, 46f, 25f, 5f) addWave(ContextCompat.getColor(this@MainActivity, R.color.colorPink), false, 0f, 30f, 20f, 50f, 46f, 30f, 30f) addWave(Color.parseColor("#8596dee9"), true, 0f, 15f, 10f, 10f, 40f, 20f, 5f)}

实现思路

  • 横坐标是固定的, 纵坐标需要跟随曲线传入的数值去动态的调整
  • 绘制坐标轴: 纵横交错的网格
  • 根据用户传入坐标数值去绘制坐标轴上的数值
  • 给X轴和Y轴添加单位信息
  • 根据用户传入的具体的数值绘制曲线(这里不采用Bezier, 不容易精确的控制顶点的位置)
  • 绘制填充效果
  • 添加属性动画
  • 代码实现

    /** * Created by FrankChoo on 2017/12/29. * Email: frankchoochina@gmail.com * Version: 1.0 * Description: 表格自定义View */public class TableView extends View { private List<WaveConfigData> mWaves;// 数值集合 // 坐标轴的数值 private int mCoordinateYCount = 8; private float[] mCoordinateXValues;// 外界传入 private float[] mCoordinateYValues;// 动态计算 // 坐标的单位 private String mXUnit; private String mYUnit; // 所有曲线中所有数据中的最大值 private float mGlobalMaxValue;// 用于确认是否需要调整坐标系 private Paint mCoordinatorPaint; private Paint mTextPaint; private Paint mWrapPaint; // 坐标轴上描述性文字的空间大小 private int mTopUnitHeight;// 顶部Y轴单位高度 private int mBottomTextHeight; private int mLeftTextWidth; // 网格尺寸 private int mGridWidth, mGridHeight; private float mAnimProgress; public TableView(Context context) { this(context, null); } public TableView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); } public TableView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); post(new Runnable() { @Override public void run() { showAnimator(); } }); } private void init() { // 初始化数据集合的容器 mWaves = new ArrayList<>(); // 坐标系的单位 mBottomTextHeight = dp2px(40);// X轴底部字体的高度 mLeftTextWidth = mBottomTextHeight;// Y轴左边字体的宽度 mTopUnitHeight = dp2px(30);// 顶部Y轴的单位 // 初始化坐标轴Paint mCoordinatorPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); mCoordinatorPaint.setColor(Color.LTGRAY); // 初始化文本Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); mTextPaint.setColor(Color.GRAY); mTextPaint.setTextSize(sp2px(12)); // 初始化曲线Paint mWrapPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG); mWrapPaint.setPathEffect(new CornerPathEffect(200f)); } /** * 配置坐标轴信息 * * @param xUnit X 轴的单位 * @param yUnit Y 轴的单位 * @param coordinateXValues X 坐标轴上的数值 */ public void setupCoordinator(String xUnit, String yUnit, float... coordinateXValues) { mXUnit = xUnit; mYUnit = yUnit; mCoordinateXValues = coordinateXValues; } /** * 添加一条曲线, 确保与横坐标的数值对应 * * @param color * @param isCoverRegion * @param values */ public void addWave(int color, boolean isCoverRegion, float... values) { mWaves.add(new WaveConfigData(color, isCoverRegion, values)); // 根据value的值去计算纵坐标的数值 float maxValue = 0; for (float value : values) { maxValue = Math.max(maxValue, value); } if (maxValue < mGlobalMaxValue) return; mGlobalMaxValue = maxValue; // 保证网格的数值都为 5 的倍数 float gridValue = mGlobalMaxValue / (mCoordinateYCount - 1); if (gridValue % 5 != 0) { gridValue += 5 - (gridValue % 5); } // 给纵坐标的数值赋值 mCoordinateYValues = new float[mCoordinateYCount]; for (int i = 0; i < mCoordinateYCount; i++) { mCoordinateYValues[i] = i * gridValue; } invalidate(); } @Override protected void onDraw(Canvas canvas) { drawCoordinate(canvas); drawWrap(canvas); } public void showAnimator() { ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f).setDuration(1000); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { mAnimProgress = (float) animation.getAnimatedValue(); invalidate(); } }); animator.start(); } /** * 绘制坐标系 */ private void drawCoordinate(Canvas canvas) { Point start = new Point(); Point stop = new Point(); // 1. 绘制横轴线和纵坐标单位 int xLineCount = mCoordinateYValues.length; mGridHeight = (getHeight() - getPaddingTop() - getPaddingBottom() - mBottomTextHeight - mTopUnitHeight) / (xLineCount - 1); for (int i = 0; i < xLineCount; i++) { start.x = getPaddingLeft() + mLeftTextWidth; start.y = getHeight() - getPaddingBottom() - mBottomTextHeight - mGridHeight * i; stop.x = getRight() - getPaddingRight(); stop.y = start.y; // 绘制横轴线 canvas.drawLine(start.x, start.y, stop.x, stop.y, mCoordinatorPaint); // 绘制纵坐标单位 if (i == 0) continue; String drawText = String.valueOf((int) mCoordinateYValues[i]); Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt(); float offsetY = ((fontMetrics.bottom - fontMetrics.top) / 2 + fontMetrics.bottom) / 2; float baseLine = start.y + offsetY; float left = getPaddingLeft() + mLeftTextWidth / 2 - mTextPaint.measureText(drawText) / 2; canvas.drawText(drawText, left, baseLine, mTextPaint); // 绘制Y轴单位 if (i == xLineCount - 1) { drawText = mYUnit; baseLine = getPaddingTop() + mTopUnitHeight / 2; canvas.drawText(drawText, left, baseLine, mTextPaint); } } // 2. 绘制纵轴线和横坐标单位 int yLineCount = mCoordinateXValues.length; mGridWidth = (getWidth() - getPaddingLeft() - getPaddingRight() - mLeftTextWidth) / (yLineCount - 1); for (int i = 0; i < yLineCount; i++) { start.x = getPaddingTop() + mLeftTextWidth + mGridWidth * i; start.y = getPaddingTop() + mTopUnitHeight; stop.x = start.x; stop.y = getHeight() - mBottomTextHeight - getPaddingBottom(); // 绘制纵轴线 canvas.drawLine(start.x, start.y, stop.x, stop.y, mCoordinatorPaint); // 绘制横坐标单位 String drawText = String.valueOf((int) mCoordinateXValues[i]); Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt(); float offsetY = ((fontMetrics.bottom - fontMetrics.top) / 2 + fontMetrics.bottom) / 2; float baseLine = getHeight() - getPaddingBottom() - mBottomTextHeight / 2 + offsetY; float left = start.x - mTextPaint.measureText(drawText) / 2; // 绘制X轴单位 if (i == 0) { drawText = mXUnit; left = getPaddingLeft() + mLeftTextWidth / 2 - mTextPaint.measureText(drawText) / 2; } canvas.drawText(drawText, left, baseLine, mTextPaint); } } /** * 绘制曲线 */ private void drawWrap(Canvas canvas) { canvas.clipRect(new RectF( mLeftTextWidth, getPaddingTop() + mTopUnitHeight, (getRight() - getPaddingRight()) * mAnimProgress, getHeight() - getPaddingBottom() - mBottomTextHeight) ); float yHeight = mGridHeight * (mCoordinateYCount - 1); for (WaveConfigData wave : mWaves) { Path path = new Path(); path.moveTo(0, getHeight()); float maxY = mCoordinateYValues[mCoordinateYCount - 1];// Y轴坐标的最大值 for (int index = 1; index < wave.values.length; index++) { path.lineTo( mLeftTextWidth + mGridWidth * index, getHeight() - getPaddingBottom() - mBottomTextHeight - yHeight * (wave.values[index] / maxY) ); } if (wave.isCoverRegion) { mWrapPaint.setStyle(Paint.Style.FILL); path.lineTo(getRight() - getPaddingRight(), getHeight()); path.close(); } else { mWrapPaint.setStyle(Paint.Style.STROKE); mWrapPaint.setStrokeWidth(10); } mWrapPaint.setColor(wave.color); canvas.drawPath(path, mWrapPaint); } } private int dp2px(float dp) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics()); } private int sp2px(float sp) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics()); } public static class WaveConfigData { int color; boolean isCoverRegion; float values[]; public WaveConfigData(int color, boolean isCoverRegion, float[] values) { this.color = color; this.isCoverRegion = isCoverRegion; this.values = values; } }}

    以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

    声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。

    相关文章