视差效果 |颤振中的可滚动背景图像
parallax effect | scrollable background image in flutter
我正在尝试实现可滚动的背景图像(视差)。
就像在主屏幕启动器中一样。
一个例子:
在 Evie 启动器中:
this video
我试过使用文档中提到的 AnimatedBuilder
here 这样的。
我正在使用 ValueNotifier<double>
作为 AnimatedBuilder 小部件动画的侦听器。
完整代码是这样
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,
);
}
}
图片可以找到here
现在我有两个问题:
- 使用
fit: BoxFit.fitHeight
时的图像没有完全溢出。目前是 this
- 因为动画完成后该值将变为零,所以它会像这样捕捉:
this video
我尝试在 _notifier.value
变为零之前存储该值,并在它 returns 为零时使用它,但它导致了我在上面的视频中向您展示的那种奇怪的转变。
你有什么建议可以在 flutter 中制作类似可滚动壁纸的东西?
像这样
Design
这并不像我想象的那么微不足道。
TLDR; Github 阅读评论。
我使用了我提到的 ValueNotifier<double>
来控制滚动。
然后我使用 OverflowBox
代替 Transform.translate
及其 alignment
属性。这是根据渲染前的notifier.value
计算出来的。
并以全屏模式显示图像:
我用了AspectRatio
with a child DecoratedBox
,它的decoration
是一个BoxDecoration
,它的image
是一个ImageProvider
。
所有代码都可以在 github 上找到 here。 (阅读评论)
和this issue on github has slightly detailed info and a less complicated alternate implementation by Antonello Galipò
我正在尝试实现可滚动的背景图像(视差)。 就像在主屏幕启动器中一样。
一个例子: 在 Evie 启动器中: this video
我试过使用文档中提到的 AnimatedBuilder
here 这样的。
我正在使用 ValueNotifier<double>
作为 AnimatedBuilder 小部件动画的侦听器。
完整代码是这样
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,
);
}
}
图片可以找到here
现在我有两个问题:
- 使用
fit: BoxFit.fitHeight
时的图像没有完全溢出。目前是 this - 因为动画完成后该值将变为零,所以它会像这样捕捉: this video
我尝试在 _notifier.value
变为零之前存储该值,并在它 returns 为零时使用它,但它导致了我在上面的视频中向您展示的那种奇怪的转变。
你有什么建议可以在 flutter 中制作类似可滚动壁纸的东西?
像这样
Design
这并不像我想象的那么微不足道。
TLDR; Github 阅读评论。
我使用了我提到的 ValueNotifier<double>
来控制滚动。
然后我使用 OverflowBox
代替 Transform.translate
及其 alignment
属性。这是根据渲染前的notifier.value
计算出来的。
并以全屏模式显示图像:
我用了AspectRatio
with a child DecoratedBox
,它的decoration
是一个BoxDecoration
,它的image
是一个ImageProvider
。
所有代码都可以在 github 上找到 here。 (阅读评论)
和this issue on github has slightly detailed info and a less complicated alternate implementation by Antonello Galipò