如何在Flutter中限制滚动视图中的滚动距离? [英] How to limit scroll distance in a scroll view in Flutter?

查看:614
本文介绍了如何在Flutter中限制滚动视图中的滚动距离?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我制作了一个页面,该页面在具有背景图像的容器中包含的列中包含多个文本字段和按钮。而且这个容器本身就是滚动视图小部件的子代。



因此,当一个人单击其中一个字段时,他们的键盘将弹出(占据屏幕的一部分) ),这意味着某些按钮/字段不在屏幕上,这就是scrollview小部件的作用所在。



这里的问题是我想限制滚动视图的距离



最低按钮下方有一些空格,我不希望用户能够一直滚动到那里。这也使体验变得简单,并且没有让用户超滚动过他应该输入的字段。



但是由于背景图像是滚动视图的一部分该视图将允许用户向下滚动到图像底部。我想限制这个范围。



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



这里是相关的(如果我的任何代码看起来真的很糟糕,请这么说,我是新来的)

  class LoginPageConstructor扩展StatelessWidget {
@override
窗口小部件build(BuildContext context){
AssetImage loginBackgroundAsset =
new AssetImage( assets / loginscreen / backgroundrock.png);
// var _scrollController = new ScrollController(
// initialScrollOffset:200.0,
// keepScrollOffset:true);
返回新的Scaffold(
主体:new容器(
子代:new ListView(键:new PageStorageKey( Divider 1)),
//控制器:_scrollController,
个孩子:< Widget> [
新Stack(孩子:< Widget> [
新Container(
约束:新BoxConstraints.expand(高度:640.0),
装饰) :new BoxDecoration(
图片:new DecorationImage(
图片:loginBackgroundAsset,fit:BoxFit.cover)),
子级:new Column(
子级:< Widget> [
新分隔线(高度:300.0,),
新分隔线(孩子:新UsernameText(),),
新分隔线(高度:8.0,),
新分隔线(孩子:新的PasswordText(),),
新的Divider(),
新的LoginButton(),
新的Divider(),
新的SignUpButto n(),
],
))
])
],
),
));
}
}


解决方案

自动将字段滚动到视图中,听起来您正在与

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

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

///一个小部件,确保在聚焦时始终可见。
类确保VisibleWhenFocused扩展了StatefulWidget {
const GuaranteeVisibleWhenFocused({
密钥,
需要this.child,
需要this.focusNode,
this。 curve:Curves.ease,
this.duration:const Duration(毫秒:100),
}):super(key:key);

///我们将监视以确定该子节点是否已聚焦的节点
最后的FocusNode focusNode;

///我们要包装
最终Widget子元素的子Widget;

///我们将用来滚动到视图中的曲线。
///
///默认为Curves.ease。
最终曲线曲线;

///我们将用来滚动查看视图的持续时间
///
///默认为100毫秒。
的最终持续时间;

确保VisibleWhenFocusedState createState()=>新的sureVisibleWhenFocusedState();
}

类确保VisibleWhenFocusedState扩展State< EnsureVisibleWhenFocused> {
@override
void initState(){
super.initState();
widget.focusNode.addListener(_ensureVisible);
}

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

Future< Null> _ensureVisible()async {
//等待键盘进入视图
// TODO:位置在度量值更改时似乎不会通知侦听器,
//可能是在周围的NotificationListener scrollable可以避免
//需要在此处插入延迟。
等待新的Future.delayed(const持续时间(毫秒:600));

如果(!widget.focusNode.hasFocus)
返回;

最终的RenderObject对象= context.findRenderObject();
final RenderAbstractViewport视口= RenderAbstractViewport.of(object);
assert(viewport!= null);

ScrollableState scrollableState = Scrollable.of(上下文);
assert(scrollableState!= null);

ScrollPosition position = scrollableState.position;
双重对齐;
if(position.pixels> viewport.getOffsetToReveal(object,0.0)){
//移至视口顶部
alignment = 0.0;
}否则if(position.pixels< viewport.getOffsetToReveal(object,1.0)){
//移至视口底部
alignment = 1.0;
} else {
//无需滚动即可显示子级
的收益;
}
position.ensureVisible(
对象,
对齐方式:对齐,
持续时间:widget.duration,
曲线:widget.curve,
);
}

小部件build(BuildContext context)=> widget.child;
}

类LoginPage扩展了StatefulWidget {
LoginPageState createState()=>新的LoginPageState();
}

类LoginPageState扩展了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 '),
),
正文:新Container(
子级:new ListView(
物理特性:new NeverScrollableScrollPhysics(),
键:new PageStorageKey( Divider 1 ),
子级:< Widget> [
新Container(
约束:new BoxConstraints.expand(height:640.0),
装饰:new BoxDecoration(
图像:新DecorationImage(
图像:新NetworkImage(
'https://flutter.io/images/flutter-mark-square-100.png',
),
适合:BoxFit.cover,
),
),
子级:new Column(
子级:< Widget> [
新Container(
高度:300.0,
),
新的Center(
子节点:新的SecureVisibleWhenFocused(
focusNode:_usernameFocusNode,
子节点:new TextFormField(
focusNode:_usernameFocusNode ,
装饰:new InputDecoration(
labelText:'Username',
),
),
),
),
新Container (高度:8.0),
新Center(
子对象:新的EnsureVisibleWhenFocused(
focusNode:_passwordFocusNode,
子对象:new TextFormField(
focusNode:_passwordFocusNode,
obscureText:true,
装饰:new InputDecoration(
labelText:'Password',
),
),
),
),
新Container(),
新RaisedButton(
onPressed:(){},
子级:new Text('Log in',
),
new Divider(),
RaisedButton(
onPressed: (){},
子级:new Text('Sign up'),
),
],
),
),
],
),
),
);
}
}


I've made a page that contains several textfields and buttons in a column which is contained in a container that has a background image. And this container is itself the child of a scrollview widget.

So when a person clicks on one of the fields, their keyboard will pop up (taking a portion of the screen), which means some buttons/fields are offscreen, which is where the scrollview widget serves its purpose.

The problem here is that I want to limit how far the scroll view allows a user to scroll.

There are some blank space under the lowest button, and I don't want the user to be able to scroll all the way there. This is too keep the experience simple and not have the user "overscroll" past the fields he should be typing in.

But since the background image is part of the scroll view the view will allow a user to scroll as far down as the bottom of the image. I want to limit this.

As a follow-up I'm trying to figure out how to set an initial scroll position. (So that when clicking on a field the scroll view scrolls down to very first text field, so all fields are in view. without the user needing to scroll down to them. However I don't want this scroll position to be re-applied every time the user clicks on a field, of course.)

Here is the relevant (if any of my code looks really bad please say so, I'm new to programming in general and accept any advice to improve):

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

解决方案

For auto-scrolling the fields into view, it sounds like you are wrestling with issue 10826. I posted a workaround on that issue. I adapted the workaround to your sample code; see below. (You may want to tweak it a little.)

If you want to prevent users from scrolling, you might want to just ensure that all the fields are visible using the same techniques below and then use a NeverScrollableScrollPhysics as the physics of the ListView. Or if you're feeling ambitious you could implement a custom scroll physics as shown in the Gallery example. If I were you I'd hold out for #10826 to be fixed, though.

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

这篇关于如何在Flutter中限制滚动视图中的滚动距离?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆