Android开发

数据处理流程:

1graph LR 2心率数据-->心率仓库 3心率仓库-->根据采样率获取心率数据 4根据采样率获取心率数据--> 打印数据

思路篇:

  • 整个控件分成上下两层。上层画线条,下层画表格
    • 线条篇
      • 1.线条决定使用Path来画,而Path的数据,则使用一个Int数组来保存
      • 2.Int数组的大小,是依据采样频率 * 显示秒数 来决定的
      • 3.读取数据赋值到Path里,需要指定 x , y 的值
      • 4.X 依据采样频率,可以计算出每个点的 X 的值
      • 5.Y 的位置,则是依据值的大小,以及控件应该设置一个MAX最大值的大小的比例,计算出Y的绝对位置。
      • 6.线条走动,则是将数组内数据的移动 Int[n] = Int[n+1]
      • 7.在实际情况中,极有可能是先采集的数据,再对数据进行播放,所以控件内部需要维护一个数据仓库,数据添加不需要考虑其他问题,而速率问题则由控件内部维护。
      • 8.而速率,则是根据采样率,来进行控制速度从数据仓库定时取出。
      • 9.但是在实际情况中,有时候需要对速率进行慢速播放,实速播放,以及加速播放。所以需要一个控制播放速度。
    • 表格篇
      • 1.线条绘制由一个基准线标准,可以将线条的绘制维持在基准线上下,而不会导致线条偏移离谱
      • 2.由基准线衍生出来的表格,需要可以自定义表格的行数,线条宽度,以及颜色,等。

代码篇:

先看属性:

1<?xml version="1.0" encoding="utf-8"?> 2<resources> 3 <declare-styleable name="HeartView"> 4 <!--心电线的宽度--> 5 <attr name="heart_line_border" format="dimension" /> 6 <!--每个表格的行数(就是小格子数)--> 7 <attr name="heart_grid_row" format="integer" /> 8 <!--大表格的边框的宽度--> 9 <attr name="heart_grid_border" format="dimension" /> 10 <!--每个小格子的宽高--> 11 <attr name="heart_grid_row_height" format="dimension" /> 12 <!--每个小格子的线的宽度--> 13 <attr name="heart_grid_line_border" format="dimension" /> 14 <!--基准线--> 15 <attr name="heart_base_line" format="integer" /> 16 <!--最大值--> 17 <attr name="heart_max" format="integer" /> 18 <!--最小值--> 19 <attr name="heart_min" format="integer" /> 20 <!--数据采集频率--> 21 <attr name="heart_hz" format="integer" /> 22 <!--一个控件,可以显示的心率的时长--> 23 <attr name="heart_show_seconds" format="float" /> 24 <!--心率线条的颜色--> 25 <attr name="heart_color" format="color" /> 26 <!--表格线条的颜色--> 27 <attr name="heart_grid_line_color" format="color" /> 28 <!--表格边框的颜色--> 29 <attr name="heart_grid_border_color" format="color" /> 30 <!--控制播放速度的调整 值越小,播放速度越慢 --> 31 <attr name="heart_speed" format="float" /> 32 </declare-styleable> 33</resources>

实现代码:

1import android.content.Context; 2import android.content.res.TypedArray; 3import android.graphics.Canvas; 4import android.graphics.Color; 5import android.graphics.Paint; 6import android.graphics.Path; 7import android.util.AttributeSet; 8import android.view.View; 9 10import androidx.annotation.ColorInt; 11import androidx.annotation.FloatRange; 12 13import java.util.Timer; 14import java.util.TimerTask; 15import java.util.concurrent.LinkedBlockingDeque; 16 17/** 18 * 显示心电的控件 19 */ 20public class HeartView extends View 21{ 22 // 数据的最大值 23 private int Max; 24 // 数据的最小值 25 private int Min; 26 // 数据一秒钟采集频率,默认100个点一秒种 27 private int hz; 28 // 控件显示几秒钟的心跳,默认显示2秒钟的心跳 29 private float showSeconds; 30 // 要画的基准线 31 private int baseLine; 32 // 每个方格的行数 33 private int grid_row; 34 // 每个方格的高度 35 private int grid_row_height; 36 // 心率线条的颜色 默认红色 37 private int heartColor; 38 // 表格线条的颜色 默认灰色 39 private int heart_grid_line_color; 40 // 表格边框的颜色 默认灰色 41 private int heart_grid_border_color; 42 // 心电线的宽度 43 private int heart_line_border; 44 // 大表格的边框的宽度 45 private int heart_grid_border; 46 // 每个小格子的线的宽度 47 private int heart_grid_line_border; 48 // 速度控制 49 private float heart_speed; 50 51 private int viewHeight = 0; 52 private int viewWidth = 0; 53 54 // 画笔 55 private Paint paint; 56 // 需要画心电的路径 57 private Path path = new Path(); 58 // 根据显示秒数,以及采样频率算出总共需要申请多少个内存的数据 59 private int[] showTimeDatas; 60 // 待显示的数据队列 61 private LinkedBlockingDeque<Integer> dataQueue = new LinkedBlockingDeque<>(); 62 // 定时运行栈 63 private HeartTask heartTask = null; 64 // 精准定时器 65 private Timer timer = new Timer(); 66 67 public HeartView(Context context) 68 { 69 this(context, null); 70 } 71 72 public HeartView(Context context, AttributeSet attrs) 73 { 74 this(context, attrs, 0); 75 } 76 77 public HeartView(Context context, AttributeSet attrs, int defStyleAttr) 78 { 79 super(context, attrs, defStyleAttr); 80 paint = new Paint(); 81 paint.setStyle(Paint.Style.STROKE); 82 paint.setAntiAlias(true); 83 paint.setColor(Color.RED); 84 // 线条交界处,钝化处理,看起来是圆点 85 paint.setStrokeJoin(Paint.Join.ROUND); 86 87 TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.HeartView); 88 // 心电线的宽度 89 heart_line_border = typedArray.getDimensionPixelSize(R.styleable.HeartView_heart_line_border, (int) dip2px(context, 1f)); 90 // 每个表格的行数(就是小格子数,默认5格 91 grid_row = typedArray.getInt(R.styleable.HeartView_heart_grid_row, 5); 92 // 大表格的边框的宽度 93 heart_grid_border = typedArray.getDimensionPixelSize(R.styleable.HeartView_heart_grid_border, (int) dip2px(context, 2f)); 94 // 每个小格子的宽高 95 grid_row_height = typedArray.getDimensionPixelSize(R.styleable.HeartView_heart_grid_row_height, (int) dip2px(context, 10f)); 96 // 每个小格子的线的宽度 97 heart_grid_line_border = typedArray.getDimensionPixelSize(R.styleable.HeartView_heart_grid_line_border, (int) dip2px(context, 1f)); 98 // 基准线,默认2000 99 baseLine = typedArray.getInteger(R.styleable.HeartView_heart_base_line, 2000); 100 // 最大值,默认4000 101 Max = typedArray.getInteger(R.styleable.HeartView_heart_max, 4096); 102 // 最小值,默认0 103 Min = typedArray.getInteger(R.styleable.HeartView_heart_min, 0); 104 // 数据采集频率,默认100个点一秒钟 105 hz = typedArray.getInteger(R.styleable.HeartView_heart_hz, 100); 106 // 一个控件,可以显示的心率的时长 ,默认为2秒钟 107 showSeconds = typedArray.getFloat(R.styleable.HeartView_heart_show_seconds, 2f); 108 // 心率线条的颜色 默认红色 109 heartColor = typedArray.getColor(R.styleable.HeartView_heart_color, Color.RED); 110 // 表格线条的颜色 默认绿色 111 heart_grid_line_color = typedArray.getColor(R.styleable.HeartView_heart_grid_line_color, Color.parseColor("#DBDBDB")); 112 // 表格边框的颜色 默认绿色 113 heart_grid_border_color = typedArray.getColor(R.styleable.HeartView_heart_grid_border_color, Color.parseColor("#DBDBDB")); 114 // 播放速度的控制 115 heart_speed = typedArray.getFloat(R.styleable.HeartView_heart_speed, 1.0f); 116 typedArray.recycle(); 117 118 // 速度怎么可以小于0 119 if (heart_speed < 0) 120 { 121 throw new RuntimeException("Attributes heart_speed Can Not < 0 "); 122 } 123 // 最小值怎么可以大于或等于最大值 124 if (Min >= Max) 125 { 126 throw new RuntimeException("Attributes heart_min Can Not >= heart_max "); 127 } 128 129 showTimeDatas = new int[(int) (showSeconds * hz)]; 130 } 131 132 @Override 133 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 134 { 135 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 136 viewHeight = measureHeight(heightMeasureSpec); 137 viewWidth = measureWidth(widthMeasureSpec); 138 path.moveTo(0, viewHeight); 139 } 140 141 /** 142 * 重新部署发点任务 143 */ 144 private synchronized void publishJob() 145 { 146 // 根据采集的频率,自动算出每一个点之间暂停的时间 147 long yield = (int) (1000 / (hz * heart_speed)); 148 if (heartTask != null) 149 { 150 heartTask.cancel(); 151 heartTask = null; 152 } 153 heartTask = new HeartTask(); 154 timer.scheduleAtFixedRate(heartTask, 0, yield); 155 } 156 157 /** 158 * 设置表格的行数 159 * 160 * @param grid_row 161 */ 162 public void setGrid_row(int grid_row) 163 { 164 this.grid_row = grid_row; 165 } 166 167 /** 168 * 设置每个小方格的高度 169 * 170 * @param height 171 */ 172 public void setGrid_row_height(int height) 173 { 174 this.grid_row_height = height; 175 } 176 177 /** 178 * 设置线条颜色 179 * 180 * @param color 181 */ 182 public void setHeartColor(@ColorInt int color) 183 { 184 this.heartColor = color; 185 } 186 187 /** 188 * 设置画小表格的颜色 189 * 190 * @param color 191 */ 192 public void setHeartGridLineColor(@ColorInt int color) 193 { 194 this.heart_grid_line_color = color; 195 } 196 197 /** 198 * 设置大表格边框颜色 199 * 200 * @param color 201 */ 202 public void setHeartGridBorderColor(@ColorInt int color) 203 { 204 this.heart_grid_border_color = color; 205 } 206 207 /** 208 * 设置线条宽度 209 * 210 * @param border 211 */ 212 public void setHeartLineBorder(@ColorInt int border) 213 { 214 this.heart_line_border = border; 215 } 216 217 /** 218 * 设置大格边框线宽 219 * 220 * @param border 221 */ 222 public void setHeartGridBorder(int border) 223 { 224 this.heart_grid_border = border; 225 } 226 227 /** 228 * 设置小格线宽 229 * 230 * @param border 231 */ 232 public void setHeartGridLineBorder(int border) 233 { 234 this.heart_grid_line_border = border; 235 } 236 237 /** 238 * 设置倍速 239 * 240 * @param speed 241 */ 242 public void setHeartSpeed(@FloatRange(from = 0.0, to = Float.MAX_VALUE) float speed) 243 { 244 this.heart_speed = speed; 245 // 速度怎么可以小于0 246 if (heart_speed < 0) 247 { 248 throw new RuntimeException("Attributes heart_speed Can Not < 0 "); 249 } 250 publishJob(); 251 } 252 253 /** 254 * 添加一个点,会自动依据频率来动态显示 255 * 256 * @param point 257 */ 258 public synchronized void offer(int point) 259 { 260 dataQueue.offer(point); 261 if (heartTask == null) 262 { 263 publishJob(); 264 } 265 } 266 267 /** 268 * 添加一组点,自动依据频率来动态显示 269 * 270 * @param points 271 */ 272 public void offer(int[] points) 273 { 274 for (int i = 0; i < points.length; i++) 275 offer(points[i]); 276 } 277 278 /** 279 * 设置显示死数据,没有动态走动效果 280 */ 281 public synchronized void setData(int[] points) 282 { 283 // 如果传过来的数据 比要显示的短,那么先根据数据长度替换,再将尾巴数据清空 284 // 传递数据:[5,6] 285 // 显示数据:[1,1,1] 286 // 替换数据:[5,6,1] 287 // 尾巴清空:[5,6,0] 288 if (points.length <= showTimeDatas.length) 289 { 290 System.arraycopy(points, 0, showTimeDatas, 0, points.length); 291 for (int i = points.length; i < showTimeDatas.length; i++) 292 { 293 showTimeDatas[i] = 0; 294 } 295 } else 296 { 297 // 如果传过来的数据,比显示的要长,那么以显示的长度为依据进行数据替换 298 // 传递数据:[5,6,7] 299 // 显示数据:[1,2] 300 // 替换数据:[5,6] 301 System.arraycopy(points, 0, showTimeDatas, 0, showTimeDatas.length); 302 } 303 postInvalidate(); 304 } 305 306 /** 307 * 设置每秒的采集频率 308 * 309 * @param hz 310 */ 311 public synchronized void setHz(int hz) 312 { 313 this.hz = hz; 314 this.showTimeDatas = new int[(int) (showSeconds * hz)]; 315 publishJob(); 316 } 317 318 /** 319 * 设置最大值 320 * 321 * @param max 322 */ 323 public synchronized void setMax(int max) 324 { 325 this.Max = max; 326 // 最小值怎么可以大于或等于最大值 327 if (Min >= Max) 328 { 329 throw new RuntimeException("Attributes heart_min Can Not >= heart_max "); 330 } 331 } 332 333 /** 334 * 设置最小值 335 * 336 * @param min 337 */ 338 public void setMin(int min) 339 { 340 Min = min; 341 // 最小值怎么可以大于或等于最大值 342 if (Min >= Max) 343 { 344 throw new RuntimeException("Attributes heart_min Can Not >= heart_max "); 345 } 346 } 347 348 /** 349 * 设置控件显示几秒钟的数据 350 * 351 * @param showSeconds 352 */ 353 public synchronized void setShowSeconds(float showSeconds) 354 { 355 this.showSeconds = showSeconds; 356 this.showTimeDatas = new int[(int) (showSeconds * hz)]; 357 } 358 359 /** 360 * 清空图案 361 */ 362 public synchronized void clear() 363 { 364 for (int i = 0; i < showTimeDatas.length; i++) 365 showTimeDatas[i] = 0; 366 postInvalidate(); 367 } 368 369 /** 370 * 设置基准线 371 * 372 * @param baseLine 373 */ 374 public void setBaseLine(int baseLine) 375 { 376 this.baseLine = baseLine; 377 } 378 379 @Override 380 protected void onDraw(Canvas canvas) 381 { 382 super.onDraw(canvas); 383 int[] showDatas = showTimeDatas; 384 // 画表格 385 int baseY = calculateY(baseLine - Min, Max - Min, viewHeight); 386 // 基准线以上 387 for (int y = baseY; y > 0; y -= grid_row_height) 388 { 389 if ((baseY - y) / grid_row_height % grid_row == 0) 390 { 391 paint.setStrokeWidth(heart_grid_border); 392 paint.setColor(heart_grid_border_color); 393 } else 394 { 395 paint.setStrokeWidth(heart_grid_line_border); 396 paint.setColor(heart_grid_line_color); 397 } 398 canvas.drawLine(0, y, viewWidth, y, paint); 399 } 400 // 基准线以下 401 for (int y = baseY; y < viewHeight; y += grid_row_height) 402 { 403 if ((y - baseY) / grid_row_height % grid_row == 0) 404 { 405 paint.setStrokeWidth(heart_grid_border); 406 paint.setColor(heart_grid_border_color); 407 } else 408 { 409 paint.setStrokeWidth(heart_grid_line_border); 410 paint.setColor(heart_grid_line_color); 411 } 412 canvas.drawLine(0, y, viewWidth, y, paint); 413 } 414 // 中心线以右 415 int centerX = viewWidth / 2; 416 for (int x = centerX; x < viewWidth; x += grid_row_height) 417 { 418 if ((x - centerX) / grid_row_height % grid_row == 0) 419 { 420 paint.setStrokeWidth(heart_grid_border); 421 paint.setColor(heart_grid_border_color); 422 } else 423 { 424 paint.setStrokeWidth(heart_grid_line_border); 425 paint.setColor(heart_grid_line_color); 426 } 427 canvas.drawLine(x, 0, x, viewHeight, paint); 428 } 429 // 中心线以左 430 for (int x = centerX; x > 0; x -= grid_row_height) 431 { 432 if ((centerX - x) / grid_row_height % grid_row == 0) 433 { 434 paint.setStrokeWidth(heart_grid_border); 435 paint.setColor(heart_grid_border_color); 436 } else 437 { 438 paint.setStrokeWidth(heart_grid_line_border); 439 paint.setColor(heart_grid_line_color); 440 } 441 canvas.drawLine(x, 0, x, viewHeight, paint); 442 } 443 444 445 // 画心电 446 paint.setColor(heartColor); 447 paint.setStrokeWidth(heart_line_border); 448 int firstData = showDatas[0]; 449 int firstY = calculateY(firstData - Min, Max - Min, viewHeight); 450 path.reset(); 451 path.moveTo(0, firstY); 452 for (int i = 0; i < showDatas.length; i++) 453 { 454 int value = showDatas[i]; 455 int x = (int) (((float) i / showDatas.length) * viewWidth); 456 int y = calculateY(value - Min, Max - Min, viewHeight); 457 path.lineTo(x, y); 458 } 459 canvas.drawPath(path, paint); 460 } 461 462 463 /** 464 * 根据最大值,控件高度,计算出当前值对应的控件的 Y 坐标 465 * 466 * @param value 参与计算的值 467 * @param Region 最大值 - 最小值的区域 468 * @param viewHeight 控件高度 469 * @return 470 */ 471 private static int calculateY(int value, int Region, int viewHeight) 472 { 473 return viewHeight - ((int) (((float) value / Region) * viewHeight)); 474 } 475 476 /** 477 * 测量自定义View的高度 478 */ 479 private int measureHeight(int heightMeasureSpec) 480 { 481 int heightResult = 0; 482 int heightSpecMode = View.MeasureSpec.getMode(heightMeasureSpec); 483 int heightSpecSize = View.MeasureSpec.getSize(heightMeasureSpec); 484 switch (heightSpecMode) 485 { 486 case View.MeasureSpec.UNSPECIFIED: 487 { 488 heightResult = heightSpecSize; 489 } 490 break; 491 case View.MeasureSpec.AT_MOST: 492 { 493 heightResult = View.MeasureSpec.getSize(heightMeasureSpec); 494 } 495 break; 496 case View.MeasureSpec.EXACTLY: 497 { 498 heightResult = View.MeasureSpec.getSize(heightMeasureSpec); 499 } 500 } 501 return heightResult; 502 } 503 504 /** 505 * 测量自定义View的宽度 506 */ 507 private int measureWidth(int widthMeasureSpec) 508 { 509 int widthResult = 0; 510 int widthSpecMode = View.MeasureSpec.getMode(widthMeasureSpec); 511 int widthSpecSize = View.MeasureSpec.getSize(widthMeasureSpec); 512 switch (widthSpecMode) 513 { 514 case View.MeasureSpec.UNSPECIFIED: 515 { 516 widthResult = widthSpecSize; 517 } 518 break; 519 case View.MeasureSpec.AT_MOST: 520 { 521 widthResult = View.MeasureSpec.getSize(widthMeasureSpec); 522 } 523 break; 524 case View.MeasureSpec.EXACTLY: 525 { 526 widthResult = View.MeasureSpec.getSize(widthMeasureSpec); 527 } 528 } 529 return widthResult; 530 } 531 532 533 /** 534 * dp 转 px 535 * 536 * @param context 上下文 537 * @param dipValue dp值 538 * @return 539 */ 540 private float dip2px(Context context, float dipValue) 541 { 542 float scale = context.getResources().getDisplayMetrics().density; 543 return dipValue * scale + 0.5f; 544 } 545 546 547 /** 548 * 释放资源 549 */ 550 public synchronized void recycle() 551 { 552 if (heartTask != null) 553 { 554 heartTask.cancel(); 555 } 556 timer.cancel(); 557 } 558 559 /** 560 * 发点的任务 561 */ 562 private class HeartTask extends TimerTask 563 { 564 @Override 565 public void run() 566 { 567 try 568 { 569 Integer point = dataQueue.poll(); 570 if (point != null) 571 { 572 for (int i = 0; i < showTimeDatas.length; i++) 573 { 574 if (i + 1 < showTimeDatas.length) 575 { 576 showTimeDatas[i] = showTimeDatas[i + 1]; 577 } else 578 { 579 showTimeDatas[i] = point; 580 } 581 } 582 postInvalidate(); 583 } else 584 { 585 cancel(); 586 heartTask = null; 587 } 588 } catch (Exception e) 589 { 590 e.printStackTrace(); 591 } 592 } 593 } 594} 595 596

示意图:

更新时间:2019-08-04 使用timer进行速度控制,可精确到毫秒级实时,代替 Thread.sleep() 函数对毫秒级控制的不准确性。因为 Thread.sleep 会将线程转为等待状态,而没有在就绪状态,状态恢复对比毫秒级比较耗时。倘若次数较多累加,则时间延误明显。

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

皕杰报表之UUID

​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

2020年前端实用代码段,为你的工作保驾护航

有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )