贝塞尔曲线在Android中的应用

时间:2022-10-02 05:46:24

       今天要讲解的内容是Android中贝塞尔曲线的应用。可能很多人对贝塞尔曲线不甚了解,这里先对它的概念做一下简单介绍。

       贝塞尔曲线由多个点组成:起始点、终止点以及0到n个相互分离的中间点。根据中间点的不同,可以分为线性贝塞尔曲线、二阶贝塞尔曲线、三阶贝塞尔曲线和高阶贝塞尔曲线。一般的矢量图形软件通过它来精确画出曲线,贝塞尔曲线由线段与节点组成,节点是可拖动的支点,线段像可伸缩的皮筋。对于三阶贝塞尔曲线,它由两个锚点P0、P3和两个中间点P1、P2组成。曲线起始于P0走向P1,并从P2的方向来到P3。曲线一般不会经过P1和P2,这两个点只是提供方向资讯。P0和P1之间的间距,决定了曲线在转而趋进P3之前,走向P2方向的“长度有多长”。关于贝塞尔曲线的更多内容,可以从这里了解:http://blog.csdn.net/androidzhaoxiaogang/article/details/8680330

下面是二阶、三阶和四阶曲线的效果图,红色曲线为最终绘制出来的结果,大家可以了解一下。

 贝塞尔曲线在Android中的应用                   贝塞尔曲线在Android中的应用                   贝塞尔曲线在Android中的应用


       有个网站为我们提供了工具,可以生成对应的二阶贝塞尔曲线的数值:cubic-bezier.com。拖拽左边图像中的2个中间点,就会在右边显示两个中间点归一化的坐标值。我们还可以点击SAVE按钮将曲线保存下来,然后点击GO就可以查看通过当前曲线计算出来的差值器的效果。具体功能看下面的图。

贝塞尔曲线在Android中的应用


贝塞尔曲线在Android中的应用

       了解了贝塞尔曲线的基本概念,下面来看一下贝塞尔曲线在Android中的具体应用。贝塞尔曲线在Android中主要有三个用途:动画差值器、绘制动画轨迹、实现平滑绘图。

动画差值器

       我们知道Android动画可以设置差值器Interpolator来修饰动画效果,其作用是把0到1的浮点值变化映射到另一个浮点值变化,然后将这个值作为动画的变化率。定义差值器需要实现Interpolator接口,然后根据三阶贝塞尔曲线的公式,得到下面的差值器。其中p1、p2是两个中间点的坐标,需要调用者传入来得到不同的曲线。如果我们要得到曲线横轴方向上的变化率,就将两个中间点的y坐标带入公式;否则,就将两个中间点的x坐标带入公式。

public class BezierInterpolator implements Interpolator {
float p1;
float p2;
BezierInterpolator(float p1, float p2) {
this.p1 = p1;
this.p2 = p2;
}
@Override
public float getInterpolation(float t) {
return 0 * (1 - t) * (1 - t) * (1 - t)
+ 3 * p1 * t * (1 - t) * (1 - t)
+ 3 * p2 * t * t * (1 - t)
+ 1 * t * t * t;
}
}
绘制动画轨迹

       说明这个问题之前,必须先了解属性动画中的TypeEvaluator。TypeEvaluator有什么作用呢?简单来说,就是告诉系统动画如何从初始值过度到结束值。其实这个TypeEvaluator也可以理解为差值器,只是它的定义是泛型的,我们可以将泛型参数设置为PointF,就能得到当前运动轨迹的坐标位置。而Interpolator只是一个线性的差值器,只能得到运动速率的变化,不能得到运动轨迹。如果还不理解,就看下面的代码吧。

public class BezierEvaluator implements TypeEvaluator<PointF> {
PointF mPointF1;
PointF mPointF2;

public BezierEvaluator(PointF mPointF1, PointF mPointF2) {
this.mPointF1 = mPointF1;
this.mPointF2 = mPointF2;
}

@Override
public PointF evaluate(float t, PointF point0, PointF point3) {
//t 百分比 0~1
PointF pointF = new PointF();
pointF.x = point0.x * (1 - t) * (1 - t) * (1 - t)
+ 3 * mPointF1.x * t * (1 - t) * (1 - t)
+ 3 * mPointF2.x * t * t * (1 - t)
+ point3.x * t * t * t;

pointF.y = point0.y * (1 - t) * (1 - t) * (1 - t)
+ 3 * mPointF1.y * t * (1 - t) * (1 - t)
+ 3 * mPointF2.y * t * t * (1 - t)
+ point3.y * t * t * t;
return pointF;
}
}

    private ValueAnimator getBezierValueAnimator() {
PointF pointf0 = new PointF(0, 0);
PointF pointf1 = new PointF(width / 8, height * 7 / 8);
PointF pointf2 = new PointF(width * 7 / 8, height / 8);
PointF pointf3 = new PointF(width - 100, height - 100);

//通过贝塞尔曲线公式,自定义估值器
final BezierEvaluator evaluator = new BezierEvaluator(pointf1, pointf2);
//将估值器传入属性动画,不断的修改控件的坐标
ValueAnimator animator = ValueAnimator.ofObject(evaluator, pointf0, pointf3);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
PointF pointf = (PointF) animation.getAnimatedValue();
mImageView.setX(pointf.x);
mImageView.setY(pointf.y);
}
});
animator.setTarget(mImageView);
animator.setDuration(3000);
animator.setInterpolator(new BezierInterpolator(1.61f, -0.26f));

return animator;
}

实现平滑绘图

       实现一个绘图功能,基本思路就是处理Touch事件,通过调用lineTo去更新Path,然后通过Canvas的drawPath不断地重绘。然而,这样绘出来的曲线在弯曲处不平滑。如果要绘制出平滑的曲线,就需要用到贝塞尔曲线。Android的Path类的quadTo方法恰恰就封装了二阶贝塞尔曲线的功能。下面看具体的实现代码。

public class BezierDrawView extends View {
private float mX;
private float mY;

private final Paint mGesturePaint = new Paint();
private final Path mPath = new Path();

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

public BezierDrawView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public BezierDrawView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mGesturePaint.setAntiAlias(true);
mGesturePaint.setStyle(Paint.Style.STROKE);
mGesturePaint.setStrokeWidth(8);
mGesturePaint.setColor(Color.RED);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
handleDown(event);
break;
case MotionEvent.ACTION_MOVE:
handleMove(event);
}
invalidate();
return true;
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawPath(mPath, mGesturePaint);
}

private void handleDown(MotionEvent event) {
mPath.reset();
float x = event.getX();
float y = event.getY();

mX = x;
mY = y;
mPath.moveTo(x, y);
}

private void handleMove(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();

final float preX = mX;
final float preY = mY;

final float dx = Math.abs(x - preX);
final float dy = Math.abs(y - preY);

//两点之间的距离大于等于3时,生成贝塞尔绘制曲线
if (dx >= 3 || dy >= 3) {
//设置贝塞尔曲线的操作点为起点和终点的一半
float cX = (x + preX) / 2;
float cY = (y + preY) / 2;

//二次贝塞尔,实现平滑曲线;previousX, previousY为操作点,cX, cY为终点
mPath.quadTo(preX, preY, cX, cY);
// mPath.lineTo(x, y);

mX = x;
mY = y;
}
}
}

       对比一下通过quadTo和lineTo绘制出来曲线的不同,左边的曲线比右边的曲线要平滑一些,仔细看右边的图会发现曲线上有明显的拐角。

 贝塞尔曲线在Android中的应用                 贝塞尔曲线在Android中的应用                                      

         下面是贝塞尔曲线在Android中应用的最终动效图,我们把三个功能集成到了一个页面中。

贝塞尔曲线在Android中的应用


点我下载Demo


参考文章:

http://www.cnblogs.com/benhero/p/4377374.html?utm_source=tuicool&utm_medium=referral

http://blog.csdn.net/androidzhaoxiaogang/article/details/8680330