身体数据心率图表

代码

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;



import java.util.ArrayList;
import java.util.List;

import androidx.annotation.ColorRes;
import androidx.annotation.Nullable;

/**
 * 心率图
 */
public class HeartRateView extends View {

   private int mDefaultWith = 800; //最小宽度
   private int mDefaultHeight = 300;//最小高度

   private int mWidth;
   private int mHeight;

   private Paint mLinePaint;//线条画笔
   private float mLineWidth;//线的粗细
   private int mLineColor;//线的颜色

   private Paint mFillPaint;//下边填充画笔

   private Paint mTextPaint;//文本画笔
   private int mTextColor;//文本颜色
   private int mTextSize;//字体大小

   private int mMaxYNum;//Y轴最大值
   private int mMaxXNum;//X轴最大值 分钟
   private int mTextPadding;//文本与线的距离

   private int mItemYNum;//竖向几个
   private int mItemXNum;//横向几个
   private long mStartTime = -1;
   private List<HeartRateView.HeartData> dataList;
// private int mCacheVolume = 500;//缓存数据量

   Paint.FontMetrics mTextPaintFm;
   private Bitmap mWaveLineBitmap;//折线绘制图片

   private int mOffsetTime;//向右偏移2s
   private Canvas mCanvasWaveLine; //图片绘制工具
   private Path mWaveLinePath;//折线路径
   private Rect mWaveLineBitmapSrc;
   private RectF mWaveLineBitmapDst;
   private float mWaveLineBitmapWidth;
   private int mWaveLineBitmapHeight;
   private Path mFillPath;//填充路径
   private List mRemoveList = new ArrayList();
   private float mScreenPercentage = 0.6f;//取值0.1f~1f 占屏比

   public HeartRateView(Context context) {
      this(context, null);
   }

   public HeartRateView(Context context, @Nullable AttributeSet attrs) {
      this(context, attrs, 0);
   }

   public HeartRateView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
      this(context, attrs, defStyleAttr, 0);
   }

   public HeartRateView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
      super(context, attrs, defStyleAttr, defStyleRes);
      init(context, attrs);
   }

   public <T extends HeartRateView.HeartData> void setDataList(List<T> dataList) {
      this.dataList = (List<HeartData>) dataList;
   }

   public <T extends HeartRateView.HeartData> void addData(T data) {
      if (dataList == null) {
         throw new NullPointerException();
      }
      dataList.add(data);
   }

   //初始化
   private void init(Context context, AttributeSet attrs) {
      TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.HeartRateView);
      mLineWidth = array.getDimensionPixelSize(R.styleable.HeartRateView_hrv_line_width, 3);
      mLineColor = array.getColor(R.styleable.HeartRateView_hrv_line_color, Color.GREEN);
      mTextColor = array.getColor(R.styleable.HeartRateView_hrv_text_color, Color.WHITE);
      mTextSize = array.getDimensionPixelSize(R.styleable.HeartRateView_hrv_text_size, 20);
      mMaxYNum = array.getInt(R.styleable.HeartRateView_hrv_max_y_size, 200);
      mMaxXNum = array.getInt(R.styleable.HeartRateView_hrv_max_x_size, 60);
      mTextPadding = array.getDimensionPixelSize(R.styleable.HeartRateView_hrv_text_padding, 10);
      mItemYNum = array.getInt(R.styleable.HeartRateView_hrv_item_y_num, 5);
      mItemXNum = array.getInt(R.styleable.HeartRateView_hrv_item_x_num, 12);
      mOffsetTime = array.getInt(R.styleable.HeartRateView_hrv_offset_time, 2);
      mScreenPercentage = array.getFloat(R.styleable.HeartRateView_hrv_screen_percentage, 0.6f);
      if (mScreenPercentage > 1f || mScreenPercentage <= 0f) {
         mScreenPercentage = 1f;
      }
      array.recycle();
      //线
      mLinePaint = new Paint();
      mLinePaint.setColor(mLineColor);
      mLinePaint.setAntiAlias(true);
      mLinePaint.setStrokeWidth(mLineWidth);
      mLinePaint.setStyle(Paint.Style.STROKE);
      mLinePaint.setStrokeCap(Paint.Cap.ROUND);
      mLinePaint.setStrokeJoin(Paint.Join.ROUND);
      mLinePaint.setPathEffect(new CornerPathEffect(10));
      //填充
      mFillPaint = new Paint();
      mFillPaint.setColor(mLineColor);
      mFillPaint.setAlpha(30);
      mFillPaint.setStrokeWidth(mLineWidth);
      mFillPaint.setAntiAlias(true);
      mFillPaint.setStyle(Paint.Style.FILL);
      //文本
      mTextPaint = new Paint();
      mTextPaint.setColor(mTextColor);
      mTextPaint.setTextSize(mTextSize);
      mTextPaint.setStrokeWidth(0.5f);
      mTextPaint.setAntiAlias(true);
      mTextPaint.setTextAlign(Paint.Align.LEFT);
      mTextPaint.setStyle(Paint.Style.FILL);
//    mStartTime = System.currentTimeMillis();//开始时间

      mTextPaintFm = mTextPaint.getFontMetrics();
      mWaveLinePath = new Path();
      mWaveLineBitmapSrc = new Rect();
      mWaveLineBitmapDst = new RectF();
      mFillPath = new Path();
   }

   @Override
   protected void onDraw(Canvas canvas) {
      super.onDraw(canvas);
      float YTextMaxWith = mTextPaint.measureText(mMaxYNum + "");
      float XTextMaxWith = mTextPaint.measureText(mMaxXNum + "");

      float mLeft = YTextMaxWith + getPaddingLeft() + mTextPadding;//折线绘制左坐标
      float mRight = (XTextMaxWith / 2f) + mWidth - getPaddingRight();//折线绘制右坐标
      float mTop = getPaddingTop();//折线绘制上坐标
      float mBottom = mHeight - XTextMaxWith - getPaddingBottom() - mTextPadding;//折线绘制上坐标
      float itemHeight = (mBottom - mTop - 10) / mItemYNum; //每个item像素高度
      //绘制横线和文本
      for (int i = 0; i < mItemYNum + 1; i++) {
         //绘制线和文本
         float mYLineY = mBottom - (i * itemHeight);//线的位置
         int num = mMaxYNum / mItemYNum * i;
         drawYLineText(canvas, mLeft, mRight, mYLineY, num);
      }
      if (mStartTime != -1) {

         float XItemWith = (mRight - mLeft) / mItemXNum;//横向间隔像素
         int mTimeInterval = mMaxXNum / mItemXNum; //横向时间间隔
         long nowTime = System.currentTimeMillis();
         double times = (nowTime - mStartTime) / 1000d / mTimeInterval;//需要绘制的个数
         //绘制X动态文本
         for (long i = 0; i < times; i++) {
//          float mStatX = mRight - (mRight - mLeft) * (nowTime - mStartTime) / (mMaxXNum * 1000f);
            float mStatX = mRight - ((mRight - mLeft) * (1f - mScreenPercentage)) - (mRight - mLeft) * (nowTime - mStartTime) / (mMaxXNum * 1000f);
            float TextX = mStatX + i * XItemWith;
            if (TextX > mLeft + mTextPadding) {
               drawXText(canvas, TextX, mBottom, i * mTimeInterval);
            }
         }
         //绘制折线
         drawWaveLine(canvas, mLeft, mTop, mRight, mBottom, itemHeight, nowTime);
         postInvalidateDelayed(20);
      }
   }

   /**
    * 绘制数据折线
    *
    * @param canvas
    */
   private void drawWaveLine(Canvas canvas, float mLeft, float mTop, float mRight, float mBottom, float itemHeight, long nowTime) {
      if (dataList != null && dataList.size() > 0) {
         mRemoveList.clear();
         float onePixXTime = mMaxXNum * 1000f / (mRight - mLeft);//一个像素代表的值
         float onePixYValue = (mMaxYNum * 1000f / mItemYNum) / itemHeight;
         mWaveLinePath.reset();
         mFillPath.reset();
         mCanvasWaveLine.save();
         mCanvasWaveLine.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
         int minIndex = -1;
         //绘制波浪线
         float mPreX = 0;
         float mPreY = 0;
         for (int index = 0; index < dataList.size(); index++) {
            HeartRateView.HeartData item = dataList.get(index);
            if (item.isValid()) {
               float IndexX = mWaveLineBitmapWidth - ((nowTime - mStartTime) - (item.getDataTime() - mStartTime)) / onePixXTime;
               float IndexY = mWaveLineBitmapHeight - item.getDataNum() * 1000f / onePixYValue;
//             mCanvasWaveLine.drawText(item.getDataNum()+"",IndexX,IndexY-10,mTextPaint);
               if (IndexX < 0) {
                  mRemoveList.add(item);
                  minIndex = -1;
                  continue;
               }
               if (minIndex == -1) {
                  minIndex = index;
                  //移动到这里
                  mFillPath.moveTo(IndexX, mWaveLineBitmapHeight);
                  mWaveLinePath.moveTo(IndexX, IndexY);
                  mFillPath.lineTo(IndexX, IndexY);
                  mPreX = IndexX;
                  mPreY = IndexY;
               } else {
                  //链接到这里
//                mWaveLinePath.quadTo(mPreX, mPreY, IndexX, IndexY);
                  mWaveLinePath.lineTo(IndexX, IndexY);
                  mFillPath.lineTo(IndexX, IndexY);
                  mPreX = IndexX;
                  mPreY = IndexY;
               }
            } else {
               if (minIndex != -1) {
                  minIndex = -1;
                  mFillPath.lineTo(mPreX, mWaveLineBitmapHeight);
                  mFillPath.close();
               }
            }
         }
         mCanvasWaveLine.drawPath(mWaveLinePath, mLinePaint);
         mFillPath.lineTo(mWaveLineBitmapWidth, mWaveLineBitmapHeight);
         mFillPath.close();
         mCanvasWaveLine.drawPath(mFillPath, mFillPaint);

         int bitmapLeft = (int) (mWaveLineBitmapWidth / 2 - (mRight - mLeft) / 2);
         int bitmapRight = (int) (bitmapLeft + (mRight - mLeft));
         int bitmapBottom = (int) mWaveLineBitmapHeight;
         int bitmapTop = (int) (bitmapBottom - (mBottom - mTop));
//       mWaveLineBitmapSrc.set(bitmapLeft, bitmapTop, bitmapRight, bitmapBottom);//图片
         mWaveLineBitmapSrc.set((int) (bitmapLeft + (bitmapRight - bitmapLeft) * (1f - mScreenPercentage)), bitmapTop, bitmapRight, bitmapBottom);//图片
//       mWaveLineBitmapDst.set(mLeft, mTop, mRight, mBottom);//位置
         mWaveLineBitmapDst.set(mLeft, mTop, (mRight - (mRight - mLeft) * (1f - mScreenPercentage)), mBottom);//位置
         canvas.drawBitmap(mWaveLineBitmap, mWaveLineBitmapSrc, mWaveLineBitmapDst, mLinePaint);
         mCanvasWaveLine.restore();
         dataList.removeAll(mRemoveList);
      }

   }


   /**
    * 绘制 X 文本
    *
    * @param canvas
    * @param TextX   文本中心x坐标
    * @param mBottom 最底部横线Y高度
    * @param num     绘制文本
    */
   private void drawXText(Canvas canvas, float TextX, float mBottom, long num) {
      float textLeft = TextX - mTextPaint.measureText(num + "");
      float baseLineY = mBottom + mTextPadding - mTextPaintFm.top;
      canvas.drawText(num + "", textLeft, baseLineY, mTextPaint);

   }

   /**
    * 绘制Y
    *
    * @param canvas
    * @param mLeft   线条最左侧
    * @param mRight  线条最右侧
    * @param mYLineY 线条高度
    * @param num     文本内容
    */
   private void drawYLineText(Canvas canvas, float mLeft, float mRight, float mYLineY, int num) {
      canvas.drawLine(mLeft, mYLineY, mRight, mYLineY, mTextPaint);
      String text = num + "";
      float baseLineX = mLeft - mTextPadding - mTextPaint.measureText(text);
      float baseLineY = mYLineY + (mTextPaintFm.bottom - mTextPaintFm.top) / 2 - mTextPaintFm.bottom;
      canvas.drawText(text, baseLineX, baseLineY, mTextPaint);
   }

   @Override
   protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
      super.onLayout(changed, left, top, right, bottom);
      mWidth = getWidth();
      mHeight = getHeight();
      RemeasureBitMap();

   }

   /**
    * 重新测量图片大小
    */
   private void RemeasureBitMap() {
      float XTextMaxWith = mTextPaint.measureText(mMaxXNum + "");
      float YTextMaxWith = mTextPaint.measureText(mMaxYNum + "");
      float mLeft = getPaddingLeft() + YTextMaxWith + mTextPadding;//折线绘制左坐标
      float mRight = mWidth - getPaddingRight() + (XTextMaxWith / 2);//折线绘制右坐标
      float offsetX = mOffsetTime * (mRight - mLeft) / 60;//偏移两秒
      mWaveLineBitmap = Bitmap.createBitmap((int) ((mRight - mLeft) + (2 * offsetX)), mHeight, Bitmap.Config.ARGB_8888);
      mWaveLineBitmapWidth = mWaveLineBitmap.getWidth();
      mWaveLineBitmapHeight = mWaveLineBitmap.getHeight();
      mCanvasWaveLine = new Canvas(mWaveLineBitmap);
   }

   @Override
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
      int widthMode = MeasureSpec.getMode(widthMeasureSpec);
      int widthSize = MeasureSpec.getSize(widthMeasureSpec);
      int heightMode = MeasureSpec.getMode(heightMeasureSpec);
      int heightSize = MeasureSpec.getSize(heightMeasureSpec);
      if (widthMode == MeasureSpec.UNSPECIFIED || widthMode == MeasureSpec.AT_MOST) {
         widthSize = mDefaultWith;
      }
      if (heightMode == MeasureSpec.UNSPECIFIED || heightMode == MeasureSpec.AT_MOST) {
         heightSize = mDefaultHeight;
      }
      setMeasuredDimension(widthSize, heightSize);
   }


   public static abstract class HeartData {
      private long dataTime;

      public abstract boolean isValid();//有效数据

      public abstract int getDataNum();//获取数量

      public long getDataTime() {
         return dataTime;
      }

      public void setDataTime(long dataTime) {
         this.dataTime = dataTime;
      }
   }

   public void startMeasure() {
      mStartTime = System.currentTimeMillis();//开始时间
      invalidate();
   }

   public void stopMeasure() {
      mStartTime = -1;//开始时间
      invalidate();
   }

   public void setLineWidth(float mLineWidth) {
      this.mLineWidth = mLineWidth;
   }

   public void setLineColor(@ColorRes int mLineColor) {
      this.mLineColor = mLineColor;
   }

   public void setTextColor(@ColorRes int mTextColor) {
      this.mTextColor = mTextColor;
   }

   public void setTextSize(int mTextSize) {
      this.mTextSize = mTextSize;
      requestLayout();
   }

   public void setMaxYNum(int mMaxYNum) {
      this.mMaxYNum = mMaxYNum;
   }

   public void setMaxXNum(int mMaxXNum) {
      this.mMaxXNum = mMaxXNum;
   }

   public void setTextPadding(int mTextPadding) {
      this.mTextPadding = mTextPadding;
      requestLayout();
   }

   public void setItemYNum(int mItemYNum) {
      this.mItemYNum = mItemYNum;
   }

   public void setItemXNum(int mItemXNum) {
      this.mItemXNum = mItemXNum;
   }

   public void setOffsetTime(int mOffsetTime) {
      this.mOffsetTime = mOffsetTime;
      requestLayout();
   }

   public void setScreenPercentage(float mScreenPercentage) {
      if (mScreenPercentage > 1f || mScreenPercentage <= 0f) {
         mScreenPercentage = 1f;
      }
      this.mScreenPercentage = mScreenPercentage;
   }
}

资源

<declare-styleable name="HeartRateView">
    <attr name="hrv_line_width" format="dimension" />
    <attr name="hrv_line_color" format="color" />
    <attr name="hrv_text_color" format="color" />
    <attr name="hrv_text_size" format="dimension" />
    <attr name="hrv_max_y_size" format="integer" />
    <attr name="hrv_max_x_size" format="integer" />
    <attr name="hrv_text_padding" format="dimension" />
    <attr name="hrv_item_y_num" format="integer" />
    <attr name="hrv_item_x_num" format="integer" />
    <attr name="hrv_offset_time" format="integer" />
    <attr name="hrv_screen_percentage" format="float" />
</declare-styleable>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值