31

I'm making an Android app and I've got a tricky thing to do. I need to draw a path on a canvas but the drawing should be animated (ie. drawing point after point with a slight delay).

Is it possible to make something like this using Android SDK? If not, how could I produce this effect?

Romain Piel
  • 11,017
  • 15
  • 71
  • 106

4 Answers4

34

Try this code, I used it to draw a heartbeat using Path & Canvas:

public class TestActivity extends Activity {

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new HeartbeatView(this));

    }

    public static class HeartbeatView extends View {

        private static Paint paint;
        private int screenW, screenH;
        private float X, Y;
        private Path path;
        private float initialScreenW;
        private float initialX, plusX;
        private float TX;
        private boolean translate;
        private int flash;
        private Context context;


        public HeartbeatView(Context context) {
            super(context);

            this.context=context;

            paint = new Paint();
            paint.setColor(Color.argb(0xff, 0x99, 0x00, 0x00));
            paint.setStrokeWidth(10);
            paint.setAntiAlias(true);
            paint.setStrokeCap(Paint.Cap.ROUND);
            paint.setStrokeJoin(Paint.Join.ROUND);
            paint.setStyle(Paint.Style.STROKE);
            paint.setShadowLayer(7, 0, 0, Color.RED);


            path= new Path();
            TX=0;
            translate=false;

            flash=0;

        }

        @Override
        public void onSizeChanged (int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);

            screenW = w;
            screenH = h;
            X = 0;
            Y = (screenH/2)+(screenH/4)+(screenH/10);

            initialScreenW=screenW;
            initialX=((screenW/2)+(screenW/4));
            plusX=(screenW/24);

            path.moveTo(X, Y);

        }



        @Override
        public void onDraw(Canvas canvas) {
            super.onDraw(canvas);

            //canvas.save();    


            flash+=1;
            if(flash<10 || (flash>20 && flash<30))
            {
                paint.setStrokeWidth(16);
                paint.setColor(Color.RED);
                paint.setShadowLayer(12, 0, 0, Color.RED);
            }
            else
            {
                paint.setStrokeWidth(10);
                paint.setColor(Color.argb(0xff, 0x99, 0x00, 0x00));
                paint.setShadowLayer(7, 0, 0, Color.RED);
            }

            if(flash==100)
            {
                flash=0;
            }

            path.lineTo(X,Y);
            canvas.translate(-TX, 0);
            if(translate==true)
            {
                TX+=4;
            }

            if(X<initialX)
            {
                X+=8;
            }
            else
            {
                if(X<initialX+plusX)
                {
                    X+=2;
                    Y-=8;
                }
                else
                {
                    if(X<initialX+(plusX*2))
                    {
                        X+=2;
                        Y+=14;
                    }
                    else
                    {
                        if(X<initialX+(plusX*3))
                        {
                            X+=2;
                            Y-=12;
                        }
                        else
                        {
                            if(X<initialX+(plusX*4))
                            {
                                X+=2;
                                Y+=6;
                            }
                            else
                            {
                                if(X<initialScreenW)
                                {
                                    X+=8;
                                }
                                else
                                {
                                    translate=true;
                                    initialX=initialX+initialScreenW;
                                }
                            }
                        }
                    }
                }

            }

            canvas.drawPath(path, paint);


            //canvas.restore(); 

            invalidate();
        }
    }

}

It uses drawing a Path point by point with couple of effects using counters. You can take what you need and transfer it to SurfaceView which is more efficient.

Hesham Saeed
  • 5,358
  • 7
  • 37
  • 57
  • 4
    This is looking good but I can't see any animation in your code. This is a static drawing isn't it? – Romain Piel Aug 20 '12 at 12:53
  • My bad, I just tried you code and it's awesome! Great job man! – Romain Piel Aug 20 '12 at 12:57
  • Yea it looks static but it actually changes with the global variables, the function `invalidate()` is like you have an animation and you call for the next frame, before that you are increasing what you want, thats how the animation is generated. Happy Coding! – Hesham Saeed Aug 20 '12 at 14:16
  • 5
    This may work sometimes, but this is a bad approach to animating your path. The onDraw() method shouldn't be used to do the actual updates because if the device gets busy rendering other things then your animation will slow down. Animations are supposed to drop frames if the device can't handle it. The onDraw() should only draw the current state and you should have a separate thread/ValueAnimator making the changes to your instance variables which control the size/position of the draw. – Kenny Wyland Jul 12 '15 at 19:20
  • sorry but where is the source of HeartbeatView do you github link – shareef Nov 19 '15 at 13:02
  • How to make it drawing infinitely, currently it stops after come time – Akshay Panchal Aug 05 '16 at 12:58
3

I hope this is what you are looking for. It draws the path on user touch, you could simply tweek it to achieve what you desire.

public class MyCanvas extends Activity implements OnTouchListener{

        DrawPanel dp;
        private ArrayList<Path> pointsToDraw = new ArrayList<Path>();
        private Paint mPaint;
        Path path;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            // TODO Auto-generated method stub
            super.onCreate(savedInstanceState);
            dp = new DrawPanel(this);
            dp.setOnTouchListener(this);
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
            requestWindowFeature(Window.FEATURE_NO_TITLE);
            mPaint = new Paint();
            mPaint.setDither(true);
            mPaint.setColor(Color.WHITE);
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setStrokeJoin(Paint.Join.ROUND);
            mPaint.setStrokeCap(Paint.Cap.ROUND);
            mPaint.setStrokeWidth(30);

            FrameLayout fl = new FrameLayout(this);  
            fl.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));  
            fl.addView(dp);  
            setContentView(fl);  

        }

        @Override
        protected void onPause() {
            // TODO Auto-generated method stub
            super.onPause();
            dp.pause();
        }



        @Override
        protected void onResume() {
            // TODO Auto-generated method stub
            super.onResume();
            dp.resume();
        }



        public class DrawPanel extends SurfaceView implements Runnable{

            Thread t = null;
            SurfaceHolder holder;
            boolean isItOk = false ;

            public DrawPanel(Context context) {
                super(context);
                // TODO Auto-generated constructor stub
                holder = getHolder();
            }

            @Override
            public void run() {
                // TODO Auto-generated method stub
                while( isItOk == true){

                    if(!holder.getSurface().isValid()){
                        continue;
                    }

                    Canvas c = holder.lockCanvas();
                    c.drawARGB(255, 0, 0, 0);
                    onDraw(c);
                    holder.unlockCanvasAndPost(c);
                }
            }

            @Override
            protected void onDraw(Canvas canvas) {
                // TODO Auto-generated method stub
                super.onDraw(canvas);
                            synchronized(pointsToDraw)
                            {
                for (Path path : pointsToDraw) {
                    canvas.drawPath(path, mPaint);
                }
                            }
            }

            public void pause(){
                isItOk = false;
                while(true){
                    try{
                        t.join();
                    }catch(InterruptedException e){
                        e.printStackTrace();
                    }
                    break;
                }
                t = null;
            }

            public void resume(){
                isItOk = true;  
                t = new Thread(this);
                t.start();

            }



        }


        @Override
        public boolean onTouch(View v, MotionEvent me) {
            // TODO Auto-generated method stub
                    synchronized(pointsToDraw)
                    {
            if(me.getAction() == MotionEvent.ACTION_DOWN){
                path = new Path();
                path.moveTo(me.getX(), me.getY());
                //path.lineTo(me.getX(), me.getY());
                pointsToDraw.add(path);
            }else if(me.getAction() == MotionEvent.ACTION_MOVE){
                path.lineTo(me.getX(), me.getY());
            }else if(me.getAction() == MotionEvent.ACTION_UP){
                //path.lineTo(me.getX(), me.getY());
            }
            }       
            return true;

        }

    }
Kazekage Gaara
  • 14,972
  • 14
  • 61
  • 108
  • I think this is the best answer so far but it's not exactly what I'm looking for. In your case this will work because a drawing from the finger will give enough point to produce the animation. In my case, there is no interaction with the user's finger. The path is something provided by the app. The points can be too far an then there will be no animation. – Romain Piel Aug 20 '12 at 12:52
  • You can check for the screen size first, and then limit the points to be within that range. – Kazekage Gaara Aug 20 '12 at 12:57
  • Yes I think if we combine the two answers here, we get something cool. Cheers! – Romain Piel Aug 20 '12 at 12:58
  • U got any solution for animating a canvas path? if so could u please share your view. – Manikandan Oct 29 '15 at 14:58
3

I have made it with ObjectAnimator. We have any Path and our CustomView (in wich we'll draw our path)

    private CustomView view;
    private Path path;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        view = findViewById(R.id.custom_view);
        path = new Path();
        path.moveTo(0f, 0f);
        path.lineTo(getResources().getDimension(R.dimen.point_250), 0f);
        path.lineTo(getResources().getDimension(R.dimen.point_250), getResources().getDimension(R.dimen.point_150));

        findViewById(R.id.btnStart).setOnClickListener(v -> {
            test();
        });
    }

    private void test() {
        ValueAnimator pathAnimator = ObjectAnimator.ofFloat(view, "xCoord", "yCoord", path);
        pathAnimator.setDuration(5000);
        pathAnimator.start();
    }

And just pass our "xCoord" and "yCoord" to CustomView

public class CustomView extends View {

    private Paint paint;
    private float xCoord;
    private float yCoord;

    private Path path = new Path();

    public void setXCoord(float xCoord) {
        this.xCoord = xCoord;
    }

    public void setYCoord(float yCoord) {
        this.yCoord = yCoord;
        path.lineTo(xCoord, yCoord);
        invalidate();
    }

    public CustomView(Context context) {
        super(context);
        init();
    }

    public CustomView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    void init() {
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setDither(true);
        paint.setColor(Color.BLACK);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeJoin(Paint.Join.ROUND);
        paint.setStrokeCap(Paint.Cap.ROUND);
        paint.setStrokeWidth(20);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.drawPath(path, paint);

    }

}
Roman
  • 31
  • 2
0

This might help... It draws adjacent circles instead of a path to simulate an animatable path.

public class PathAnimatable {
private final float CIRCLE_SIZE = 2.5f;
public float SPPED_SCALE = 1f;
private float steps = 0;
private float pathLength;
private PathMeasure pathMeasure;
private float totalStepsNeeded;
private float[] point = new float[]{0f, 0f};
private float stride;

public PathAnimatable() {
  this(null);
}

public PathAnimatable(Path path) {
  super(path);
  init();
}

private void init() {
  pathMeasure = new PathMeasure(path, false);
  pathLength = pathMeasure.getLength();
  stride = CIRCLE_SIZE * 0.5f;
  totalStepsNeeded = pathLength / stride;
  steps = 0;
}

@Override
public void setPath(Path path) {
  super.setPath(path);
  init();
}

// Called this from your locked canvas loop function
public void drawShape(Canvas canvas, Paint paint) {
  if (steps <= pathLength) {
    for (float i = 0; i < steps ; i += stride) {
      pathMeasure.getPosTan(i, point, null);
      canvas.drawCircle(point[0], point[1], CIRCLE_SIZE, paint);
    }
    steps += stride * SPPED_SCALE;
  } else {
    steps = 0;
  }
}
}
Baffled
  • 23
  • 1
  • 5