初步学习:
看图:
相信大家已经见到过此图很多了,最近学习了系列文章,写下这个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);
}
}
具体详细请阅读下面的参考文章:
参考文章: