This is the third day of my participation in the August More text Challenge. For details, see:August is more challenging

We all know that there’s an “auxiliary Touch” switch in the iPhone’s Settings, which acts like an on-screen soft keyboard that you can drag anywhere on the screen. It is translucent when it is idle and opaque when it is operated by the user. On iOS native, you can load a custom view into the keyWindow, so that the view is at the top of all the views.

Effect display:

Step 1. Encapsulate custom full-screen floating components and display all components in the app in the form of Child

In fact, this is a bit of a grandiose, roughly means to encapsulate a component, inside the form of a frame layout. The entire content of the app is displayed as the main component. At this time, it is necessary to simply encapsulate the levitating frame component as the upper component of the frame layout, and then use the GestureDetector to monitor the dragging gesture, and then move the levitating component. Then, the general layout is completed.

Here’s the main layout code:

GeneralFloatOnScreenView code:

class GeneralFloatOnScreenView extends StatefulWidget { Widget child; GeneralFloatOnScreenView({required this.child}); @override State<StatefulWidget> createState() { return new GeneralFloatOnScreenViewState(); }}Copy the code

GeneralFloatOnScreenViewState code

Attribute declarations

// Frame layout top distance double _top = 0; // Double _left = 0; Double _width = 50; double _width = 50; double _width = 50; Double _parentWidth = 0; double _parentWidth = 0; double _parentWidth = 0; bool _isInitData = false; Double _opacity = 0; // Opacity = 0; // Late AnimationController _controller; // Late Animation<double> _animation; // The floating component is declared as a property to prevent multiple refreshes if the component has some separate logic inside it. late Widget _contentWidget;Copy the code

Initialize, create the component in the Widget initialization so that as long as the current component does not disappear from the screen, the wantKeepAlive setting will not be re-initialized even without the wantKeepAlive setting. A layer of GestureDetector gestures is nested on the levitating component to perform the drag and drop movement.

@override
void initState() {
  // TODO: implement initState
  super.initState();
  _contentWidget = new GestureDetector(
    onPanUpdate: (DragUpdateDetails details){
      _left += details.delta.dx;
      _top += details.delta.dy;
      _changePosition();
    },
    onPanEnd: (DragEndDetails details){
      _changePosition();
      //判断悬浮组件左右回归操作
      _animateMoveBackAction();
    },
    onPanCancel: (){
      //当取消手势时进行边缘判断
      _changePosition();
    },
    onPanStart: (DragStartDetails details){
      //开始拖拽时将悬浮框透明度设置为1.0
      setState(() {
        _opacity = 1.0;
      });
    },
    child: new Container(
      decoration: BoxDecoration(
        borderRadius: BorderRadius.all(Radius.circular(_width / 2.0)),
        color: Colors.red,
      ),
      width: _width,
      height: _width,
    ),
  );
  
  //这里初始化动画类
  _controller =
      AnimationController(duration: Duration(milliseconds: 0), vsync: this);
  _animation =
      Tween(begin: _left, end: _left).animate(_controller);
}
Copy the code

The build method, there’s nothing special to note here, it just adds the frame layout for wrapping.

@override Widget build(BuildContext context) {// Only evaluate the required properties once if(_isInitData == false) {_top = MediaQuery.of(context).size.height - 200; _left = 15; _parentWidth = MediaQuery.of(context).size.width; _isInitData = true; } return new Stack( fit: StackFit.passthrough, children: <Widget>[ this.widget.child, Positioned( top: _top, left: _left, child: new Opacity( child: _contentWidget, opacity: _opacity ), ) ], ); }Copy the code

Step two, how to move and judge the edge of gestures?

When the GestureDetector gesture listens to the position movement of the floating component in the onPanUpdate method, you can directly modify the values of the attributes _left and _top. Here, pay attention to the screen edge judgment.

Void _changePosition(){if(_left < 0) {_left = 0; } if(_left >= MediaQuery.of(context).size.width - _width){ _left = MediaQuery.of(context).size.width - _width; } if(_top < 0) { _top = 0; } if(_top >= MediaQuery.of(context).size.height - _width) { _top = MediaQuery.of(context).size.height - _width; } // Refresh the interface setState(() {}); }Copy the code

Step 3. How to drag and drop gesture to end the hover component and return to the edge animation?

When the GestureDetector gesture listens to the execution of the onPanEnd method, judge the position relationship between the position of the central axis of the current floating component and the screen (or the central axis of the parent component). The left side is close to the left edge, and the right side is close to the edge. The animation is not added to an animation component, but is executed directly, listening for changes in the value of the animation to float the component backwards. Set the hover component’s transparency to translucent when the animation’s direct state is finished.

Void animatemoveBackAction (){double centerX = _left + _width / 2.0; double toPositionX = 0; double needMoveLength = 0; If (centerX <= _parentWidth / 2.0) {needMoveLength = _left; } else { needMoveLength = (_parentWidth - _left - _width); } double precent = (needMoveLength/(parentwidth / 2.0)); int time = (600 * precent).ceil(); If (centerX <= _parentWidth / 2.0){// return toPositionX = 0; } else {// back to the right edge toPositionX = _parentWidth -_width; } // In this case, because the animation execution time needs to be reset according to the desired offset distance, the previous animation controller is destroyed and then created. _controller.dispose(); _controller = AnimationController(duration: Duration(milliseconds: time), vsync: this); _animation = Tween(begin: _left, end: toPositionX * 1.0). Animate (_controller); _animation.addListener(() { _left = _animation.value.toDouble(); setState(() { }); }); _animation.addStatusListener((status) { if(status == AnimationStatus.completed){ Future.delayed(Duration(microseconds: 100),(){setState(() {_opacity = 0.3; }); }); }}); _controller.forward(); }Copy the code

Note that the destruction of the animation controller is performed when the component is destroyed. Note that the related operations of the component are performed first and then the super.dispos() is executed.

@override
void dispose() {
  // TODO: implement dispose
  _controller.dispose();
  super.dispose();
}
Copy the code

Step 4. How to use this encapsulated component?

Wrap the GeneralFloatOnScreenView around all components of the previous app and pass it to the custom component as a child

Home: new Scaffold(// where the new BottomTabbarWidget() is all the components of the app and passes it as a child to the custom component) new GeneralFloatOnScreenView(child: new BottomTabbarWidget()), )Copy the code

Well, simple imitation iOS full screen floating window to achieve good, code poor, god do not spray, if it is helpful to everyone, but also very pleased.