Flutter 可拖动吸边的悬浮球

```
class DevEgg extends StatefulWidget {
static Widget inject({
@required bool enable,
@required String assetName,
@required void Function() onPressed,
@required Widget child,
}) {
if (!enable) {
return child;
}
return DevEgg(
assetName: assetName,
onPressed: onPressed,
child: child,
);
}

final Widget child;
final String assetName;
final void Function() onPressed;

const DevEgg({Key key, this.assetName, this.onPressed, this.child}) : super(key: key);

@override
_DevEggState createState() => _DevEggState();
}

class _DevEggState extends State {
bool _isShow = false;

@override
Widget build(BuildContext context) {
return MultiClickWidget(
triggerClickCount: 5,
onTriggerClick: () {
setState(() {
_isShow = !_isShow;
});
},
child: Stack(
children: _isShow
? [widget.child, _EggWidget(assetName: widget.assetName, onPressed: widget.onPressed)]
: [widget.child],
),
);
}
}

class _EggWidget extends StatefulWidget {
final String assetName;
final void Function() onPressed;

const _EggWidget({Key key, @required this.assetName, @required this.onPressed}) : super(key: key);

@override
State createState() => _EggState();
}

class _EggState extends State<_EggWidget> with TickerProviderStateMixin {
final Size screenSize = MediaQueryData.fromWindow(WidgetsBinding.instance.window).size;
final double statusbarHeight =
MediaQueryData.fromWindow(WidgetsBinding.instance.window).padding.top;

Offset _point;
Rect _borderRect;
double onPanEndSrcX;
double onPanEndDstX;

AnimationController _sizeAc;
AnimationController _asideAc;
Animation _scaleAnim;
bool isAnimatingWhenTaped = false;

@override
void initState() {
super.initState();
// 从 sp 读取
_point = Offset(0, screenSize.height / 2);
_borderRect = Rect.fromLTRB(0, statusbarHeight, screenSize.width - ScaleAnimEgg.eggTotalSize,
screenSize.height - ScaleAnimEgg.eggTotalSize);

_sizeAc = AnimationController(duration: const Duration(milliseconds: 100), vsync: this)
  ..addListener(() => setState(() {}))
  ..addStatusListener((status) {
    if (status == AnimationStatus.completed && isAnimatingWhenTaped) {
      // 如果动画正序播放完了,并且点击的时候是正在执行动画,那么就需要复原
      _sizeAc.reverse();
    }
  });
_scaleAnim = Tween(begin: 1.0, end: 0.75)
    .animate(CurvedAnimation(parent: _sizeAc, curve: Curves.easeIn));

_asideAc = AnimationController(duration: const Duration(milliseconds: 100), vsync: this)
  ..addListener(() => setState(() {
        _point =
            Offset(onPanEndSrcX + _asideAc.value * (onPanEndDstX - onPanEndSrcX), _point.dy);
      }));
Tween(begin: 0.0, end: 1.0)
    .animate(CurvedAnimation(parent: _asideAc, curve: Curves.easeOutCirc));

}

@override
Widget build(BuildContext context) {
return Positioned(
left: _point.dx,
top: _point.dy,
child: GestureDetector(
onPanDown: (_) {
// 点击和划动事件一定会执行
// 吸边动画执行中直接返回
if (_asideAc.isAnimating) return;

        isAnimatingWhenTaped = false;
top Created with Sketch.