如何在Flutter的滚动视图中限制滚动距离?

Sha*_*cks 5 dart flutter

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

因此,当一个人点击其中一个字段时,他们的键盘会弹出(占据屏幕的一部分),这意味着一些按钮/字段在屏幕外,这是scrollview小部件用于其目的的地方.

这里的问题是我想限制滚动视图允许用户滚动的距离.

最低按钮下面有一些空白区域,我不希望用户能够一直滚动到那里.这也是让体验变得简单,并且不会让用户"过度滚动"超过他应该输入的字段.

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

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

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

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(),
              ],
            ))
          ])
        ],
      ),
    ));
  }
}
Run Code Online (Sandbox Code Playgroud)

Col*_*son 8

对于将字段自动滚动到视图中,听起来像是在与问题10826进行摔跤.我在这个问题上发布了一个解决方法.我将解决方法改为您的示例代码; 见下文.(你可能想稍微调整一下.)

如果要防止用户滚动,你可能只想确保所有领域都可见使用下面的相同的技术,然后使用一个NeverScrollableScrollPhysics作为physicsListView.或者,如果您有野心,可以实现自定义滚动物理,如Gallery示例中所示.如果我是你,我会坚持要#10826修复.

视频

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'),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)