Смещение начала мозаичного изображения во Flutter

Я пытаюсь создать фон параллакса в приложении Flutter, и самый эффективный способ его создания - использовать Stackс изображением, заполняющим экран в качестве фона, а затем мой список вверху. Изображение выложено плиткойImageRepeatустановлен на оси Y. План состоит в том, чтобы сместить исходную точку плитки синхронно сScrollControllerЯ использую для своего списка. Затем я могу настроить исходную точку мозаичного изображения, чтобы создать эффект параллакса. Это должно быть очень просто. Вот код для контекста:

Stack(
          children: [
            SizedBox.expand(
              child: Image(
                image: AssetImage('assets/images/tiled_background_leaf.jpg'),
                repeat: ImageRepeat.repeatY,
              ),
            ),
            CustomScrollView(
              controller: _controller,
              slivers: [ ...

Моя проблема в том, что у изображения нет offset собственность, или originдолжность. Мне нужен совет, как это сделать проще всего. Я видел, что есть нестандартные рисовальщики, методы холста и т. Д., Но все они кажутся чрезвычайно сложными, когда в рамкахImage виджет, или, возможно, внутри другого виджета, который дал бы мне тот же эффект параллакса.

1 ответ

Спасибо @pskink за ответ (см. Комментарии выше).

Вот код для панели инструментов, которая имеет прокручиваемый список статей и мозаичное изображение с параллаксной прокруткой в ​​качестве фона...

class DashboardRoot extends StatefulWidget {
  DashboardRoot({Key key}) : super(key: key);

  @override
  _DashboardRootState createState() => _DashboardRootState();
}

class _DashboardRootState extends State<DashboardRoot> {
  int _currentIndex = 0;
  ScrollController _controller;

  double _offsetY = 0.0;

  _scrollListener() {
    setState(() {
      _offsetY = _controller.offset;
    });
  }

  @override
  void initState() {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      var state = Provider.of<ArticlesState>(context, listen: false);
      state.initArticleStream();
    });
    _controller = ScrollController();
    _controller.addListener(_scrollListener);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
        bottomNavigationBar: AppBottomNavigationBar(),
        body: Stack(
          children: [
            SizedBox.expand(
              child: Image(
                image: AssetImage('assets/images/tiled_background_leaf.jpg'),
                repeat: ImageRepeat.repeatY,
                alignment: FractionalOffset(0, (_offsetY / 1000) * -1),
              ),
            ),
            CustomScrollView(
              controller: _controller,
              slivers: [
                SliverAppBar(
                  elevation: 0.0,
                  floating: true,
                  expandedHeight: 120,
                  flexibleSpace: FlexibleSpaceBar(
                    title: Text(NavigationManager
                        .instance.menuItems[_currentIndex].title),
                  ),
                  actions: <Widget>[
                    IconButton(
                      icon: Icon(Icons.settings),
                      onPressed: () => {
                        locator<NavigationService>()
                            .navigateTo(SettingsNavigator.routeName)
                      },
                    ),
                    IconButton(
                      icon: Icon(Icons.menu),
                      onPressed: () => {RootScaffold.openDrawer(context)},
                    ),
                  ],
                ),
                Consumer<ArticlesState>(
                  builder: (context, state, child) {
                    final List<Article> list = state.articles;
                    if (list == null) {
                      return SliverToBoxAdapter(
                        child: Center(
                          child: CircularProgressIndicator(
                              backgroundColor: Colors.amber, strokeWidth: 1),
                        ),
                      );
                    } else if (list.length > 0) {
                      return SliverGrid(
                        gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
                          maxCrossAxisExtent: 200.0,
                          mainAxisSpacing: 10.0,
                          crossAxisSpacing: 10.0,
                          childAspectRatio: 1.0,
                        ),
                        delegate: SliverChildBuilderDelegate(
                          (BuildContext context, int index) {
                            Article article = list[index];
                            return ArticleCell(
                                article: article,
                                cellTapHandler: () {
                                  Navigator.pushNamed(
                                      context, ArticleDetail.routeName,
                                      arguments: new ArticleDetailArguments(
                                          article.docId, article.heading));
                                });
                          },
                          childCount: list.length,
                        ),
                      );
                    } else {
                      return Center(
                        child: Text("No Articles"),
                      );
                    }
                  },
                ),
              ],
            ),
          ],
        ));
  }
}

Обратите внимание на Stack имеет фоновое изображение внутри развернутого SizedBoxтак что он заполняет пространство экрана. Слой выше - этоCustomScrollView который имеет SliverGrid и прочее.

Важный момент - это Image:

child: Image(
                image: AssetImage('assets/images/tiled_background_leaf.jpg'),
                repeat: ImageRepeat.repeatY,
                alignment: FractionalOffset(0, (_offsetY / 1000) * -1),
              ),

а также собственность _offsetY который установлен ScrollController слушатель, когда пользователи прокручивают:

double _offsetY = 0.0;

  _scrollListener() {
    setState(() {
      _offsetY = _controller.offset;
    });
  }

В ImageСвойство alignment используется для установки выравнивания по верхнему краю, центру, левому краю и т. д., но оно также может быть произвольным смещением. ВFractionalOffset значение - это диапазон 0..1но установка большего числа выше или ниже нуля также абсолютно нормально. Поскольку изображение также выложено плиткой с использованиемImageRepeat.repeatY исходная точка мозаичного изображения перерисовывается с использованием выравнивания, и, изменяя число, вы можете создать красивый эффект прокрутки параллакса.

Заметить, что FractionalOffset(0, (_offsetY / 1000) * -1) имеет значение смещения, деленное на 1000 (это ваша скорость, и чем выше значение, тем медленнее параллакс фона (воспринимайте это как расстояние между двумя слоями). Умножение числа на -1 переключает между положительным и отрицательным число и изменяет направление параллакса.

Другие вопросы по тегам