Drawer animation implemented by Flutter

This article will deepen the View drag example, using Flutter Animation, Interpolator and Animated Builder to teach you how to achieve drawer effect with animation. First look at the effect:

By conceiving, we can imagine that the way to realize drawer is to display two widgets by stacking them with Stack control. Gesture Detector is used to monitor gesture sliding and dynamically move the top Widget. When the gesture is finished, the top Widget is dynamically sliding to the end position by animation effect according to the distance of gesture sliding. Yes.

Implementing the bottom Widget

class DownDrawerWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(child: Center(child: Text("bottom Widget",),),);
  }
}

This Widget is too simple to go into details.

Implementing top Widget

class UpDrawerWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(child: Center(child: Text("Top Widget",),),);
  }
}

The implementation is the same as the bottom.

Implementing removable containers

Both of the above widgets are simply for display, so they inherit the Stateless Widget. Next we need to move the top Widget dynamically according to gestures, so we need to inherit the Stateful Widget.

// Top Widget
class HomePageWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => HomePageState();
}

class HomePageState extends State<HomePageWidget>
    with SingleTickerProviderStateMixin {

  @override
  void initState() {...}

  @override
  void dispose() {...}

  @override
  Widget build(BuildContext context) {...}

  void _onViewDragDown(DragDownDetails callback) {...}

  void _onViewDrag(DragUpdateDetails callback) {...}

  void _onViewDragUp(DragEndDetails callback) {...}
}

Initialization state initState()

This method is the callback function of the system when the Widget is initialized. We need to initialize the animation in this function.

AnimationController controller;
@override
void initState() {
    // Initialize the animation controller, which limits the animation time to 200 milliseconds
    controller = new AnimationController(vsync: this, duration: const Duration(milliseconds: 200));
    // The vsync object binds the animation timer to a visual widget, so when the widget is not displayed, the animation timer will pause. When the widget is displayed again, the animation timer will resume execution, thus avoiding the consumption of resources when the animation-related UI is not on the current screen.
    // When using vsync: this, the State object must be with SingleTickerProviderStateMixin or TickerProviderStateMixin; TickerProviderStateMixin is suitable for multi-Animation Controller situations.

    // Setting animation curve is animation interpolator
    // You can learn more about the differentiator through this link, https://docs.flutter.io/flutter/animation/Curves-class.html. Here we use bounceOut with rebound effect.
    CurvedAnimation curve =
        new CurvedAnimation(parent: controller, curve: Curves.bounceOut);

    // Increase the animation monitor, when the gesture is over, the animation effect is achieved by dynamically calculating the distance to the target position. curve.value is the value of the current animation, ranging from 0 to 1.
    curve.addListener(() {
      double animValue = curve.value;
      double offset = dragUpDownX - dragDownX;
      double toPosition;

      // Right slip
      if (offset > 0) {
        if (offset > maxDragX / 5) {
          // open
          toPosition = maxDragX;
          isOpenState = true;
        } else {
          if (isOpenState) {
            toPosition = maxDragX;
            isOpenState = true;
          } else {
            toPosition = 0.0;
            isOpenState = false;
          }
        }
      } else {
        if (offset < (-maxDragX / 2.0)) {
          // shut
          toPosition = 0.0;
          isOpenState = false;
        } else {
          if (isOpenState) {
            toPosition = maxDragX;
            isOpenState = true;
          } else {
            toPosition = 0.0;
            isOpenState = false;
          }
        }
      }

      dragOffset = (toPosition - dragUpDownX) * animValue + dragUpDownX;
      // Refresh location
      setState(() {});
    });
  }

End Widget dispose()

When the Widget is not available and will be recycled, the system calls back the dispose() method, where we recycle the animation.

@override
void dispose() {
    controller.dispose();
}

Record press position

    double dragDownX = 0.0;
  void _onViewDragDown(DragDownDetails callback) {
    dragDownX = callback.globalPosition.dx;
  }

Refresh View position when dragging

/**
   * Maximum draggable position
   */
  final double maxDragX = 230.0;
  double dragOffset = 0.0;
  void _onViewDrag(DragUpdateDetails callback) {
    double tmpOffset = callback.globalPosition.dx - dragDownX;

    if (tmpOffset < 0) {
      tmpOffset += maxDragX;
    }

    // edge detection
    if (tmpOffset < 0) {
      tmpOffset = 0.0;
    } else if (tmpOffset >= maxDragX) {
      tmpOffset = maxDragX;
    }

    // Refresh
    if (dragOffset != tmpOffset) {
      dragOffset = tmpOffset;
      setState(() {});
    }
  }

Record position and animate when leaving hand

  /**
   * Position at the time of release
   */
  double dragUpDownX = 0.0;
  void _onViewDragUp(DragEndDetails callback) {
    dragUpDownX = dragOffset;
    // Execute animation, starting at frame 0 each time
    controller.forward(from: 0.0);
  }

Mobile Widget s

@override
  Widget build(BuildContext context) {
    return Transform.translate(
      offset: Offset(dragOffset, 0.0),
      child: Container(
        child: GestureDetector(
              onHorizontalDragDown: _onViewDragDown,
              onVerticalDragDown: _onViewDragDown,
              onHorizontalDragUpdate: _onViewDrag,
              onVerticalDragUpdate: _onViewDrag,
              onHorizontalDragEnd: _onViewDragUp,
              onVerticalDragEnd: _onViewDragUp,
              child: Container(
                child: new UpDrawerWidget(),
          ),),),);}

Flutter animation

To sum up, in order to implement animation in Flutter, we need to create an Animation Controller controller first; if there are special interpolation requirements, we need to create an interpolator, call the controller.forward() method to execute the animation, change the corresponding value through the callback of addListener(), and then call setState (){} method to refresh the position, that is, if there are special interpolation requirements. Yes.
The Flutter API also provides AnimatedBuilder to simplify the complexity of implementing animation without manually calling the addListener() method.

Tags: Android Mobile

Posted on Fri, 09 Aug 2019 03:49:22 -0700 by gardner1