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