Question

I'm trying to develop simple board game. The board is of size 9x9 fields. The balls are appearing on the fields and when the user clicks on the field with the ball, the ball starts to jumping. I implemented the animation in two ways. The first one is working, but it's not easy to add another one following animation (like little stretch or something). And the second one, which seems to be better (there is used the AnimatorSet) is not working. When user clicks on the field with the ball, the ball disappears. I have no idea why :-(.

The first class implements the board and it is the child of View:

public class BoardView extends View {
  ...

  /**
   * Initializes fields of the board.
   */
  private void initializeFields() {
    this.fields = new ArrayList<Field>();

    for (int row = 0; row < BoardView.FIELDS_NUMBER; row++) {
       for (int column = 0; column < BoardView.FIELDS_NUMBER; column++) {
          this.fields.add(new Field(this, row, column));
       }
    }
  }

  @Override
  protected void onDraw(Canvas canvas) {
    canvas.drawColor(BoardView.COLOR_ACTIVITY);

    if (this.fields == null) {
       this.initializeFields();
    }

    for (int i = 0; i < this.fields.size(); i++) {
       this.fields.get(i).draw(canvas);
    }
  }

  ...
}

The second one implements the field:

public class Field {
   ...

   /**
    * Draws itself on the screen.
    *
    * @param Canvas canvas
    */
   public void draw(Canvas canvas) {
      Rect field = this.getRect();
      int round = (int)Math.floor(this.board.getFieldSize() / 4);

      this.board.getPainter().setStyle(Paint.Style.FILL);
      this.board.getPainter().setColor(Field.COLOR_DEFAULT);

      // draw field
      canvas.drawRoundRect(new RectF(field), round, round, this.board.getPainter());

      // draw selected field
      if (this.selected) {
         this.board.getPainter().setColor(Field.COLOR_SELECTED);
         canvas.drawRoundRect(new RectF(field), round, round, this.board.getPainter());
      }

      // draw ball
      if (this.ball != null) {
         Point fieldOrigin = new Point(field.left, field.top);

         if (this.selected) {
            this.ball.animate(canvas, fieldOrigin);
         } else {
            this.ball.draw(canvas, fieldOrigin);
         }
      }
   }

   ...
}

And the last one implements the ball: Here is the first method, which completely works, but it's not flexible enough:

public class Ball {
   ...

   /**
    * Draws itself on the screen.
    *
    * @param Canvas canvas
    * @param Point fieldOrigin
    */
   public void draw(Canvas canvas, Point fieldOrigin) {
      // set painter
      Paint painter = this.field.getBoard().getPainter();

      painter.setStyle(Paint.Style.FILL);
      painter.setColor(Ball.COLORS[this.color]);

      // calculate parameters
      float halfSize = this.field.getBoard().getFieldSize() / 2;
      float cX = fieldOrigin.x + halfSize;
      float cY = fieldOrigin.y + halfSize + this.dy;
      float radius = 0.6f * halfSize;

      // draw circle
      canvas.drawCircle(cX, cY, radius, painter);

      // the code continues, because of the shadow and light simulation (radial gradients)
   }

   /**
    * Draws jumping animation.
    *
    * @param Canvas canvas
    * @param Point fieldOrigin
    */
   public void animate(Canvas canvas, Point fieldOrigin) {
      float currentDy = (this.dy - 0.1f);

      this.setDy((float)Math.abs(Math.sin(currentDy)) * (-0.15f * this.field.getBoard().getFieldSize()));
      this.draw(canvas, fieldOrigin);
      this.setDy(currentDy);

      try {
         Thread.sleep(Ball.ANIMATION_DELAY);
      } catch (InterruptedException e) {}

      this.field.invalidate();
   }

   ...
}

As you can see, the animation is implemented by sleeping the current Thread and changing parameter dy.

The second method is showing the ball on the field, but the animation is not working as I said in the beginning of the post (after click, the ball disappears):

public class BallShape {
   private Field field;
   private LayerDrawable ball;
   private int color;
   private float diameter,
              x, y;         // top left corner - THE GETTERS AND SETTERS ARE IMPLEMENTED (because of Animator)
   ...

   /**
    * Initializes the ball.
    *
    * @param Field field
    * @param int color
    */
   public BallShape(Field field, int color) {
      this.field = field;
      this.color = ((color == Ball.COLOR_RANDOM) ? Ball.randomColor() : color);

      // create ball
      float halfSize = this.field.getBoard().getFieldSize() / 2;

      this.diameter = 0.6f * field.getBoard().getFieldSize();
      float radius = this.diameter / 2;

      Rect fieldArea = field.getRect();
      this.x = fieldArea.left + halfSize - radius;
      this.y = fieldArea.top + halfSize - radius;

      // color circle
      OvalShape circleShape = new OvalShape();
      circleShape.resize(this.diameter, this.diameter);

      ShapeDrawable circle = new ShapeDrawable(circleShape);
      this.initPainter(circle.getPaint());

      // the code continues, because of the shadow and light simulation (radial gradients)

      // compound shape - ball
      ShapeDrawable[] compound = { circle };//, shadow, light };
      this.ball = new LayerDrawable(compound);
   }

   /**
    * Draws itself on the screen.
    *
    * @param Canvas canvas
    * @param Point fieldOrigin
    */
   public void draw(Canvas canvas, Point fieldOrigin) {
      canvas.save();

      canvas.translate(this.x, this.y);
      this.ball.draw(canvas);

      canvas.restore();
   }

   /**
    * Draws jumping animation.
    *
    * @param Canvas canvas
    * @param Point fieldOrigin
    */
   public void animate(Canvas canvas, Point fieldOrigin) {
      // common data
      float halfSize = this.field.getBoard().getFieldSize() / 2;
      float radius = this.diameter / 2;

      float startY = fieldOrigin.y + halfSize - radius;
      float endY = startY - halfSize + 2;

      // bounce animation
      ValueAnimator bounceAnimation = ObjectAnimator.ofFloat(this, "y", startY, endY);
      bounceAnimation.setDuration(BallShape.ANIMATION_LENGTH);
      bounceAnimation.setInterpolator(new AccelerateInterpolator());
      bounceAnimation.setRepeatCount(ValueAnimator.INFINITE);
      bounceAnimation.setRepeatMode(ValueAnimator.REVERSE);
    //bounceAnimation.start();

      // animation
      AnimatorSet bouncer = new AnimatorSet();
      bouncer.play(bounceAnimation);

      // start the animation
      bouncer.start();
   }

   ...
}

Any idea why it's not working? What I've done wrong? Thank you very, very much.

Was it helpful?

Solution

Two things I would fix.

First of all you start animation in draw() method. You should either start it in onClick() or at least set this.selected to false, to not start it on every draw(). Secondly, after your value animator changes a property, you need to redraw the BallShape. Otherwise nothing will change. For instance you can define setY(float Y) method, change Y there and call invalidate().

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top