Как развернуть карту на развод во флаттере? - PullRequest
0 голосов
/ 14 ноября 2018

Я хотел бы добиться поведения карты дизайна материала на кране. Когда я нажимаю на нее, она должна развернуться во весь экран и показать дополнительный контент / новую страницу. Как мне этого добиться?

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

Я попытался с помощью Navigator.of (context) .push () открыть новую страницу и поиграть с анимациями Hero, чтобы переместить фон карты на новый Scaffold, однако, похоже, что это не тот путь, поскольку новая страница не раскрывается с самой карты, или я не могу сделать это. Я пытаюсь добиться того же поведения, что и в материале .io, который я представил выше. Не могли бы вы как-нибудь направить меня?

Спасибо

1 Ответ

0 голосов
/ 15 ноября 2018

Некоторое время назад я пытался воспроизвести эту точную страницу / переход, и, хотя я не получал, чтобы она выглядела идеально, я все же довольно близко подошел. Имейте в виду, что это было сделано быстро и на самом деле не следует передовым методам или чему-либо еще.

Важной частью являются виджеты Героев, и особенно теги, которые сопровождают их - если они не совпадают, это не будет сделано.

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

РЕДАКТИРОВАТЬ: в ответ на комментарий я собираюсь написать объяснение того, как работает Hero (или, по крайней мере, как я думаю, что это работает = D).

По сути, когда начинается переход между страницами, базовый механизм, который выполняет переход (более или менее часть Навигатора), ищет любые «геройские» виджеты на текущей странице и новой странице. Если герой найден, его размер и положение рассчитываются для каждой из страниц.

Когда выполняется переход между страницами, герой с новой страницы перемещается в оверлей в том же месте, что и старый герой, а затем его размер и положение анимируются в соответствии с его окончательным размером и положением на новой странице. , (Обратите внимание, что вы можете изменить его, если хотите немного поработать - см. этот блог для получения дополнительной информации об этом).

Вот что ОП пытался достичь:

Когда вы нажимаете на Карту, цвет ее фона расширяется и становится цветом фона Эшафота с панелью приложения.

Самый простой способ сделать это - просто поместить саму эшафот в героя. Все остальное будет скрывать панель приложений во время перехода, так как во время перехода героя он находится в оверлее. Смотрите код ниже. Обратите внимание, что я добавил в класс, чтобы сделать переход более медленным, чтобы вы могли видеть, что происходит, поэтому, чтобы увидеть, как это происходит с нормальной скоростью, измените часть, в которой он возвращает SlowMaterialPageRoute обратно в 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);
}

Однако существуют ситуации, в которых может не быть оптимальным, чтобы весь каркас выполнял переход - возможно, он имеет много данных или предназначен для размещения в определенном объеме пространства. В этом случае, вариант сделать версию того, что вы хотите сделать героическим переходом, который по сути является «фальшивым» - то есть иметь стек с двумя слоями, один из которых является героем и имеет цвет фона, скаффолд и так далее. иначе вы хотите появиться во время перехода и еще один слой сверху, который полностью затеняет нижний слой (т.е. имеет фон с непрозрачностью 100%), который также имеет панель приложения и все, что вы хотите.

Возможно, есть лучшие способы сделать это, например, вы можете указать героя отдельно, используя метод, упомянутый в блоге, на который я ссылаюсь .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...