兩張圖教你使用二三階貝塞爾曲線 - CSDN博客 http://blog.csdn.net/DylanZhuang/article/details/51896564
Bézier curve(貝塞爾曲線)是應(yīng)用于二維圖形應(yīng)用程序的數(shù)學(xué)曲線。 曲線定義:起始點、終止點(也稱錨點)、控制點。通過調(diào)整控制點,貝塞爾曲線的形狀會發(fā)生化。 1962年,法國數(shù)學(xué)家Pierre
Bézier第一個研究了這種矢量繪制曲線的方法,并給出了詳細(xì)的計算公式,因此按照這樣的公式繪制出來的曲線就用他的姓氏來命名,稱為貝塞爾曲線。
線性公式
給定點p0、p1,線性貝塞爾曲線只是一條兩點之間的直線,公式如下:
二次方公式
二次方貝塞爾曲線的路徑由給定點p0、p1、p2的函數(shù)B(t),公式如下:
三次方公式
p0、p1、p2、p3四個點在平面或在三維空間定義了三次貝塞爾曲線。曲線起始于p0走向p1,并從p2的方向來到p3.一般不會經(jīng)過p1或者p2;這兩點只是在那里提供了方向資訊。p0和p1之間的間距,決定了曲線在轉(zhuǎn)而趨進(jìn)p3之前,走向p2方向的“長度有多長”,公式如下:
上面這段是摘自百度百科,由上面的動態(tài)圖可以看出,一階貝塞爾曲線是由兩點控制的一條直線,二階貝塞爾曲線是由一個控制點控制的曲線,三階貝塞爾曲線是由兩個控制點控制的曲線,至于三階以上的不做研究。
下面看一下二階貝塞爾曲線運行的效果圖:
設(shè)置二階貝塞爾曲線的方法如下
moveTo(float x, float y) 其中x、y坐標(biāo)代表圖中曲線靠左邊起點的坐標(biāo)位置
quadTo(float x1, float y1, float x2, float y2) 其中x1、y1坐標(biāo)代表圖中移動點的坐標(biāo),也就是我們所說的二階貝塞爾曲線的控制點坐標(biāo);x2、y2坐標(biāo)代表圖中曲線靠右邊終點的坐標(biāo)位置
首先我們要重寫view的onTouchEvent的事件,并對該事件進(jìn)行攔截,也就是返回值為true,代碼如下:
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- break;
- case MotionEvent.ACTION_MOVE:
- int moveX = (int) (event.getX());
- int moveY = (int) (event.getY());
- mControlPoint.x = moveX;
- mControlPoint.y = moveY;
- invalidate();
- break;
- }
- return true;
- }
在move事件中,獲取到控制點的坐標(biāo),并在onDraw方法中進(jìn)行路徑的繪制,代碼如下:
初始化起始點:
- mPaint = new Paint();
- mPaint.setStyle(Paint.Style.STROKE);
-
- DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
- mWidth = displayMetrics.widthPixels;
- mHeight = displayMetrics.heightPixels;
-
- mStartPoint.set(100, mHeight / 2);
- mEndPoint.set(mWidth - 100, mHeight / 2);
- mControlPoint.set(mWidth / 2, 100);
進(jìn)行繪制:
- private void drawQuadraticBezier(Canvas canvas) {
- mPaint.setColor(Color.RED);
- mPaint.setStrokeWidth(20);
- mPaint.setStyle(Paint.Style.STROKE);
- canvas.drawCircle(mControlPoint.x, mControlPoint.y, 10, mPaint);
-
- mPaint.setStrokeWidth(10);
- mPaint.setStyle(Paint.Style.FILL);
- float[] lines = {mStartPoint.x, mStartPoint.y, mControlPoint.x, mControlPoint.y,
- mControlPoint.x, mControlPoint.y, mEndPoint.x, mEndPoint.y,
- mEndPoint.x, mEndPoint.y, mStartPoint.x, mStartPoint.y};
- canvas.drawLines(lines, mPaint);
-
- mPaint.setColor(Color.GREEN);
- mPaint.setStyle(Paint.Style.STROKE);
- Path path = new Path();
- path.moveTo(mStartPoint.x, mStartPoint.y);
- path.quadTo(mControlPoint.x, mControlPoint.y, mEndPoint.x, mEndPoint.y);
- canvas.drawPath(path, mPaint);
- }
二階貝塞爾曲線到這里已經(jīng)介紹完了,接下來介紹下三階貝塞爾曲線,先看下效果圖:
設(shè)置二階貝塞爾曲線的方法如下
moveTo(float x, float y) 其中x、y坐標(biāo)代表圖中在圓周上靠左邊起點的坐標(biāo)位置
cubicTo(float x1, float y1, float x2, float y2, float x3, float y3) 其中x1、y1坐標(biāo)代表圖中左上角移動點的坐標(biāo),x2、y2坐標(biāo)代表圖中右上角移動點的坐標(biāo),x1、y1和x2、y2也就是我們所說的三階貝塞爾曲線的控制點坐標(biāo);x3、y3坐標(biāo)代表圖中在圓周上靠右邊終點的坐標(biāo)位置
首先我們要重寫view的onTouchEvent的事件,并對該事件進(jìn)行攔截,也就是返回值為true,代碼如下:
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_DOWN:
- break;
- case MotionEvent.ACTION_MOVE:
- int moveX = (int) (event.getX());
- int moveY = (int) (event.getY());
-
- int distanceX = Math.abs(mControlPoint.x - moveX);
- int distanceY = Math.abs(mControlPoint.y - moveY);
-
- int distanceX1 = Math.abs(mControlPoint1.x - moveX);
- int distanceY1 = Math.abs(mControlPoint1.y - moveY);
- if (distanceX < 50 && distanceY < 50) {
- mControlPoint.x = moveX;
- mControlPoint.y = moveY;
- } else if (distanceX1 < 50 && distanceY1 < 50) {
- mControlPoint1.x = moveX;
- mControlPoint1.y = moveY;
- }
- invalidate();
- break;
- }
- return true;
- }
在move事件中,判斷當(dāng)前觸摸的是哪個控制點,并對該控制點進(jìn)行賦值,繪制代碼如下:初始化數(shù)據(jù):
- mBloomCenterPoint.set(mWidth / 2, mHeight / 2);
- mStartPoint.set(mWidth / 2, mHeight / 2);
- mEndPoint.set(mWidth / 2, mHeight / 2);
- mControlPoint.set(mWidth / 2 - 200, 100);
- mControlPoint1.set(mWidth / 2 + 200, 100);
開始繪制:
- private void drawCubicBezier(Canvas canvas) {
- Point topPoint = new Point(mBloomCenterPoint.x, mBloomCenterPoint.y - mRadius);
- float angle1 = (mBloomCenterPoint.x - mControlPoint.x) * 1.0f / (mBloomCenterPoint.y - mControlPoint.y);
- float angle2 = (mBloomCenterPoint.x - mControlPoint1.x) * 1.0f / (mBloomCenterPoint.y - mControlPoint1.y);
-
- boolean isBig1 = false;
- boolean isBig2 = false;
- if (mControlPoint.y > mBloomCenterPoint.y) {
- isBig1 = true;
- }
- if (mControlPoint1.y > mBloomCenterPoint.y) {
- isBig2 = true;
- }
- //獲取三階貝塞爾曲線的起始點的值
- mStartPoint = getFixPoint(topPoint, angle1, isBig1);
- mEndPoint = getFixPoint(topPoint, angle2, isBig2);
-
- mPaint.setColor(Color.RED);
- mPaint.setStrokeWidth(1);
- mPaint.setStyle(Paint.Style.STROKE);
- canvas.drawCircle(mControlPoint.x, mControlPoint.y, 10, mPaint);
- canvas.drawCircle(mControlPoint1.x, mControlPoint1.y, 10, mPaint);
- canvas.drawCircle(mBloomCenterPoint.x, mBloomCenterPoint.y, mRadius, mPaint);
-
- mPaint.setStrokeWidth(10);
- mPaint.setStyle(Paint.Style.FILL);
- float[] lines = {mStartPoint.x, mStartPoint.y, mControlPoint.x, mControlPoint.y,
- mControlPoint.x, mControlPoint.y, mControlPoint1.x, mControlPoint1.y,
- mControlPoint1.x, mControlPoint1.y, mEndPoint.x, mEndPoint.y,
- mEndPoint.x, mEndPoint.y, mStartPoint.x, mStartPoint.y};
- canvas.drawLines(lines, mPaint);
-
- mPaint.setStrokeWidth(10);
- mPaint.setColor(Color.GREEN);
- mPaint.setStyle(Paint.Style.FILL);
- Path path = new Path();
- path.moveTo(mStartPoint.x, mStartPoint.y);
- path.cubicTo(mControlPoint.x, mControlPoint.y, mControlPoint1.x, mControlPoint1.y, mEndPoint.x, mEndPoint.y);
- canvas.drawPath(path, mPaint);
- }
- private Point getFixPoint(Point topPoint, float angle, boolean isBig) {
- double radian = Math.atan(angle);
- if (isBig) {
- radian += Math.PI;
- }
- double sin = Math.sin(radian);
- double cos = Math.cos(radian);
- int x = (int) (topPoint.x - mRadius * sin);
- int y = (int) (topPoint.y + mRadius * (1 - cos));
-
- Point point = new Point(x, y);
- return point;
- }
高級進(jìn)階像360安全衛(wèi)士清理內(nèi)存的動態(tài)效果大家應(yīng)該都不陌生吧,我們現(xiàn)在用二階貝塞爾曲線實現(xiàn)這樣的效果,先上效果圖:
首先我們初始化數(shù)據(jù),代碼如下:
- private void init() {
- DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
- mScreenWidth = displayMetrics.widthPixels;
- mScreenHeight = displayMetrics.heightPixels;
-
- int height = mScreenHeight * 7 / 10;
- mStartPoint.set(mScreenWidth / 10, height);
- mEndPoint.set(mScreenWidth * 9 / 10, height);
-
- mRadius = 100;
- }
然后重寫onTouchEvent事件,不斷的重繪紅色的球和綠色的曲線,當(dāng)只有在球與線接觸時,才進(jìn)行二階貝塞爾曲線的繪制,touch事件的代碼如下:
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- switch (event.getAction()) {
- case MotionEvent.ACTION_MOVE:
- int moveX = (int) (event.getX());
- int moveY = (int) (event.getY());
-
- mControlPoint.x = moveX;
- mControlPoint.y = moveY;
- invalidate();
- break;
- case MotionEvent.ACTION_UP:
- int x = mControlPoint.x;
- int y = mControlPoint.y;
- if (y > mStartPoint.y && x > mScreenWidth * 2 / 5
- && x < mScreenWidth * 3 / 5) {
- startAnim();
- }
- break;
- }
- return true;
- }
當(dāng)執(zhí)行ACTION_UP事件時,判斷此時控制點是否進(jìn)行了二階變換,如果是,則進(jìn)行動畫的繪制,動畫效果的代碼如下:
- private void startAnim() {
- ValueAnimator valueAnimator = ValueAnimator.ofInt(mControlPoint.y, -10);
- valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mControlPoint.y = (int) animation.getAnimatedValue();
- invalidate();
- }
- });
- valueAnimator.setDuration(1000);
- valueAnimator.start();
- }
下面看下球跟線接觸時,視圖是怎么繪制的,代碼如下:
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- Paint paint = new Paint();
- paint.setStrokeWidth(10);
- paint.setStyle(Paint.Style.STROKE);
- paint.setColor(Color.GREEN);
-
- int x = mControlPoint.x;
- int y = mControlPoint.y;
- int height = mStartPoint.y;
- if (y > mStartPoint.y && x > mScreenWidth * 2 / 5
- && x < mScreenWidth * 3 / 5) {
- height = y + y - mStartPoint.y;
- }
-
- Path path = new Path();
- path.moveTo(mStartPoint.x, mStartPoint.y);
- path.quadTo(mScreenWidth / 2, height, mEndPoint.x, mEndPoint.y);
- canvas.drawPath(path, paint);
-
- paint.setStyle(Paint.Style.FILL);
- paint.setColor(Color.RED);
- canvas.drawCircle(x, y - mRadius, mRadius, paint);
- }
代碼中控制點高度的計算,是通過二階變換公式相減得到的,到目前為止,該過程的繪制代碼已全部列出。在進(jìn)行三階貝塞爾曲線變換的時候,綠色部分有點像個花瓣,下面我們用三階貝塞爾曲線,繪制一朵花,效果圖如下:
我們先用進(jìn)行下數(shù)據(jù)的初始化操作,定義些常量,代碼如下:
- public interface BloomOption {
- //用于控制產(chǎn)生隨機(jī)花瓣個數(shù)范圍
- int minPetalCount = 8;
- int maxPetalCount = 12;
- //用于控制產(chǎn)生延長線倍數(shù)范圍
- float minPetalStretch = 2f;
- float maxPetalStretch = 3.5f;
- //用于控制產(chǎn)生花朵半徑隨機(jī)數(shù)范圍
- int minBloomRadius = 100;
- int maxBloomRadius = 300;
- }
并進(jìn)行數(shù)據(jù)的一些初始化操作:
- private void init() {
- DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
- int screenWidth = displayMetrics.widthPixels;
- int screenHeight = displayMetrics.heightPixels;
- mBloomCenterPoint.set(screenWidth / 2, screenHeight / 2 - 200);
- petals = new ArrayList<>();
- initPetalData();
- }
- private void initPetalData() {
- int petalCount = RandomUtil.randomInt(minPetalCount, maxPetalCount);
- //每個花瓣應(yīng)占用的角度
- float angle = 360f / petalCount;
- int startAngle = RandomUtil.randomInt(0, 90);
-
- for (int i = 0; i < petalCount; i++) {
- //隨機(jī)產(chǎn)生第一個控制點的拉伸倍數(shù)
- float stretchA = RandomUtil.random(minPetalStretch, maxPetalStretch);
- //隨機(jī)產(chǎn)生第二個控制地的拉伸倍數(shù)
- float stretchB = RandomUtil.random(minPetalStretch, maxPetalStretch);
- //計算每個花瓣的起始角度
- int beginAngle = startAngle + (int) (i * angle);
-
- PetalView petal = new PetalView(stretchA, stretchB, beginAngle, angle);
- petals.add(petal);
- }
- }
下面進(jìn)行綠色線條的繪制,代碼如下:
- private void drawStem(Canvas canvas) {
- Paint paint = new Paint();
- paint.setStrokeWidth(10);
- paint.setColor(Color.GREEN);
- paint.setStyle(Paint.Style.STROKE);
-
- Path path = new Path();
- path.moveTo(mBloomCenterPoint.x, mBloomCenterPoint.y);
- path.quadTo(mBloomCenterPoint.x + 50, mBloomCenterPoint.y + 200, mBloomCenterPoint.x - 50, mBloomCenterPoint.y + 600);
- canvas.drawPath(path, paint);
- }
下面進(jìn)行花的繪制,代碼如下:onDraw方法:
- int radius = RandomUtil.randomInt(minBloomRadius, maxBloomRadius);
- int size = petals.size();
- MyPoint point = new MyPoint(mBloomCenterPoint.x, mBloomCenterPoint.y);
- for (int i = 0; i < size; i++) {
- PetalView petal = petals.get(i);
- if (petal != null) {
- petal.render(point, radius, canvas);
- }
- }
PetalView.java:
- public class PetalView {
- private static final String TAG = "PetalView";
-
- private float stretchA;//第一個控制點延長線倍數(shù)
- private float stretchB;//第二個控制點延長線倍數(shù)
- private float startAngle;//起始旋轉(zhuǎn)角,用于確定第一個端點
- private float angle;//兩條線之間夾角,由起始旋轉(zhuǎn)角和夾角可以確定第二個端點
- private int radius = 100;//花芯的半徑
- private Path path = new Path();//用于保存三次貝塞爾曲線
- private Paint paint = new Paint();
-
- public PetalView(float stretchA, float stretchB, float startAngle, float angle) {
- this.stretchA = stretchA;
- this.stretchB = stretchB;
- this.startAngle = startAngle;
- this.angle = angle;
- paint.setColor(Color.RED);
- }
-
- public void render(MyPoint p, int radius, Canvas canvas) {
- if (this.radius <= radius) {
- this.radius += 25;
- }
- draw(p, canvas);
- }
-
- private void draw(MyPoint p, Canvas canvas) {
- path = new Path();
- //將向量(0,radius)旋轉(zhuǎn)起始角度,第一個控制點根據(jù)這個旋轉(zhuǎn)后的向量計算
- MyPoint t = new MyPoint(0, this.radius).rotate(RandomUtil.degrad(this.startAngle));
- //第一個端點,為了保證圓心不會隨著radius增大而變大這里固定為3
- MyPoint v1 = new MyPoint(0, 3).rotate(RandomUtil.degrad(this.startAngle));
- //第二個端點
- MyPoint v2 = t.clone().rotate(RandomUtil.degrad(this.angle));
- //延長線,分別確定兩個控制點
- MyPoint v3 = t.clone().mult(this.stretchA);
- MyPoint v4 = v2.clone().mult(this.stretchB);
- //由于圓心在p點,因此,每個點要加圓心坐標(biāo)點
- v1.add(p);
- v2.add(p);
- v3.add(p);
- v4.add(p);
-
- path.moveTo(v1.x, v1.y);
- //參數(shù)分別是:第一個控制點,第二個控制點,終點
- path.cubicTo(v3.x, v3.y, v4.x, v4.y, v2.x, v2.y);
- canvas.drawPath(path, paint);
- }
- }
MyPoint.java:
- public class MyPoint {
- public int x;
- public int y;
-
- public MyPoint() {
- }
-
- public MyPoint(int x, int y) {
- this.x = x;
- this.y = y;
- }
-
- //旋轉(zhuǎn)
- public MyPoint rotate(float theta) {
- int x = this.x;
- int y = this.y;
- this.x = (int) (Math.cos(theta) * x - Math.sin(theta) * y);
- this.y = (int) (Math.sin(theta) * x + Math.cos(theta) * y);
- return this;
- }
-
- //乘以一個常數(shù)
- public MyPoint mult(float f) {
- this.x *= f;
- this.y *= f;
- return this;
- }
-
- //復(fù)制
- public MyPoint clone() {
- return new MyPoint(this.x, this.y);
- }
-
- //向量相減
- public MyPoint subtract(MyPoint p) {
- this.x -= p.x;
- this.y -= p.y;
- return this;
- }
-
- //向量相加
- public MyPoint add(MyPoint p) {
- this.x += p.x;
- this.y += p.y;
- return this;
- }
-
- public MyPoint set(int x, int y) {
- this.x = x;
- this.y = y;
- return this;
- }
-
- @Override
- public String toString() {
- return "MyPoint{" +
- "x=" + x +
- ", y=" + y +
- '}';
- }
- }
github地址
參考文章:http://www./demo/jiaoben1892/index.html
|