首页 文章

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

提问于
浏览
4

我创建了一个页面,其中包含一个列中的几个文本字段和按钮,该列包含在具有背景图像的容器中 . 而这个容器本身就是一个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(),
              ],
            ))
          ])
        ],
      ),
    ));
  }
}

1 回答

  • 6

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

    如果您想阻止用户滚动,您可能只想确保使用下面相同的技术可见所有字段,然后使用 NeverScrollableScrollPhysics 作为 ListViewphysics . 或者,如果您有野心,可以实现自定义滚动物理,如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'),
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }
    

相关问题