τα στοιχεία επικοινωνίας μου
Ταχυδρομείο[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Η εφαρμογή εφέ φυσικής κίνησης σε εφαρμογές Flutter μπορεί να βελτιώσει σημαντικά την εμπειρία του χρήστη.Αυτό το άρθρο θα παρουσιάσει λεπτομερώς τον τρόπο δημιουργίας μιας κινούμενης διεπαφής μπάλας που προσομοιώνει τη φυσική σύγκρουση στο Flutter. Η υλοποίηση του κύριου κώδικα βασίζεται στην ενοποίησηsensors_plus
Συνδέστε για να λάβετε τα δεδομένα του αισθητήρα επιτάχυνσης της συσκευής.
Πριν ξεκινήσετε, βεβαιωθείτε ότιpubspec.yaml
Προσθήκη στο αρχείοsensors_plus
Συνδέω:
dependencies:
flutter:
sdk: flutter
sensors_plus: 4.0.2
μετά τρέξεflutter pub get
εντολή για απόκτηση εξαρτήσεων.
Θα εφαρμόσουμε ένα πρόγραμμα που ονομάζεταιPhysicsBallWidget
Το προσαρμοσμένο γραφικό στοιχείο περιλαμβάνει κυρίως τα ακόλουθα μέρη:
Πρώτα ορίστεBall
Κλάση που χρησιμοποιείται για την αναπαράσταση βασικών πληροφοριών για κάθε μπάλα, όπως όνομα:
class Ball {
final String name;
Ball({required this.name});
}
BadgeBallConfig
Η κλάση χρησιμοποιείται για τη διαχείριση της κατάστασης και της συμπεριφοράς κάθε μπάλας, συμπεριλαμβανομένων της επιτάχυνσης, της ταχύτητας, της θέσης και άλλων πληροφοριών:
class BadgeBallConfig {
final Acceleration _acceleration = Acceleration(0, 0);
final double time = 0.02;
late Function(Offset) collusionCallback;
Size size = const Size(100, 100);
Speed _speed = Speed(0, 0);
late Offset _position;
late String name;
double oppositeAccelerationCoefficient = 0.7;
void setPosition(Offset offset) {
_position = offset;
}
void setInitSpeed(Speed speed) {
_speed = speed;
}
void setOppositeSpeed(bool x, bool y) {
if (x) {
_speed.x = -_speed.x * oppositeAccelerationCoefficient;
if (_speed.x.abs() < 5) _speed.x = 0;
}
if (y) {
_speed.y = -_speed.y * oppositeAccelerationCoefficient;
if (_speed.y.abs() < 5) _speed.y = 0;
}
}
void setAcceleration(double x, double y) {
_acceleration.x = x * oppositeAccelerationCoefficient;
_acceleration.y = y * oppositeAccelerationCoefficient;
}
Speed getCurrentSpeed() => _speed;
Offset getCurrentCenter() => Offset(
_position.dx + size.width / 2,
_position.dy + size.height / 2,
);
Offset getCurrentPosition() => _position;
void inertiaStart(double x, double y) {
if (x.abs() > _acceleration.x.abs()) _speed.x += x;
if (y.abs() > _acceleration.y.abs()) _speed.y += y;
}
void afterCollusion(Offset offset, Speed speed) {
_speed = Speed(
speed.x * oppositeAccelerationCoefficient,
speed.y * oppositeAccelerationCoefficient,
);
_position = offset;
collusionCallback(offset);
}
Offset getOffset() {
var offsetX = (_acceleration.x.abs() < 5 && _speed.x.abs() < 3) ? 0.0 : _speed.x * time + (_acceleration.x * time * time) / 2;
var offsetY = (_acceleration.y.abs() < 5 && _speed.y.abs() < 6) ? 0.0 : _speed.y * time + (_acceleration.y * time * time) / 2;
_position = Offset(_position.dx + offsetX, _position.dy + offsetY);
_speed = Speed(
_speed.x + _acceleration.x * time,
_speed.y + _acceleration.y * time,
);
return _position;
}
}
class Speed {
double x;
double y;
Speed(this.x, this.y);
}
class Acceleration {
double x;
double y;
Acceleration(this.x, this.y);
}
PhysicsBallWidget
Η τάξη είναι το κύριο συστατικό και είναι υπεύθυνη για το χειρισμό της λογικής και της κίνησης της μπάλας:
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_xy/application.dart';
import 'package:flutter_xy/xydemo/ball/ball_model.dart';
import 'package:sensors_plus/sensors_plus.dart';
//https://github.com/yixiaolunhui/flutter_xy
class PhysicsBallWidget extends StatefulWidget {
final List<Ball> ballList;
final double height;
final double width;
const PhysicsBallWidget({
required this.ballList,
required this.width,
required this.height,
Key? key,
}) : super(key: key);
State<StatefulWidget> createState() => _PhysicsBallState();
}
class _PhysicsBallState extends State<PhysicsBallWidget> {
List<Widget> badgeBallList = [];
List<ValueKey<BadgeBallConfig>> keyList = [];
late Size ballSize;
void initState() {
super.initState();
fillKeyList();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
App.get().addPersistentFrameCallback(travelHitMap);
});
}
void dispose() {
App.get().removePersistentFrameCallback(travelHitMap);
super.dispose();
}
Widget build(BuildContext context) {
fillWidgetList();
return Stack(
children: badgeBallList,
);
}
void fillKeyList() {
var badgeSize = (widget.width - 20) / 6;
badgeSize = (badgeSize >= 84.0 || badgeSize <= 0.0 || !badgeSize.isFinite)
? 84.0
: badgeSize;
var maxCount = ((widget.height - badgeSize) ~/ badgeSize) *
(widget.width ~/ badgeSize);
if (widget.ballList.length >= maxCount) {
badgeSize = 50.0;
}
ballSize = Size(badgeSize, badgeSize);
var initOffsetX = 0.0;
var initOffsetY = widget.height - badgeSize;
for (var element in widget.ballList) {
keyList.add(ValueKey<BadgeBallConfig>(
BadgeBallConfig()
..size = ballSize
..name = element.name
..setPosition(Offset(initOffsetX, initOffsetY)),
));
initOffsetX += badgeSize;
if (initOffsetX + badgeSize > widget.width - 20) {
initOffsetX = 0;
initOffsetY -= badgeSize;
}
}
}
void fillWidgetList() {
badgeBallList.clear();
for (var e in keyList) {
badgeBallList.add(
BallItemWidget(
key: e,
limitWidth: widget.width,
limitHeight: widget.height,
onTap: () {},
),
);
}
}
void travelHitMap(Duration timeStamp) {
for (var i = 0; i < keyList.length - 1; i++) {
for (var j = i + 1; j < keyList.length; j++) {
hit(keyList[i].value, keyList[j].value);
}
}
}
void hit(BadgeBallConfig a, BadgeBallConfig b) {
final distance = a.size.height / 2 + b.size.height / 2;
final w = b.getCurrentCenter().dx - a.getCurrentCenter().dx;
final h = b.getCurrentCenter().dy - a.getCurrentCenter().dy;
if (sqrt(w * w + h * h) <= distance) {
var aOriginSpeed = a.getCurrentSpeed();
var bOriginSpeed = b.getCurrentSpeed();
var aOffset = a.getCurrentPosition();
var angle = atan2(h, w);
var sinNum = sin(angle);
var cosNum = cos(angle);
var aCenter = [0.0, 0.0];
var bCenter = coordinateTranslate(w, h, sinNum, cosNum, true);
var aSpeed = coordinateTranslate(
aOriginSpeed.x, aOriginSpeed.y, sinNum, cosNum, true);
var bSpeed = coordinateTranslate(
bOriginSpeed.x, bOriginSpeed.y, sinNum, cosNum, true);
var vxTotal = aSpeed[0] - bSpeed[0];
aSpeed[0] = (2 * 10 * bSpeed[0]) / 20;
bSpeed[0] = vxTotal + aSpeed[0];
var overlap = distance - (aCenter[0] - bCenter[0]).abs();
aCenter[0] -= overlap;
bCenter[0] += overlap;
var aRotatePos =
coordinateTranslate(aCenter[0], aCenter[1], sinNum, cosNum, false);
var bRotatePos =
coordinateTranslate(bCenter[0], bCenter[1], sinNum, cosNum, false);
var bOffset
X = aOffset.dx + bRotatePos[0];
var bOffsetY = aOffset.dy + bRotatePos[1];
var aOffsetX = aOffset.dx + aRotatePos[0];
var aOffsetY = aOffset.dy + aRotatePos[1];
var aSpeedF =
coordinateTranslate(aSpeed[0], aSpeed[1], sinNum, cosNum, false);
var bSpeedF =
coordinateTranslate(bSpeed[0], bSpeed[1], sinNum, cosNum, false);
a.afterCollusion(
Offset(aOffsetX, aOffsetY), Speed(aSpeedF[0], aSpeedF[1]));
b.afterCollusion(
Offset(bOffsetX, bOffsetY), Speed(bSpeedF[0], bSpeedF[1]));
}
}
List<double> coordinateTranslate(
double x, double y, double sin, double cos, bool reverse) {
return reverse
? [x * cos + y * sin, y * cos - x * sin]
: [x * cos - y * sin, y * cos + x * sin];
}
}
BallItemWidget
Οι τάξεις χρησιμοποιούνται για να εμφανίσουν συγκεκριμένα κάθε μπάλα και να χειριστούν τα κινούμενα σχέδια και τα συμβάντα της:
import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_xy/application.dart';
import 'package:flutter_xy/xydemo/ball/ball_model.dart';
import 'package:sensors_plus/sensors_plus.dart';
class BallItemWidget extends StatefulWidget {
final double limitWidth;
final double limitHeight;
final Function onTap;
const BallItemWidget({
required this.limitWidth,
required this.limitHeight,
required this.onTap,
Key? key,
}) : super(key: key);
State<StatefulWidget> createState() => BallItemState();
}
class BallItemState extends State<BallItemWidget> {
final List<StreamSubscription<dynamic>> _streamSubscriptions = [];
late BadgeBallConfig config;
Duration sensorInterval = SensorInterval.normalInterval;
var color = Color.fromARGB(
255,
Random().nextInt(256),
Random().nextInt(256),
Random().nextInt(256),
);
Timer? timer;
double x = 0;
double y = 0;
double limitY = 0;
double limitX = 0;
void initState() {
super.initState();
initData();
_streamSubscriptions.add(
accelerometerEvents.listen(
(AccelerometerEvent event) {
config.setAcceleration(
-double.parse(event.x.toStringAsFixed(1)) * 50,
double.parse(event.y.toStringAsFixed(1)) * 50,
);
},
),
);
_streamSubscriptions.add(
userAccelerometerEvents.listen(
(UserAccelerometerEvent event) {
config.inertiaStart(
double.parse(event.x.toStringAsFixed(1)) * 50,
-double.parse(event.y.toStringAsFixed(1)) * 20,
);
},
),
);
timer = Timer.periodic(const Duration(milliseconds: 20), (timer) {
if (!SchedulerBinding.instance.hasScheduledFrame) {
SchedulerBinding.instance.scheduleFrame();
}
});
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
App.get().addPersistentFrameCallback(updatePosition);
});
}
void dispose() {
super.dispose();
for (var subscription in _streamSubscriptions) {
subscription.cancel();
}
App.get().removePersistentFrameCallback(updatePosition);
timer?.cancel();
timer = null;
}
Widget build(BuildContext context) {
return AnimatedPositioned(
left: x,
top: y,
duration: const Duration(milliseconds: 16),
child: GestureDetector(
onTap: () {
widget.onTap.call();
},
child: Container(
width: config.size.width,
alignment: Alignment.center,
height: config.size.height,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: color, width: 2.w),
),
child: Text(
config.name,
style: TextStyle(fontSize: 16.w, color: Colors.red),
),
),
),
);
}
void initData() {
limitX = widget.limitWidth;
limitY = widget.limitHeight;
config = (widget.key as ValueKey<BadgeBallConfig>).value;
config.collusionCallback = (offset) {
setState(() {
x = offset.dx;
y = offset.dy;
config.setPosition(offset);
});
};
x = config.getCurrentPosition().dx;
y = config.getCurrentPosition().dy;
}
void updatePosition(Duration timeStamp) {
setState(() {
var tempX = config.getOffset().dx;
var tempY = config.getOffset().dy;
if (tempX < 0) {
tempX = 0;
config.setOppositeSpeed(true, false);
}
if (tempX > limitX - config.size.width) {
tempX = limitX - config.size.width;
config.setOppositeSpeed(true, false);
}
if (tempY < 0) {
tempY = 0;
config.setOppositeSpeed(false, true);
}
if (tempY > limitY - config.size.height) {
tempY = limitY - config.size.height;
config.setOppositeSpeed(false, true);
}
x = tempX;
y = tempY;
config.setPosition(Offset(x, y));
});
}
}
BallListPage
Η τάξη είναι μια δοκιμαστική σελίδα, που χρησιμοποιείται για την εμφάνιση του φυσικού εφέ κίνησης της μπάλας:
import 'package:flutter/material.dart';
import 'package:flutter_xy/xydemo/ball/ball_model.dart';
import 'package:flutter_xy/xydemo/ball/ball_widget.dart';
class BallListPage extends StatefulWidget {
const BallListPage({super.key});
State<BallListPage> createState() => _BallListPageState();
}
class _BallListPageState extends State<BallListPage> {
final List<Ball> badgeList = [
Ball(name: '北京'),
Ball(name: '上海'),
Ball(name: '天津'),
Ball(name: '徐州'),
Ball(name: '南京'),
Ball(name: '苏州'),
Ball(name: '杭州'),
Ball(name: '合肥'),
Ball(name: '武汉'),
Ball(name: '常州'),
Ball(name: '香港'),
Ball(name: '澳门'),
Ball(name: '新疆'),
Ball(name: '成都'),
Ball(name: '宿迁'),
];
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
PhysicsBallWidget(
ballList: badgeList,
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
),
],
),
);
}
}
Μέσω αυτού του ιστολογίου, δείχνουμε πώς να εφαρμόσετε ένα φυσικό εφέ κινούμενων σχεδίων μπάλας στο Flutter και να ενσωματώσετεsensors_plus
Συνδέστε για να λάβετε τα δεδομένα του αισθητήρα επιτάχυνσης της συσκευής. Ελπίζω ότι αυτό το ιστολόγιο μπορεί να σας βοηθήσει να επιτύχετε παρόμοια αποτελέσματα στην ανάπτυξη Flutter.
Για λεπτομέρειες, ανατρέξτε στη διεύθυνση: github.com/yixiaolunhui/flutter_xy