1

I am trying to write an android application that draws Circles at random positions over and over forever. This is already done in my code. The next objective for me is to slowly animate these circles to make them "grow" onto the screen. Basically increment the radius of the circle from 0 to 300 very fast I did this by creating a for loop like this.

for(int i = 0;i< 300; i++){
            canvas.drawCircle(randomWidthOne, randomHeightOne,i, newPaint);
        }

unfortunately this does not perform the desired result. Instead it just displays the circles at the end radius of 300. Here is my code for the class that draws the circles. Please let me know if there is anything in the class that is interfering with what I am trying to accomplish.

public class SplashLaunch extends View{
    Handler cool = new Handler();
    DrawingView v;
    Paint newPaint = new Paint();
    int randomWidthOne = 0;
    int randomHeightOne = 0;
    private float radiusNsix = 10;
    private float radiusNfive = 25;
    private float radiusNfour = 50;
    private float radiusNthree = 100;
    private float radiusNtwo = 150;
    private float radiusNone = 200;
    private float radiusZero = 250;
    private float radiusOne = 300;
    final int redColorOne = Color.RED;
    final int greenColorOne = Color.GREEN;
    private static int lastColorOne;
    double startTime = System.currentTimeMillis();
    ObjectAnimator radiusAnimator;
    private final Random theRandom = new Random();
    public SplashLaunch(Context context) {
        super(context);
        // TODO Auto-generated constructor stub
    }

    private final Runnable circleUpdater = new Runnable() {
        @Override 
        public void run() {
            lastColorOne = theRandom.nextInt(2) == 1 ? redColorOne : greenColorOne;
            newPaint.setColor(lastColorOne); 
            cool.postDelayed(this, 1000);
            invalidate();
        }
    };

    @Override
    protected void onAttachedToWindow(){
        super.onAttachedToWindow();
        cool.post(circleUpdater);
    }
    protected void onDetachedFromWindow(){
        super.onDetachedFromWindow();
        cool.removeCallbacks(circleUpdater);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub
        super.onDraw(canvas);
        canvas.drawColor(Color.WHITE);
        if(theRandom == null){
            randomWidthOne =(int) (theRandom.nextInt((int) Math.abs(getWidth()-radiusOne/2)) + radiusOne/2f);
            randomHeightOne = (theRandom.nextInt((int)Math.abs((getHeight()-radiusOne/2 + radiusOne/2f))));
        }else {
            randomWidthOne =(int) (theRandom.nextInt((int) Math.abs(getWidth()-radiusOne/2)) + radiusOne/2f);
            randomHeightOne = (theRandom.nextInt((int)Math.abs((getHeight()-radiusOne/2 + radiusOne/2f))));
        }
        for(int i = 0;i< 300; i++){
            canvas.drawCircle(randomWidthOne, randomHeightOne,i, newPaint);
        }
    }




    public void setRadiusOne(float value){
        this.radiusOne = value;
        invalidate();
    }


    public int startAnimation(int animationDuration) {

        if (radiusAnimator == null || !radiusAnimator.isRunning()) {

            // Define what value the radius is supposed to have at specific time values
            Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
            Keyframe kf2 = Keyframe.ofFloat(0.5f, 180f);
            Keyframe kf1 = Keyframe.ofFloat(1f, 360f);

            // If you pass in the radius, it will be calling setRadius method, so make sure you have it!!!!!
            PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("radiusOne", kf0, kf1, kf2);
            radiusAnimator = ObjectAnimator.ofPropertyValuesHolder(this, pvhRotation);
            radiusAnimator.setInterpolator(new LinearInterpolator());
            radiusAnimator.setDuration(animationDuration);
            radiusAnimator.start();
        }
        else {
            Log.d("Circle", "I am already running!");
        }
        return animationDuration;
    }

    public void stopAnimation() {
        if (radiusAnimator != null) {
            radiusAnimator.cancel();
            radiusAnimator = null;
        }
    }

    public boolean getAnimationRunning() {
        return radiusAnimator != null && radiusAnimator.isRunning();
    }

}
Alex Salauyou
  • 14,185
  • 5
  • 45
  • 67
Paul Trueman
  • 149
  • 11
  • Add some delay in the loop. Thread.sleep() can be one option – Jabir May 05 '15 at 06:26
  • @Jabir you propose to make UI thread sleep? – Alex Salauyou May 05 '15 at 06:27
  • 1
    `onDraw()` callback is called on each frame. What you do now is draw 300 circles in 1 frame. So to create animation, you need 1) create circles and remember their coordinates and radius; 2) on every `onDraw()` draw those circles with incremented radius; 3) at the end of `onDraw()` call `this.invalidate()`. I show this idea in another answer: http://stackoverflow.com/a/29844522/3459206 (see comments also). – Alex Salauyou May 05 '15 at 06:31
  • 2
    This is not a JavaScript question... You rejected my edit, but you should really remove the tag, because Java !== JavaScript. –  May 05 '15 at 06:31
  • why do i need to remember their coordinates and radius? @SashaSalauyou – Paul Trueman May 05 '15 at 06:55
  • @PaulTrueman because otherwise you'll generate random coordinates and radius on each frame. `onDraw()` method doesn't make frame-to-frame processing, it is just a callback that Android calls when it needs to draw a custom view. If you look at Android source, you'll see how it works. – Alex Salauyou May 05 '15 at 07:00

2 Answers2

0

Have a look at the property animator.

Manish Shrivastava
  • 30,617
  • 13
  • 97
  • 101
Mladen Oršolić
  • 1,352
  • 3
  • 23
  • 43
  • 2
    this is not an answer – Paul Trueman May 05 '15 at 06:32
  • This is reasonable when every circle is a separate object – Alex Salauyou May 05 '15 at 06:32
  • i have already tried this the code above has this in their i do not understand this – Paul Trueman May 05 '15 at 06:33
  • if you have any idea how to use this startAnimation() method let me know! – Paul Trueman May 05 '15 at 06:33
  • `ObjectAnimator animator = ObjectAnimator.ofFloat(circleObject, "propertyName", startValue, endValue); anim.setDuration(1000); anim.start();` – Mladen Oršolić May 05 '15 at 06:36
  • @MladenOršolić where do you see any kind of `circleObject` in OPs code? – Alex Salauyou May 05 '15 at 06:38
  • @SashaSalauyou `circleObject` is an instance of `SplashLaunch` – pskink May 05 '15 at 06:50
  • I don't think it is @pskink – Paul Trueman May 05 '15 at 07:23
  • by the way if you look at the startAnimations(int duration) method above in my code it has the same stuff the problem is that i do not know how to use it – Paul Trueman May 05 '15 at 07:24
  • @PaulTrueman just use ObjectAnimator.ofFloat, it works like a charm – pskink May 05 '15 at 07:37
  • How? i dont know how ObjectAnimator works i have tried reading the tutorials on android it makes no sense at all – Paul Trueman May 05 '15 at 07:39
  • none at all let me explain to you what goes on when i see a couple of sentences on the help page for android on object animator. "hello, Today we will go over object animator. Object animator comes from another class that we have not gone over yet also an alien made this class, oh and good luck trying to look up this class you will only be referred to another class and then another and then another. In the last class it will say something like "oh hey x - triangle / theSpaceTimeContinium simple = x2, but of course this is simplified as triangle - hat" " – Paul Trueman May 05 '15 at 07:42
  • and if that makes no sense to you thats how all these links that people post about ObjectAnimator are – Paul Trueman May 05 '15 at 07:43
  • besides the ArrayList<> and List<> @SashaSalauyou code all makes sense to me. – Paul Trueman May 05 '15 at 07:44
  • just start with a simple: ObjectAnimator.ofFloat(mySplashLaunch, "radius", 100, 200), set its duration and start it – pskink May 05 '15 at 07:44
  • @PaulTrueman Sasha's solution in a wrong one: your animation will not be running with a same speed on different devices: really use Animators for that – pskink May 05 '15 at 07:52
0

Call this.invalidate() to force redraw on next frame:

private int x = -1;
private int y = -1;
private int r = -1;
private int stepsLeft = 300;


@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (x < 0) {                             // generate new circle
        int[] xy = generateXY();
        x = xy[0];
        y = xy[1];
        r = 0;
    }
    if (stepsLeft > 0) {
        canvas.drawColor(Color.WHITE);
        canvas.drawCircle(x, y, r, newPaint); // draw circle with new radius
        r++;                                  // increment radius
        stepsLeft--;                          // decrement steps
        this.invalidate();                    // invalidate the view
    }
}



private int[] generateXY() {
    if (theRandom == null) {
        randomWidthOne =(int) (theRandom.nextInt((int) Math.abs(getWidth()-radiusOne/2)) + radiusOne/2f);
        randomHeightOne = (theRandom.nextInt((int)Math.abs((getHeight()-radiusOne/2 + radiusOne/2f))));
    } else {
        randomWidthOne =(int) (theRandom.nextInt((int) Math.abs(getWidth()-radiusOne/2)) + radiusOne/2f);
        randomHeightOne = (theRandom.nextInt((int)Math.abs((getHeight()-radiusOne/2 + radiusOne/2f))));
    }
    return new int[]{randomWidthOne, randomHeightOne};
}
Alex Salauyou
  • 14,185
  • 5
  • 45
  • 67
  • why to make it so complex? you have Animators to do things like that... also it will work with different speed on different devices which is not a good UX – pskink May 05 '15 at 07:01
  • i have the animator in there i do not know how to use it – Paul Trueman May 05 '15 at 07:02
  • @pskink you can downvote if you don't like. I just show an idea. Constant speed can be achieved via interpolation, doesn't matter. If you look how `ObjectAnimator` works, you'll see it works in a very similar way. – Alex Salauyou May 05 '15 at 07:08
  • this works, but it only draws one circle then a blank screen? – Paul Trueman May 05 '15 at 07:09
  • Generate and draw as much circles as you need, storing coordinates not in x and y, but in `List` – Alex Salauyou May 05 '15 at 07:10
  • @SashaSalauyou interpolators will NOT help in any way here, basically you call invalidate without any time frame control, so when a device is very fast it will run much faster than on a slow device, using the Animators you make sure that the duration is constant on different devices – pskink May 05 '15 at 07:12
  • yeah it only draws one circle then the program is finished well not finished, but it fails to draw other circles – Paul Trueman May 05 '15 at 07:22
  • @pskink you can add time control and any kind of `TimeInterpolator` here. Drawing many simple circles each as separate view is far worse approach I believe. – Alex Salauyou May 05 '15 at 07:23
  • @PaulTrueman see update answer, where I do create `n` circles instead of 1; check if `generateXY()` works correctly (i. e. generates different coordinates--I took it from you code as is). – Alex Salauyou May 05 '15 at 07:26
  • hello @SashaSalauyou how would i keep drawing multiple circles? – Paul Trueman May 05 '15 at 07:26
  • @SashaSalauyou it seems that you are misleading `TimeInterpolator` with an object that can run something in a specified time like `Handler`, `TimeInterpolator` is just an interface that acts like function t' = f(t) that interpolates one input float value to another output float value, see for example linear interpolator http://androidxref.com/4.4.4_r1/xref/frameworks/base/core/java/android/view/animation/LinearInterpolator.java – pskink May 05 '15 at 07:30
  • @SashaSalauyou shouldn't it be xys = new ArrayList – Paul Trueman May 05 '15 at 07:32
  • @pskink thanks, this was exactly what I meant. More of that, it is supposed to accept float of range 0...1 as input. – Alex Salauyou May 05 '15 at 07:32
  • @PaulTrueman I lazy initialize it: `if (xys == null) {xys = new ArrayList<>();` – Alex Salauyou May 05 '15 at 07:33
  • @SashaSalauyou you misunderstand i need to create multiple circles, but like this - one circle is drawn then animated to 300 then deleted another circle is drawn the same another circle is drawn the same and so on forever – Paul Trueman May 05 '15 at 07:35
  • so not ten circles at the same time, but rather one circle after the other @SashaSalauyou – Paul Trueman May 05 '15 at 07:36
  • @PaulTrueman well, I would do the following. After a circle ends growing (`stepsLeft = 0`) save view bitmap (like `bitmap = content.getDrawingCache();`, but I'm not sure), then generate new x, y and set steps and radius to initial value, on each `onDraw()` first draw saved bitmap then the circle. This can be optimized if you don't redraw a full bitmap, but only part of it that lays behind a circle. – Alex Salauyou May 05 '15 at 07:44
  • never worked with bitmaps before i dont know why you'd need one. Everything is drawn with graphics. secondly im not sure how i would generate a new x, y. The only way i could think of is to put it in the runnable method, but this would surely cause problems. – Paul Trueman May 05 '15 at 07:47
  • @PaulTrueman new x, y are generated by `generateXY()` method. I'd remember bitmap in order not to redraw all circles on each frame--if you want your app to run "forever", after some time you'll need to draw many circles, which could lead to performance decrease. – Alex Salauyou May 05 '15 at 07:52
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/76963/discussion-between-paul-trueman-and-sasha-salauyou). – Paul Trueman May 05 '15 at 07:55