[英]How to scroll underlying widget in Flutter?
I have a simple app with two screens.我有一个带有两个屏幕的简单应用程序。 The first screen is a scrollable
ListView
and the second screen is basically empty and transparent.第一个屏幕是一个可滚动的
ListView
,第二个屏幕基本上是空的和透明的。 If I pushed the second screen with Navigator.push()
on top of the first screen I'd like to be able to scroll the underlying first screen.如果我在第一个屏幕上使用
Navigator.push()
推动第二个屏幕,我希望能够滚动底层的第一个屏幕。
Here is my code:这是我的代码:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView.builder(
itemBuilder: (context, index) {
return Text("$index");
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
PageRouteBuilder<void>(
opaque: false, // push route with transparency
pageBuilder: (context, animation, secondaryAnimation) => Foo(),
),
);
},
child: Icon(Icons.add),
),
);
}
}
class Foo extends StatelessWidget {
const Foo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white.withOpacity(0.5),
appBar: AppBar(
title: Text("I'm on top"),
),
);
}
}
How can scroll the list in the backgound while the second screen is in the foreground?当第二个屏幕在前台时,如何在后台滚动列表?
Although this is not a solution with a second screen, it creates a similar effect using the Stack
and IgnorePointer
widgets:虽然这不是第二个屏幕的解决方案,但它使用
Stack
和IgnorePointer
小部件创建了类似的效果:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _applyOverlay = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: _applyOverlay
? IconButton(
icon: Icon(
Icons.arrow_back_ios_sharp,
),
onPressed: () => setState(
() => _applyOverlay = false,
),
)
: null,
title: Text(_applyOverlay ? 'Overlay active' : widget.title),
),
body: Stack(
children: [
ListView.builder(
itemBuilder: (context, index) {
return Text("$index");
},
),
if (_applyOverlay)
// Wrap container (or your custom widget) with IgnorePointer to ignore any user input
IgnorePointer(
child: Container(
height: double.infinity,
width: double.infinity,
color: Colors.white.withOpacity(0.5),
),
),
],
),
floatingActionButton: _applyOverlay
? null
: FloatingActionButton(
onPressed: () {
setState(() => _applyOverlay = true);
},
child: Icon(Icons.add),
),
);
}
}
I found a solution that works even with two screens.我找到了一个即使有两个屏幕也能工作的解决方案。 The idea is to have two
ScrollController
s each in one screen and add a listener the ScrollController
in the overlay that triggers the ScrollController
of the underlying widget.想法是在一个屏幕中各有两个
ScrollController
并在覆盖层中添加一个监听器ScrollController
来触发底层小部件的ScrollController
。
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
const INITIAL_OFFSET = 5000.0;
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final ScrollController controller = ScrollController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView.builder(
controller: controller,
itemBuilder: (context, index) {
return Text("$index");
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
PageRouteBuilder<void>(
opaque: false, // push route with transparency
pageBuilder: (context, animation, secondaryAnimation) => Foo(
controller: controller,
),
),
);
},
child: Icon(Icons.add),
),
);
}
}
class Foo extends StatefulWidget {
final ScrollController controller;
const Foo({required this.controller, Key? key}) : super(key: key);
@override
State<Foo> createState() => _FooState();
}
class _FooState extends State<Foo> {
final ScrollController controller = ScrollController(
initialScrollOffset: INITIAL_OFFSET,
);
@override
void initState(){
super.initState();
controller.addListener(() {
widget.controller.animateTo(
controller.offset - INITIAL_OFFSET,
duration: const Duration(milliseconds: 1),
curve: Curves.linear,
);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white.withOpacity(0.5),
appBar: AppBar(
title: Text("I'm on top"),
),
body: SingleChildScrollView(
controller: controller,
child: Container(
height: 2 * INITIAL_OFFSET,
color: Colors.white.withOpacity(0.5),
),
),
);
}
}
There are still a few problems with this workaround:这种解决方法仍然存在一些问题:
I finally found a satisfying answer that does not contain any kind of dirty workarounds.我终于找到了一个令人满意的答案,其中不包含任何肮脏的解决方法。 I use a
Listener
in the second screen to detect OnPointerMoveEvents
which are basically scroll events.我在第二个屏幕中使用
Listener
来检测基本上是滚动事件的OnPointerMoveEvents
。
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final ScrollController controller = ScrollController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView.builder(
controller: controller,
itemBuilder: (context, index) {
return Text("$index");
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
PageRouteBuilder<void>(
opaque: false, // push route with transparency
pageBuilder: (context, animation, secondaryAnimation) => Foo(
controller: controller,
),
),
);
},
child: Icon(Icons.add),
),
);
}
}
class Foo extends StatefulWidget {
final ScrollController controller;
const Foo({required this.controller, Key? key}) : super(key: key);
@override
State<Foo> createState() => _FooState();
}
class _FooState extends State<Foo> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white.withOpacity(0.5),
appBar: AppBar(
title: Text("I'm on top"),
),
body: Listener(
onPointerMove: (event){
var newPosition = widget.controller.position.pixels - event.delta.dy;
widget.controller.jumpTo(newPosition);
},
child: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
color: Colors.red.withOpacity(0.5),
),
),
);
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.