前言
前段时间需要修改系统进度条的视觉效果,并查看了SeekBar的style样式,发现里面用到了以前没留意的一个图形技术-贝塞尔曲线。现在刚好项目不是特别忙就来研究一下。
贝塞尔曲线有一阶贝塞尔曲线,二阶贝塞尔曲线,三阶贝塞尔曲线...N阶贝塞尔曲线。一阶贝塞尔曲线就是一条直线,这里不做详解,这里主要用两个样例来看看二阶和三阶贝塞尔曲线可以做什么。
贝塞尔曲线理论知识
二阶贝塞尔曲线由三个点来控制,起点,终点和控制点。形状是一个抛物线的效果。
公式如下:

公式可能看起来比较晕,详细解读可以看看http://www.cnblogs.com/wjtaigwh/p/6647114.html这篇文章的解读,从图像中可以看出来二阶贝塞尔曲线有起点,终点和一个控制点来决定图形形状。
P0为起点,P1位控制点,P2为终点,t从0到1变化,比如当前t = 0.4,那么我们从P0出发,找出P0P1线段长度4/10的位置点P3,并且找出P1P2线段长度4/10的位置点P4,接着我们从P3出发,找出P3P4线段长度4/10的位置点,这个点就是t数值时刻,二阶贝塞尔曲线上点。
有兴趣的同学可以从这个图形出发推导出公式看看是否与上面给出的公式一致。
三阶贝塞尔曲线则有四个点来控制,起点,终点和两个控制点。
公式如下:

其实三阶以上图形的绘制很二阶曲线很相似。
P0为起点,P3为终点,P1,P2为控制点。t依然为0.4。第一步,从P0出发找出P0P1线段长度4/10的位置点P4,从P1出发找出P1P2线段长度4/10的位置点P5,从P2出发找出P2P3线段长度4/10的位置点P6,现在产生了三个新的点P4,P5,P6。第二步,从P4出发找出P4P5线段长度4/10的位置点P7,从P5出发找出P5P6线段长度4/10的位置点P8,现在多出了两个新的点P7,P8。第四步,从P7出发找出P7P9线段长度4/10的位置点P9。好了,P9就是t数值时贝塞尔曲线上的点。
二阶贝塞尔曲线要经历2步,三阶贝塞尔曲线要经过3步计算,意思类推,四阶贝塞尔曲线就要经过四步计算了。是不是很简单呢?
下面我们来写一下根据t和n个点来计算t时刻的坐标吧。n个点绘制出来的就是n-1阶贝塞尔曲线了。
public float calculate(float t, float... values){
for(int i= values.length-1;i>0;i--)
for (int j =0;j<i;j++){
values[j] = values[j] + values[j+1];
}
return values[0];
}
这里我们把x分量和y分量分开来计算就可以得到t时刻的点坐标了,那么我们可以把0-1划分的很细,比如10000份,那么就可以得到10000个贝塞尔曲线上的点,再把相邻的点连接起来就可以看到一条平滑的曲线了,这就是贝塞尔曲线,不得不感叹数学的魅力,也致敬一下贝塞尔这个数学家吧=_=。
贝塞尔曲线能干什么
在我们的现实生活中其实有很多曲线的体现,比如海中波浪的形状,各种电子设备棱角的设计等等,那么我们就可以用贝塞尔曲线来模拟出这些曲线,这里会有一个特殊的形状,圆形。二阶贝塞尔曲线和三阶贝塞尔曲线都能模拟出圆形。先看看三阶贝塞尔曲线怎么模拟圆形吧。后面会用到
这里其实是有一篇论文来着,地址忘了读者自己去找吧,哈哈,反正知道一个0.552284749831这个数值就好了,我们把圆形分为四份来绘制,比如我们制定圆心为(100,100),绘制一个半径为100的圆形,那么我们就绘制(100,0)到(200,100)这1/4的圆弧吧,找到从(100,0)到(200,0)这条线段长度100 * 0.552284749831位置点,这里就指定为(155.22, 0)吧,同理还有一个点(200,100-55.22)这个点,这两个点就是1/4圆弧曲线的控制点,起点为(100,0)和(200,100)。这四个点绘制出来的曲线就十分接近1/4圆弧了。记住0.5522...这个数值,哈哈。
贝塞尔曲线早android中最大的作用就是制作动画效果了吧,不多说了,看了我做的两个小实例就明白啦,当然我也是接触不就,所以模拟的效果也没那么逼真,讲究这看吧。
贝塞尔曲线实例--圆形到心形的动画效果
public class HeartView extends View{
private static final float RATIO = 0.5522f;
private static final int INIT_X = 400;
private static final int INIT_Y = 400;
private static final int RADIUS = 200;
private static final int DURATION = 500;
private PointF mCenterPoint = new PointF();
private PointF mLeftPoint = new PointF();
private PointF mTopPoint = new PointF();
private PointF mRightPoint = new PointF();
private PointF mBottomPoint = new PointF();
private PointF mLC1Point = new PointF();
private PointF mLC2Point = new PointF();
private PointF mTC1Point = new PointF();
private PointF mTC2Point = new PointF();
private PointF mRC1Point = new PointF();
private PointF mRC2Point = new PointF();
private PointF mBC1Point = new PointF();
private PointF mBC2Point = new PointF();
private Path mPath = new Path();
private Paint mPaint = new Paint();
private ValueAnimator mAnimator;
public HeartView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public HeartView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public HeartView(Context context) {
this(context, null);
}
private void init(){
mPaint.setStyle(Style.FILL_AND_STROKE);
mPaint.setStrokeWidth(5);
mPaint.setColor(Color.RED);
mCenterPoint.set(INIT_X, INIT_Y);
computePoint();
mAnimator = ValueAnimator.ofInt(0, DURATION);
mAnimator.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float ratio = ((int)animation.getAnimatedValue()) / (DURATION * 1.0f);
computePoint();
mLeftPoint.set(mCenterPoint.x - RADIUS, mCenterPoint.y - RADIUS * RATIO * 0.2f * ratio);
mRightPoint.set(mCenterPoint.x + RADIUS, mCenterPoint.y - RADIUS * RATIO * 0.2f * ratio);
mTopPoint.set(mCenterPoint.x, mCenterPoint.y - RADIUS + RADIUS * 0.50f * ratio);
mBC1Point.set(mBottomPoint.x - RADIUS * RATIO , mBottomPoint.y - (RADIUS * RATIO * 0.80f * ratio));
mBC2Point.set(mLeftPoint.x, mLeftPoint.y + RADIUS * RATIO + RATIO * RADIUS * 0.00f * ratio);
mRC1Point.set(mRightPoint.x, mRightPoint.y + RADIUS * RATIO + RATIO * RADIUS * 0.00f * ratio);
mRC2Point.set(mBottomPoint.x + RADIUS * RATIO, mBottomPoint.y - (RADIUS * RATIO * 0.80f * ratio));
invalidate();
}
});
mAnimator.setDuration(DURATION);
mAnimator.setStartDelay(1000);
mAnimator.start();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
}
private void computePoint(){
mLeftPoint.set(mCenterPoint.x - RADIUS, mCenterPoint.y);
mTopPoint.set(mCenterPoint.x, mCenterPoint.y - RADIUS);
mRightPoint.set(mCenterPoint.x + RADIUS, mCenterPoint.y);
mBottomPoint.set(mCenterPoint.x, mCenterPoint.y + RADIUS);
mLC1Point.set(mLeftPoint.x, mLeftPoint.y - RADIUS * RATIO);
mLC2Point.set(mTopPoint.x - RADIUS * RATIO, mTopPoint.y);
mTC1Point.set(mTopPoint.x + RADIUS * RATIO, mTopPoint.y);
mTC2Point.set(mRightPoint.x, mRightPoint.y - RADIUS * RATIO);
mRC1Point.set(mRightPoint.x, mRightPoint.y + RADIUS * RATIO);
mRC2Point.set(mBottomPoint.x + RADIUS * RATIO, mBottomPoint