视差效果 | Flutter 中可滚动的背景图片

3
我正在尝试实现可滚动的背景图像(视差效果),类似于主屏幕启动器。 例如在 Evie 启动器中: this video 我已经尝试使用文档中提到的 AnimatedBuilder,如此使用: here 我正在使用 ValueNotifier<double> 作为 AnimatedBuilder Widget 的动画监听器。
完整代码如下:
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'PageView Scrolling',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage>{
  ValueNotifier<double> _notifier;
  double _prevnotifier;

  double getOffset(){
    if (_notifier.value == 0 && _prevnotifier != null){
      return _prevnotifier;
    }
    return _notifier.value;
  }

  @override
  void dispose() {
    _notifier?.dispose();
    super.dispose();
  }

  @override
  void initState() {
    super.initState();
    _notifier = ValueNotifier<double>(0);
    _prevnotifier = _notifier.value;

    _notifier.addListener(
       (){
         print('object ${_notifier.value}');
           if (_notifier.value != 0)
             _prevnotifier = _notifier.value;
       }
    );
  }

  @override
  Widget build(BuildContext context) {
    print("Size is ${MediaQuery.of(context).size}");
    return Scaffold(
      body: Stack(
        children: <Widget>[
           AnimatedBuilder(
             animation: _notifier,
             builder: (context, _) {
               return Transform.translate(
                 offset: Offset(-getOffset() * 60, 0),
                 child: Image.network(
                   "https://w.wallhaven.cc/full/r2/wallhaven-r276qj.png",
                   height: MediaQuery.of(context).size.height,
                   fit: BoxFit.fitHeight
                 ),
               );
            },
          ),
          NotifyingPageView(
            notifier: _notifier,
          ),
        ],
      ),
    );
  }
}

class NotifyingPageView extends StatefulWidget {
  final ValueNotifier<double> notifier;

  const NotifyingPageView({Key key, this.notifier}) : super(key: key);

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

class _NotifyingPageViewState extends State<NotifyingPageView> {
  int _previousPage;
  PageController _pageController;

  void _onScroll() {
    // Consider the page changed when the end of the scroll is reached
    // Using onPageChanged callback from PageView causes the page to change when
    // the half of the next card hits the center of the viewport, which is not
    // what I want

    if (_pageController.page.toInt() == _pageController.page) {
      _previousPage = _pageController.page.toInt();
    }
    widget.notifier?.value = _pageController.page - _previousPage;
  }

  @override
  void initState() {
    _pageController = PageController(
       initialPage: 0,
       viewportFraction: 0.9,
     )..addListener(_onScroll);

     _previousPage = _pageController.initialPage;
     super.initState();
  }

  List<Widget> _pages = List.generate(
    10,
    (index) {
      return Container(
        height: 10,
        alignment: Alignment.center,
          color: Colors.transparent,
          child: Text(
            "Card number $index",
            style: TextStyle(
              color: Colors.teal,
              fontWeight: FontWeight.bold,
              fontSize: 25,
            ),
          ),
        );
      },
  );

  @override
  Widget build(BuildContext context) {
    return PageView(
      children: _pages,
      controller: _pageController,
    );
  }
}

这张图片可以在这里找到。

现在我有两个问题:

  • 使用fit:BoxFit.fitHeight时,图像没有完全溢出。它目前是这样的
  • 由于动画完成后该值将变为零,因此它会像这样弹回: 这个视频

我尝试在_notifier.value变为零之前存储该值,并在其返回零时使用它,但结果是那个奇怪的转换,如上面的视频所示。

你建议如何在Flutter中制作类似可滚动壁纸的东西?

类似于这样的东西

设计


1
Phani,我已经回答了你提出的问题!https://github.com/magicleon94/pagaview_scrolling/issues/1 - magicleon94
1个回答

2

我以为这不是一件那么简单的事情。

TLDR; Github可以查看评论。

我使用了ValueNotifier<double>, 像我之前提到的那样来控制滚动条。

然后,我使用了OverflowBox ,并设置了它的alignment属性。其计算方式基于渲染前的notifier.value

要在全屏模式下显示图像:

我使用了AspectRatio,并添加了一个子节点DecoratedBox,其decoration是一个BoxDecoration,其中image是一个ImageProvider

所有的代码都可以在此处在github上找到。(请阅读评论)

此问题在github上有稍微详细的信息,以及Antonello Galipò提供的不那么复杂的实现方案。


我知道仅提供链接的答案是不被鼓励的,但是这段代码太大了。不能确定我的Github账户十年后是否还存在。 - Phani Rithvij
很棒的解决方案,它很有帮助。实际上,我用水平列表视图进行了更改。 - shuidi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接