Technology sharing

Volitantem cognoscit corporis globus collisionem effectus

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Effectus

Insert imaginem descriptionis hic

introductio

Effectus animationis corporis exsequens in applicationibus Flutteris usuario usui magno usui esse potest.Articulus hic singillatim introducebit quomodo pilam animatam interfaciem creare, quae concursum corporis in Flutter simulatsensors_plusObturaculum-in ad obtinendam vim sensoris fabricae datae.

Praeparatio

Priusquam incipias, fac tibipubspec.yamlAdd to filesensors_plusPlugin:

dependencies:
  flutter:
    sdk: flutter
  sensors_plus: 4.0.2
  • 1
  • 2
  • 3
  • 4

tunc curruntflutter pub getimperio clientelae obtinere.

Code structure

Nos progressio ad effectum deducendi dictaPhysicsBallWidgetConsuetudo philosophica maxime includit partes sequentes:

  • Classis pila: repraesentat basic notitia cuiusque pilae.
  • BadgeBallConfig class: statum et mores cuiusque pila administrat.
  • PhysicsBallWidget class: componentia principalia, inter logicam et pilam animationem.
  • BallItemWidget class: specie content pro unaquaque pila ostendit.
  • BallListPage class: pagina test, ostendens effectum animationis pila corporis.

Classis pila

Primum defineBallClassis fundamentalia informationes circa singulas pilas repraesentabat, ut nomen:

class Ball {
  final String name;

  Ball({required this.name});
}
  • 1
  • 2
  • 3
  • 4
  • 5

BadgeBallConfig classis

BadgeBallConfigClassis ad statum et mores cuiusque globi administrandum adhibetur, incluso acceleratione, velocitate, positione et aliis indiciis;

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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85

PhysicsBallWidget classis

PhysicsBallWidgetClassis principale est et pertinet ad logicam et animationem pilae tractandam:

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];
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163

BallItemWidgetClass

BallItemWidgetClasses in specie singulas pilas ostentant et suas animationes et eventus tractant;

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));
    });
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157

BallListPage classis

BallListPageClassis est pagina eliquata, adhibita ad effectum animationis pilae corporis exhibendam;

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,
          ),
        ],
      ),
    );
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

finitione

Per hoc blogum ostendimus quomodo efficiendum effectum animationis pilae corporis in Flutter et integraresensors_plus Obturaculum-in ad obtinendam vim sensoris fabricae datae. Spero hoc blog auxilium te posse similes effectus in Flutter evolutione consequi.
For details, vide: github.com/yixiaolunhui/flutter_xy