Flutter 解决App登录页面软键盘遮挡住登录按钮或顶起底部控件的问题

这篇具有很好参考价值的文章主要介绍了Flutter 解决App登录页面软键盘遮挡住登录按钮或顶起底部控件的问题。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


1 软键盘问题点对比效果图

问题点

Flutter 解决App登录页面软键盘遮挡住登录按钮或顶起底部控件的问题

最终效果图

Flutter 解决App登录页面软键盘遮挡住登录按钮或顶起底部控件的问题


2 解决软键盘将底部布局顶上去的问题

2.1 方式一:修改resizeToAvoidBottomInset属性

问题点: 当前使用的是Column布局,弹窗软键盘后页面超出范围。

A RenderFlex overflowed by 0.533 pixels on the bottom.

Flutter 解决App登录页面软键盘遮挡住登录按钮或顶起底部控件的问题

解决方式

Scaffold或者CupertinoPageScaffold中设置resizeToAvoidBottomInset为false

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      resizeToAvoidBottomInset:false,
      body: ...,
    );
  }

2.2 方式二:使用可滑动布局

不修改resizeToAvoidBottomInset属性的话,可以使用ListViewSingleChildScrollViewCustomScrollView等布局构建页面。

3 弹出软键盘时滚动布局到指定位置(登录按钮下方)

3.1 问题点描述

在此登录页面布局中使用上述2种方式都会存在问题。

  • 小屏幕手机中,弹出软键盘会将登录按钮挡住
  • 直接使用ListView时,无法将第三方登录布局至于底部

Flutter 解决App登录页面软键盘遮挡住登录按钮或顶起底部控件的问题


3.2 实现方式

  1. 在Column布局中使用 ListView + 底部第三方登录
  2. 在ListView中底部加一个可控制高度的SizeBox
  3. 设置resizeToAvoidBottomInset属性为false
  4. 监听软键盘弹出并获取其高度
  5. 改变ListView中底部SizeBox的高度
  6. 滑动ListView到指定位置(使用GlobalKey来确定)

简要代码

class _LoginPageState extends State<LoginPage> with WidgetsBindingObserver {

  // 软键盘高度
  double _keyboardHeight = 0;

  // 可控制ListView滑动
  final _scrollController = ScrollController();

  // 用于获取目标Widget的位置坐标
  final _targetWidgetKey = GlobalKey();

  @override
  void initState() {
    super.initState();
    // 添加监听,didChangeMetrics
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  // 当应用程序的尺寸发生变化时会调用
  @override
  void didChangeMetrics() {
    // 获取页面高度
    var pageHeight = MediaQuery.of(context).size.height;
    if (pageHeight <= 0) {
      return;
    }

    // 软键盘顶部  px
    final keyboardTopPixels =
        window.physicalSize.height - window.viewInsets.bottom;
    // 转换为 dp
    final keyboardTopPoints = keyboardTopPixels / window.devicePixelRatio;
    // 软键盘高度
    final keyboardHeight = pageHeight - keyboardTopPoints;

    setState(() {
      _keyboardHeight = keyboardHeight;
    });

    if (keyboardHeight <= 0) {
      return;
    }
    // 获取目标位置的坐标
    RenderBox? renderBox =
        _targetWidgetKey.currentContext?.findRenderObject() as RenderBox?;
    if (renderBox == null) {
      return;
    }
    // 转换为全局坐标
    final bottomOffset =
        renderBox.localToGlobal(Offset(0, renderBox.size.height));
    final targetDy = bottomOffset.dy;
    // 获取要滚动的距离
    // 即被软键盘挡住的那段距离 加上 _scrollController.offset 已经滑动过的距离
    final offsetY =
        keyboardHeight - (pageHeight - targetDy) + _scrollController.offset;
    // 滑动到指定位置
    if (offsetY > 0) {
      _scrollController.animateTo(
        offsetY,
        duration: kTabScrollDuration,
        curve: Curves.ease,
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      // 避免底部布局被软键盘顶上来
      resizeToAvoidBottomInset: false,
      body: GestureDetector(
        behavior: HitTestBehavior.opaque,
        // 点击空白位置关闭软键盘
        onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
        child: Column(
          children: [
            Expanded(
              child: ListView(
                controller: _scrollController,
                children: [
                  ...
                  // 一系列输入框Widget
                  ...
                  // 弹出的软键盘位于此Widget之下
                  Row(
                    key: _targetWidgetKey,
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                     ...
                    ],
                  ),
                  // 动态变换高度,保证ListView可滑动
                  SizedBox(height: _keyboardHeight)
                ],
              ),
            ),
            /// 底部的布局
            Row(
              children: const [
              ...
              ],
            ),
          ],
        ),
      ),
    );
  }
}

效果图
Flutter 解决App登录页面软键盘遮挡住登录按钮或顶起底部控件的问题


4 登录页面完整代码

带删除和眼睛按钮的输入框控件

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class UserTextField extends StatefulWidget {
  final TextEditingController controller;
  final TextInputType? keyboardType;
  final String? placeholder;
  final bool usedInPassword;
  final Widget? suffixWidget;
  final int? maxLength;

  const UserTextField({
    Key? key,
    required this.controller,
    this.keyboardType,
    this.placeholder,
    this.usedInPassword = false,
    this.suffixWidget,
    this.maxLength,
  }) : super(key: key);

  @override
  State<StatefulWidget> createState() => _UserTextFieldState();
}

class _UserTextFieldState extends State<UserTextField> {
  var _showClearIcon = false;
  var _showEyeIcon = false;
  late bool _obscurePassword;

  @override
  void initState() {
    super.initState();
    _obscurePassword = widget.usedInPassword;

    widget.controller.addListener(() {
      var isNotEmpty = widget.controller.text.isNotEmpty;
      setState(() {
        _showEyeIcon = isNotEmpty;
        _showClearIcon = isNotEmpty;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return CupertinoTextField(
      controller: widget.controller,
      keyboardType: widget.keyboardType,
      onChanged: (_) {},
      placeholder: widget.placeholder,
      style: const TextStyle(color: Colors.black),
      placeholderStyle: const TextStyle(color: Colors.grey),
      maxLength: widget.maxLength,
      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
      decoration: BoxDecoration(
        color: Colors.white,
        border: Border.all(color: Colors.grey, width: 0.5),
        borderRadius: BorderRadius.circular(26),
      ),
      obscureText: _obscurePassword,
      obscuringCharacter: "*",
      suffix: widget.suffixWidget ??
          (widget.usedInPassword ? _buildPasswordEyeIcon() : _buildClearIcon()),
    );
  }

  Widget _buildClearIcon() {
    return _showClearIcon
        ? CupertinoButton(
            padding: const EdgeInsets.fromLTRB(0, 0, 8, 0),
            child: const Icon(Icons.clear, size: 18),
            onPressed: () => widget.controller.clear(),
          )
        : const SizedBox(width: 8.0);
  }

  Widget _buildPasswordEyeIcon() {
    return _showEyeIcon
        ? CupertinoButton(
            padding: const EdgeInsets.fromLTRB(0, 0, 8, 0),
            child: Icon(
              _obscurePassword ? Icons.visibility_off : Icons.visibility,
              size: 18,
            ),
            onPressed: () {
              setState(() => _obscurePassword = !_obscurePassword);
            },
          )
        : const SizedBox(width: 8.0);
  }
}

登录页面文章来源地址https://www.toymoban.com/news/detail-440473.html

import 'dart:ui';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'user_text_field.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const CupertinoApp(
      theme: CupertinoThemeData(
        primaryColor: Colors.red,
        scaffoldBackgroundColor: Colors.white,
      ),
      debugShowCheckedModeBanner: false,
      home: LoginPage(),
    );
  }
}

class LoginPage extends StatefulWidget {
  const LoginPage({Key? key}) : super(key: key);

  @override
  State<StatefulWidget> createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> with WidgetsBindingObserver {
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  final _codeController = TextEditingController();

  // 软键盘高度
  double _keyboardHeight = 0;

  // 可控制ListView滑动
  final _scrollController = ScrollController();

  // 用于获取目标Widget的位置坐标
  final _targetWidgetKey = GlobalKey();

  @override
  void initState() {
    super.initState();
    // 添加监听,didChangeMetrics
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  // 当应用程序的尺寸发生变化时会调用
  @override
  void didChangeMetrics() {
    // 获取页面高度
    var pageHeight = MediaQuery.of(context).size.height;
    if (pageHeight <= 0) {
      return;
    }

    // 软键盘顶部  px
    final keyboardTopPixels =
        window.physicalSize.height - window.viewInsets.bottom;
    // 转换为 dp
    final keyboardTopPoints = keyboardTopPixels / window.devicePixelRatio;
    // 软键盘高度
    final keyboardHeight = pageHeight - keyboardTopPoints;

    setState(() {
      _keyboardHeight = keyboardHeight;
    });
    if (keyboardHeight <= 0) {
      return;
    }
    // 获取目标位置的坐标
    RenderBox? renderBox =
        _targetWidgetKey.currentContext?.findRenderObject() as RenderBox?;
    if (renderBox == null) {
      return;
    }
    // 转换为全局坐标
    final bottomOffset =
        renderBox.localToGlobal(Offset(0, renderBox.size.height));
    final targetDy = bottomOffset.dy;
    // 获取要滚动的距离
    // 即被软键盘挡住的那段距离 加上 _scrollController.offset 已经滑动过的距离
    final offsetY =
        keyboardHeight - (pageHeight - targetDy) + _scrollController.offset;
    // 滑动到指定位置
    if (offsetY > 0) {
      _scrollController.animateTo(
        offsetY,
        duration: kTabScrollDuration,
        curve: Curves.ease,
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      resizeToAvoidBottomInset: false,
      body: GestureDetector(
        behavior: HitTestBehavior.opaque,
        onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
        child: Column(
          children: [
            Expanded(
              child: ListView(
                controller: _scrollController,
                children: [
                  SafeArea(
                    child: Align(
                      alignment: Alignment.centerRight,
                      child: CupertinoButton(
                        onPressed: () {},
                        child: const Icon(CupertinoIcons.clear, size: 24),
                      ),
                    ),
                  ),
                  const Padding(
                    padding: EdgeInsets.fromLTRB(16, 16, 16, 16),
                    child: Text(
                      '你好,\n欢迎使用Flutter App',
                      style: TextStyle(
                        fontSize: 24,
                        color: Colors.black,
                        fontWeight: FontWeight.w500,
                      ),
                    ),
                  ),
                  const SizedBox(height: 40),
                  Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 16),
                    child: UserTextField(
                      controller: _emailController,
                      keyboardType: TextInputType.emailAddress,
                      placeholder: '请输入邮箱',
                    ),
                  ),
                  const SizedBox(height: 16.0),
                  Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 16),
                    child: UserTextField(
                      controller: _passwordController,
                      keyboardType: TextInputType.visiblePassword,
                      usedInPassword: true,
                      placeholder: '请输入密码',
                    ),
                  ),
                  const SizedBox(height: 16.0),
                  Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 16),
                    child: UserTextField(
                      controller: _codeController,
                      keyboardType: TextInputType.number,
                      placeholder: '请输入6位验证码',
                    ),
                  ),
                  const SizedBox(height: 16.0),
                  CupertinoButton(
                    padding: const EdgeInsets.all(16),
                    child: Container(
                      height: 44,
                      width: double.infinity,
                      alignment: Alignment.center,
                      decoration: const BoxDecoration(
                        color: Colors.red,
                        borderRadius: BorderRadius.all(Radius.circular(22)),
                      ),
                      child: const Text(
                        '登录',
                        style: TextStyle(color: Colors.white),
                      ),
                    ),
                    onPressed: () {},
                  ),
                  Row(
                    key: _targetWidgetKey,
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    children: [
                      CupertinoButton(
                        minSize: 24,
                        alignment: Alignment.topCenter,
                        padding: const EdgeInsets.symmetric(
                            horizontal: 18, vertical: 0),
                        onPressed: () {},
                        child: const Text(
                          '忘记密码?',
                          style: TextStyle(fontSize: 14),
                        ),
                      ),
                      CupertinoButton(
                        minSize: 24,
                        alignment: Alignment.topCenter,
                        padding: const EdgeInsets.symmetric(
                            horizontal: 18, vertical: 0),
                        onPressed: () {},
                        child:
                            const Text('立即注册', style: TextStyle(fontSize: 14)),
                      ),
                    ],
                  ),
                  SizedBox(height: _keyboardHeight)
                ],
              ),
            ),
            Row(
              children: const [
                SizedBox(width: 16),
                Expanded(child: Divider()),
                SizedBox(width: 8),
                Text(
                  '其它登录方式',
                  style: TextStyle(fontSize: 13, color: Colors.grey),
                ),
                SizedBox(width: 8),
                Expanded(child: Divider()),
                SizedBox(width: 16),
              ],
            ),
            Row(
              crossAxisAlignment: CrossAxisAlignment.center,
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                CupertinoButton(
                  onPressed: () {},
                  child: const Icon(Icons.facebook, size: 44),
                ),
                const SizedBox(width: 32),
                CupertinoButton(
                  onPressed: () {},
                  child: const Icon(Icons.apple, size: 44),
                ),
              ],
            ),
            const SizedBox(height: 12),
          ],
        ),
      ),
    );
  }
}

到了这里,关于Flutter 解决App登录页面软键盘遮挡住登录按钮或顶起底部控件的问题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【项目复盘Vue2-移动端】IOS使用Vant表单组件输入框获取焦点后放大页面,安卓软键盘遮挡问题

    使用Vue+Vant做的移动端项目,在登录界面使用Vant的表单组件van-input时发现在真机测试IOS会出现获取焦点输入时会撑大原页面的宽度,每一个输入框获取到焦点就会页面就会放大一点点,需要双指缩放才能回到原来的比例。 经过一番百度后发现: 在移动端开发项目中,发现页

    2024年02月12日
    浏览(69)
  • uniapp input 被键盘遮挡的解决方案

    在uniapp登录页遇到了input被键盘遮挡的和一点击输入框(或两个输入框来回切换输入)页面就闪现刷新的问题。解决如下: 闪现/刷新:用uniapp自身的 input 标签,替换uview的 u-input ,用 @input 事件替换掉 v-model 属性 遮挡:增加三个标签属性 :always-embed=“true”:adjust-position=“tr

    2024年02月11日
    浏览(53)
  • 解决uni-app微信小程序底部输入框,键盘弹起时页面整体上移问题

    做了一个记录页面(类似单方聊天页),输入框在底部;当弹出键盘时,页面整体上移,页面头信息会消失不见 比如一个记录页面,需要在键盘弹出时: 底部的输入框跟随键盘上弹 页面头固定在顶部不动 聊天信息区域(即内容区)调整高度,该区域局部滚动 底部输入框f

    2024年02月13日
    浏览(178)
  • 解决uni-app微信小程序底部input输入框,键盘弹起时页面整体上移问题

    一.存在的问题:           微信小程序聊天界面,当input 框获取焦点时会自动调起手机键盘,当键盘弹起时,会导致页面整体上移,页面头信息会消失不见。 二.需要实现的效果 键盘弹出时, 底部的输入框跟随键盘上弹 ; 页面头固定在顶部不动; 3.聊天信息区域(即内

    2024年02月11日
    浏览(71)
  • 解决 uni-app 微信小程序 input 输入框在底部时,键盘弹起页面整体上移问题

    设置使键盘弹起使页面不上移 设置输入框所在盒子为绝对定位 键盘弹起时获取键盘高度 设置输入框所在盒子的 bottom 的键盘高度

    2024年02月05日
    浏览(67)
  • 解决uni-app微信小程序input,textarea输入框在底部时,键盘弹起页面整体上移问题

    问题是这样的input ,textarea获取焦点时会自动调起手机键盘,设置 :adjust-position=“true”,和不设置都会导致键盘弹起时页面整体上移 问题分析 input 获取焦点时会自动调起手机键盘,设置 :adjust-position=“true”,会导致键盘弹起时页面整体上移 思路: 设置使键盘弹起使页面不

    2024年02月11日
    浏览(71)
  • uniapp 内嵌 webview 客服网页,呼出键盘遮挡输入框问题解决记录

    有很多情况需要在app端内嵌一个H5客服网页,但这个页面一般都是有打字的需求,但由于大部分情况下网页都是默认铺满整个画面,导致键盘弹出时出现遮挡输入框的问题。 直接上代码:

    2024年02月06日
    浏览(41)
  • 全屏Activity弹出键盘不顶起布局

    最近遇到的一个问题是全屏Activity中要求弹出键盘不顶起布局,首先windowSoftInputMode的取值是有多个的,在全屏场景下adjustPan是没有用的,需要使用adjustResize首先确保键盘不顶起布局。         android:windowSoftInputMode=\\\"stateHidden|adjustResize\\\" 但是单纯设置windowSoftInputMode在布局方面

    2024年02月09日
    浏览(36)
  • uniapp,app端时input组件弹起系统键盘时,键盘会顶住页面导致页面会上移

    这个时候,我们可以修改键盘的弹出模式。 App平台软键盘弹出有 adjustResize|adjustPan 两种模式,默认为 adjustPan 模式,小程序平台只支持 adjustPan 模式,H5平台因不同浏览器而异 配置方式,在 pages.json 中配置 style 官方描述:修改系统键盘的模式

    2024年01月18日
    浏览(42)
  • 【Android】Dialog弹出软键盘时把布局顶起来的实现

    demo效果如下: 软键盘和EditText保持一定间距的实现思路,可以设置EditText 的 paddingBottom

    2024年01月20日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包