您的位置:首页 > 移动开发 > Android开发

贝塞尔曲线学习

2015-11-09 10:18 651 查看
初步学习:

看图:



相信大家已经见到过此图很多了,最近学习了系列文章,写下这个demo,加了个自己的动画。

有了上面的动画,和贝塞尔曲线,如果想让一个小球沿着路径运动如下图:



上个原理图:



下面是实现的代码:

package test.vko.cn.ttapplication.weight;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.BounceInterpolator;

import java.util.ArrayList;
import java.util.List;

import test.vko.cn.ttapplication.commonutils.GeometryUtil;

/**
* Created by JiaRH on 2015/11/6.
*/
public class BenzierView extends View {
private Paint mPaint;
private int color = Color.parseColor("#990000");
private Path mPath;
PointF A;//起点
PointF B;//操作点1
PointF C;//操作点2
PointF D;//终点
PointF M;//锚点
private List<PointF> points;
/**
* 存储过圆心的直线与该圆的两个交点
*/
private PointF[] pointFs1 = new PointF[2];
private PointF[] pointFs2 = new PointF[2];
private float radius = 100f;

private boolean isStrech;

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

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

public BenzierView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPaintAndPath();
initPointfs();
}

private void initPointfs() {
A = new PointF(100f, 100f);
B = new PointF(800f, 100f);
C = new PointF(100f, 800f);
D = new PointF(800f, 800f);

if (points == null) {
points = new ArrayList<>();

}

points.add(A);
points.add(B);
points.add(C);
points.add(D);
}

private void initPaintAndPath() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.RED);
//        mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeWidth(2.0f);
mPath = new Path();

}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawCircle(canvas);
mPath.moveTo(points.get(0).x, points.get(0).y);
//        cubicLine(canvas);
drawBenZier();
canvas.drawPath(mPath, mPaint);
invalidate();

}

private void drawBenZier() {
pointFs1 = GeometryUtil.getIntersectionPoints(A, radius, 1d);
pointFs2 = GeometryUtil.getIntersectionPoints(D, radius, 1d);
mPath.reset();
mPath.moveTo(pointFs1[1].x, pointFs1[1].y);//移动到E
//        M = GeometryUtil.getPointByPercent(pointFs1[0], pointFs2[0], .5f);//选取HG的中点为锚点
//        M = GeometryUtil.getPointByPercent(A, D, .5f);//选取AD的中点为锚点
M = GeometryUtil.getPointByPercent(pointFs1[1], pointFs2[0], .5f);//选取EG的中点为锚点
mPath.quadTo(M.x, M.y, pointFs2[1].x, pointFs2[1].y);//画线EF
mPath.lineTo(pointFs2[0].x, pointFs2[0].y);//线段FG
//        M = GeometryUtil.getPointByPercent(pointFs2[0], pointFs1[1], .5f);//选取的中点为锚点
mPath.quadTo(M.x, M.y, pointFs1[0].x, pointFs1[0].y);//画线GH
mPath.lineTo(pointFs1[1].x, pointFs1[1].y);//线段HE
}

@Override
public boolean onTouchEvent(MotionEvent event) {

int downX, downY;

float currentx=getWidth()/2, currentY=getHeight()/2;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = (int) event.getRawX();
downY = (int) event.getRawY();

Rect rect = new Rect();
rect.left = 0;
rect.right = (int) radius * 2 + rect.left;
rect.top = 0;
rect.bottom = (int) radius * 2 + rect.top;

if (rect.contains(downX, downY)) {
isStrech = false;
} else {
isStrech = true;
}

break;
case MotionEvent.ACTION_MOVE:
if (isStrech) {
currentx = points.get(3).x = event.getX();
currentY = points.get(3).y = event.getY();
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
//                points.get(3).x = getWidth() / 2;
//                points.get(3).y = getHeight() / 2;
startAni(currentx,currentY);
break;
default:
break;
}
invalidate();
return true;
}

private void startAni(float xs,float ys) {
ValueAnimator x = ValueAnimator.ofFloat(xs-300 , xs+300);
x.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
points.get(3).x = (float) animation.getAnimatedValue();
postInvalidate();
}
});
x.setRepeatMode(0);
x.setDuration(3000);
x.setInterpolator(new BounceInterpolator());
x.start();
ValueAnimator y = ValueAnimator.ofFloat(ys-300 , ys+300);
x.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
points.get(3).y = (float) animation.getAnimatedValue();
postInvalidate();
}
});
y.setRepeatMode(0);
y.setDuration(3000);
y.setInterpolator(new BounceInterpolator());
y.start();

}

private void drawCircle(Canvas canvas) {
canvas.drawCircle(points.get(0).x, points.get(0).y, radius, mPaint);
canvas.drawCircle(points.get(3).x, points.get(3).y, radius, mPaint);

}

private void cubicLine(Canvas canvas) {
//两个锚点的测试
mPath.cubicTo(points.get(1).x, points.get(1).y, points.get(2).x, points.get(2).y, points.get(3).x, points.get(3).y);
}
}
<strong><span style="font-size:24px;">有个工具类是网上摘的:(感谢原作者)</span></strong>
package test.vko.cn.ttapplication.commonutils;

import android.graphics.PointF;

/**
* 几何图形工具
*/
public class GeometryUtil {

/**
* As meaning of method name.
* 获得两点之间的距离
* @param p0
* @param p1
* @return
*/
public static float getDistanceBetween2Points(PointF p0, PointF p1) {
float distance = (float) Math.sqrt(Math.pow(p0.y - p1.y, 2) + Math.pow(p0.x - p1.x, 2));
return distance;
}

/**
* Get middle point between p1 and p2.
* 获得两点连线的中点
* @param p1
* @param p2
* @return
*/
public static PointF getMiddlePoint(PointF p1, PointF p2) {
return new PointF((p1.x + p2.x) / 2.0f, (p1.y + p2.y) / 2.0f);
}

/**
* Get point between p1 and p2 by percent.
* 根据百分比获取两点之间的某个点坐标
* @param p1
* @param p2
* @param percent
* @return
*/
public static PointF getPointByPercent(PointF p1, PointF p2, float percent) {
return new PointF(evaluateValue(percent, p1.x , p2.x), evaluateValue(percent, p1.y , p2.y));
}

/**
* 根据分度值,计算从start到end中,fraction位置的值。fraction范围为0 -> 1
* @param fraction
* @param start
* @param end
* @return
*/
public static float evaluateValue(float fraction, Number start, Number end){
return start.floatValue() + (end.floatValue() - start.floatValue()) * fraction;
}

/**
* Get the point of intersection between circle and line.
* 获取 通过指定圆心,斜率为lineK的直线与圆的交点。
*
* @param pMiddle The circle center point.
* @param radius The circle radius.
* @param lineK The slope of line which cross the pMiddle.
* @return
*/
public static PointF[] getIntersectionPoints(PointF pMiddle, float radius, Double lineK) {
PointF[] points = new PointF[2];

float radian, xOffset = 0, yOffset = 0;
if(lineK != null){
radian= (float) Math.atan(lineK);//得到该角的角度
xOffset = (float) (Math.sin(radian) * radius);//得到对边的长
yOffset = (float) (Math.cos(radian) * radius);//得到邻边的长
}else {
xOffset = radius;
yOffset = 0;
}
points[0] = new PointF(pMiddle.x + xOffset, pMiddle.y - yOffset);//右上
points[1] = new PointF(pMiddle.x - xOffset, pMiddle.y + yOffset);//左下

return points;
}
}


看看上上面的运动小球实现方法:

主要看用到的PathMeasure,及其getPosTan()方法



package test.vko.cn.ttapplication.weight;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.BounceInterpolator;
import android.view.animation.LinearInterpolator;

import java.util.ArrayList;
import java.util.List;

import test.vko.cn.ttapplication.commonutils.GeometryUtil;

/**
* Created by JiaRH on 2015/11/6.
*/
public class BenzierView extends View {
private Paint mPaint;
private int color = Color.parseColor("#990000");
private Path mPath;
PointF A;//起点
PointF B;//操作点1
PointF C;//操作点2
PointF D;//终点
PointF M;//锚点
private List<PointF> points;
/**
* 存储过圆心的直线与该圆的两个交点
*/
private PointF[] pointFs1 = new PointF[2];
private PointF[] pointFs2 = new PointF[2];
private float radius = 100f;

private boolean isStrech;
<span style="color:#33ff33;">  </span><span style="font-size:24px;color:#333333;"> /**
* 记录当前运动点的位置
*/
private float[] mCurrentPosition = new float[2];
private PathMeasure mPathMeature;
public BenzierView(Context context) {
this(context, null);
}

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

public BenzierView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initPaintAndPath();
initPointfs();
}

private void initPointfs() {
A = new PointF(100f, 100f);
B = new PointF(800f, 100f);
C = new PointF(100f, 800f);
D = new PointF(800f, 800f);

if (points == null) {
points = new ArrayList<>();

}

points.add(A);
points.add(B);
points.add(C);
points.add(D);
/**
* 获取直径与圆的两个交点
*/
pointFs1 = GeometryUtil.getIntersectionPoints(A, radius, 1d);

/**
* 初始化小球的位置
*/
mCurrentPosition[0] = pointFs1[1].x;
mCurrentPosition[1] = pointFs1[1].y;

}

private void initPaintAndPath() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.RED);
mPaint.setStyle(Paint.Style.STROKE);
//        mPaint.setStyle(Paint.Style.FILL);
mPaint.setStrokeWidth(2.0f);
mPath = new Path();

}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawCircle(canvas);
mPath.moveTo(points.get(0).x, points.get(0).y);
//        cubicLine(canvas);
drawBenZier();
canvas.drawPath(mPath, mPaint);
drawBall(canvas);
postInvalidate();

}

private void drawBall(Canvas canvas) {

mPaint.setStyle(Paint.Style.FILL);

canvas.drawCircle(mCurrentPosition[0], mCurrentPosition[1], 30, mPaint);
}

private void drawBenZier() {
pointFs2 = GeometryUtil.getIntersectionPoints(D, radius, 1d);
mPath.reset();
mPath.moveTo(pointFs1[1].x, pointFs1[1].y);//移动到E
//        M = GeometryUtil.getPointByPercent(pointFs1[0], pointFs2[0], .5f);//选取HG的中点为锚点
//        M = GeometryUtil.getPointByPercent(A, D, .5f);//选取AD的中点为锚点
M = GeometryUtil.getPointByPercent(pointFs1[1], pointFs2[0], .5f);//选取EG的中点为锚点
mPath.quadTo(M.x, M.y, pointFs2[1].x, pointFs2[1].y);//画线EF
mPath.lineTo(pointFs2[0].x, pointFs2[0].y);//线段FG
//        M = GeometryUtil.getPointByPercent(pointFs2[0], pointFs1[1], .5f);//选取的中点为锚点
mPath.quadTo(M.x, M.y, pointFs1[0].x, pointFs1[0].y);//画线GH
mPath.lineTo(pointFs1[1].x, pointFs1[1].y);//线段HE

mPathMeature = new PathMeasure(mPath, true);

}

@Override
public boolean onTouchEvent(MotionEvent event) {

int downX, downY;

float currentx = getWidth() / 2, currentY = getHeight() / 2;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downX = (int) event.getRawX();
downY = (int) event.getRawY();

Rect rect = new Rect();
rect.left = 0;
rect.right = (int) radius * 2 + rect.left;
rect.top = 0;
rect.bottom = (int) radius * 2 + rect.top;

if (rect.contains(downX, downY)) {
isStrech = false;
} else {
isStrech = true;
}

break;
case MotionEvent.ACTION_MOVE:
if (isStrech) {
currentx = points.get(3).x = event.getX();
currentY = points.get(3).y = event.getY();
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
//                points.get(3).x = getWidth() / 2;
//                points.get(3).y = getHeight() / 2;
startAni(currentx, currentY);
break;
default:
break;
}
invalidate();
return true;
}

private void startAni(float xs, float ys) {
ValueAnimator x = ValueAnimator.ofFloat(xs - 300, xs + 300);
x.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
points.get(3).x = (float) animation.getAnimatedValue();
postInvalidate();
}
});
x.setRepeatMode(0);
x.setDuration(3000);
x.setInterpolator(new BounceInterpolator());
x.start();
ValueAnimator y = ValueAnimator.ofFloat(ys - 300, ys + 300);
x.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
points.get(3).y = (float) animation.getAnimatedValue();
postInvalidate();
}
});
y.setRepeatMode(0);
y.setDuration(3000);
y.setInterpolator(new BounceInterpolator());
y.start();

startBallAni();

}

private void startBallAni() {
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, mPathMeature.getLength());
valueAnimator.setDuration(15000);
//        valueAnimator.setRepeatMode(valueAnimator.REVERSE);
valueAnimator.setRepeatCount(Integer.MAX_VALUE);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (float) animation.getAnimatedValue();
mPathMeature.getPosTan(value, mCurrentPosition, null);
postInvalidate();
}
});
valueAnimator.start();
}

private void drawCircle(Canvas canvas) {
mPaint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(points.get(0).x, points.get(0).y, radius, mPaint);
canvas.drawCircle(points.get(3).x, points.get(3).y, radius, mPaint);

}

private void cubicLine(Canvas canvas) {
mPath.cubicTo(points.get(1).x, points.get(1).y, points.get(2).x, points.get(2).y, points.get(3).x, points.get(3).y);
}
}




具体详细请阅读下面的参考文章:

参考文章:


BezierDemo源码解析-实现qq消息气泡拖拽消失的效果

http://bezier.method.ac/

http://gavinliu.cn/2015/03/30/Android-Animation-%E8%B4%9D%E5%A1%9E%E5%B0%94%E6%9B%B2%E7%BA%BF%E4%B9%8B%E7%BE%8E/

http://spencermortensen.com/articles/bezier-circle/

http://stackoverflow.com/questions/1734745/how-to-create-circle-with-b%C3%A9zier-curves
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息