Bouncing Ball in Flutter

Jesse Garcia
7 min readMay 19, 2022

--

WHAT WE’LL BUILD:

A Flutter app with a ball that bounces off the edges of the screen. You can tap the screen to make the ball change directions, or long press the screen to reset the ball.

WHAT YOU’LL LEARN:

Some basic Flutter (stateful widgets and setState, adding packages, Container, BoxDecoration, Stack, Positioned, Offset) and some math.

Let’s get started with the default counter app and reduce it to this:

Notice MyHomePageis now DotScreen. (You can change a name with Shift+F6 in Android Studio or F2 in VS Code.)

The Scaffoldwidget has a Stackin it since the dot will be stacked on top of the background widget that will detect where we tap.

In order to get the position of where we tap, we’ll import the positioned_tap_detector_2package by adding the following to pubspec.yaml andmain.dart:

Inside the Stack we need to add our dot on top of the background.

PositionedTapDetector2 is the background. The Container is the dot, which we’ll create with the following:

We’ll create a variable _circleSize outside the build method. By using a variable, if we ever want the dot to be a different size, we can change the value of this variable instead of changing all the values manually.

We can make the variable final since we won’t be changing it, but you would remove the final if you wanted to add a way to change the dot size. The underscore means that the variable is private to this DotScreen, and it will be helpful to distinguish it from variables we’ll pass inside of methods that we’ll create later, which won’t have an underscore.

Where is the dot on the screen?

We’ll need to wrap the Container in a Positioned widget to specify where it is on the screen. Since the dot will be moving, we’ll need to use a variable. The Offset widget is used for specifying positions on the screen. The top left corner of the screen is Offset(0,0), and the x and y coordinates increase as you go right and down, respectively. You can get individual x- and y-coordinates with .dx and .dy that we’ll use in our Positioned widget.

The ? is used when defining _circlePosition because we’re not giving it an initial value. This is because we want to start the dot in the middle of the screen, and we’ll need the BuildContext that comes with the build method to do so.

The bang ! operator is used because we didn’t give an initial value to _circlePosition. Without it, the compiler complains that _circlePosition might be null and thus it won’t know where to draw the dot. The ! implies that _circlePosition will definitely not be null since we will give it an initial value:

The ??= means this operation will only happen if _circlePosition is null, which should only be when we first launch the app. MediaQuery.of(context).size.width / 2 is half your screen’s width. We subtract _circleSize because technically, the position of the dot is the top left corner of an imaginary square around the dot. We want the middle of the dot to be in the middle of the screen instead of the top left corner of that imaginary square to be in the middle of the screen.

Quick math review

To make the ball move to where we tap on the screen, we first need to find the slope between where the dot currently is on the screen and where we tapped.

Given two points with coordinates x1, y1 and x2, y2, the slope between them is given by the formula (y2-y1)/(x2-x1). HOWEVER, since the y-coordinate increases as you move down the screen (which is the opposite of a normal graph in math class), we’ll have to change the signs of the y-coordinates.

To find the slope, notice that the onTap parameter in the PositionedTapDetector2 widget has a position. We can use position.global to get the point on the screen where we tapped. If the current _circlePosition is x1, y1 and position.global is x2, y2, then the slope formula, with the signs changed for the y-coordinates, is

which we’ll save to a _slope variable

Now we can create two methods, moveRight and moveLeft, which will be called whenever we tap to the right or left of the ball; the slope being positive or negative will determine whether the ball moves up or down.

Before we see how moveLeft and moveRight are defined, we need some more math. To keep the dot from changing speeds, we’ll make it move 1 pixel at a time in any direction we tap. In general, moving x units to the right along a line means you move slope * x units up. If we want the dot to travel 1 pixel in any direction, we can use our friend the Pythagorean theorem (a² + b² = c²) to solve for x.

We’ll need the Dart math package: import 'dart:math';. We’ll define a variable for x in our class and use the above formula inside the moveRight and moveLeft methods.

setState is used to rebuild the screen with the dot in a different place

For moveRIght, the x-coordinate of _circlePositionincreases by _xDistance. When moving right, a positive slope means the dot should move up. Since moving up the screen makes the y-coordinate of the Offset widget DECREASE, then we subtract slope * _xDistance instead of adding it. We’ll do the opposite for the x- and y-coordinates inmoveLeft:

At this point, the dot moves only once when we tap the screen.

How do we make the dot keep moving?

import 'dart:async';

We’ll add a timer that repeats the method many times a second. Many high-end phones can run at 120 frames per second, which is about 8 milliseconds per frame.

We also need the timer to stop when we tap the screen so that it’ll stop repeating in that direction and start in a new direction. To do this, we’ll define a _tapCount variable that increases every time we tap the screen.

Timer.periodic added to make the dot keep moving.

Both methods now take an integer parameter to stop the current timer and start a new one when the screen is tapped.

At this point, the dot moves around but doesn’t bounce.

How do we make the dot bounce off the edges?

If the ball is moving right, it can bounce off of the top, bottom, or right side. If it bounces off the top or bottom, it will keep moving right but with the slope changing signs. If it bounces off the right, it’ll move left with the slope changing signs.

We can add the following to moveRight:

Since the official Offset of the ball is the top left corner of an imaginary square around the ball, notice we subtract _circleSize when checking if the ball has reached the bottom or the right edge of the screen. Otherwise, the ball won’t bounce until the top left the ball has reached the bottom or the right edge of the screen.

We do something similar for moveLeft:

Now we just need to make the ball reset when we long press the screen.

That’s it!

Here is the full main.dart file:

--

--