The App Store is apple’s benchmark for design.

Let’s briefly implement one of the features on the App Store’s front page.

Look at the picture first:

As you can see, there are two points to note here:

  1. The card scales when clicked, and bounces back when released or swiped.
  2. Elements are shared when jumping to a new page.

Results:

Signal processing

There are two levels of gesture events in Flutter.

The first layer has raw pointer events, which describe the position and movement of Pointers (for example, touch, mouse, and stylus) on the screen.

The second layer has gestures that describe semantic actions consisting of one or more pointer movements.

Simple gesture processing, which we use the GestureDetector packaged with Flutter, is sufficient.

Here we use the GestureDetector for image scaling.

Let’s take a look at what GestureDetector offers us:

  • OnTapDown: press
  • OnTap: Tap action
  • OnTapUp: lift
  • OnTapCancel: Triggers onTapDown but does not complete an onTap action
  • OnDoubleTap: double-click
  • OnLongPress: long press
  • OnScaleStart, onScaleUpdate, onScaleEnd: Scale
  • onVerticalDragDown, onVerticalDragStart, onVerticalDragUpdate, onVerticalDragEnd, onVerticalDragCancel, OnVerticalDragUpdate: Move in vertical direction
  • onHorizontalDragDown, onHorizontalDragStart, onHorizontalDragUpdate, onHorizontalDragEnd, onHorizontalDragCancel, OnHorizontalDragUpdate: Move in the horizontal direction
  • OnPanDown, onPanStart, onPanUpdate, onPanEnd, onPanCancel: drag (touch the screen, move on the screen)

Now that we know these methods, we can analyze which ones are suitable for us to do this effect:

We can see that the card starts to zoom when our fingers touch it and bounces back when we start to move or lift it.

According to the above GestureDetector method, we can see that onPanDown and onPanCancel seem to be very suitable for our needs.

So let’s try it:

Now that we have a way to listen for gestures, let’s write an animation.

How do I scale Card, the Flutter has a Widget called ScaleTransition.

As usual point open source to read the comments:

/// Animates the scale of a transformed widget.
Copy the code

The component that animates scale.

OnPanDown, onPanCancel:

Widget createItemView(int index) {
  var game = _games[index]; // Get data
  // Define the animation controller
  var _animationController = AnimationController(
    vsync: this,
    duration: Duration(milliseconds: 200));// Define the animation
  var _animation =
    Tween<double>(begin: 1, end: 0.98).animate(_animationController);
  return GestureDetector(
    onPanDown: (details) {
      print('onPanDown');
      _animationController.forward(); // Play animation when clicked
    },
    onPanCancel: () {
      print('onPanCancel');
      _animationController.reverse(); // Bounce back animation while cancel
    },

    child: Container(
      height: 450,
      margin: EdgeInsets.symmetric(horizontal: 30, vertical: 10),
      child: ScaleTransition(
        scale: _animation, // Define the animation
        child: Stack( // The rounded image is the background with the text above
          children: <Widget>[
            Positioned.fill(
              child: ClipRRect(
                borderRadius: BorderRadius.all(Radius.circular(15)),
                child: Image.asset(
                  game.imageUrl,
                  fit: BoxFit.cover,
                ),
              ),
            ),
            
            Padding(
              padding: const EdgeInsets.all(18.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                  Text(
                    game.headText,
                    style: TextStyle(
                      fontSize: 16,
                      color: Colors.grey,
                    ),
                  ),
                  
                  Expanded(
                    child: Text(
                      game.title,
                      style: TextStyle(
                        fontSize: 30,
                        color: Colors.white,
                      ),
                    ),
                  ),
                  
                  Text(
                    game.footerText,
                    style: TextStyle(
                      fontSize: 16,
                      color: Colors.grey,
                    ),
                  ),
                ],
              ),
            )
          ],
        ),
      )),
  );
}
Copy the code

This completes the animation we just showed above.

There is one caveat here:

Each item in the ListView must have an animation.

Otherwise, if all items share an animation, click on one of them and all items will be animated.

Hero animation

Now that we’re done with the zoom effect, it’s time to jump.

In Android, element sharing has been available since version 5.0 to achieve this effect.

In Flutter we can use Hero to achieve this effect.

Open the official website to see the introduction:

A widget that marks its child as being a candidate for hero animations.

When a PageRoute is pushed or popped with the Navigator, the entire screen's content is replaced. An old route disappears and a new route appears. If there's a common visual feature on both routes then it can be helpful for orienting the user for the feature to physically move from one page to the other during the routes' transition. Such an animation is called a hero animation. The hero widgets "fly" in the Navigator's overlay during the transition and while they're in-flight they're, by default, not shown in their original locations in the old and new routes.

To label a widget as such a feature, wrap it in a Hero widget. When navigation happens, the Hero widgets on each route are identified by the HeroController. For each pair of Hero widgets that have the same tag, a hero animation is triggered.

If a Hero is already in flight when navigation occurs, its flight animation will be redirected to its new destination. The widget shown in-flight during the transition is, by default, the destination route's Hero's child.

For a Hero animation to trigger, the Hero has to exist on the very first frame of the new page's animation. Routes must not contain more than one Hero for each tag.Copy the code

In a nutshell:

Hero animation is a shared Widget that can switch between the old route and the new route during route switching. Because the location and appearance of the shared Widget on the new route page may be different, it will gradually transition during route switching, thus generating a Hero animation. To trigger the Hero animation, the Hero must exist in the first frame of the new page animation. And there can only be one Hero tag in a route.Copy the code

All that said, how do you use it?

// Page 1
Hero(
  tag: "avatar".// The Hero tag must be the same for both routing pages
  child: ClipOval(
    child: Image.asset("images/avatar.png",
                       width: 50.0,),),),// Page 2
Center(
  child: Hero(
    tag: "avatar".// The Hero tag must be the same for both routing pages
    child: Image.asset("images/avatar.png"),),)Copy the code

As you can see, just add Hero and tag to the widget you want to share.

Give it a try:

child: Container(
  height: 450,
  margin: EdgeInsets.symmetric(horizontal: 30, vertical: 10),
  child: ScaleTransition(
    scale: _animation,
    child: Hero(
      tag: 'hero',
      child: Stack(
        children: <Widget>[
          Positioned.fill(
            child: ClipRRect(
              borderRadius: BorderRadius.all(Radius.circular(15)),
              child: Image.asset(
                game.imageUrl,
                fit: BoxFit.cover,
              ),
            ),
          ),
   
Copy the code

Run a look:

What the hell is a straight black screen?

Error message:

There are multiple heroes that share the same tag within a subtree. Multiple Hero widgets use the same tagCopy the code

Let’s change it:

child: Container(
  height: 450,
  margin: EdgeInsets.symmetric(horizontal: 30, vertical: 10),
  child: ScaleTransition(
    scale: _animation,
    child: Hero(
      tag: 'hero${game.title}',
      child: Stack(
        children: <Widget>[
          Positioned.fill(
            child: ClipRRect(
              borderRadius: BorderRadius.all(Radius.circular(15)),
              child: Image.asset(
                game.imageUrl,
                fit: BoxFit.cover,
              ),
            ),
          ),
 / /... The code is omitted
Copy the code

Let’s populate the tag with data from the ListView so that it doesn’t repeat itself:

What the hell is this jump when there are two underscores under the text?

The reason for this is that the source Hero is added to the superposition layer of Flutter and there is no Theme in the superposition layer.

There is no Scaffold in the stack so the underline occurs.

The solutions are as follows:

TextStyle: TextDecoration. None,

Now there’s no problem at all:

conclusion

We do have some problems with Flutter when we first learn to Flutter.

Don’t be upset. Open the source code, or go to the Flutter website to find this class. Read the comments and demo.

The code has been uploaded to GitHub: github.com/wanglu1209/…