Skip to content Skip to sidebar Skip to footer

How To Draw A Path On An Android Canvas With Animation?

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

Solution 1:

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

publicclassTestActivityextendsActivity {

    /** Called when the activity is first created. */@OverridepublicvoidonCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(newHeartbeatView(this));

    }

    publicstaticclassHeartbeatViewextendsView {

        privatestatic Paint paint;
        privateint screenW, screenH;
        privatefloat X, Y;
        private Path path;
        privatefloat initialScreenW;
        privatefloat initialX, plusX;
        privatefloat TX;
        privateboolean translate;
        privateint flash;
        private Context context;


        publicHeartbeatView(Context context) {
            super(context);

            this.context=context;

            paint = newPaint();
            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= newPath();
            TX=0;
            translate=false;

            flash=0;

        }

        @OverridepublicvoidonSizeChanged(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);

        }



        @OverridepublicvoidonDraw(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.

Solution 2:

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.

publicclassMyCanvasextendsActivityimplementsOnTouchListener{

        DrawPanel dp;
        privateArrayList<Path> pointsToDraw = newArrayList<Path>();
        privatePaint mPaint;
        Path path;

        @OverrideprotectedvoidonCreate(Bundle savedInstanceState) {
            // TODO Auto-generated method stubsuper.onCreate(savedInstanceState);
            dp = newDrawPanel(this);
            dp.setOnTouchListener(this);
            getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
            requestWindowFeature(Window.FEATURE_NO_TITLE);
            mPaint = newPaint();
            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 = newFrameLayout(this);  
            fl.setLayoutParams(newLayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));  
            fl.addView(dp);  
            setContentView(fl);  

        }

        @OverrideprotectedvoidonPause() {
            // TODO Auto-generated method stubsuper.onPause();
            dp.pause();
        }



        @OverrideprotectedvoidonResume() {
            // TODO Auto-generated method stubsuper.onResume();
            dp.resume();
        }



        publicclassDrawPanelextendsSurfaceViewimplementsRunnable{

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

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

            @Overridepublicvoidrun() {
                // TODO Auto-generated method stubwhile( isItOk == true){

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

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

            @OverrideprotectedvoidonDraw(Canvas canvas) {
                // TODO Auto-generated method stubsuper.onDraw(canvas);
                            synchronized(pointsToDraw)
                            {
                for (Path path : pointsToDraw) {
                    canvas.drawPath(path, mPaint);
                }
                            }
            }

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

            publicvoidresume(){
                isItOk = true;  
                t = newThread(this);
                t.start();

            }



        }


        @OverridepublicbooleanonTouch(View v, MotionEvent me) {
            // TODO Auto-generated method stubsynchronized(pointsToDraw)
                    {
            if(me.getAction() == MotionEvent.ACTION_DOWN){
                path = newPath();
                path.moveTo(me.getX(), me.getY());
                //path.lineTo(me.getX(), me.getY());
                pointsToDraw.add(path);
            }elseif(me.getAction() == MotionEvent.ACTION_MOVE){
                path.lineTo(me.getX(), me.getY());
            }elseif(me.getAction() == MotionEvent.ACTION_UP){
                //path.lineTo(me.getX(), me.getY());
            }
            }       
            returntrue;

        }

    }

Solution 3:

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

privateCustomView view;
    privatePath path;

    @OverrideprotectedvoidonCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        view = findViewById(R.id.custom_view);
        path = newPath();
        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();
        });
    }

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

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

publicclassCustomViewextendsView {

    private Paint paint;
    privatefloat xCoord;
    privatefloat yCoord;

    privatePathpath=newPath();

    publicvoidsetXCoord(float xCoord) {
        this.xCoord = xCoord;
    }

    publicvoidsetYCoord(float yCoord) {
        this.yCoord = yCoord;
        path.lineTo(xCoord, yCoord);
        invalidate();
    }

    publicCustomView(Context context) {
        super(context);
        init();
    }

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

    voidinit() {
        paint = newPaint();
        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);
    }

    @OverrideprotectedvoidonDraw(Canvas canvas) {
        super.onDraw(canvas);

        canvas.drawPath(path, paint);

    }

}

Solution 4:

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

publicclassPathAnimatable {
privatefinalfloatCIRCLE_SIZE=2.5f;
publicfloatSPPED_SCALE=1f;
privatefloatsteps=0;
privatefloat pathLength;
private PathMeasure pathMeasure;
privatefloat totalStepsNeeded;
privatefloat[] point = newfloat[]{0f, 0f};
privatefloat stride;

publicPathAnimatable() {
  this(null);
}

publicPathAnimatable(Path path) {
  super(path);
  init();
}

privatevoidinit() {
  pathMeasure = newPathMeasure(path, false);
  pathLength = pathMeasure.getLength();
  stride = CIRCLE_SIZE * 0.5f;
  totalStepsNeeded = pathLength / stride;
  steps = 0;
}

@OverridepublicvoidsetPath(Path path) {
  super.setPath(path);
  init();
}

// Called this from your locked canvas loop functionpublicvoiddrawShape(Canvas canvas, Paint paint) {
  if (steps <= pathLength) {
    for (floati=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;
  }
}
}

Post a Comment for "How To Draw A Path On An Android Canvas With Animation?"