How I replicated the iOS sliding row animation in Flutter
🤔 What is it?
In the Slidable widget, the developer can choose how the slide actions animate through what I named SlidableDelegates. The SlidableDrawerDelegate is one of the built-in ones:
The animation I tried to replicate is the one you can found in the iOS Reminder app:
🎓 The animation in depth
We can describe this animation like this:
- The actions are stacked so that the element closest to the tile is the lowest in the stack.
- Initially all actions are placed outside, right next to the tile.
- The actions move, for the same duration, from their starting position to their final position.
😐 images speak louder than words 🎨 😁
We laid out how the animation should work, now let’s see how we can do it in Flutter.
🔧 How it works
Before showing you some code, let me explain how I designed this:
As we can see above, the SlidableDrawerDelegate inherits from SlidableStackDelegate which inherits from the SlidableDelegate.
When the Slidable builds, it calls the
buildActionsmethod of the
SlidableDelegate. It’s an abstract method, so the
buildActions method of the
SlidableStackDelegate is called:
The purpose of this class is to create the animation used to slide the tile to the desired direction and delegate the animation of the actions to the subclasses (through the
To do that we create a
Stack with two children: the first child represents the animation of the actions and the second child is the animation of the tile. So that the tile is always painted on top of the actions.
The animation of the tile is simply created with the SlideTransition widget for which we set an animation that starts from the zero offset and ends at the desired offset (the total length of the actions in the desired direction).
We can represent this animation like this (actions are purposely removed):
Now let’s see how we animate the actions in the case of a
We have seen earlier that the animation of the actions are delegated to the
buildStackActions of the
SlidableStackDelegate subclasses. So let’s see how this method is implemented for
It’s a little more complicated that the previous method, so let’s break down this one piece by piece.
🌴 Widget Tree
Let’s focus on how the actions are built by taking a look at the widget tree:
◼️ The first Positioned widget is used to constrain this component to take all the place in the
Stack built by the
◼️ The LayoutBuilder allows us to know the parent Stack extent. We use it then to compute the actual extent of an action.
◼️ The AnimatedBuilder builds its child whenever the actionsMoveAnimation changes (which is the animation used to slide the tile).
◼️ The Stack is then used to produce as many layers as actions. So, every child in the Stack is the widget of an action.
◼️ Finally each action has a Positioned widget as a parent in order to be placed at the desired position.
💡 Connecting the dots
Now that we saw the widget structure, let’s focus on the code:
◼️ Lines 9–10, we get the extent of an action:
getMaxExtent is just a utility method used to get either the width or the heigth, depending on the direction the widget can slide.
◼️ Lines 13–18, we compute the position animation for each action:
As we saw earlier, initially all actions are placed outside, right next to the tile. That means here that all actions start to the position
-actionExtent (Line 3 above).
The end position for each action is then determined by its index in the action list (Line 4 above). For secondary actions, the lowest the index, the closest the action is to the tile at the end (inverted for actions).
◼️ Lines 25–32, we create the action widget with the desired position:
As mentioned before, actions and secondary actions are laid out in a different order. The last action will be the closest to the tile, whereas it’s the first for secondary actions.
displayIndex is used for that.
createPositioned is a utility method which creates a Positioned widget depending on the direction the widget can slide and the type of actions shown (secondary or not).
Finally we use the
build method of the
actionDelegate to get the actual action.
In the below gif, the Archive action is the first declared in the action list.
We can see that it starts at the same position as the Share action but end up the furthest to the tile.
I explained here how I replicated the iOS sliding row animation in Flutter, through the Slidable widget and its SlidableDrawerDelegate. Now feel free to create your own SlidableDelegates 😄.
If you have any questions / suggestions, leave a comment below ✏️.