一、写在前面的话

最近朋友找帮忙,需要绘制心率波形,要细腻流畅。于是研究了一下,于是就有了这篇文章。本文主要讲一下笔者的思路,控件不是实现的很好,后面有时间再打磨。主要是笔者觉得这个过程挺有意思的,就想和大家分享一下。目前的效果就像上面那样,因为手机录屏录出来掉帧太严重,就录了个视频做了张gif图,实际上面的很丝滑,下面的效果不是很理想,具体原因后面会介绍。

二、心率线的绘制思路

1、线要平滑,要有曲率,还有向左平移。

这里我用三阶贝塞尔曲线来画,其实二阶也可以,但是我以前的项目画的是三阶的,所以就直接搬过来用了,偷个懒,哈哈。

//实际点的集合
private List<PointF> mPointList;
//控制点的集合
private List<PointF> mControlPointList;private void drawLine(Canvas canvas) {linePath.reset();mPointList.clear();mControlPointList.clear();linePath.moveTo(0, 0);int offset = w / maxSize;int allY = high - low;if (allY == 0) {return;}for (int i = 0; i < data.size(); i++) {mPointList.add(new PointF(i * offset, h * (data.get(i) - low) / allY));if (mPointList.get(i).y < top) {mPointList.get(i).y = top;}}//初始化控制点if (mPointList.size() < 2) {return;}for (int i = 0; i < mPointList.size(); i++) {if (i == 0) {//第一个点PointF nextPoint = mPointList.get(i + 1);float controlX = (float) (mPointList.get(i).x + (nextPoint.x - mPointList.get(i).x) * rage);float controlY = mPointList.get(i).y;if (controlY > h - top) {controlY = h - top;}mControlPointList.add(new PointF(controlX, controlY));} else if (i == mPointList.size() - 1) {//最后一个点PointF nextPoint = mPointList.get(i - 1);float controlX = (float) (mPointList.get(i).x + (nextPoint.x - mPointList.get(i).x) * rage);float controlY = mPointList.get(i).y;if (controlY > h - top) {controlY = h - top;}mControlPointList.add(new PointF(controlX, controlY));} else {PointF lastPoint = mPointList.get(i - 1);PointF nextPoint = mPointList.get(i + 1);double k = (nextPoint.y - lastPoint.y) / (nextPoint.x - lastPoint.x);double b = mPointList.get(i).y - k * mPointList.get(i).x;//添加前控制点float lastControlX = (float) (mPointList.get(i).x - (mPointList.get(i).x - lastPoint.x) * rage);float lastControlY = (float) (k * lastControlX + b);if (lastControlY > h - top) {lastControlY = h - top;}mControlPointList.add(new PointF(lastControlX, lastControlY));//添加后控制点float nextControlX = (float) (mPointList.get(i).x + (nextPoint.x - mPointList.get(i).x) * rage);float nextControlY = (float) (k * nextControlX + b);if (nextControlY > h - top) {nextControlY = h - top;}mControlPointList.add(new PointF(nextControlX, nextControlY));//中间的其他点}}mLinePaint.setStyle(Paint.Style.STROKE);for (int i = 0; i < mPointList.size(); i++) {if (i == 0) {linePath.moveTo(mPointList.get(i).x, mPointList.get(i).y);} else {
//                if (mPointList.get(i).y == mPointList.get(i - 1).y) {
//                    //如果值没有变化就画直线
//                    linePath.lineTo(mPointList.get(i).x, mPointList.get(i).y);
//                } else {linePath.cubicTo(mControlPointList.get((i - 1) * 2).x, mControlPointList.get((i - 1) * 2).y, mControlPointList.get((i - 1) * 2 + 1).x, mControlPointList.get((i - 1) * 2 + 1).y, mPointList.get(i).x, mPointList.get(i).y);
//                }}}if (mPointList.size() < 1) {return;}if (data.size() >= maxSize) {canvas.translate(-offset * (data.size() - maxSize), 0);}canvas.drawPath(linePath, mLinePaint);}
    public void addData(int values) {if (data.size() == 0) {high = values;} else if (data.size() == 1) {//最大最小值的赋值if (values > high) {low = high;high = values;} else {low = values;}} else {if (values > high) {high = values;} else if (values < low) {low = values;}}//        if (data.size() > maxSize) {
//            data.remove(0);
//        }Log.d("wave", "h=" + high + ",l=" + low + ",v=" + values + ",size=" + data.size());data.add(values);}

上面我贴出了划线和添加值的代码。最大最小值是动态变化的,这里应对可能输入的各种大小的值。线的平移有种方式:1、当list中的点达到最大值,就移除最前面的点;2、当list中的点达到最大值,就将整个画布平移;两种实现的效果是一样的。

对于类似心率的效果,我描了几个坐标,模拟了一下心率。

    private int[] data = new int[]{50, 50, 50, 50, 50, 50, 50, 50, 60, 40, 50, 40, 50, 90, 10, 45, 40, 50, 60, 70, 35, 50, 50, 50};

按顺序post这些值进去就是图二的效果。但是有一个问题,就是每一次重绘,动画不是连贯的,下面介绍解决方案。

2、让动画变得丝滑一些。

那么,怎么让动画变得流畅一些呢。大家都知道,动画是由一帧一帧的图像组成的,理论上,只需要我的帧数足够多,那么我的动画就足够流畅。

上面的图二我设置的最多的点为:

 //屏幕上的点数量private int maxSize = 60;

就是屏幕上取得横坐标的点最大为60个。为什么是60个不是600,6000个呢。因为我用来绘制的data的进度不够,而且如果你用来绘制的data的精度足够高,甚至达到每一个屏幕的X坐标对应一个Y坐标,都可以不用贝塞尔曲线画出丝滑的心率曲线。但是这显示是不可行的,除非你有耐心把你想的曲线的每一个坐标都放进data里面。

但是我们是不是没有效率高一点,更程序话一点的方法呢。我想到的是使用Sin函数+SurfaceView去实现。

3、用Sin+SurfaceView实现心率波形图

Sin函数是虚线,而且振幅和相位都可以由变量控制,每一个Y坐标与X坐标的关系都可以用公式表示,而且把每一段sin函数拉伸变换之后,可以得到与心率曲线类似的曲线。而SurfaceView是可以实时刷新页面的view,这样我们只要让每次绘制的时候,让X坐标加上一个常数,通过控制这个常数的大小,就可以控制曲线的速度。而且每一次绘制的时候,改变Sin的相位和振幅,那么最后就可以得到一条平滑而且丝滑的心率曲线了。原理就是这样,下面上代码。

   public class HeartSurfaceWaveView extends SurfaceView {
...private void drawSin(Canvas canvas) {if (x > w * 4 / 5) {canvas.translate(-(x - w * 4 / 5), 0);}z = (int) (Math.abs(x % w) % (110 + (Math.random() * 5)));if (x == 0) {sinPath.reset();int y = (int) (z * Math.sin(x * 2 * Math.PI / 180) + 200);sinPath.moveTo(0, y);}x = x + 5;int y = (int) (z * Math.sin(x * 2 * Math.PI / 180) + 200);if (Math.abs(x % w - w) < w * 3 / 4) {y = 200;}sinPath.lineTo(x, y);canvas.drawPath(sinPath, mLinePaint);}
...
}

如上所示,动画的平移我采用的是平移画布,然后用常数Z去控制曲线的形状。在某些时刻需要绘制直线。x+5为每一帧x前进5个单位。画出来就是图一那样子的。因为是gif图有些掉帧。实际是很丝滑的。

三、结束

其实这个曲线还未真正完成,因为博主年前有些工作要忙,就先做到这里,年后在研究,研究出来再发源码。如果有需要这个半成品的同学跟我说一下,我提前发你也可。

最后祝大家新年快乐,注意防护,过一个好年。

Android-自定义心电图控件相关推荐

  1. Android 自定义组合控件小结

    Android 自定义组合控件小结 引言 接触Android UI开发的这段时间以来,对自定义组合控件有了一定的了解,为此小结一下,本文小结内容主要讨论的是如何使用Android SDK提供的布局和控 ...

  2. android自定义table,Android 自定义表格控件

    Android 自定义表格控件 发布时间:2018-08-20 17:07, 浏览次数:487 , 标签: Android 1.简介 tabview是一款开源表格控件,可以通过xml属性设置行列数.设 ...

  3. Android自定义时间控件不可选择未来时间

    本文出自:http://blog.csdn.net/dt235201314/article/details/78718066 Android自定义时间控件选择开始时间到结束时间 Android自定义时 ...

  4. Android自定义组合控件--EditText和Button组合成带有清空EditText内容功能的复合控件

    目标:实现EditText和Button组合成带有清空EditText内容功能的复合控件,可以通过代码设置自定义控件的相关属性. 实现效果为: (1)在res/layout目录下编写自定义组合控件的布 ...

  5. android 自定义switch控件,Android中switch自定义样式

    android 原生开关按钮控件 Switch 提供样式自定义方式,可供我们修改为适合我们开发使用的样式控件,自定义样式过程如下: 自定义switch切换drawable 新建swith_thumb. ...

  6. android 自定义view控件,Android 自定义View——自定义View控件

    Android给我们提供了大量的View控件,但这还是远远满足不了我们的要求,有时候开发所需要的控件形式是在Android提供的控件中是不存在,这就需要我们自己去定义一个.那么如何自定义控件? 学习自 ...

  7. android身高控件_RuleView Android 自定义标尺控件(选择身高、体重等) @codeKK Android开源站...

    尺子刻度 -- 自定义 view 自定义 view 学习(第一章) 1.自定义刻度尺控件 在我们想要获取用户的身高体重等信息时,直接让他们输入显然不够友好偶然看到一款 App 用了类似刻度尺的界面让用 ...

  8. android 自定义ImageView控件实现圆形图片-适用于用户头像

    android开发中常常涉及到一种情况,就是将用户上传的图片以圆形样式显示,但是用户上传的图片可以有直角.圆角.正方形等多种不确定样式,这时就用到了自定义ImageView控件,在安卓客户端使接收到的 ...

  9. Android自定义日历控件,自带农历节假日,已经开源,即取即用~

    关注本人的更多博客:http://www.cnblogs.com/liushilin/ 该自定义日历控件已经开源:github地址 可能不少的小伙伴都有看楼主昨天发的自定义日历控件,虽然实现功能不多, ...

  10. Android自定义组合控件

    目标:实现textview和ImageButton组合,可以通过Xml设置自定义控件的属性. 1.控件布局:以Linearlayout为根布局,一个TextView,一个ImageButton. Xm ...

最新文章

  1. 娃哈哈信息部李钒助阵FBS2017 共探食品饮料信息化之路
  2. “==”和equals方法的区别
  3. CHM格式的帮助文档,打开时乱码
  4. go学习笔记-标准库
  5. 什么?在 VSCode 里也能用 Postman了?
  6. .net foreach 未执行完就到下一行了_PHP五十个提升执行效率的小技巧,和常见问题...
  7. Eclipse Java Build Path详解
  8. 字符串分割 异常 泛型 练习
  9. jms.jar 2.0_JMS API 2.0生产者和使用者
  10. 【web组件库系列】封装自己的字体图标库
  11. 优雅地关闭kubernetes中的nginx
  12. CEO面试你时喜欢问的十六个问题
  13. 2012年托福听力真题词汇总结
  14. 范德堡大学计算机专业课程,2020年范德堡大学专业设置
  15. iso马甲包是什么意思?如何制作上传
  16. 华为国产系统Android,国产手机系统即将出现!华为将抛弃安卓:成功研发自有手机系统...
  17. Serializer序列化器使用
  18. C#计算两个日期间隔年数、月数、天数
  19. 标准程序流程图的符号及使用约定
  20. 4万高考冒名顶替事件_高考生冒名顶替上大学事件内幕调查

热门文章

  1. Anchor box的理解
  2. 向科技要智慧,人脸识别智能门禁解锁智慧社区新未来
  3. 计算机的发展经历了四代,代的划分是根据计算机的,计算机的发展经历了四代,\代\的划分是根据计算机的运算速度来划分....
  4. 揭秘!2020二维码防伪核心技术
  5. vue-cli中解决css引用图片打包后找不到文件资源的问题
  6. 数据结构中堆、栈和队列的理解
  7. 计算机图形学————绘制动态的行星系
  8. 查理·芒格的投资之道
  9. 水晶报表如何完美导出一个Excel表格
  10. Google 音乐播放器