Flutter - 当 Android 键盘弹出时,TextField 不显示

5
我的问题是当我聚焦于文本框时,键盘会弹出,但布局不会调整以保持在视图中。这个问题只出现在Android上而不是iOS。这里是我所说的截图:

在Android上的变化前后:

Before After

iOS上的前后:

enter image description here enter image description here

这是我的类:

class PlayerSelectionPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    int itemCount = Provider.of<PlayerProvider>(context).getPlayerList.length;
    SystemChrome.setPreferredOrientations([
      DeviceOrientation.portraitUp,
    ]);
    return Scaffold(
      appBar: AppBar(
        title: Text(AppLocalizations.of(context).translate('player_selection_page_title')),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: (){
          HapticFeedback.mediumImpact();
          Navigator.push(context, MaterialPageRoute(builder: (context) => HomePage(), settings: RouteSettings(name: 'Home page')));
        },
        child: Icon(
          Icons.chevron_right,
          size: 30,
          color: Colors.white,
        ),
      ),
      body: itemCount > 0 ? ListView.builder(
          itemCount: itemCount,
          itemBuilder: (context, index) {
            return Column(
              children: [
                PlayerDismissible(index),
                Divider(
                  height: 0,
                )
              ],
            );
          }) : Container(
          padding: EdgeInsets.all(20),
          alignment: Alignment.topCenter,
          child: Text(AppLocalizations.of(context).translate('player_selection_page_empty_text'), textAlign: TextAlign.center, style: Theme.of(context).textTheme.subtitle2)
      ),
      bottomSheet: BottomPlayerBar(),
    );
  }
}

这是我的BottomPlayerBar():

class BottomPlayerBar extends StatefulWidget{
  @override
  _BottomPlayerBarState createState() => _BottomPlayerBarState();
}

class _BottomPlayerBarState extends State<BottomPlayerBar> {
  String playerName;
  FocusNode myFocusNode;


  @override
  void initState() {
    super.initState();
    myFocusNode = FocusNode();
    SchedulerBinding.instance.addPostFrameCallback((_) {
      if (ModalRoute.of(context).isCurrent) {
        myFocusNode.requestFocus();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Container(
        height: 80, color: Theme.of(context).primaryColor,
        padding: EdgeInsets.only(top: 20, bottom: 25, left: 20, right: 70),
        child: TextField(
          focusNode: myFocusNode,
          textCapitalization: TextCapitalization.words,
          onChanged: (val) => playerName = val.trim(),
          onSubmitted: (val) {
            if (playerName != null && playerName != '') {
              Provider.of<PlayerProvider>(context, listen: false).addPlayer(playerName);
              HapticFeedback.lightImpact();
              myFocusNode.requestFocus();
            } else {
              myFocusNode.unfocus();
            }
          },
          maxLength: 19,
          autocorrect: false,
          decoration: new InputDecoration(
              counterText: "",
              border: new OutlineInputBorder(
                borderSide: BorderSide.none,
                borderRadius: const BorderRadius.all(
                  const Radius.circular(30.0),
                ),
              ),
              filled: true,
              contentPadding: EdgeInsets.symmetric(vertical: 0, horizontal: 20),
              hintStyle: GoogleFonts.rubik(color: Colors.grey[500], fontWeight: FontWeight.bold),
              hintText: AppLocalizations.of(context).translate('player_selection_page_hint'),
              fillColor: Colors.white),
        )
    );
  }

  @override
  void dispose() {
    super.dispose();
    myFocusNode.dispose();
  }
}


这是我的Android清单文件:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="myApp">
    <application
        android:name="io.flutter.app.FlutterApplication"
        android:label="myApp !"
        android:icon="@mipmap/ic_launcher">
        <activity
            android:name=".MainActivity"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <meta-data
              android:name="io.flutter.embedding.android.LaunchTheme"
              android:resource="@style/LaunchTheme"
              />
            <meta-data
              android:name="io.flutter.embedding.android.SplashScreenDrawable"
              android:resource="@drawable/launch_background"
              />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
            <intent-filter>
                <action android:name="FLUTTER_NOTIFICATION_CLICK" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>
</manifest>


你能否在Android清单文件中发布设置? - Nemi Shah
这就是它,但我不确定它会如何有所帮助。 - Simon B
由于您的“android:windowSoftInputMode =”adjustResize“”,它应该做您想要的事情。 - Rod
我应该删除这行代码吗? - Simon B
它没有起作用... - Simon B
9个回答

7

您可以像下面展示的那样,将BottomPlayerBar小部件包装在Transform小部件中。

Transform.translate(
    offset: Offset(0.0, -1 * MediaQuery.of(context).viewInsets.bottom),
    child: BottomPlayerBar(),
    );

1
调整页面大小以适应软键盘出现是通过在AndroidManifest中的android:windowSoftInputMode活动参数处理的。请参阅官方文档
如果在AndroidManifest.xml中有android:windowSoftInputMode="adjustResize",则当软键盘出现时,文本字段将自动重新定位。如果AndroidManifest.xml中没有该参数,则不会发生相同的行为。
您可以通过修改AndroidManifest来重新检查此问题。这也将解释为什么它在iOS中隐含地处理,但在Android中无法正常工作。
附注:修改AndoridManifest.xml时,热重载可能无法正常工作。您需要实际停止并重新启动应用程序。

1
使用 margin 属性来设置你的 BottomPlayerBar 小部件:
margin: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),

1
android/app/src/main/res/values/styles.xml

对我来说,将下面的项目属性从true更改为false

<item name="android:windowFullscreen">false</item>

我也遇到了这个问题。这行代码是使用flutter_native_splash包后产生的结果。

非常感谢,这正是问题所在! - Simon B

0
根据应用程序对我的评论,我添加了一个StatefulBuilder,以便底部表格能够获得填充变更通知。

  showModalBottomSheet(
      backgroundColor: Colors.transparent,
      barrierColor: Colors.black.withOpacity(0.2),
      elevation: 0.0,
      context: context,
      isScrollControlled: true,
      builder: (_) {
        return StatefulBuilder(builder: (context, StateSetter stateSetter) {
          return Padding(
            padding: MediaQuery.of(context).viewInsets,
            child: Column(
              children: [
                TextField(),
                TextField(),
                TextField(),
                TextField(),
              ],
            ),
          );
        });
      },
    );


0

你可以在showModalBottomSheet中添加isScrollControlled = true,这将允许底部表单占据所需的全部高度,从而更好地保证TextField不会被键盘遮挡。

showModalBottomSheet(
    shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(25.0))),
    backgroundColor: Colors.black,
    context: context,
    isScrollControlled: true,
    builder: (context) => Padding(
      padding: const EdgeInsets.symmetric(horizontal:18 ),
      child: BottomPlayerBar(),
          ),
    ));

0

这就是为什么我通常不尝试使用内置参数 Scaffold。 为了节省解决平台依赖问题的时间,我会使用简单的 Widgets 来实现您想要的结果。

Widget build(BuildContext context) {
  return Scaffold(
    body: SingleChildScrollView(
      physics: NeverScrollableScrollPhysics(),
      child: Column(
            mainAxisSize: MainAxisSize.max,
            children: <Widget>[

              // YOU BODY VIEW 85 HEIGHT %
              ConstrainedBox(
                constraints: BoxConstraints(
                  minWidth: MediaQuery.of(context).size.width,
                  minHeight: MediaQuery.of(context).size.height * 0.85,
                ),
                child: MainWidget()
              ),

              // YOUR BOTTOM VIEW 15 %
              ConstrainedBox(
                constraints: BoxConstraints(
                  minWidth: MediaQuery.of(context).size.width,
                  minHeight: MediaQuery.of(context).size.height * 0.15,
                ),
                child: BottomPlayerBar()
              ),
            ],
        ),
    ),
  );
}

针对您上面的示例,PlayerSelectionPage

class PlayerSelectionPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    int itemCount = Provider.of<PlayerProvider>(context).getPlayerList.length;
    SystemChrome.setPreferredOrientations([
      DeviceOrientation.portraitUp,
    ]);
    return Scaffold(
      appBar: AppBar(
        title: Text(AppLocalizations.of(context).translate('player_selection_page_title')),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: (){
          HapticFeedback.mediumImpact();
          Navigator.push(context, MaterialPageRoute(builder: (context) => HomePage(), settings: RouteSettings(name: 'Home page')));
        },
        child: Icon(
          Icons.chevron_right,
          size: 30,
          color: Colors.white,
        ),
      ),
      body: SingleChildScrollView(
        physics: NeverScrollableScrollPhysics(),
        child: Column(
              mainAxisSize: MainAxisSize.max,
              children: <Widget>[

                // NEW VIEW
                ConstrainedBox(
                constraints: BoxConstraints(
                  minWidth: MediaQuery.of(context).size.width,
                  minHeight: MediaQuery.of(context).size.height * 0.85,
                ),
              child: itemCount > 0 ? ListView.builder(
                  itemCount: itemCount,
                  itemBuilder: (context, index) {
                    return Column(
                      children: [
                        PlayerDismissible(index),
                        Divider(
                          height: 0,
                        )
                      ],
                    );
                  }) : Container(
                  padding: EdgeInsets.all(20),
                  alignment: Alignment.topCenter,
                  child: Text(AppLocalizations.of(context).translate('player_selection_page_empty_text'), textAlign: TextAlign.center, style: Theme.of(context).textTheme.subtitle2)
              ),
            ),

                // NEW VIEW
                ConstrainedBox(
                  constraints: BoxConstraints(
                    minWidth: MediaQuery.of(context).size.width,
                    minHeight: MediaQuery.of(context).size.height * 0.15,
                  ),
                  child: BottomPlayerBar()
                ),
              ],
          ),
      ),
      body: itemCount > 0 ? ListView.builder(
          itemCount: itemCount,
          itemBuilder: (context, index) {
            return Column(
              children: [
                PlayerDismissible(index),
                Divider(
                  height: 0,
                )
              ],
            );
          }) : Container(
          padding: EdgeInsets.all(20),
          alignment: Alignment.topCenter,
          child: Text(AppLocalizations.of(context).translate('player_selection_page_empty_text'), textAlign: TextAlign.center, style: Theme.of(context).textTheme.subtitle2)
      ),
    );
  }
}

0
一个简单的解决方案可能是将底部填充用于您的BottomPlayerBar,
而不是,
padding: EdgeInsets.only(top: 20, bottom: 25, left: 20, right: 70),

试一下,

padding: EdgeInsets.only(top: 20, bottom: 25 + MediaQuery.of(context).padding.bottom, left: 20, right: 70),

这里我们只是添加到脚手架底部的任何填充。

MediaQuery.of(context).padding.bottom

0
你可以像这样使用一个 stack

Widget build(BuildContext context) {
  return Scaffold(
    body: Stack(
      children: [
        Column(
          children: [
            Flexible(
              child: ListView.builder(itemBuilder: null),
            ),
            Container(
              width: double.infinity,
              height: 100,
              child: TextField(),
            )
          ],
        )
      ],
    ),
  );
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接