प्रौद्योगिकी साझेदारी

फ्लुटर-शारीरिककन्दुकस्य टकरावप्रभावं साक्षात्करोति

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
  • 1
  • 2
  • 3
  • 4

ततः धावतुflutter pub getआश्रयान् प्राप्तुं आदेशः ।

संहिता संरचना

वयं नामकं कार्यक्रमं कार्यान्विष्यामःPhysicsBallWidgetकस्टम् विजेट् मुख्यतया निम्नलिखितभागाः समाविष्टाः सन्ति ।

  • कन्दुकवर्गः : प्रत्येकस्य कन्दुकस्य मूलभूतसूचनाः प्रतिनिधियति ।
  • BadgeBallConfig class: प्रत्येकस्य कन्दुकस्य स्थितिं व्यवहारं च प्रबन्धयति ।
  • PhysicsBallWidget class: मुख्यघटकः, यत्र कन्दुकस्य तर्कः एनिमेशनं च समाविष्टम् अस्ति ।
  • BallItemWidget class: विशेषतया प्रत्येकस्य कन्दुकस्य कृते विजेट् प्रदर्शयति ।
  • BallListPage वर्गः: परीक्षणपृष्ठं, भौतिकगोलसजीवीकरणप्रभावं दर्शयति ।

कन्दुकवर्गः

प्रथमं परिभाषयतुBallप्रत्येकस्य कन्दुकस्य विषये मूलभूतसूचनाः प्रतिनिधितुं प्रयुक्तः वर्गः, यथा नाम:

class Ball {
  final String name;

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

BadgeBallConfig वर्गः

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);
}
  • 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 वर्गः

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];
  }
}
  • 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 इति

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));
    });
  }
}
  • 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 वर्गः

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,
          ),
        ],
      ),
    );
  }
}
  • 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

उपसंहारे

अस्य ब्लोग् इत्यस्य माध्यमेन वयं Flutter इत्यस्मिन् भौतिक-गोल-एनिमेशन-प्रभावं कथं कार्यान्वितुं, एकीकृत्य च दर्शयामःsensors_plus यन्त्रस्य त्वरणसंवेदकदत्तांशं प्राप्तुं प्लग्-इन् । आशासे यत् एषः ब्लोग् भवन्तं Flutter विकासे अपि एतादृशं प्रभावं प्राप्तुं साहाय्यं कर्तुं शक्नोति।
विस्तरेण पश्यन्तु: github.com/yixiaolunhui/flutter_xy