一张图带你彻底了解二阶贝塞尔曲线
Android,贝塞尔曲线,原理,自定义view2016-06-10
上一篇自定义View中,贝塞尔曲线出现的频率很高,有小伙伴就问到关于贝塞尔曲线控制点坐标怎么计算的问题。一阶贝塞尔曲线是一条直线,确定起点终点即可,三阶贝塞尔曲线有两个控制点,相对比较复杂。二阶贝塞尔曲线只有一个控制点,在实际开发中使用的很多。今天讨论的就是关于二阶贝塞尔曲线的控制点坐标计算问题。
到底怎样一张图就能够彻底了解二阶贝塞尔曲线呢,往下看就知道了:
设置二阶贝塞尔曲线的方法:
moveTo(float x,float y)
其中x,y的坐标代表图中曲线左边起点的位置坐标
quadTo(float x1, float y1, float x2, float y2 )
其中x1,y1的坐标就是图中小圆点的位置,也就是控制点的坐标
x2,y2的坐标就是图中曲线右边终点的位置坐标
代码中怎么实现的,一起看一下:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(10);
path.reset();
path.moveTo(mWidth * 1 / 8, mHeight * 1 / 5);
path.quadTo(xWidth, yHeight, mWidth * 7 / 8, mHeight * 1 / 5);
canvas.drawPath(path, paint);
paint.setStyle(Paint.Style.FILL);
//控制点
canvas.drawCircle(xWidth, yHeight, 10, paint);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
xWidth = x;
yHeight = y;
postInvalidate();
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
其实就是重写了onTouchEvent(MotionEvent ev)方法,在action为MotionEvent.ACTION_MOVE的时候,得到滑动时候的x,y坐标,然后赋值给 path.quadTo(xWidth, yHeight, mWidth * 7 / 8, mHeight * 1 / 5)方法中的前两个参数,也就是控制点的坐标。然后调用postInvalidate()来重新进行绘制即可。最后记得在onTouchEvent(MotionEvent ev)方法后返回true,表示View消耗当前滑动事件。
哈哈,看完是不是觉得很简单了。基于这张原理图,实际开发中很多应用。我后来做了一些改进,算是小小拓展一下。看看下面的效果图:
是不是一种很熟悉的赶脚,什么清理小火箭呀,弹性球呀,都是换汤不换药的东东。撸代码:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(10);
path.reset();
path.moveTo(mWidth * 1 / 8, mHeight * 2 / 5);
path.quadTo(mWidth * 4 / 8, yHeight, mWidth * 7 / 8, mHeight * 2 / 5);
canvas.drawPath(path, paint);
paint.setStyle(Paint.Style.FILL);
//绘制圆
canvas.drawCircle(mWidth * 4 / 8, yCircle, mWidth / 10, paint);
}
与之前第一张图不一样的是,我让path.quadTo()的第一个参数固定住了,这样就保证弧线只会在竖直方向上进行改变。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int action = ev.getAction();
int y = (int) ev.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
yDown = y;
break;
case MotionEvent.ACTION_MOVE:
yMove = y;
int dy = yMove - yDown;
//如果是从上往下滑动
if (dy > 0 && Math.abs(dy) > mTouchSlop * 2) {
isSlide = true;
}
break;
case MotionEvent.ACTION_UP:
break;
}
return super.dispatchTouchEvent(ev);
}
重写了dispatchTouchEvent(MotionEvent ev)的方法是为了判断是否是从上往下滑动,确保从下往上滑动时无效的。为了使滑动明显一点,让竖直方向上的滑动距离大于默认的滑动距离的两倍,才设置滑动有效。
@Override
public boolean onTouchEvent(MotionEvent ev) {
int action = ev.getAction();
int x = (int) ev.getX();
int y = (int) ev.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
if (isSlide) {
yHeight = y;
postInvalidate();
yCircle = (mHeight * 2 / 5 - mWidth / 10) + (yHeight - mHeight * 2 / 5) / 2;
if (yHeight > mHeight * 7 / 10) {
startAnim();
}
}
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
重写onTouchEvent(MotionEvent ev)方法,在action为MotionEvent.ACTION_MOVE时候,如果滑动有效,并且滑动y坐标大于高度的7/10的时候,开启我们的动画。
private void startAnim() {
//圆球动画
ValueAnimator circleYAnimator = ValueAnimator.ofFloat(yCircle, (0 - mWidth / 10));
circleYAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
yCircle = (float) animation.getAnimatedValue();
postInvalidate();
}
});
//弧线动画
ValueAnimator arcYAnimator = ValueAnimator.ofInt(yHeight, mHeight * 2 / 5);
arcYAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
yHeight = (int) animation.getAnimatedValue();
postInvalidate();
}
});
animSet.setDuration(3000);
animSet.play(circleYAnimator).before(arcYAnimator);
animSet.start();
}
这里又要提到属性动画的优越性了,不仅可以作用在view上,也可以作用于某个对象上。只需要设置好开始值与结束值,添加一个动画的监听,就能够得到变化的值,再使用postInvalidate()方法,从而调用onDraw()方法来进行数值的改变并重新绘制。最后设置一个组合动画—-AnimatorSet,圆的动画完成以后,再执行弧线的动画。
OK,相信看到这里,你对二阶贝塞尔曲线有了新的认识,下一篇自定义View再见~~