简体   繁体   English

如何在 Flutter 中滚动底层小部件?

[英]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:虽然这不是第二个屏幕的解决方案,但它使用StackIgnorePointer小部件创建了类似的效果:

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:这种解决方法仍然存在一些问题:

  1. This does not work for infinite lists.这不适用于无限列表。
  2. The scroll behavior is bad because the background is only scrolled when the scroller ends his gesture by put the finger up.滚动行为很糟糕,因为只有当滚动条结束手势时,背景才会滚动。
  3. The sizes of the both screens doesn't match.两个屏幕的大小不匹配。 That leads to bad effects like scrolling in areas that doesn't exists in the other screen.这会导致不良影响,例如在另一个屏幕中不存在的区域中滚动。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM