【问题标题】:How to scroll underlying widget in Flutter?如何在 Flutter 中滚动底层小部件?
【发布时间】:2021-12-04 18:20:07
【问题描述】:

我有一个带有两个屏幕的简单应用。第一个屏幕是可滚动的ListView,第二个屏幕基本上是空的和透明的。如果我在第一个屏幕上使用Navigator.push() 推动第二个屏幕,我希望能够滚动底层的第一个屏幕。

这是我的代码:

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"),
      ),
    );
  }
}

第二个屏幕在前台时如何在后台滚动列表?

【问题讨论】:

标签: flutter


【解决方案1】:

虽然这不是第二个屏幕的解决方案,但它使用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),
            ),
    );
  }
}

【讨论】:

  • 感谢您的回答!不幸的是,这并不是很令人满意,因为在我的真实应用程序中,这两个屏幕非常复杂。我不想将它们合并到一个小部件中。对于这个看起来很简单的问题,我希望还有更好的解决方案。
  • @DarkMath 似乎这个答案很有帮助,无论它变得多么复杂,您都可以拆分小部件并将其用作包装器。
  • 我还需要在push()pop() 期间进行页面转换。
【解决方案2】:

我找到了一个解决方案,即使在两个屏幕上也能正常工作。想法是在一个屏幕中各有两个ScrollControllers,并在触发底层小部件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),
        ),
      ),
    );
  }
}

此解决方法仍有一些问题:

  1. 这不适用于无限列表。
  2. 滚动行为很糟糕,因为只有在滚动条结束手势时,才会滚动背景。
  3. 两个屏幕的尺寸不匹配。这会导致不良影响,例如在其他屏幕中不存在的区域中滚动。

【讨论】:

    【解决方案3】:

    我终于找到了一个令人满意的答案,它不包含任何肮脏的解决方法。我在第二个屏幕中使用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),
            ),
          ),
        );
      }
    }
    

    【讨论】:

      猜你喜欢
      • 2021-07-31
      • 2020-09-10
      • 1970-01-01
      • 2019-07-16
      • 1970-01-01
      • 2018-08-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多