ЧТО МЫ СОЗДАЕМ:

Приложение Flutter с мячом, который отскакивает от краев экрана. Вы можете коснуться экрана, чтобы заставить мяч изменить направление, или долго нажимать на экран, чтобы сбросить мяч.

ЧТО ВЫ УЗНАЕТЕ:

Немного базового Flutter (виджеты с состоянием и setState, добавление пакетов, Container, BoxDecoration, Stack, Positioned, Offset) и немного математики.

Давайте начнем с приложения счетчика по умолчанию и сократим его до этого:

Обратите внимание, что MyHomePageтеперь DotScreen. (Вы можете изменить имя с помощью Shift+F6 в Android Studio или F2 в VS Code.)

В Scaffoldвиджете есть Stack, так как точка будет располагаться поверх фонового виджета, который будет определять, куда мы нажимаем.

Чтобы получить позицию, где мы нажимаем, мы импортируем пакет positioned_tap_detector_2, добавив следующее в pubspec.yaml andmain.dart:

Внутри Stack нам нужно добавить нашу точку поверх фона.

PositionedTapDetector2 — фон. Container — это точка, которую мы создадим следующим образом:

Мы создадим переменную _circleSize вне метода сборки. Используя переменную, если мы когда-нибудь захотим, чтобы точка была другого размера, мы можем изменить значение этой переменной вместо того, чтобы изменять все значения вручную.

Мы можем сделать переменную final, поскольку мы не будем ее изменять, но вы должны удалить final, если хотите добавить способ изменения размера точки. Подчеркивание означает, что переменная является частной для этого DotScreen, и будет полезно отличить ее от переменных, которые мы будем передавать внутри методов, которые мы создадим позже, которые не будут иметь подчеркивания.

Где точка на экране?

Нам нужно обернуть Container в виджет Positioned, чтобы указать, где он находится на экране. Поскольку точка будет двигаться, нам нужно использовать переменную. Виджет Offset используется для указания позиций на экране. Верхний левый угол экрана равен Offset(0,0), а координаты x и y увеличиваются при движении вправо и вниз соответственно. Вы можете получить отдельные координаты x и y с помощью .dx и .dy, которые мы будем использовать в нашем виджете Positioned.

? используется при определении _circlePosition, потому что мы не присваиваем ему начальное значение. Это потому, что мы хотим запустить точку в середине экрана, и для этого нам понадобится BuildContext, который идет с методом build.

Оператор bang ! используется, потому что мы не присвоили начальное значение _circlePosition. Без него компилятор жалуется, что _circlePosition может быть нулевым, и поэтому он не будет знать, где ставить точку. ! подразумевает, что _circlePosition определенно не будет нулевым, поскольку мы присвоим ему начальное значение:

??= означает, что эта операция произойдет только в том случае, если _circlePosition имеет значение null, что должно быть только при первом запуске приложения. MediaQuery.of(context).size.width / 2 — это половина ширины вашего экрана. Мы вычитаем _circleSize, потому что технически точка находится в верхнем левом углу воображаемого квадрата вокруг точки. Мы хотим, чтобы середина точки находилась посередине экрана, а не левый верхний угол воображаемого квадрата находился посередине экрана.

Краткий математический обзор

Чтобы заставить мяч двигаться туда, где мы нажимаем на экран, нам сначала нужно найти наклон между тем, где точка в данный момент находится на экране, и тем, где мы нажали.

Даны две точки с координатами x1, y1 и x2, y2, наклон между ними задается формулой (y2-y1)/(x2-x1). ОДНАКО, поскольку координата y увеличивается при движении вниз по экрану (что противоположно обычному графику на уроке математики), нам придется изменить знаки координат y.

Чтобы найти наклон, обратите внимание, что параметр onTap в виджете PositionedTapDetector2 имеет значение position. Мы можем использовать position.global, чтобы получить точку на экране, куда мы нажали. Если текущее _circlePosition равно x1, y1, а position.global равно x2, y2, то формула наклона с измененными знаками для y-координат будет

которую мы сохраним в переменную _slope

Теперь мы можем создать два метода, moveRight и moveLeft, которые будут вызываться всякий раз, когда мы нажимаем вправо или влево от шара; положительный или отрицательный наклон будет определять, будет ли мяч двигаться вверх или вниз.

Прежде чем мы увидим, как определяются moveLeft и moveRight, нам нужно еще немного математики. Чтобы точка не меняла скорость, мы заставим ее двигаться на 1 пиксель за раз в любом направлении, в котором мы касаемся. Как правило, перемещение на xединиц вправо по линии означает перемещение вверх по наклону* на xединиц. Если мы хотим, чтобы точка переместилась на 1 пиксель в любом направлении, мы можем использовать нашу знакомую теорему Пифагора (a² + b² = c²) для определения x.

Нам понадобится математический пакет Dart: import 'dart:math';. Мы определим переменную для x в нашем классе и используем приведенную выше формулу внутри методов moveRight и moveLeft.

Для moveRIght координата x _circlePositionувеличивается на _xDistance. При движении вправо положительный наклон означает, что точка должна двигаться вверх. Поскольку при перемещении вверх по экрану координата y виджета Offset УМЕНЬШАЕТСЯ, мы вычитаем slope * _xDistance вместо прибавления. Сделаем наоборот для координат x и y inmoveLeft:

На данный момент точка перемещается только один раз, когда мы нажимаем на экран.

Как заставить точку двигаться?

import 'dart:async';

Мы добавим таймер, который повторяет метод много раз в секунду. Многие телефоны высокого класса могут работать со скоростью 120 кадров в секунду, что составляет около 8 миллисекунд на кадр.

Нам также нужно, чтобы таймер останавливался, когда мы нажимаем на экран, чтобы он перестал повторяться в этом направлении и начинался в новом направлении. Для этого мы определим переменную _tapCount, которая увеличивается каждый раз, когда мы нажимаем на экран.

Оба метода теперь принимают целочисленный параметр, чтобы остановить текущий таймер и запустить новый при касании экрана.

В этот момент точка движется, но не отскакивает.

Как сделать так, чтобы точка отскакивала от краев?

Если мяч движется правильно, он может отскакивать от верхней, нижней или правой стороны. Если он отскочит от верха или низа, то продолжит движение вправо, но с изменением знака наклона. Если он отскакивает вправо, он будет двигаться влево со знаками изменения наклона.

Мы можем добавить следующее к moveRight:

Поскольку официальное смещение мяча — это верхний левый угол воображаемого квадрата вокруг мяча, обратите внимание, что мы вычитаем _circleSize при проверке, достиг ли мяч нижнего или правого края экрана. В противном случае мяч не будет отскакивать, пока верхний левый мяч не достигнет нижнего или правого края экрана.

Делаем нечто подобное для moveLeft:

Теперь нам просто нужно сделать так, чтобы мяч сбрасывался при длительном нажатии на экран.

Вот и все!

Вот полный файл main.dart: