dart - 如何在 Flutter 的 ScrollView 中限制滚动距离?

标签 dart flutter

我创建了一个页面,其中包含多个文本字段和列中的按钮,该列包含在具有背景图像的容器中。而这个容器本身就是一个 ScrollView 小部件的子级。

所以当一个人点击其中一个字段时,他们的键盘会弹出(占据屏幕的一部分),这意味着一些按钮/字段在屏幕外,这就是 ScrollView 小部件发挥作用的地方。

这里的问题是我想限制 ScrollView 允许用户滚动的距离。

最下方的按钮下方有一些空白区域,我不希望用户能够一直滚动到那里。这也让体验保持简单,并且不会让用户“过度滚动”他应该输入的字段。

但由于背景图像是 ScrollView 的一部分,因此该 View 将允许用户向下滚动到图像的底部。我想限制这个。

作为后续,我试图弄清楚如何设置初始滚动位置。 (因此,当单击一个字段时, ScrollView 会向下滚动到第一个文本字段,因此所有字段都在 View 中。用户无需向下滚动到它们。但是我不希望重新应用此滚动位置当然,每次用户点击一个字段时。)

这里是相关的(如果我的任何代码看起来真的很糟糕,请说出来,我一般是编程新手并接受任何改进建议):

class LoginPageConstructor extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    AssetImage loginBackgroundAsset =
        new AssetImage("assets/loginscreen/backgroundrock.png");
//    var _scrollController = new ScrollController(
//        initialScrollOffset: 200.0,
//        keepScrollOffset: true);
    return new Scaffold(
        body: new Container(
      child: new ListView(key: new PageStorageKey("Divider 1"),
//        controller: _scrollController,
        children: <Widget>[
          new Stack(children: <Widget>[
            new Container(
            constraints: new BoxConstraints.expand(height: 640.0),
              decoration: new BoxDecoration(
                  image: new DecorationImage(
                      image: loginBackgroundAsset, fit: BoxFit.cover)),
            child: new Column(
              children: <Widget>[
                new Divider(height: 300.0,),
                new Center(child: new UsernameText(),),
                new Divider(height: 8.0,),
                new Center(child: new PasswordText(),),
                new Divider(),
                new LoginButton(),
                new Divider(),
                new SignUpButton(),
              ],
            ))
          ])
        ],
      ),
    ));
  }
}

最佳答案

为了将字段自动滚动到 View 中,听起来您正在与 issue 10826 搏斗。 .我发布了 workaround在那个问题上。我根据您的示例代码调整了解决方法;见下文。 (您可能需要稍微调整一下。)

如果您想阻止用户滚动,您可能只想使用以下相同的技术确保所有字段都可见,然后使用 NeverScrollableScrollPhysics 作为 physics ListView。或者,如果您有野心,您可以实现自定义滚动物理,如 Gallery example 所示。 .不过,如果我是你,我会坚持修复 #10826。

video

import 'package:meta/meta.dart';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

void main() {
  runApp(new MaterialApp(home: new LoginPage()));
}

/// A widget that ensures it is always visible when focused.
class EnsureVisibleWhenFocused extends StatefulWidget {
  const EnsureVisibleWhenFocused({
    Key key,
    @required this.child,
    @required this.focusNode,
    this.curve: Curves.ease,
    this.duration: const Duration(milliseconds: 100),
  }) : super(key: key);

  /// The node we will monitor to determine if the child is focused
  final FocusNode focusNode;

  /// The child widget that we are wrapping
  final Widget child;

  /// The curve we will use to scroll ourselves into view.
  ///
  /// Defaults to Curves.ease.
  final Curve curve;

  /// The duration we will use to scroll ourselves into view
  ///
  /// Defaults to 100 milliseconds.
  final Duration duration;

  EnsureVisibleWhenFocusedState createState() => new EnsureVisibleWhenFocusedState();
}

class EnsureVisibleWhenFocusedState extends State<EnsureVisibleWhenFocused> {
  @override
  void initState() {
    super.initState();
    widget.focusNode.addListener(_ensureVisible);
  }

  @override
  void dispose() {
    super.dispose();
    widget.focusNode.removeListener(_ensureVisible);
  }

  Future<Null> _ensureVisible() async {
    // Wait for the keyboard to come into view
    // TODO: position doesn't seem to notify listeners when metrics change,
    // perhaps a NotificationListener around the scrollable could avoid
    // the need insert a delay here.
    await new Future.delayed(const Duration(milliseconds: 600));

    if (!widget.focusNode.hasFocus)
      return;

    final RenderObject object = context.findRenderObject();
    final RenderAbstractViewport viewport = RenderAbstractViewport.of(object);
    assert(viewport != null);

    ScrollableState scrollableState = Scrollable.of(context);
    assert(scrollableState != null);

    ScrollPosition position = scrollableState.position;
    double alignment;
    if (position.pixels > viewport.getOffsetToReveal(object, 0.0)) {
      // Move down to the top of the viewport
      alignment = 0.0;
    } else if (position.pixels < viewport.getOffsetToReveal(object, 1.0)) {
      // Move up to the bottom of the viewport
      alignment = 1.0;
    } else {
      // No scrolling is necessary to reveal the child
      return;
    }
    position.ensureVisible(
      object,
      alignment: alignment,
      duration: widget.duration,
      curve: widget.curve,
    );
  }

  Widget build(BuildContext context) => widget.child;
}

class LoginPage extends StatefulWidget {
  LoginPageState createState() => new LoginPageState();
}

class LoginPageState extends State<LoginPage> {
  FocusNode _usernameFocusNode = new FocusNode();
  FocusNode _passwordFocusNode = new FocusNode();

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Example App'),
      ),
      body: new Container(
        child: new ListView(
          physics: new NeverScrollableScrollPhysics(),
          key: new PageStorageKey("Divider 1"),
          children: <Widget>[
            new Container(
              constraints: new BoxConstraints.expand(height: 640.0),
              decoration: new BoxDecoration(
                image: new DecorationImage(
                  image: new NetworkImage(
                    'https://flutter.io/images/flutter-mark-square-100.png',
                  ),
                  fit: BoxFit.cover,
                ),
              ),
              child: new Column(
                children: <Widget>[
                  new Container(
                    height: 300.0,
                  ),
                  new Center(
                    child: new EnsureVisibleWhenFocused(
                      focusNode: _usernameFocusNode,
                      child: new TextFormField(
                        focusNode: _usernameFocusNode,
                        decoration: new InputDecoration(
                          labelText: 'Username',
                        ),
                      ),
                    ),
                  ),
                  new Container(height: 8.0),
                  new Center(
                    child: new EnsureVisibleWhenFocused(
                      focusNode: _passwordFocusNode,
                      child: new TextFormField(
                        focusNode: _passwordFocusNode,
                        obscureText: true,
                        decoration: new InputDecoration(
                          labelText: 'Password',
                        ),
                      ),
                    ),
                  ),
                  new Container(),
                  new RaisedButton(
                    onPressed: () {},
                    child: new Text('Log in'),
                  ),
                  new Divider(),
                  new RaisedButton(
                    onPressed: () {},
                    child: new Text('Sign up'),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

关于dart - 如何在 Flutter 的 ScrollView 中限制滚动距离?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46172962/

相关文章:

flutter - 添加由 Flutter 中的 CustomScrollView 中的函数返回的 Sliver 的动态列表

android-studio - Flutter Emulator 连接到服务协议(protocol)时出错

android - 如何将方法 channel 添加到 Flutter Fragment

flutter - 你应该注入(inject)一个接口(interface)还是它的实现?

flutter - 带有项目Flutter的FutureBuilder

flutter - 如何在 Flutter 中将图像 url 转换为 XFile?

android - 为什么Flutter导入提供程序包不起作用?

dart - Flutter Http错误SocketException : OS Error: Connection refused

flutter - 如何改变 MaterialPageRoute 的状态?

flutter 如何在单击文本表单字段时显示日期选择器