英文:
Scrollable view over background widget in Flutter
问题
I want to make something similar to the Google weather app where there is a background image displayed underneath scrollable content, but the content starts off the screen and only comes into view when the user scrolls.
My first idea was to use a Stack
with a ListView
as a child. This works in that the content is scrollable and the image is in the background, but the content does not start off the bottom of the screen.
If I add a SizedBox(height: 500)
to the top of the ListView
, I can scroll the content into view, but obviously when the SizedBox
reaches the top, the scrolling stops, and if I resize the app, the SizedBox
needs to be resized.
I'm using a Lottie animation instead of an image for the background, but the concept is the same.
Animation from Lottie Files: https://lottiefiles.com/139998-day-and-night-landscape
Lottie Flutter package: https://pub.dev/packages/lottie
Can anyone recommend a good way to achieve the desired effect?
英文:
I want to make something similar to the Google weather app where there is a background image displayed underneath scrollable content, but the content starts off the screen and only comes into view when the user scrolls.
My first idea was to use a Stack
with a ListView
as a child. This works in that the content is scrollable and the image is in the background, but the content does not start off the bottom of the screen.
import 'package:lottie/lottie.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Lottie.asset('assets/landscape.zip'),
),
Column(
children: [
const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
"Good Morning!",
style: TextStyle(fontWeight: FontWeight.bold),
),
),
Expanded(
child: ListView(
padding: const EdgeInsets.all(8.0),
children: [
const SizedBox(height: 500),
Container(
color: Colors.grey[300],
height: 200,
margin: const EdgeInsets.only(bottom: 10),
),
Container(
color: Colors.grey[300],
height: 200,
),
],
),
),
],
)
],
),
));
}
}
If I add a SizedBox(height: 500)
to the top of the ListView
, I can scroll the content into view, but obviously when the SizedBox
reaches the top, the scrolling stops, and if I resize the app the SizedBox
needs to be resized.
I'm using a Lottie animation instead of an image for the background, but the concept is the same.
Animation from Lottie Files: https://lottiefiles.com/139998-day-and-night-landscape
Lottie Flutter package: https://pub.dev/packages/lottie
Can anyone recommend a good way to achieve the desired effect?
答案1
得分: 1
我尝试通过使用Stack
来呈现背景和前景页面的行为。
前景页面位于垂直PageView
内,以重现用户停留在整个页面时的步进效果。
使用PageView
的ScrollController
可以检测到第一页(用于显示背景的空白页)和第二页之间的过渡。
如果将此绑定到执行颜色过渡的额外图层,可以使用Opacity
小部件获得很酷的效果。
可能需要一些调整才能完美实现您想要的设计,但我认为它相当不错。
PS:视频将在两天后过期 https://streamable.com/obizkq
class TestAppHomePage extends StatefulWidget {
@override
TestAppHomePageState createState() => TestAppHomePageState();
}
class TestAppHomePageState extends State<TestAppHomePage> {
final PageController controller = PageController();
double currentPageValue = 0.0;
@override
void initState() {
controller.addListener(() {
if (controller.page != null && controller.page! <= 1.0) {
setState(() {
currentPageValue = controller.page!;
});
}
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Image.asset(
'assets/background_image.png',
fit: BoxFit.cover,
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
),
Opacity(
opacity: currentPageValue,
child: Container(
color: Colors.green,
),
),
PageView(
scrollDirection: Axis.vertical,
controller: controller,
children: const <Widget>[
CustomPage(
title: '',
backgroundColor: Colors.transparent,
),
Opacity(
opacity: currentPageValue,
child: CustomPage(
title: 'Page 2',
backgroundColor: Colors.transparent,
),
),
CustomPage(
title: 'Page 3',
backgroundColor: Colors.blue,
),
],
),
],
));
}
}
class CustomPage extends StatelessWidget {
final String title;
final Color backgroundColor;
const CustomPage(
{super.key, required this.title, required this.backgroundColor});
@override
Widget build(BuildContext context) {
return Container(
color: backgroundColor,
child: Center(
child: Text(title),
),
);
}
}
英文:
I tried to reproduce the behavior you are describing by using a Stack
to present the background and the front pages.
The front pages are inside a vertical PageView
in order to reproduce the stepper effect as the user stops on the entire page.
Using the ScrollController
of the PageView
you can detect the transition between the first page (which is empty to display the background) and the second page.
If you bind this to an additional layer that performs the color transition with an Opacity
widget you have something pretty cool.
It may require a few adjustments to perfectly achieve the design you want but it's pretty neat in my opinion.
PS: Video expires in two days https://streamable.com/obizkq
class TestAppHomePage extends StatefulWidget {
@override
TestAppHomePageState createState() => TestAppHomePageState();
}
class TestAppHomePageState extends State<TestAppHomePage> {
final PageController controller = PageController();
double currentPageValue = 0.0;
@override
void initState() {
controller.addListener(() {
if (controller.page != null && controller.page! <= 1.0) {
setState(() {
currentPageValue = controller.page!;
});
}
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Image.asset(
'assets/background_image.png',
fit: BoxFit.cover,
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
),
Opacity(
opacity: currentPageValue,
child: Container(
color: Colors.green,
),
),
PageView(
scrollDirection: Axis.vertical,
controller: controller,
children: const <Widget>[
CustomPage(
title: '',
backgroundColor: Colors.transparent,
),
Opacity(
opacity: currentPageValue,
child: CustomPage(
title: 'Page 2',
backgroundColor: Colors.transparent,
),
),
CustomPage(
title: 'Page 3',
backgroundColor: Colors.blue,
),
],
),
],
));
}
}
class CustomPage extends StatelessWidget {
final String title;
final Color backgroundColor;
const CustomPage(
{super.key, required this.title, required this.backgroundColor});
@override
Widget build(BuildContext context) {
return Container(
color: backgroundColor,
child: Center(
child: Text(title),
),
);
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论