Android 曲线图的绘制示例代码

本文介绍了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)
}

实现思路

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

代码实现

/**
 * 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;
    }
  }
}

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

您可能感兴趣的文章:

  • Android实现简易的柱状图和曲线图表实例代码
  • Android实现价格走势自定义曲线图
  • Android 自定义View实现芝麻分曲线图效果
  • Android 游戏开发中绘制游戏触摸轨迹的曲线图
(0)

相关推荐

  • Android 游戏开发中绘制游戏触摸轨迹的曲线图

    本篇文章主要来讲解怎样绘制游戏触摸轨迹的曲线图. 我们在onTouchEvent方法中,可以获取到触摸屏幕时手指触摸点的x.y坐标,如何用这些点形成一条无规则轨迹并把这条无规则轨迹曲线显示在屏幕上就是本篇文章的主旨内容. Android Path类 Android提供了一个Path类 , 顾名思义这个类可以设置曲线路径轨迹.任何无规则的曲线实际上都是由若干条线段组成,而线段的定义为两点之间最短的一条线.path类就 可以记录这两点之间的轨迹,那么若干个Path 就是我们须要绘制的无规则曲线. 下

  • Android实现价格走势自定义曲线图

    本文是引用开源图表库框架 MPAndroidChart的LineChart 地址:https://github.com/PhilJay/MPAndroidChart 1.需求: (1)动态添加RadioButton,点击改变下面的LineChart数据 (2)LineChart绘制价格走势图,只显示最低点的小圆点和View,手指滑动,MarkView数据变化. (3) 服务端返回端数据,不是每一天端数据,但是x轴显示的必须是每一天的数据,这里是有我自己处理过的.返回里需要显示点的数组,之前的时间

  • Android实现简易的柱状图和曲线图表实例代码

    前言 之前有写过一个图表lib,但是开发的速度,大多很难跟上产品需求变化的脚步,所以修改了下原先的图表库,支持图表下面能整合table显示对应的类目,用曲线替换了折线,支持多曲线的显示,增加了显示的动画,增加了一些可定制的属性,支持水平柱状图和叠加柱状图,以及多曲线图和饼状图的显示,下面话不多说了,来一起看看详细的介绍吧. 1.效果图 2.各种图表的使用方式 1.饼状图 这个和原先的使用一样,只不过增加了一个动画,可以参看之前的文章,饼状图使用. 2.水平多柱状图 2.1 xml布局 <well

  • Android 自定义View实现芝麻分曲线图效果

    1.简介 其实这个效果几天之前就写了,但是一直没有更新博客,本来想着把芝麻分雷达图也做好再发博客的,然后今天看到鸿洋的微信公众号有朋友发了芝麻分的雷达图,所以就算了,算是一个互补吧.平时文章也写的比较少,所以可能有点杂乱,有什么需要改进的地方欢迎给出建议,不胜感激. 效果图: 2.步骤: 初始化View的属性 初始化画笔 绘制代表最高分和最低分的两根虚线 绘制文字 绘制代表月份的属性 绘制芝麻分折线 绘制代表芝麻分的圆点 绘制选中分数的悬浮文字以及背景 处理点击事件 3.编码: 初始化View属

  • Android 曲线图的绘制示例代码

    本文介绍了Android 曲线图的绘制示例代码,分享给大家,具体如下: 效果展示 效果展示.gif 使用方式 // 初始化数据表格相关 with(mTableView) { // 配置坐标系 setupCoordinator("日", "人", /*这里是横坐标的值*/0f, 5f, 10f, 15f, 20f, 25f, 30f) // 添加曲线, 确保纵坐标的数值位数相等 addWave(ContextCompat.getColor(this@MainActiv

  • ImageView 实现Android colorPikcer 选择器的示例代码

    本文介绍了ImageView 实现Android colorPikcer 选择器的示例代码,分享给大家,具体如下: Android colorPikcer 选择器 环形的ColorPicker,主要思路是: Color 选在放在ImageView 的background上面,根据点击的位置判断选择的颜色. 重写onTouch,在onTouch 里面判断点击点的颜色. 根据当前选择的颜色设置图片的src. 获取Bitmap 在 ColorPickerView 构造函数中初始化 Bitmap.因为g

  • Android进程间通信实践的示例代码

    本文介绍了Android进程间通信实践的示例代码,分享给大家,具体如下: 因为线程间的内存是共享的,所以它们之间的通信简单,比如可以通过共享变量等方式实现.而进程间想要通信就要麻烦许多了.要想实现进程间通信,我们需要在不同进程之间定义一套它们可以共同理解的接口描述语言,也即 IDL.比较常用的 IDL 有 JSON.Protocol Buffers 等.而 Android 不同进程之间的通信也有个特别的语言,叫 AIDL(Android Interface Definition Language

  • Android 滚动时间选择的示例代码

    效果图 复制代码直接用!!!! 1.导入依赖 implementation 'com.bigkoo:pickerview:2.1.0' 2.三个bean类 PickerViewData public class PickerViewData implements IPickerViewData { private String content; public PickerViewData(String content) { this.content = content; } public voi

  • android异步生成图片的示例代码

    下面来说说在Android上如果异步生成图片,通过xml布局用View排版好图片样式,在子线程生成一张图片,以满足生成用来分享的图片等需求(生成图片前设置可变元素,如用户的头像,昵称等). 效果 点击按钮生成图片: 特性 通过布局和View的方式设计图片样式. 在子线程中生成和保存图片. 封装好工具类,直接使用即可. 核心代码 private Bitmap createBitmap(View view) { int widthSpec = View.MeasureSpec.makeMeasure

  • Android解析json数据示例代码(三种方式)

    Json数据 复制代码 代码如下: [{"code":"110000","sheng":"11","di":"00","xian":"00","name":"北京市","level":1},{"code":"659004","sheng&q

  • Android支付宝支付的示例代码

    上一篇,我们已经详细讲解了Android微信支付,今天接着为大家带来支付宝支付,支付宝支付相对微信支付要简单一些,吐槽一下,而且支付宝文档确实比微信的文档好了不少,下面开始讲解支付流程. 1.首先给出官方文档的地址 Android集成支付宝流程 2.在开始集成和开发前,首先了解一下常用的接入方式和架构建议: 支付流程.png 在开始下一步之前,给大家看看网上的很多操作. 网上的代码.png 这一大堆代码,后面还有,百来行吧,而且支付宝Demo貌似也是这么干的,估计一些新司机就有点懵逼了,至于吗?

  • Android实现图片压缩示例代码

    核心思想是通过BitmapFactory.Options来缩放图片,主要是用到了它的inSampleSize参数(采样率) 当inSampleSize为1的时候,采样后的图片大小为图片的原始大小: 当inSampleSize为2的时候,采样后的图片的宽和高是原来的1/2,也就是说,它的像素点是原来的1/4,占的内存自然就是原来的1/4了.以此类推. 当inSampleSize小于1的时候,效果和等于1的时候是一样的. 压缩流程如下: 1.BitmapFactory.Options 的inJust

  • Android 国际货币格式化的示例代码

    简评:今天介绍下 Android 中国际货币格式化的一个小小知识点. 目前为止,货币格式化最简单的方式是调用 NumberFormat.getCurrencyInstance() 获得 NumberFormat 实例来把数字格式化为货币格式的字符串(当然也可以把字符串转换成数字).可以根据当前设备的位置来获取 java.util.Currency 实例再以此进行货币的格式化. 但如果我们的应用只接受特定的几种货币,那这种随着用户设备位置而修改货币格式就不是一个好的做法. 你可能会说「这简单啊,那

  • Android 摄像头高斯模糊的示例代码

    好久没写文章了,之前项目中有过这个需求但是时间紧就在上面盖了个半透明的白色图片,效果..... 好了,不废话,先看一下效果吧 注意了,这不是对单纯的图片进行高斯模糊,而是对摄像头实时处理 原理: 大体讲一下实现原理,摄像头回调的每一帧通过RenderScript将字节数组转换为Bitmap,再对Bitmap进行高斯模糊处理.流畅度还是不错的.毕竟RenderScript使用的是GPU去计算,速度比普通的用CPU计算的方法快的多 核心代码: /** * 转换数据并进行模糊处理 */ public

随机推荐