当前位置: 移动技术网 > 移动技术>移动开发>Android > Android如何自定义一个心电图控件?

Android如何自定义一个心电图控件?

2020年07月27日  | 移动技术网移动技术  | 我要评论

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

  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为20s
        if (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;
    @Override
    public 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() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float value = (float) valueAnimator.getAnimatedValue();
                if (velocity < 0 && xInit > minXInit) {//向左滑动
                    if (xInit - value <= minXInit)
                        xInit = minXInit;
                    else
                        xInit = xInit - value;
                } else if (velocity > 0 && xInit < maxXInit) {//向右滑动
                    if (xInit + value >= maxXInit)
                        xInit = maxXInit;
                    else
                        xInit = xInit + value;
                }
                invalidate();
            }
        });
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {
                isScrolling = true;
            }
            @Override
            public void onAnimationEnd(Animator animator) {
                isScrolling = false;
            }
            @Override
            public void onAnimationCancel(Animator animator) {
                isScrolling = false;
            }
            @Override
            public void onAnimationRepeat(Animator animator) {
            }
        });
        animator.start();
    }
    
    /**
     * 获取速度
     *
     * @return
     */
    private float getVelocity() {
        if (velocityTracker != null) {
            velocityTracker.computeCurrentVelocity(1000);
            return velocityTracker.getXVelocity();
        }
        return 0;
    }

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

本文地址:https://blog.csdn.net/qq_29382709/article/details/107533070

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网