摘要:在我们的日常生活中,通常需要用仪器测量心率数据,来观测身体是否在一个健康的范围之内, 下面的心率图就是用一个胎心仪测量的一个宝宝心率图。
自定义控件如下图所示:
画好这样的一个心电图视图需要自定义一个控件具体步骤如下:
一 自定义属性

  1. 在attrs.xml下自定义如下属性,在我们使用此自定义控件的时候,可以根据需求改变样式。
   <declare-styleable name="chartView"><!-- xy坐标轴颜色 --><attr name="xylinecolor" format="color" /><!-- xy坐标轴宽度 --><attr name="xylinewidth" format="dimension" /><!-- xy坐标轴文字颜色 --><attr name="xytextcolor" format="color" /><!-- xy坐标轴文字大小 --><attr name="xytextsize" format="dimension" /><!-- 折线图中折线的颜色 --><attr name="linecolor" format="color" /><!-- x轴各个坐标点水平间距 --><attr name="interval" format="dimension" /><!-- 背景颜色 --><attr name="bgcolor" format="color" /><!--是否在ACTION_UP时,根据速度进行自滑动,建议关闭,过于占用GPU--><attr name="isScroll" format="boolean" /></declare-styleable>
  1. 使用自定义属性
 TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.chartView, defStyleAttr, 0);int count = array.getIndexCount();for (int i = 0; i < count; i++) {int attr = array.getIndex(i);switch (attr) {case R.styleable.chartView_xylinecolor://xy坐标轴颜色xylinecolor = array.getColor(attr, xylinecolor);break;case R.styleable.chartView_xylinewidth://xy坐标轴宽度xylinewidth = (int) array.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, xylinewidth, getResources().getDisplayMetrics()));break;case R.styleable.chartView_xytextcolor://xy坐标轴文字颜色xytextcolor = array.getColor(attr, xytextcolor);break;case R.styleable.chartView_xytextsize://xy坐标轴文字大小xytextsize = (int) array.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, xytextsize, getResources().getDisplayMetrics()));break;case R.styleable.chartView_linecolor://折线图中折线的颜色linecolor = array.getColor(attr, linecolor);break;case R.styleable.chartView_interval://x轴各个坐标点水平间距interval = (int) array.getDimension(attr, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, interval, getResources().getDisplayMetrics()));break;case R.styleable.chartView_bgcolor: //背景颜色bgcolor = array.getColor(attr, bgcolor);break;case R.styleable.chartView_isScroll://是否在ACTION_UP时,根据速度进行自滑动isScroll = array.getBoolean(attr, isScroll);break;}}array.recycle();

二 绘制图形

  1. 绘制X轴以及上方的水平虚线,水平轴的时间文字及描点。
   //绘制X轴坐标canvas.drawLine(xOri, yOri + xylinewidth / 2, width, yOri + xylinewidth / 2, xyPaint);//绘制x轴刻度for (int i = 0; i < xValue.size(); i++) {float x = xInit + interval * i;if (i == 0 && x - interval >= xOri) {String text = TimeUtil.getChartTime(0);Rect rect = getTextBounds(text, xyTextPaint);canvas.drawText(text, 0, text.length(), x - interval - rect.width() / 2, yOri + xylinewidth + dpToPx(4) + rect.height(), xyTextPaint);canvas.drawCircle(xOri, yOri, radiu, linePaint);}if (x >= xOri) {//只绘制从原点开始的区域xyTextPaint.setColor(xytextcolor);canvas.drawLine(x, yOri - yLength * (yValue.size() - 1) + xylinewidth / 2, x, yOri, (i + 1) % 3 == 0 ? xyPaint : xyredPaint);//绘制X轴文本if ((i + 1) % 3 == 0) {String text = TimeUtil.getChartTime((i + 1) * perLengTime);Rect rect = getTextBounds(text, xyTextPaint);canvas.drawText(text, 0, text.length(), x - rect.width() / 2 - dpToPx(2), yOri + xylinewidth + dpToPx(4) + rect.height(), xyTextPaint);canvas.drawCircle(x, yOri, radiu, linePaint);}}}
  1. 绘制Y轴以及上方的水平虚线,水平轴的心率数值文字及描点。
canvas.drawLine(xOri - xylinewidth / 2, yOri - yLength * (yValue.size() - 1), xOri - xylinewidth / 2, yOri, xyPaint);for (int i = 0; i < yValue.size(); i++) {//绘制Y轴刻度canvas.drawLine(xOri, yOri - yLength * i + xylinewidth / 2, width, yOri - yLength * i + xylinewidth / 2, xyPaint);canvas.drawCircle(xOri, yOri - yLength * i, radiu, linePaint);if (i > 0) {canvas.drawLine(xOri, yOri - yLength * i + xylinewidth / 2 + 1.0f / 3 * yLength, width, yOri - yLength * i + xylinewidth / 2 + 1.0f / 3 * yLength, xyredPaint);canvas.drawLine(xOri, yOri - yLength * i + xylinewidth / 2 + 2.0f / 3 * yLength, width, yOri - yLength * i + xylinewidth / 2 + 2.0f / 3 * yLength, xyredPaint);}xyTextPaint.setColor(xytextcolor);//绘制Y轴文本String text = yValue.get(i) + "";Rect rect = getTextBounds(text, xyTextPaint);canvas.drawText(text, 0, text.length(), xOri - xylinewidth - dpToPx(3) - rect.width(), yOri - yLength * i + rect.height() / 2, xyTextPaint);int length = xValue.size() / 12;if (length > 0) {for (int j = 1; j <= length; j++) {float x1 = xInit + interval * j * 12;if (x1 > xOri + interval * 6)canvas.drawText(text, 0, text.length(), x1 - interval - rect.width() - dpToPx(3), yOri - yLength * i + rect.height() / 2, xyTextPaint);}}}
  1. 绘制心率单位
private void drawUnit(Canvas canvas, int yLength) {xyTextPaint.setTextSize(dpToPx(6));String text = "FHR/bpm";Rect rect = getTextBounds(text, xyTextPaint);//第一竖排的单位canvas.drawText("FHR/bpm", 0, text.length(), xOri - xylinewidth - dpToPx(3) - rect.width(), yOri - yLength * (1.0f * (yValue.size() - 1) / 2) + rect.height() / 2, xyTextPaint);//后面竖排的单位int length = xValue.size() / 12;if (length > 0) {for (int j = 1; j <= length; j++) {float x1 = xInit + interval * j * 12;if (x1 > xOri + interval * 6)canvas.drawText(text, 0, text.length(), x1 - interval - rect.width() - dpToPx(3), yOri - yLength * (1.0f * (yValue.size() - 1) / 2) + rect.height() / 2, xyTextPaint);}}}xyTextPaint);
  1. 绘制正常绿色区域(120-160心率)
  RectF f = new RectF();f.top = yOri - yLength * (4 + 1.0f / 3);f.bottom = yOri - yLength * 3;f.left = xOri;f.right = width;canvas.drawRect(f, okAreaPaint);
  1. 绘制折线图
  /*** 绘制折线* @param canvas*/private void drawBrokenLine(Canvas canvas) {//perLengTime初始为5s 如果value的长度超过一分钟 那么需要改变perlengTime为20sif (value.size() < 2) return;linePaint.setStyle(Paint.Style.STROKE);linePaint.setColor(linecolor);//绘制折线Path path = new Path();float x = xInit - interval + interval * (betweemTime + base) / perLengTime;float y = yOri - yOri * (1 - 0.1f) * (value.get(0) - yValue.get(0)) / (yValue.get(yValue.size() - 1) - yValue.get(0));path.moveTo(x, y);for (int i = 1; i < value.size(); i += 3) {x = getXLength(i);y = getYLength(i);if (isContinuous) {setContinuousPath(i, path, x, y);} else {setNoContinuousPath(i, path, x, y);}}linePaint.setStrokeWidth(dpToPx(1f));canvas.drawPath(path, linePaint);}
  1. 最后一个心率动态打点
  /*** 最后一个心率点* @param canvas*/private void drawSelectPointLinePoint(Canvas canvas) {if (value.size() == 0) return;int position = value.size() - 1;float dp7 = dpToPx(5);float x = xInit - interval + 1.0f * interval * ((position + 1) * betweemTime + base) / perLengTime;float y = yOri - yOri * (1 - 0.1f) * (value.get(position) - yValue.get(0)) / (yValue.get(yValue.size() - 1) - yValue.get(0));linePaint.setStyle(Paint.Style.FILL);linePaint.setColor(markColor);linePaint.setStrokeWidth(dpToPx(1));canvas.drawCircle(x, y, dp7, linePaint);linePaint.setStrokeWidth(dpToPx(2));canvas.drawLine(x, 0, x, getHeight(), linePaint);}

三.处理滑动事件和点击事件
根据当前手势滑动的距离来动态更新绘制心率图,根据手指按下和松开的xy方向的距离来判断点击事件(这个可以进行横竖屏切换)具体代码如下:

private float startX, x1, y1, x2, y2;boolean isTouch;boolean isClick;//是否正在滑动private boolean isScrolling = false;@Overridepublic boolean onTouchEvent(MotionEvent event) {if (isScrolling)return super.onTouchEvent(event);this.getParent().requestDisallowInterceptTouchEvent(true);//当该view获得点击事件,就请求父控件不拦截事件obtainVelocityTracker(event);switch (event.getAction()) {case MotionEvent.ACTION_DOWN:startX = event.getX();isClick = true;x1 = event.getX();y1 = event.getY();break;case MotionEvent.ACTION_MOVE:if (interval * xValue.size() > width - xOri) {//当期的宽度不足以呈现全部数据float dis = event.getX() - startX;startX = event.getX();if (xInit + dis < minXInit) {xInit = minXInit;} else if (xInit + dis > maxXInit) {xInit = maxXInit;} else {xInit = xInit + dis;}invalidate();}isClick = false;break;case MotionEvent.ACTION_UP:if (event.getX() + getLeft() < getRight() && event.getY() + getTop() < getBottom()) {x2 = event.getX();y2 = event.getY();if (Math.abs(x1 - x2) < 6 && Math.abs(y1 - y2) < 6) {if (onChartClickListener != null)onChartClickListener.onChartClick();return false;// 距离较小,当作click事件来处理}}scrollAfterActionUp();this.getParent().requestDisallowInterceptTouchEvent(false);recycleVelocityTracker();break;case MotionEvent.ACTION_CANCEL:this.getParent().requestDisallowInterceptTouchEvent(false);recycleVelocityTracker();break;}return true;}

手指抬起后的滑动处理,这个比较耗GPU性能,建议关闭

 /*** 手指抬起后的滑动处理*/private void scrollAfterActionUp() {if (!isScroll)return;final float velocity = getVelocity();float scrollLength = maxXInit - minXInit;if (Math.abs(velocity) < 10000)//10000是一个速度临界值,如果速度达到10000,最大可以滑动(maxXInit - minXInit)scrollLength = (maxXInit - minXInit) * Math.abs(velocity) / 10000;ValueAnimator animator = ValueAnimator.ofFloat(0, scrollLength);animator.setDuration((long) (scrollLength / (maxXInit - minXInit) * 1000));//时间最大为1000毫秒,此处使用比例进行换算animator.setInterpolator(new DecelerateInterpolator());animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator valueAnimator) {float value = (float) valueAnimator.getAnimatedValue();if (velocity < 0 && xInit > minXInit) {//向左滑动if (xInit - value <= minXInit)xInit = minXInit;elsexInit = xInit - value;} else if (velocity > 0 && xInit < maxXInit) {//向右滑动if (xInit + value >= maxXInit)xInit = maxXInit;elsexInit = xInit + value;}invalidate();}});animator.addListener(new Animator.AnimatorListener() {@Overridepublic void onAnimationStart(Animator animator) {isScrolling = true;}@Overridepublic void onAnimationEnd(Animator animator) {isScrolling = false;}@Overridepublic void onAnimationCancel(Animator animator) {isScrolling = false;}@Overridepublic void onAnimationRepeat(Animator animator) {}});animator.start();}/*** 获取速度** @return*/private float getVelocity() {if (velocityTracker != null) {velocityTracker.computeCurrentVelocity(1000);return velocityTracker.getXVelocity();}return 0;}

四 结语
整个心率图的自定义控件就已经结束了,然后通过设置xy轴的数据,以及具体的折线图数据就可以显示一个非常完美的折线图了。

Android如何自定义一个心电图控件?相关推荐

  1. 让一个图片填满一个控件_如何在Android中实现一个全景图控件(二)

    一.背景 在 如何在Android中实现一个全景图控件(一)中,介绍了项目的一些基本情况(有 demo 演示),如果项目对你有帮助,希望文章赏个赞,项目 star 一下. 项目地址:https://g ...

  2. android numberpicker 自定义,Android的自定义数字Picker控件-NumberPicker使用方法

    android-numberpicker是github的一个项目,提供了安卓中的自定义数字拾取控件,它的效果如图所示: 应用该控件也非常简单,使用如下几步即可: 1.从github上下载该控件,地址为 ...

  3. 【Android】自定义View和控件时出现Binary XML file line #报错行数: Binary XML file line #9: Error inflating class 类路径

    方法一: 原因:自定义视图.控件的的那个类继承了View或其它布局类的时候没有实现所有的构造方法. 错误写法: 正确写法: 方法二: 原因:参数不匹配 在attrs文件上写的是: <attr n ...

  4. android绘制view的撤销,DrawingView android 上的一个涂鸦控件。可以设置画笔的粗细,颜色,撤销上一笔涂鸦,提供保存图片的接口。 @codeKK Android开源站...

    DrawingView 的原型来自DrawingView-Android,是 android 的一个可涂鸦控件. 之所以做这个控件是因为前段时间写了一个截图应用需要用到涂鸦功能,现在把涂鸦的控件单独拿 ...

  5. 自定义一个倒计时控件

    主要需求 能进行倒计时 可以随时取消.终止倒计时 流畅跳动 先看实现效果 这是一个倒计时5s的动画,可能录制帧数导致没有完全显示,大于1秒时每秒显示,小于1秒时显示小数,带加速度先快后慢跳动 实现思路 ...

  6. android自定义view获取控件,android 自定义控件View在Activity中使用findByViewId得到结果为null...

    转载:http://blog.csdn.net/xiabing082/article/details/48781489 1.  大家常常自定义view,,然后在xml 中添加该view 组件..如果在 ...

  7. Android在Activity中动态增加xml自定义样式布局控件(引用xml布局文件和循环增加控件)

    工程目录: MainActivity package com.example.test1121;import androidx.appcompat.app.ActionBar; import andr ...

  8. Android自定义的下拉列表框控件

    一.概述 Android中的有个原生的下拉列表控件Spinner,但是这个控件有时候不符合我们自己的要求, 比如有时候我们需要类似windows 或者web网页中常见的那种下拉列表控件,类似下图这样的 ...

  9. android日历价格控件,Android 自定义价格日历控件

    介绍 上个星期项目有一个日历价格的需求,类似一个商品在不同的日期价格可能会不同,由于时间给得特别紧所以打算找个合适的开源项目进行修改.参考了网上大多数是通过继承view直接draw一个monthVie ...

最新文章

  1. 如何通俗的理解面向对象编程
  2. 通过游戏来学习CSS的Flex布局
  3. JAVA_OA(十四)番外:JAVAWEB防止表单重复提交的方法整合(包括集群部署)
  4. php fpm.conf 注释,使用sed处理php-fpm.conf和nginx.conf文本里的注释信息
  5. 科大讯飞 ai算法挑战赛_为井字游戏挑战构建AI算法
  6. 下一个全排列_下一个排列
  7. distinct sql用法_十分钟搞懂SQL数据分析
  8. mysql web客户端_mycli--让你惊艳的mysql客户端
  9. 基于stm32单片机外文文献_13个基于STM32的经典项目设计实例,全套资料~-嵌入式系统-与非网...
  10. MarkdownPad2行内公式如何用`$$`替代`\\(\\)`---MathJax风格化配置
  11. 被罗马人称为的三大蛮族去向和苏格拉的独立
  12. Python3 粗略计算PI的值
  13. 百度网盘批量分享管理综合工具
  14. python英文单词整理
  15. respond_to 和 respond_with
  16. 在工业污水处理中实现施耐德PLC的远程监控和上下载
  17. 一线互联网技术总监的忠告:我们精通那么多技术为何还是做不好一个项目?
  18. 服务器文件夹重定向,文件夹重定向
  19. 如何用PS切图和输出网页?
  20. 取名居:牛宝宝起名必读!

热门文章

  1. 永中软件自己越描越黑
  2. c语言在尾部添加新节点,在单链表最后插入节点
  3. 2022年湖南省导游资格(导游服务能力)练习题及答案
  4. linux(ubuntu)查看硬件设备命令
  5. PHP artisan命令
  6. java preferences设置_Java利用Preferences设置个人偏好
  7. 2020下半年中小学教资考试教育知识与能力试题(中学)——主观题
  8. 浅谈getaddrinfo
  9. Spring问题研究之bean的属性xml注入List类型不匹配
  10. 怎样知道mysql的驱动是什么_MySQL连接查询到底什么是驱动表?看了这里你应该就明白了...