web-dev-qa-db-fra.com

Comment développer une carte au robinet en flutter?

Je voudrais obtenir le comportement de la carte de conception matérielle au robinet. Quand je le touche, il devrait s’agir d’être en plein écran et révéler du contenu supplémentaire/une nouvelle page. Comment puis-je y arriver?

https://material.io/design/components/cards.html#behavior

J'ai essayé avec Navigator.of (context) .Push () de révéler une nouvelle page et de jouer avec des animations de héros pour déplacer l'arrière-plan de la carte vers un nouvel échafaudage. Toutefois, il semble que ce ne soit pas la voie à suivre car une nouvelle page ne révèle pas la carte lui-même, ou je ne peux pas le faire. J'essaie d'obtenir le même comportement que dans le material.io que j'ai présenté ci-dessus. Pourriez-vous me guider d'une manière ou d'une autre?

Je vous remercie

4
Matt

Il y a quelque temps, j'ai essayé de reproduire cette page/transition exacte et, bien que je ne l'aie pas parfaitement ressemblée, je suis restée assez proche. Gardez à l'esprit que cela a été mis en place rapidement et ne suit pas vraiment les meilleures pratiques ou quoi que ce soit.

La partie importante est les widgets Hero, et en particulier les balises qui les accompagnent - s'ils ne correspondent pas, ils ne le feront pas.

import 'package:flutter/material.Dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          backgroundColor: Colors.deepPurple,
        ),
        body: ListView.builder(
          itemBuilder: (context, index) {
            return TileItem(num: index);
          },
        ),
      ),
    );
  }
}

class TileItem extends StatelessWidget {
  final int num;

  const TileItem({Key key, this.num}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Hero(
      tag: "card$num",
      child: Card(
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.all(
            Radius.circular(8.0),
          ),
        ),
        clipBehavior: Clip.antiAliasWithSaveLayer,
        child: Stack(
          children: <Widget>[
            Column(
              children: <Widget>[
                AspectRatio(
                  aspectRatio: 485.0 / 384.0,
                  child: Image.network("https://picsum.photos/485/384?image=$num"),
                ),
                Material(
                  child: ListTile(
                    title: Text("Item $num"),
                    subtitle: Text("This is item #$num"),
                  ),
                )
              ],
            ),
            Positioned(
              left: 0.0,
              top: 0.0,
              bottom: 0.0,
              right: 0.0,
              child: Material(
                type: MaterialType.transparency,
                child: InkWell(
                  onTap: () async {
                    await Future.delayed(Duration(milliseconds: 200));
                    Navigator.Push(
                      context,
                      MaterialPageRoute(
                        builder: (context) {
                          return new PageItem(num: num);
                        },
                        fullscreenDialog: true,
                      ),
                    );
                  },
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class PageItem extends StatelessWidget {
  final int num;

  const PageItem({Key key, this.num}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    AppBar appBar = new AppBar(
      primary: false,
      leading: IconTheme(data: IconThemeData(color: Colors.white), child: CloseButton()),
      flexibleSpace: Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
            colors: [
              Colors.black.withOpacity(0.4),
              Colors.black.withOpacity(0.1),
            ],
          ),
        ),
      ),
      backgroundColor: Colors.transparent,
    );
    final MediaQueryData mediaQuery = MediaQuery.of(context);

    return Stack(children: <Widget>[
      Hero(
        tag: "card$num",
        child: Material(
          child: Column(
            children: <Widget>[
              AspectRatio(
                aspectRatio: 485.0 / 384.0,
                child: Image.network("https://picsum.photos/485/384?image=$num"),
              ),
              Material(
                child: ListTile(
                  title: Text("Item $num"),
                  subtitle: Text("This is item #$num"),
                ),
              ),
              Expanded(
                child: Center(child: Text("Some more content goes here!")),
              )
            ],
          ),
        ),
      ),
      Column(
        children: <Widget>[
          Container(
            height: mediaQuery.padding.top,
          ),
          ConstrainedBox(
            constraints: BoxConstraints(maxHeight: appBar.preferredSize.height),
            child: appBar,
          )
        ],
      ),
    ]);
  }
}

EDIT: en réponse à un commentaire, je vais écrire une explication sur le fonctionnement de Hero (ou au moins comment je pense que cela fonctionne = D).

Fondamentalement, lorsqu'une transition entre pages est lancée, le mécanisme sous-jacent qui effectue la transition (plus ou moins dans le navigateur) recherche les widgets "héros" de la page actuelle et de la nouvelle page. Si un héros est trouvé, sa taille et sa position sont calculées pour chacune des pages.

Au fur et à mesure que la transition entre les pages est effectuée, le héros de la nouvelle page est déplacé vers une superposition au même endroit que l'ancien héros, puis sa taille et sa position sont animées vers sa taille finale et sa position dans la nouvelle page. (Notez que vous pouvez changer si vous voulez avec un peu de travail - voir ce blog pour plus d'informations à ce sujet).

Voici ce que le PO essayait de réaliser:

Lorsque vous appuyez sur une carte, sa couleur d'arrière-plan s'agrandit et devient la couleur d'arrière-plan d'un échafaudage avec une barre d'applications.

La façon la plus simple de le faire est simplement de placer l'échafaud lui-même dans le héros. Tout le reste obscurcira la barre d’application pendant la transition, car lorsqu’elle effectue la transition du héros, elle est superposée. Voir le code ci-dessous. Notez que j'ai ajouté à une classe pour ralentir la transition afin que vous puissiez voir ce qui se passe, ainsi, à vitesse normale, changez la partie où elle renvoie un SlowMaterialPageRoute à un MaterialPageRoute.

That looks something like this:
import 'Dart:math';

import 'package:flutter/cupertino.Dart';
import 'package:flutter/material.Dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          backgroundColor: Colors.deepPurple,
        ),
        body: ListView.builder(
          itemBuilder: (context, index) {
            return TileItem(num: index);
          },
        ),
      ),
    );
  }
}

Color colorFromNum(int num) {
  var random = Random(num);
  var r = random.nextInt(256);
  var g = random.nextInt(256);
  var b = random.nextInt(256);
  return Color.fromARGB(255, r, g, b);
}

class TileItem extends StatelessWidget {
  final int num;

  const TileItem({Key key, this.num}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Hero(
      tag: "card$num",
      child: Card(
        color: colorFromNum(num),
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.all(
            Radius.circular(8.0),
          ),
        ),
        clipBehavior: Clip.antiAliasWithSaveLayer,
        child: Stack(
          children: <Widget>[
            Column(
              children: <Widget>[
                AspectRatio(
                  aspectRatio: 485.0 / 384.0,
                  child: Image.network("https://picsum.photos/485/384?image=$num"),
                ),
                Material(
                  type: MaterialType.transparency,
                  child: ListTile(
                    title: Text("Item $num"),
                    subtitle: Text("This is item #$num"),
                  ),
                )
              ],
            ),
            Positioned(
              left: 0.0,
              top: 0.0,
              bottom: 0.0,
              right: 0.0,
              child: Material(
                type: MaterialType.transparency,
                child: InkWell(
                  onTap: () async {
                    await Future.delayed(Duration(milliseconds: 200));
                    Navigator.Push(
                      context,
                      SlowMaterialPageRoute(
                        builder: (context) {
                          return new PageItem(num: num);
                        },
                        fullscreenDialog: true,
                      ),
                    );
                  },
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class PageItem extends StatelessWidget {
  final int num;

  const PageItem({Key key, this.num}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Hero(
      tag: "card$num",
      child: Scaffold(
        backgroundColor: colorFromNum(num),
        appBar: AppBar(
          backgroundColor: Colors.white.withOpacity(0.2),
        ),
      ),
    );
  }
}

class SlowMaterialPageRoute<T> extends MaterialPageRoute<T> {
  SlowMaterialPageRoute({
    WidgetBuilder builder,
    RouteSettings settings,
    bool maintainState = true,
    bool fullscreenDialog = false,
  }) : super(builder: builder, settings: settings, fullscreenDialog: fullscreenDialog);

  @override
  Duration get transitionDuration => const Duration(seconds: 3);
}

Cependant, il existe des situations dans lesquelles il peut ne pas être optimal de laisser l'ensemble de l'échafaudage effectuer la transition - peut-être qu'il contient beaucoup de données ou est-il conçu pour s'adapter à une quantité d'espace spécifique. Dans ce cas, une option pour créer une version de ce que vous voulez faire de la transition de héros qui est essentiellement un "faux" - c'est-à-dire avoir une pile avec deux couches, une qui est le héros et a une couleur de fond, un échafaudage, etc. sinon, vous souhaitez afficher au cours de la transition et un autre calque supérieur masquant complètement le calque inférieur (avec un arrière-plan avec une opacité de 100%) qui comporte également une barre d'applications et tout ce que vous voulez.

Il y a probablement de meilleures façons de le faire que cela - par exemple, vous pouvez spécifier le héros séparément en utilisant la méthode mentionnée dans le blog auquel j'ai lié .

3
rmtmckenzie

ajoutez elevation:0 dans AppBar pour un résultat parfait!

0
Giorgio Modoni