web-dev-qa-db-de.com

Wie kann man eine Karte beim Flattertippen erweitern?

Ich möchte das Verhalten der Materialdesign-Karte im Griff erreichen. Wenn ich darauf tippe, sollte es den Vollbildmodus erweitern und zusätzlichen Inhalt/neue Seite anzeigen. Wie erreiche ich das?

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

Ich habe mit Navigator.of (context) .Push () versucht, eine neue Seite anzuzeigen und mit Hero-Animationen zu spielen, um den Kartenhintergrund auf ein neues Gerüst zu verschieben. Es scheint jedoch nicht der Weg zu gehen, da eine neue Seite nicht von der Karte angezeigt wird selbst, oder ich kann es nicht schaffen. Ich versuche, dasselbe Verhalten wie in dem oben genannten material.io zu erreichen. Würdest du mich bitte irgendwie führen?

Vielen Dank

4
Matt

Vor einiger Zeit habe ich versucht, die exakte Seite/den Übergang zu replizieren, und obwohl ich es nicht perfekt aussehen konnte, kam ich mir ziemlich nahe. Denken Sie daran, dass dies schnell zusammengestellt wurde und nicht den bewährten Methoden oder irgendetwas befolgt.

Der wichtige Teil sind die Hero-Widgets und vor allem die Tags, die mit ihnen verbunden sind. Wenn sie nicht übereinstimmen, werden sie es nicht tun.

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,
          )
        ],
      ),
    ]);
  }
}

BEARBEITEN: Als Antwort auf einen Kommentar schreibe ich eine Erklärung, wie Hero funktioniert (oder zumindest, wie ich denke, dass es funktioniert = D).

Wenn ein Übergang zwischen Seiten gestartet wird, sucht der zugrunde liegende Mechanismus, der den Übergang ausführt (mehr oder weniger ein Teil des Navigators), nach Helden-Widgets auf der aktuellen Seite und der neuen Seite. Wenn ein Held gefunden wird, werden seine Größe und Position für jede Seite berechnet.

Wenn der Übergang zwischen den Seiten ausgeführt wird, wird der Held von der neuen Seite zu einer Überlagerung an derselben Stelle wie der alte Held verschoben, und dann werden Größe und Position des Helden in Richtung der endgültigen Größe und Position auf der neuen Seite animiert. (Beachten Sie, dass Sie dies mit ein bisschen Arbeit ändern können - siehe dieses Blog für weitere Informationen dazu).

Dies wollte das OP erreichen:

Wenn Sie auf eine Karte tippen, wird deren Hintergrundfarbe erweitert und wird zu einer Hintergrundfarbe eines Gerüsts mit einer Appbar.

Am einfachsten ist es, das Gerüst selbst in den Helden zu legen. Alles andere wird die AppBar während des Übergangs verdecken, da sie während des Heldenübergangs in einer Überlagerung angezeigt wird. Siehe den Code unten. Beachten Sie, dass ich eine Klasse hinzugefügt habe, um den Übergang langsamer zu gestalten, sodass Sie sehen können, was vor sich geht. Wenn Sie also sehen, dass bei normaler Geschwindigkeit der Teil geändert wird, in dem er eine SlowMaterialPageRoute zu einer MaterialPageRoute zurückschiebt.

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);
}

Es gibt jedoch Situationen, in denen es möglicherweise nicht optimal ist, das gesamte Gerüst den Übergang durchführen zu lassen. Möglicherweise enthält es viele Daten oder ist so konzipiert, dass es in einen bestimmten Raum passt. In diesem Fall eine Option, um eine Version des Heldenübergangs zu erstellen, bei dem es sich im Wesentlichen um eine Fälschung handelt, dh um einen Stapel mit zwei Schichten, von denen eine der Held ist und eine Hintergrundfarbe, ein Gerüst und was auch immer hat Andernfalls möchten Sie während des Übergangs auftauchen und oben eine weitere Ebene, die die unterste Ebene vollständig verdeckt (dh einen Hintergrund mit einer Deckkraft von 100% aufweist), die auch eine App-Leiste und alles, was Sie möchten, hat.

Es gibt wahrscheinlich bessere Möglichkeiten, dies zu tun. Beispielsweise können Sie den Helden mithilfe der in des Blogs, auf das ich verlinkt habe, angegeben.

3
rmtmckenzie

fügen Sie elevation:0 in AppBar hinzu, um ein perfektes Ergebnis zu erhalten!

0
Giorgio Modoni