基于Canvas绘制的图表

  1. 自定义view
package com.msqsoft.hodi_rent_app.view;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import com.msqsoft.hodi_rent_app.R;

import java.util.Random;

/**
 * @author gaopengfei
 */
public class GesturePulseView extends View {

    /**
     * 画文字与画脉搏指数的画笔
     * 这里健康指数为了显示成带折线下有阴影效果的样式,使用倒画的方式
     * 默认为显示全的阴影,通过健康指数的不同在阴影从上往下画透明梯形来做成折线下的阴影效果
     */
    private Paint mTxtPaint, mPulsePaint, mTouchEffectPaint, mDashLinePaint;
    /** 当前view的宽高尺寸(像素) */
    private int mViewWidth, mViewHeight;
    /** 每小时所占宽度 */
    private float mUnitWidth;
    /** 每2小时所占宽度(左右两边预留10像素间距) */
    private float mUnitTimeWidth;
    /** 下方时间轴的预设高度 */
    private int mTimelineHeight = 30;
    /** 每个小时的脉搏指数,24条线是25个点 */
    private int[] mData = new int[25];
    /** 盐分,让数据*盐分来实现数据正常显示在屏幕上,具体盐分大小需要具体适配调整 */
    private float mSalt;

    /** onTouch时的回调 */
    private OnPulseFrequencyTouchListener mPulseFrequencyTouchListener;
    /** onTouch down时的坐标 */
    private float mStartX = -5;

    /** 缓冲 */
    private Canvas mCacheCanvas;
    private Bitmap mCacheBitmap;

    public GesturePulseView(Context context, AttributeSet attrs) {
        super(context, attrs);

        // 初始化文字画笔,宽度2,打开抗锯齿,画白色
        mTxtPaint = new Paint();
        mTxtPaint.setStrokeWidth(2);
        mTxtPaint.setAntiAlias(true);
        mTxtPaint.setColor(Color.WHITE);

        // 初始化背景色画笔,打开抗锯齿,画背景色#5F8CCF
        // 这个#5F8CCF值是Android手机跑起来之后从显示出来的颜色抓取的
        // 如果改了背景色与每个组件的框框的话,这个值需要重新从运行效果中抓取
        mPulsePaint = new Paint();
        mPulsePaint.setAntiAlias(true);
        mPulsePaint.setColor(Color.parseColor("#5F8CCF"));

        // 初始化onTouch时竖线的画笔
        mTouchEffectPaint = new Paint();
        mTouchEffectPaint.setAntiAlias(true);
        mTouchEffectPaint.setColor(Color.parseColor("#FF9000"));
        mTouchEffectPaint.setStrokeWidth(2.0f);

        // 初始化虚线,不过因为是两种颜色的虚线,所以具体的时候再赋值颜色
        mDashLinePaint = new Paint();
        mDashLinePaint.setAntiAlias(true);
        mDashLinePaint.setStrokeWidth(2);

        // 初始化盐分
        mSalt = DensityUtil.dip2px(getContext(), 75.0f / 200.0f);

        mCacheCanvas = new Canvas();
    }

    {
        updata();
    }

    private void updata() {
        Random r = new Random();
        for (int i = 0; i < mData.length; i++) {
            mData[i] = r.nextInt(200);
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        this.mViewWidth = MeasureSpec.getSize(widthMeasureSpec);
        this.mViewHeight = MeasureSpec.getSize(heightMeasureSpec);
        this.mUnitTimeWidth = (mViewWidth - 20) / 12;
        this.mUnitWidth = mViewWidth / 24.0f;
    }

    @Override
    public void onWindowFocusChanged(boolean hasWindowFocus) {
        super.onWindowFocusChanged(hasWindowFocus);
        if (hasWindowFocus) {
            this.mViewWidth = getWidth();
            this.mViewHeight = getHeight();
            this.mUnitTimeWidth = (mViewWidth - 20) / 12;
            this.mUnitWidth = mViewWidth / 24.0f;
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mCacheBitmap == null) {// 没缓冲过,增加一份缓冲,以后直接画缓冲中的bitmap再加上touch效果就行了
            mCacheBitmap = Bitmap.createBitmap(mViewWidth, mViewHeight, Config.ARGB_8888);
            mCacheCanvas.setBitmap(mCacheBitmap);
            drawTimeline(mCacheCanvas);
            drawBackground(mCacheCanvas);
            drawLine(mCacheCanvas);
            drawDashLine(mCacheCanvas);
        }
        canvas.drawBitmap(mCacheBitmap, 0, 0, mTxtPaint);
        drawTouchEffect(canvas);
    }

    /** 画touch效果 */
    private void drawTouchEffect(Canvas canvas) {
        float x = mStartX;
        for (int i = 0; i < 25; i++) {
            if (mUnitWidth * i - 5 < mStartX && mUnitWidth * i + 5 > mStartX) {// 吸附效果
                x = mUnitWidth * i;
                canvas.drawCircle(x, mViewHeight - mTimelineHeight - mData[i] * mSalt, 5, mTouchEffectPaint);
                if (mPulseFrequencyTouchListener != null) {
                    mPulseFrequencyTouchListener.OnSleepQualityTouch(mData[i]);
                }
                break;
            }
        }
        canvas.drawLine(x, 0, x, mViewHeight - mTimelineHeight, mTouchEffectPaint);
    }

    /** 画折线,为了实现阴影效果其实画的是上方的透明梯形 */
    private void drawLine(Canvas canvas) {

        // 首先先画遮盖住阴影部分的梯形
        Path path = new Path();
        for (int i = 0; i < mData.length - 1; i++) {
            path.moveTo(i * mUnitWidth, 0);
            path.lineTo(i * mUnitWidth, mViewHeight - mTimelineHeight - mData[i] * mSalt);
            path.lineTo(i * mUnitWidth + mUnitWidth, mViewHeight - mTimelineHeight - mData[i + 1] * mSalt);
            path.lineTo(i * mUnitWidth + mUnitWidth, 0);
            canvas.drawPath(path, mPulsePaint);
        }

        // 因为24条折线是25个点,所以先手动画出第一个点,再for循环
        canvas.drawCircle(0, mViewHeight - mTimelineHeight - mData[0] * mSalt, 5, mTxtPaint);
        for (int i = 0; i < mData.length - 1; i++) {
            // 画折线
            canvas.drawLine(i * mUnitWidth, mViewHeight - mTimelineHeight - mData[i] * mSalt, i * mUnitWidth + mUnitWidth,
                    mViewHeight - mTimelineHeight - mData[i + 1] * mSalt, mTxtPaint);
            // 画点
            canvas.drawCircle(i * mUnitWidth + mUnitWidth, mViewHeight - mTimelineHeight - mData[i + 1] * mSalt, 5, mTxtPaint);
        }
    }

    /** 画时间轴 */
    private void drawTimeline(Canvas canvas) {
        canvas.drawLine(0, mViewHeight - mTimelineHeight, mViewWidth, mViewHeight - mTimelineHeight, mTxtPaint);
        mTxtPaint.setTextSize(22);
        for (int i = 0; i <= 12; i++) {
            String time;
            if (i < 5) {
                time = "0" + (i * 2);
            } else if (i < 12) {
                time = " " + (i * 2);
            } else {
                time = "00";
            }
            canvas.drawText(time, i * mUnitTimeWidth, mViewHeight - 5, mTxtPaint);
        }
    }

    /** 画阴影背景底色 */
    private void drawBackground(Canvas canvas) {
        Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.background);
        RectF rect = new RectF();
        rect.top = 0;
        rect.bottom = mViewHeight - mTimelineHeight;
        rect.left = 0;
        rect.right = mViewWidth;
        canvas.drawBitmap(bitmap, null, rect, mTxtPaint);
    }

    /** 画虚线标尺,因为高版本好像已经不持支PathEffect了,所以画虚线就是for循环出来的 */
    private void drawDashLine(Canvas canvas) {
        // 在值160与60的两个高度画两条虚线(需要注意的是,当前的取值范围为0~200)
        int highValue = 160;
        int deepValue = 60;
        mDashLinePaint.setColor(Color.parseColor("#A7DF8C"));
        for (int i = 0; i < mViewWidth; i += 10) {
            canvas.drawLine(i, mViewHeight - mTimelineHeight - highValue * mSalt, i + 5, mViewHeight - mTimelineHeight - highValue * mSalt, mDashLinePaint);
        }

        mDashLinePaint.setColor(Color.parseColor("#6AA9DA"));
        for (int i = 0; i < mViewWidth; i += 10) {
            canvas.drawLine(i, mViewHeight - mTimelineHeight - deepValue * mSalt, i + 5, mViewHeight - mTimelineHeight - deepValue * mSalt, mDashLinePaint);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        this.mStartX = event.getX();
        invalidate();
        return true;
    }

    public interface OnPulseFrequencyTouchListener {
        public void OnSleepQualityTouch(int data);
    }

    /** touch时回调方法 */
    public void SetOnPulseFrequencyTouchListener(OnPulseFrequencyTouchListener listener) {
        this.mPulseFrequencyTouchListener = listener;
    }
}
  1. 调用view
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:background="@mipmap/view_bg" >

    <TextView
        android:id="@+id/gesture_pulse"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="10dp"
        android:drawableLeft="@mipmap/pulse"
        android:text="电流"
        android:textColor="@android:color/white"
        android:textSize="20sp" />

    <TextView
        android:id="@+id/gesture_normal_value"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/gesture_pulse"
        android:layout_alignBottom="@+id/gesture_pulse"
        android:layout_alignParentRight="true"
        android:layout_marginRight="30dp"
        android:text="正常值 88~120"
        android:textColor="@android:color/white"
        android:textSize="20sp" />

    <View
        android:id="@+id/boundary"
        android:layout_width="match_parent"
        android:layout_height="2px"
        android:layout_alignParentLeft="true"
        android:layout_below="@+id/gesture_normal_value"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:background="@android:color/white" />

    <com.msqsoft.hodi_rent_app.view.GesturePulseView
        android:id="@+id/gesturepulseview"
        android:layout_width="match_parent"
        android:layout_height="150dp"
        android:layout_below="@id/boundary"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp" />

</RelativeLayout>

效果如下:
这里写图片描述

具体代码:github工程

### 使用微信小程序 Canvas API 绘制图表 #### 准备工作 为了在微信小程序中使用 `Canvas` API 绘制图表,开发者需先设置好 WXML 文件中的 `<canvas>` 标签。通过指定唯一的 `canvas-id` 属性来标识不同的画布实例,并设定其样式属性以便于布局控制[^1]。 ```xml <canvas canvas-id="myChart" style="width:300px;height:300px;"></canvas> ``` #### JavaScript 实现部分 接下来,在对应的 JS 文件里初始化并操作这个特定 ID 的画布对象。这里给出一个简单例子展示如何创建一个基础的圆形进度条作为环形图的一部分: ```javascript Page({ onReady() { const ctx = wx.createContext('2d', {id: 'myChart'}); function drawCircle(progress) { let startAngle = -Math.PI / 2; let endAngle = (progress * Math.PI * 2) - Math.PI / 2; // 设置背景圆圈颜色 ctx.setStrokeStyle('#ebebeb'); ctx.setLineWidth(8); // 绘制完整的外框圆周线 ctx.beginPath(); ctx.arc(75, 75, 60, 0, Math.PI*2, false); ctx.stroke(); // 开始绘制实际进度所占比例的部分 ctx.strokeStyle = '#ffcc00'; ctx.lineWidth = 8; ctx.lineCap = "round"; ctx.beginPath(); ctx.arc(75, 75, 60, startAngle, endAngle ,false); ctx.stroke(); // 将当前绘图上下文的内容渲染到页面上 wx.drawCanvas({canvasId:'myChart'}) } // 调用函数传入想要显示的比例值(此处假设为75%) drawCircle(.75); } }) ``` 对于更复杂的图形如柱状图,则可能涉及到更多的计算逻辑以及对坐标系的理解。由于微信小程序不支持 HTML5 中的一些特性比如 `measureText()` 方法用于测量文本尺寸,因此当需要处理文字排版时,可以通过自定义方式近似模拟该功能的效果[^2]。 另外值得注意的是,如果项目允许引入第三方库的话,也可以考虑利用成熟的可视化工具包简化开发流程,例如 D3.js 或者 ECharts for WeChat Mini Program 等[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值