最近在做手环的开发,需求有一个心率的曲线图,鉴于网上很少能找到折线图曲线图的实现,找到的符合设计师的审美需求(这是我司设计师绝对不允许的),所以也就只有自己写一个呗。
效果图如下:
里面主要用到了几个知识,贝塞尔曲线&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)