Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Possible to copy iOS App Store transition using Flutter?

Is it possible to copy the transition effect of iOS App Store using Flutter?

I tried using Hero Animation by placing two tags into the root layout of both widgets, but animation looks janky or not what I expected. But good thing about this is I am able to do iOS swipe back as I'm using MaterialPageRoute.

Source

Hero(
   tag: 'heroTag_destinationScreen',
   transitionOnUserGestures: true,
   flightShuttleBuilder: (BuildContext flightContext,
     Animation<double> animation,
     HeroFlightDirection flightDirection,
     BuildContext fromHeroContext,
     BuildContext toHeroContext,) {
        final Hero toHero = toHeroContext.widget;
            return ScaleTransition(
              scale: animation,
              child: toHero,
            );
          },
          child: GestureDetector(
            onTap: () {
              Navigator.of(context).push(
                MaterialPageRoute<void>(
                  builder: (BuildContext context) {
                    return DestinationScreen()
                  },
                ),
              );
            },
            child: Card(
                ...someCardContent
            ),
      ),
)

Destination Screen

@override
Widget build(BuildContext context) {
    return Hero(
       tag: 'heroTag_destinationScreen',
       child: Scaffold(
           appBar: ...someAppBar
            body: ...someMainBodyContent
          ),
      )
    }

Then I have been looking around and there is a package created by Flutter team which can simulate this effect using container transform. I implemented it, works awesome but then I'm not able to do iOS swipe from left to go back and shrink the layout to card view.

https://pub.dev/packages/animations

Example App Store

like image 481
FadhliS Avatar asked Jan 01 '23 00:01

FadhliS


1 Answers

here is my solution.

https://imgur.com/2WYn6TX

(Sorry for my reputation, I can't post a image.)

I customize hero transition to remake App store transition as much as possible.

      child: Hero(
        tag: widget.product.id,
        child: Image.asset(widget.product.image, fit: BoxFit.cover),
        flightShuttleBuilder:
            (flightContext, animation, direction, fromcontext, toContext) {
          final Hero toHero = toContext.widget;
          // Change push and pop animation.
          return direction == HeroFlightDirection.push
              ? ScaleTransition(
                  scale: animation.drive(
                    Tween<double>(
                      begin: 0.75,
                      end: 1.02,
                    ).chain(
                      CurveTween(
                          curve: Interval(0.4, 1.0, curve: Curves.easeInOut)),
                    ),
                  ),
                  child: toHero.child,
                )
              : SizeTransition(
                  sizeFactor: animation,
                  child: toHero.child,
                );
        },
      ),

Next, I use ScaleTransition and onVerticalDragUpdate to control pop animation.

https://imgur.com/a/xEMYOPr

double _initPoint = 0;
double _pointerDistance = 0;
GestureDetector(
  onVerticalDragDown: (detail) {
    _initPoint = detail.globalPosition.dy;
  },
  onVerticalDragUpdate: (detail) {
    _pointerDistance = detail.globalPosition.dy - _initPoint;
    if (_pointerDistance >= 0 && _pointerDistance < 200) {
        // scroll up
        double _scaleValue = double.parse((_pointerDistance / 100).toStringAsFixed(2));
        if (_pointerDistance < 100) {
          _closeController.animateTo(_scaleValue,
          duration: Duration(milliseconds: 300),
          curve: Curves.linear);
        }
    } else if (_pointerDistance >= 260) {
      if (_pop) {
        _pop = false;
        _closeController.fling(velocity: 1).then((_) {
          setState(() {
          _heightController.reverse();
          });
          Timer(Duration(milliseconds: 100), () {
            Navigator.of(context).pop();
          });
        });
      }
    } else {
      // scroll down
    }
  },
  onVerticalDragEnd: (detail) {
    if (_pointerDistance >= 550) {
      if (_pop) {
        _closeController.fling(velocity: 1).then((_) {
          setState(() {
            _heightController.reverse();
          });
          Timer(Duration(milliseconds: 100), () {
            Navigator.of(context).pop();
          });
        });
      }
    } else {
    _closeController.fling(velocity: -1);
    }
  },
  child: Hero(
    tag: _product.id,
    child: Image.asset(
      _product.image,
      fit: BoxFit.cover,
      height: 300,
    ),
  ),
),

If use Hero as a animation, you need to customize the text section transition.

Here: https://imgur.com/a/gyD6tiZ

In my case, I control text section transition by Sizetransition.

// horizontal way and vertical way.
SizeTransition(
  axis: Axis.horizontal,
  sizeFactor: Tween<double>(begin: 0.5, end: 1).animate(
    CurvedAnimation(
        curve: Curves.easeInOut, parent: _widthController),
  ),
  child: SizeTransition(
    sizeFactor: Tween<double>(begin: 0, end: 1).animate(
      CurvedAnimation(
          curve: Curves.easeInOut, parent: _heightController),
    ),
    child: Container(
      padding: EdgeInsets.only(
          left: 20, right: 20, top: 50, bottom: 30),
      width: double.infinity,
      color: Colors.white,
      constraints: BoxConstraints(
        minHeight: 650,
      ),
      child: Column(
        // title and text
        children: <Widget>[
          Text('Title', style: TextStyle(fontSize: 18)),
          SizedBox(height: 30),
          Text(_text,
              style: TextStyle(
                fontSize: 15,
              )),
        ],
      ),
    ),
  ),
),

Although it isn't the same as App Store, i hope it is helpful for you.

Source code: https://github.com/HelloJunWei/app_store_transition

If you have any suggestion, feel free to feedback or create a pull request. :)

like image 111
Neil Avatar answered Jan 31 '23 00:01

Neil