Android自定义View之贝塞尔曲线图

本文介绍如何在Android中实现自定义View来绘制心率曲线图,主要涉及贝塞尔曲线、PathMeasure和ValueAnimator的使用。通过Path.cubieTo()绘制贝塞尔曲线,利用ValueAnimator和PathMeasure创建动画效果。此外,还讨论了如何处理用户的触摸事件,以显示所选白点对应的心率数值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在做手环的开发,需求有一个心率的曲线图,鉴于网上很少能找到折线图曲线图的实现,找到的符合设计师的审美需求(这是我司设计师绝对不允许的),所以也就只有自己写一个呗。

效果图如下:
曲线图效果

里面主要用到了几个知识,贝塞尔曲线&Android的PathMeasure&ValueAnimator。

首先是贝塞尔曲线在Android中的实现,图中的弯弯曲曲的效果则是通过贝塞尔曲线实现的,有用过PS的同学应该会比较熟悉。
贝塞尔曲线在Android中是通过Path类实现,利用Path.cubieTo()即可实现一小段的贝塞尔曲线效果,上图每两个白点间是两小段贝塞尔曲线,通过这样组成的一大段Path。

//Android中贝塞尔曲线使用方法cubicTo(带锚点)
mCurvePath.cubicTo((endPointX + lastPointX) / 2, lastPointY, (lastPointX + endPointX) / 2, endPointY, endPointX, endPointY);

Path.cubieTo()方法接受六个参数,第一个和第二个分别是曲线的起点的X轴坐标和Y轴坐标,第五个和第六个是Path的终点的X轴坐标和Y轴坐标,第三个和第四个参数是锚点的X轴坐标和Y轴坐标。两个白点平均分成两小段两个白点之间有两条贝塞尔曲线,分别以横坐标中点,终点的Y轴坐标为锚点。

if (data != null && data.length > 0) {
    if (data.length >= X_AXIS_POINT_COUNT) {
        System.arraycopy(data, 0, mDataValue, 0, X_AXIS_POINT_COUNT);
    } else {
        System.arraycopy(data, 0, mDataValue, 0, data.length);
        for (int i = data.length; i < X_AXIS_POINT_COUNT; i++) {
            mDataValue[i] = 0;
        }
    }

    reset();
    //多条小贝塞尔曲线组成一条大曲线,每一条贝塞尔曲线都是三次贝塞尔曲线,两个控制点
    float lastPointX;
    float lastPointY;
    for (int i = 0; i < X_AXIS_POINT_COUNT; i++) {
        if (mDataValue[i] != 0) {
            lastPointX = mDataPointAxisX[i];
            lastPointY = mGraphAxisYStartPosition - (mDataValue[i] - Y_AXIS_START_VALUE) * mAxisYValueScale;

            for (int j = i; j < X_AXIS_POINT_COUNT; j++) {
                if (mDataValue[j] != 0) {
                    float endPointX = mDataPointAxisX[j];
                    float endPointY = mGraphAxisYStartPosition - (mDataValue[j] - Y_AXIS_START_VALUE) * mAxisYValueScale;
                    mCurvePath.setLastPoint(lastPointX, lastPointY);
                    mCurvePath.cubicTo((endPointX + lastPointX) / 2, lastPointY, (lastPointX + endPointX) / 2, endPointY, endPointX, endPointY);

                    lastPointX = endPointX;
                    lastPointY = endPointY;
                 }
            }
            break;
        }
    }
    mPathMeasure.setPath(mCurvePath, false);
    mValueAnimator.start();
} else {
    Log.e(TAG, "data can not be null!");
}

上面的代码主要是在代码中先把整一条大曲线通过每一小段的贝塞尔曲线组合好,再通过ValueAnimator和PathMeasure类组合使用造成动画效果,PathMeasure的使用很简单,查询一下API的使用方法几分钟就可以学会。

mValueAnimator = ValueAnimator.ofFloat(0f, ANIMATION_END_VALUE);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        //最终的大曲线的长度
        float length = mPathMeasure.getLength();
        float factor = (float) animation.getAnimatedValue();
        //根据动画进度获取现在的曲线长度
        float currentPathLength = length * factor;
        //获取应该绘制的白点数量
        mDrawPointNum = (int) (factor / mPerPoint);
        //获取现在长度的Path,获取到的对象会赋值给第三个参数
        mPathMeasure.getSegment(0, currentPathLength, mDstAnimationPath, true);
        invalidate();
    }
});
mValueAnimator.setDuration(ANIMATION_DURATION);

onDraw方法中调用下面的方法即可动画绘制曲线。

//根据ValueAnimation中获得的增长的值,获取对应长度的路径,造成动画效果
canvas.drawPath(mDstAnimationPath, mCurvePaint);

onDraw方法中根据动画到达的位置绘制出白色点(我们的白色指示点是根据曲线到达的地点同时绘制的)

//画每个数据点
mIndicatePointPaint.setAlpha(0xFF);
for (int j = 0; j < mDataValue.length; j++) {

    //在动画到达的地方才画出点
    if (j < mDrawPointNum && mDataValue[j] != 0) {
        float startPointX = mDataPointAxisX[j];
        float startPointY = mGraphAxisYStartPosition - (mDataValue[j] - Y_AXIS_START_VALUE) * mAxisYValueScale;
        canvas.drawCircle(startPointX, startPointY, POINT_CIRCLE_RADIUS, mIndicatePointPaint)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值