自定义控件之——心电图控件的绘制
2016-04-14 23:15
453 查看
这是我在做项目的时候的一个需求,根据蓝牙设备采集到的数据画出心电图。 由于以前没有做过类似的东西,当我拿到这个需求的时候 整个人都不好了,没办法只能硬着头皮上呗。然后就开始在网上疯狂的找Demo,皇天不负有心人,我果然没有找到完全符合我们需求的demo
,没有办法只能自己撸一个出来。先上一个效果图
(这只是demo想要好看点可以自己调)。
数据只是使用的随机数,所以看起来有点不专业,当画完一屏幕的点后,会有类似擦除效果来更新我们的心电图,就和医院的类似。横坐标与纵坐标的值都可以自己修改,比如将横坐标修改成时间,都是行的,下面开始工作。
在开始定义以前,首先介绍一下android 的坐标系统如上图,android的坐标和我们数学中学习的坐标差不多,只是将坐标原点定在了左上角,某一点的坐标我们根据宽高来确定。比如右下角的坐标:Y坐标是当前位置距顶部的高度,X是当前位置距左边的距离,假设距顶部的距离为Height,距左边的距离为width那么该点的坐标为(width,height);
说了这么多咱们开始进入正题
1、定义ECGView继承自View,并实现重写它的三个构造函数(其实只需要重写两个参数就行,但是以备不时之需还是都写了)
2.定义declare-styleable标签,命名空间的时候使用
然后在构造方法中初始化
4,绘制背景表格
怎么绘制背景网格呢?首先我们得考虑每个格子都应该有值,以便用来计算我们的点处于网格的哪个位置,比如我们垂直总共6000,总共有10个格子,那每个格子的值就应该是6000/10=600。反之我们可以通过设置格子的值和最大y值来计算格子数。这个时候我们就需要定义两个属性,一个为Y的最大值,和格子的值。还需要对外暴露设置方法。
那X轴的格子数怎么得到呢?可以通过控件的有效宽度除以格子的宽度得到。
上代码:
上面代码初始化每条线的XY坐标以及格子的数量
// 分别为上下左右缩进
private int _LeftIndent = 100;
private int _RightIndent = 100;
private int _BottomIndent = 100;
private int _TopIndent = 100;
Y坐标的文字和水平的线,和上面类似这里就不仔细说了。
5.绘制波形图
在绘制波形图之前我们需要知道点和点之间的距离是多少,也就是我们应该怎么确定X坐标,我是这样做的
_XUnitLength = (_EffectiveWidth) / (_PointMaxAmount - 1);// 两个点之间的水平间距等于有效宽减出左右缩进除以点数,因为不需要太精确,这里不做误差分析。
这是第一个方法,具体是将外面的设置进来的点,比如4800计算成在设备上准确的坐标,并添加到点的集合中去。添加完后通知界面重绘,这里如果点数已经超过了我们设置的最大点数,则将前面的点替换,这就是我们看到的当一个屏幕满了,重头开始刷新,但是后面的波形没动效果的实现原理。
画波形图的核心代码都已经给出还有一些无关紧要代码大家可以下载我的demo参考,项目已经托管到github ECGViewDemo.有什么不对或者需要优化的地方还望大牛指出。
终于写完了 ,弄这个该死的github 弄到凌晨两点。
,没有办法只能自己撸一个出来。先上一个效果图
(这只是demo想要好看点可以自己调)。
数据只是使用的随机数,所以看起来有点不专业,当画完一屏幕的点后,会有类似擦除效果来更新我们的心电图,就和医院的类似。横坐标与纵坐标的值都可以自己修改,比如将横坐标修改成时间,都是行的,下面开始工作。
在开始定义以前,首先介绍一下android 的坐标系统如上图,android的坐标和我们数学中学习的坐标差不多,只是将坐标原点定在了左上角,某一点的坐标我们根据宽高来确定。比如右下角的坐标:Y坐标是当前位置距顶部的高度,X是当前位置距左边的距离,假设距顶部的距离为Height,距左边的距离为width那么该点的坐标为(width,height);
说了这么多咱们开始进入正题
1、定义ECGView继承自View,并实现重写它的三个构造函数(其实只需要重写两个参数就行,但是以备不时之需还是都写了)
2.定义declare-styleable标签,命名空间的时候使用
<declare-styleable name="elg"> <attr name="BackLineColor" format="color"/> <attr name="TitleColor" format="color"/> <attr name="PointerLineColor" format="color"/> <attr name="TitleSize" format="dimension"/> <attr name="XYTextSize" format="dimension"/> </declare-styleable>BackLineColor为背景图中表格线的颜色,TitleColor标题颜色,PointerLineColor波形图线的颜色,TitleSize标题文字大小,XYTextSize XY轴文字的大小。
然后在构造方法中初始化
public ECGView(Context context, AttributeSet attrs) { super(context, attrs); _context = context; TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.elg); _backLineColor = typedArray.getColor(R.styleable.elg_BackLineColor, Color.GREEN); _titleColor = typedArray .getColor(R.styleable.elg_TitleColor, Color.RED); _pointerLineColor = typedArray.getColor( R.styleable.elg_PointerLineColor, Color.WHITE); _titleSize = typedArray.getDimensionPixelSize( R.styleable.elg_TitleSize, 30); _XYTextSize = typedArray.getDimensionPixelSize( R.styleable.elg_XYTextSize, 20); typedArray.recycle(); initView(); }3.定义画笔类
private void initView() { _handler = new Handler(Looper.getMainLooper()); _PaintLine = new Paint(); _PaintLine.setStrokeWidth(2.5f); _PaintLine.setColor(_pointerLineColor); _PaintLine.setAntiAlias(true); _PaintDataLine = new Paint(); _PaintDataLine.setColor(_backLineColor); _PaintDataLine.setAntiAlias(true); _PaintDataLine.setStrokeWidth(10); _XYTextPaint = new TextPaint(); _XYTextPaint.setColor(_titleColor); _XYTextPaint.setTextSize(_XYTextSize); _TitleTextPaint = new TextPaint(); _TitleTextPaint.setColor(_titleColor); _TitleTextPaint.setTextSize(_titleSize); }定义这些就开始真正的开始我们的绘制了
4,绘制背景表格
怎么绘制背景网格呢?首先我们得考虑每个格子都应该有值,以便用来计算我们的点处于网格的哪个位置,比如我们垂直总共6000,总共有10个格子,那每个格子的值就应该是6000/10=600。反之我们可以通过设置格子的值和最大y值来计算格子数。这个时候我们就需要定义两个属性,一个为Y的最大值,和格子的值。还需要对外暴露设置方法。
private float _MaxYNumber;//Y最大值 private float _EveryOneValue = 1;// 每个格子的值
<span style="white-space:pre"> </span>// 设置Y轴最大值 <span style="white-space:pre"> </span>public void setMaxYNumber(float maxYNumber) { <span style="white-space:pre"> </span>this._MaxYNumber = maxYNumber; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>// 设置格子的值 <span style="white-space:pre"> </span>public void setEffticeValue(int value) { <span style="white-space:pre"> </span>_EveryOneValue = value; <span style="white-space:pre"> </span>}通过_MaxYNumber 和_EveryOneValue我们可以知道y轴的格子数,格子应该都是正方形的,那我们怎么得到格子的高呢?我们可以通过网格的总高度除以格子数得到
那X轴的格子数怎么得到呢?可以通过控件的有效宽度除以格子的宽度得到。
上代码:
if (_isfristDrawBackGround) {//是否是第一次加載背景 _YSize = (int) (_MaxYNumber / _EveryOneValue);// 垂直格子数量 _LatticeWidth = (int) (_EffectiveHeight / _YSize);//每個格子的寬度 _XSize = ((_Width - _RightIndent - _LeftIndent) / _LatticeWidth)+1;// 水平格子数量 float curX = 0; if (_EveryNPointBold > _YSize || _EveryNPointBold > _XSize) { _EveryNPointBold = Math.min(_YSize, _XSize) / 2 + 1; } for (int i = 0; i < _XSize; i++) {//添加x坐标 _ListVLine.add(curX); curX += _LatticeWidth; } float curY = 0; for (int j = 0; j < _YSize; j++) {//添加Y坐标 _ListHLine.add(curY); curY += _LatticeWidth; } _isfristDrawBackGround = false; }
上面代码初始化每条线的XY坐标以及格子的数量
for (int i = 0; i < _ListVLine.size(); i++) { sText = 5 * i; canvas.drawText(sText + "", _ListVLine.get(i) + _TopIndent, _Height - _TopIndent + _XYTextSize, _XYTextPaint); if (i == 0) { _PaintDataLine.setStrokeWidth(8); canvas.drawLine(_ListVLine.get(i) + _LeftIndent, 0 + _TopIndent, _ListVLine.get(i) + _LeftIndent, _Height - _BottomIndent, _PaintDataLine); _PaintDataLine.setStrokeWidth(1); } else { if (i % _EveryNPointBold == 0) { _PaintDataLine.setStrokeWidth(3); canvas.drawLine(_ListVLine.get(i) + _LeftIndent, 0 + _TopIndent, _ListVLine.get(i) + _LeftIndent, _Height - _BottomIndent, _PaintDataLine); _PaintDataLine.setStrokeWidth(1); } else { canvas.drawLine(_ListVLine.get(i) + _LeftIndent, 0 + _TopIndent, _ListVLine.get(i) + _LeftIndent, _Height - _BottomIndent, _PaintDataLine); } } }上面代码主要化X坐标的文字和垂直的线,这里讲一下canva.drawLine(startx,startY,stopX,stopY)里面的几个参数,(startX,starY)为开始点,(stopX,stopY)为结束坐标,两点确定一条直线嘛!相信小伙伴们都知道。
// 分别为上下左右缩进
private int _LeftIndent = 100;
private int _RightIndent = 100;
private int _BottomIndent = 100;
private int _TopIndent = 100;
Y坐标的文字和水平的线,和上面类似这里就不仔细说了。
5.绘制波形图
在绘制波形图之前我们需要知道点和点之间的距离是多少,也就是我们应该怎么确定X坐标,我是这样做的
_XUnitLength = (_EffectiveWidth) / (_PointMaxAmount - 1);// 两个点之间的水平间距等于有效宽减出左右缩进除以点数,因为不需要太精确,这里不做误差分析。
public void setLinePoint(float curY) { Map<String, Float> temp = new HashMap<String, Float>(); temp.put(X_KEY, _CurX);//放入x坐标 _CurX += _XUnitLength; // 计算y真实坐标 float number = curY / _EveryOneValue;// 这个数应该占的格子数 if (_Height != 0) { _CurY = _Height - (_BottomIndent + number * _LatticeWidth); } if (_CurY < _TopIndent) { _CurY = _TopIndent + 10; } temp.put(Y_KEY, _CurY); // 判断当前点是否大于最大点 if (_CurP < _PointMaxAmount) { try { if (_ListPoint.size() == _PointMaxAmount && _ListPoint.get(_CurP) != null) { _ListPoint.remove(_CurP); } } catch (Exception e) { e.printStackTrace(); } _ListPoint.add(_CurP, temp); _CurP++; } else { _CurP = 0; _CurX = _RightIndent; } if (_CurP % _EveryNPointRefresh == 0) { invalidate(); } if (isSetAlarmFlag) { if (!(curY > _minAlarmNumber && curY < _maxAlarmNumber)) { _handler.post(new alarmThread( curY < _minAlarmNumber ? LOW_ALARM : HIGH_ALARM)); } } }
这是第一个方法,具体是将外面的设置进来的点,比如4800计算成在设备上准确的坐标,并添加到点的集合中去。添加完后通知界面重绘,这里如果点数已经超过了我们设置的最大点数,则将前面的点替换,这就是我们看到的当一个屏幕满了,重头开始刷新,但是后面的波形没动效果的实现原理。
public void drawWave(Canvas canvas) { for (int index = 0; index < _ListPoint.size(); index++) { if (_ListPoint.size() == _PointMaxAmount && (index >= _CurP && index < _CurP + _RemovedPointNum)) {//实现擦除效果 continue; } if (index > 0) { if (_ListPoint.get(index).get(Y_KEY) < 0 || _ListPoint.get(index).get(Y_KEY) < _TopIndent) { continue; } canvas.drawLine(_ListPoint.get(index - 1).get(X_KEY), _ListPoint.get(index - 1).get(Y_KEY), _ListPoint.get(index).get(X_KEY), _ListPoint.get(index) .get(Y_KEY), _PaintLine); canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG)); } } }drawWave 在ondraw中,是真正的画波形图的核心代码,前面所做的一切都是为了这个方法做准备。然后我们看看使用方法
private static final int MSG_DATA_CHANGE = 0x11; private ECGView _ECG; private Handler mHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); _ECG = (ECGView) findViewById(R.id.electrocardiogram); _ECG.setMaxPointAmount(100); _ECG.setRemovedPointNum(2); _ECG.setEveryNPoint(5); _ECG.setEveryNPointRefresh(1); _ECG.setEffticeValue(400); _ECG.setMaxYNumber(6000f); _ECG.setAlarmMessage(4800, 1000, "低了", "高了"); mHandler = new Handler() { @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub switch (msg.what) { case MSG_DATA_CHANGE: _ECG.setLinePoint((int)(msg.arg2)); break; default: break; } super.handleMessage(msg); } }; new Thread() { public void run() { while(true){ Message message = new Message(); message.what = MSG_DATA_CHANGE; try { sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } message.arg2 =new Random().nextInt(5000); mHandler.sendMessage(message); } } ; a46b }.start(); }
画波形图的核心代码都已经给出还有一些无关紧要代码大家可以下载我的demo参考,项目已经托管到github ECGViewDemo.有什么不对或者需要优化的地方还望大牛指出。
终于写完了 ,弄这个该死的github 弄到凌晨两点。
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories