📜 ⬆️ ⬇️

Moving pictures along an arbitrary curve


There was a task to make an animation - to move the image along a given curve. Googled and was surprised to find that several questions with a similar task on stackoverflow.com remain unanswered for more than one year. I had to roll up my sleeves, delve into the documentation and find a suitable solution.
So we have some curve. For example, built from a set of points and for beautiful, smoothed.
//  List<PointF> aPoints = new ArrayList<PointF>(); aPoints.add(new PointF(10f, 160f)); aPoints.add(new PointF(100f, 100f)); aPoints.add(new PointF(300f, 220f)); aPoints.add(new PointF(640f, 180f)); //   Path ptCurve = new Path(); PointF point = aPoints.get(0); ptCurve.moveTo(point.x, point.y); for(int i = 0; i < aPoints.size() - 1; i++){ point = aPoints.get(i); PointF next = aPoints.get(i+1); ptCurve.quadTo( point.x, point.y, (next.x + point.x) / 2, (point.y + next.y) / 2 ); } 


The task is to get the coordinates of points on our curve in order to draw our picture there. To do this, we use the PathMeasure class. With the help of this class "measure" the length of the curve. And to find the desired point, you can transfer to the object of this class the length to which the point is removed from the beginning.

So, for example, you can get the coordinates of the point in the middle of the curve:
  PathMeasure pm = new PathMeasure(ptCurve, false); float afP[] = {0f, 0f}; //   pm.getPosTan(pm.getLength() * 0.5f, afP, null); 

')
The last parameter (I passed null there) can, like coordinates, get the tangent parameters at this point.

Moreover, there is the getMatrix method, which gives a ready transformation matrix - the offset and the desired rotation. We will use it to render the sprite.
  Matrix mxTransform = new Matrix(); pm.getMatrix( pm.getLength() * 0.5f, mxTransform, PathMeasure.POSITION_MATRIX_FLAG + PathMeasure.TANGENT_MATRIX_FLAG ); mxTransform.preTranslate(-bmSprite.getWidth(), -bmSprite.getHeight()); canvas.drawBitmap(bmSprite, mxTransform, null); 


It turned out exactly what was required:


The full code is given below or you can download the project from the repository - SpriteAlongPath or use the mercury - hg clone bitbucket.org/TedBeer/spritealongpath hg clone bitbucket.org/TedBeer/spritealongpath .

 /** * User: TedBeer * Date: 30/01/12 * Time: 12:32 */ package net.tedbeer; import android.app.Activity; import android.os.Bundle; import android.content.Context; import android.graphics.*; import android.util.Log; import android.view.Display; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.widget.Toast; import java.util.*; public class moveSprite extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(new SceneView(this)); } } public class SceneView extends View { private static Bitmap bmSprite; private static Bitmap bmBackground; private static Rect rSrc, rDest; //animation step private static int iMaxAnimationStep = 20; private int iCurStep = 0; //points defining our curve private List<PointF> aPoints = new ArrayList<PointF>(); private Paint paint; private Path ptCurve = new Path(); //curve private PathMeasure pm; //curve measure private float fSegmentLen; //curve segment length public SceneView(Context context) { super(context); //destination rectangle Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); rDest = new Rect(0, 0, display.getWidth(), display.getHeight()); //load background if (bmBackground == null) { bmBackground = BitmapFactory.decodeResource(getResources(), R.drawable.winter_mountains); rSrc = new Rect(0, 0, bmBackground.getWidth(), bmBackground.getHeight()); } //load sprite if (bmSprite == null) bmSprite = BitmapFactory.decodeResource(getResources(), R.drawable.sledge3); //init random set of points aPoints.add(new PointF(10f, 160f)); aPoints.add(new PointF(100f, 100f)); aPoints.add(new PointF(300f, 220f)); aPoints.add(new PointF(640f, 180f)); //init smooth curve PointF point = aPoints.get(0); ptCurve.moveTo(point.x, point.y); for(int i = 0; i < aPoints.size() - 1; i++){ point = aPoints.get(i); PointF next = aPoints.get(i+1); ptCurve.quadTo(point.x, point.y, (next.x + point.x) / 2, (point.y + next.y) / 2); } pm = new PathMeasure(ptCurve, false); fSegmentLen = pm.getLength() / iMaxAnimationStep;//20 animation steps //init paint object paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(3); paint.setColor(Color.rgb(0, 148, 255)); } @Override protected void onDraw(Canvas canvas) { canvas.drawBitmap(bmBackground, rSrc, rDest, null); canvas.drawPath(ptCurve, paint); //animate the sprite Matrix mxTransform = new Matrix(); if (iCurStep <= iMaxAnimationStep) { pm.getMatrix(fSegmentLen * iCurStep, mxTransform, PathMeasure.POSITION_MATRIX_FLAG + PathMeasure.TANGENT_MATRIX_FLAG); mxTransform.preTranslate(-bmSprite.getWidth(), -bmSprite.getHeight()); canvas.drawBitmap(bmSprite, mxTransform, null); iCurStep++; //advance to the next step invalidate(); } else { iCurStep = 0; } } @Override public boolean onTouchEvent(MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN ) { //run animation invalidate(); return true; } return false; } } 


Update: replaced the video with another one, where you can clearly see that the sprite is also turning.
A tween-eng branch has been added to the repository, where the java-universal-tween-engine specified in the comment is used for animation.

Source: https://habr.com/ru/post/137294/


All Articles