Preface:

To prepare for the August challenge of the nuggets, I’m going to pick 31 components this month that haven’t been introduced before and do a full analysis and attribute introduction. These articles will be used as important material in the collection of the Flutter components. I hope I can stick to it, your support will be my biggest motivation ~

  • 1. [Flutter component collection] NotificationListener | August more text challenge
  • 2.【Flutter Components 】Dismissible | August more challenges[this article]

First, know Dismissible modules

Today we’re looking at a slider related component: Dismissible. This component can be swiped to remove entries, as shown in the figure below. Let’s take a look at its simplest use.

The left slide Slip right

The _HomePageState displays 60 entries via ListView. At tag1 below, a Dismissible component was wrapped around the entry during the build of the entry. The key and child arguments are passed into the construct. Where the key is used to identify the entry and the child is the entry component. The onDismissed callback is called when the entry is removed.

The Dismissible module was activated only as a result of the UI. Actual data was not removed. In order to ensure the consistency of data and UI, we generally remove the corresponding data at the same time after removal and reconstruct it, as shown in tag2.

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

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

class _HomePageState extends State<HomePage> {
  List<String> data = List.generate(60, (index) => 'the first$indexA ');

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Dismissible test'),),
      body: ListView.builder(
        itemCount: data.length,
        itemBuilder: _buildItems,
      ),
    );
  }

  Widget _buildItems(BuildContext context, int index) {
    return Dismissible( //<---- tag1
      key: ValueKey<String>(data[index]),
      child: ItemBox(
        info: data[index],
      ),
      onDismissed: (direction) =>_onDismissed(direction,index),
    );
  }

  void _onDismissed(DismissDirection direction,int index) {
    setState(() {
      data.removeAt(index); //<--- tag 2}); }}Copy the code

The ItemBox is a Container with a height of 56, which displays the text information.

class ItemBox extends StatelessWidget {
  final String info;

  const ItemBox({Key? key, required this.info}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      alignment: Alignment.center,
      height: 56,
      child: Text(
        info,
        style: TextStyle(fontSize: 20),),); }}Copy the code

2. Learn more about Dismissible modules

We’ve seen the use of Dismissible modules. As you can see from the source code below, the key and child attributes are mandatory, but there are many other attributes. Here’s a look at each of them:


1, background and secondaryBackground

Dismissible, we can specify the background component. If only background is set, then the left and right backgrounds will be the same, as shown in the green background on the left below. If background and secondaryBackground are set, the left slide background is background, and the right slide background is secondaryBackground, as shown in the following figure on the right.

Single background The double background

The code implementation is also simple, specifying the background and secondaryBackground for the component. Tag1 and tag2 are processed as follows.

Widget _buildItems(BuildContext context, int index) {
  return Dismissible(
    key: ValueKey(data[index]),
    background: buildBackground(), // tag1
    secondaryBackground: buildSecondaryBackground(), // tag2
    child: ItemBox(
      info: data[index],
    ),
    onDismissed: (direction) =>_onDismissed(direction,index),
  );
}

Widget buildBackground(){
  return Container(
    color: Colors.green,
    alignment: Alignment(0.9.0),
    child: Icon(
      Icons.check,
      color: Colors.white,
    ),
  );
}

Widget buildSecondaryBackground(){
  return Container(
    alignment: Alignment(0.9.0),
    child: Icon(
      Icons.close,
      color: Colors.white,
    ),
    color: Colors.red,
  );
}
Copy the code

2. ConfirmDismiss callback

From the source code we can see that the type of confirmDismiss is ConfirmDismissCallback. This is a function type that DismissDirection objects back and returns a bool. You can see that this callback is an asynchronous method, so we can handle asynchronous events.

-- -- -- - > [Dismissible# confirmDismiss statement] -final ConfirmDismissCallback? confirmDismiss;

typedef ConfirmDismissCallback = Future<bool?> Function(DismissDirection direction);
Copy the code

In the figure on the left below, after the slide is complete, wait two seconds before executing the subsequent logic. In effect, the item will be removed after two seconds. The confirmDismiss asynchronous method is called after the confirmDismiss asynchronous method has completed.

This callback has a Future

, or false will not remove the item. In the image on the right below, items are not removed on a green background and are removed on a red background. You can use that return value to control.
?>

Execute asynchronous events The function of the return value

The code is implemented as follows, with the confirmDismiss attribute set at tag1. The return value is to see if the direction is not startToEnd, which is to slide from left to right. That is, when you swipe from left to right, false is returned, that is, entries are not eliminated.

Widget _buildItems(BuildContext context, int index) {
  return Dismissible(
    key: ValueKey(data[index]),
    background: buildBackground(),
    secondaryBackground: buildSecondaryBackground(),
    child: ItemBox(
      info: data[index],
    ),
    onDismissed: (direction) =>_onDismissed(direction,index),
    confirmDismiss: _confirmDismiss, // tag1
  );
}

Future<bool?> _confirmDismiss(DismissDirection direction) async{
  await Future.delayed(Duration(seconds: 2));
  print('_confirmDismiss:$direction');
  returndirection! =DismissDirection.startToEnd; }Copy the code

3. Direction Sliding direction

Direction is the direction to slide, the type is DismissDirection is enumerated, and there are 7 elements.

enum DismissDirection {
  vertical,
  horizontal,
  endToStart,
  startToEnd,
  up,
  down,
  none
}
Copy the code

In the image on the left below, set startToEnd so you can’t slide from right to left. In the image on the right below, set vertical so that the item can only slide vertically. There’s a problem with sliding in the same direction as the list, though. The item responds to the vertical drag gesture, and the drag gesture fails, so the list doesn’t slide. In general, Dismissible and the list have not been transmissible in the same direction. When the list is transmissible, Dismissible can be transmissible in the vertical direction.

startToEnd vertical

4. OnResize and resizeDuration

In the vertical list, when the slide disappears, the item below will have an animation that moves up. The resizeDuration will represent the duration of the animation, and the onResize will be called back during each frame of the animation’s execution.

The default time 2s

As you can see from the source code, the default duration for resizeDuration is 300 ms.

In deep scan, it can be seen that listens for animation editor _handleResizeProgressChanged execution. And that’s where onResize fires. The other curve for this animation is _kResizeTimeCurve.

void _handleResizeProgressChanged() {
  if(_resizeController! .isCompleted) { widget.onDismissed? .call(_dismissDirection); }else {
    widget.onResize?.call();
  }
}

const Curve _kResizeTimeCurve = Interval(0.4.1.0, curve: Curves.ease);
Copy the code

5. DismissThresholds and movementDuration

DismissThresholds indicates the threshold to dismiss. The type is Map

. This means that we can set the tolerance of different slide directions. The default is 0.4. MovementDuration represents the duration of the animation to move in the direction of the slide.
,>

const double _kDismissThreshold = 0.4;

final Map<DismissDirection, double> dismissThresholds;
Copy the code
The default effect Effect of this case

The following code has the same effect as the one on the right of the image above. When the startToEnd universe is set to 0.8, the removal event is harder to trigger than the default. MovementDuration is set to 3 s.

Widget _buildItems(BuildContext context, int index) {
  return Dismissible(
    key: ValueKey(data[index]),
    background: buildBackground(),
    secondaryBackground: buildSecondaryBackground(),
    onResize: _onResize,
    resizeDuration: const Duration(seconds: 2),
    dismissThresholds: {
      DismissDirection.startToEnd: 0.8,
      DismissDirection.endToStart: 0.2,
    },
    movementDuration: const Duration(seconds: 3),
    child: ItemBox(
      info: data[index],
    ),
    direction: DismissDirection.horizontal,
    onDismissed: (direction) => _onDismissed(direction, index),
    confirmDismiss: _confirmDismiss,
  );
}
Copy the code

6. CrossAxisEndOffset

In the figure below, crossAxisEndOffset is set to -2. During the slide process, the original entry will be offset on the cross axis (here is the vertical axis). The offset is the crossAxisEndOffset * component height. As the figure on the right shows, item 4 has moved up one item height by sliding to normal.

1 2

And then finally the dragStartBehavior and the behavior, this is a generic property that you should know very well.


What can be learned from Dismissible source code

The confirmDismiss and onDismissed call-throughs in the Dismissible module were a combined approach, and asynchronous call-throughs were enabled in development. Let’s take a look at the source code of implementation: confirmDismiss callback in _confirmStartResizeAnimation method calls,

At the end of the drag and drop, will first wait for _confirmStartResizeAnimation execution, and returns true, will perform _startResizeAnimation.

The other place is when the _moveController animator finishes executing, and if the animation finishes, similar logic will be executed.

Finally, the onDismissed callback is fired in _startResizeAnimation. This is how an asynchronous method can be used to control the firing of another callback.


And since then, we’ve been using the Dismissible module. Thanks for watching. See you tomorrow