如何在键盘外观上为 Flutter 布局设置动画

Posted

技术标签:

【中文标题】如何在键盘外观上为 Flutter 布局设置动画【英文标题】:How to animate Flutter layout on keyboard appearance 【发布时间】:2019-07-01 00:14:23 【问题描述】:

我正在构建一个 Flutter 应用,主要用于 ios

我的一个视图有一个文本字段,当您点击它时会出现 iOS 键盘。问题是 - 布局不会像在原生 iOS 应用程序中那样平滑地变化。相反,它会在键盘打开动画结束之前立即跳转到最终可用的屏幕高度。

我尝试将我的 SafeArea 元素包装在 AnimatedSizeAnimatedContainer 中 - 它没有帮助。

我的布局代码:

SafeArea(child:
  Column(children:[
    TextField(...)
  ])
)

如何让键盘出现时布局平滑调整大小?

预期:

实际

【问题讨论】:

你能用 gif/截图更新你的问题吗? @miguelpruivo 附上 gif。由于 FPS 速率较低,差异并不那么明显,但我希望很清楚我想要实现的目标 - 出现键盘时平滑调整 SafeArea 的大小。 您是否尝试将输入字段仅包装在 AnimatedContainer 中?您可能想用一些代码编辑您的问题。发生的情况是,当您启动键盘时,键盘正在使用底部的viewInsets,并且正在重建视图以匹配更改,但实际上并没有与之一起动画。不过这是个好问题。 调试模式有可能吗?尝试发布 apk 或使用 --profile 你可以在github上查看这个问题https://github.com/flutter/flutter/issues/19279这可能会有所帮助 【参考方案1】:

您需要使用keyboard_visibility 包并使用它来触发您的AnimatedContainer 或AnimatedPadding

bool _isKeyboardActive = false;
@override
void initState() 
  super.initState();
  //add keyboard visibility Listener
  KeyboardVisibility.onChange.listen((event) 
    setState(() 
      _isKeyboardActive = event;
    );
  );



Widget build(BuildContext context)
    return AnimatedContainer(
       width: _isKeyboardActive ? 200 : MediaQuery.of(context).size.width,
       height: 60,
       color: Colors.red,
       duration: Duration(milliseconds: 600)
    )

以此为基础。

【讨论】:

【参考方案2】:

使用 AnimatedPadding Widget 可以实现所需的输出,虽然这并不完美,但总比没有好:d

Open issue as of 15/03/21, for reference

import 'dart:math';

import 'package:flutter/material.dart';

final Color darkBlue = Color.fromARGB(255, 18, 32, 47);

void main() 
  runApp(MyApp());


class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return MaterialApp(
      theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: MyWidget(),
        ),
      ),
    );
  


class MyWidget extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return SafeArea(
      bottom: false,
      child: Scaffold(
          // !!! Important part > to disable default scaffold insets
          resizeToAvoidBottomInset: false,
          appBar: AppBar(
            title: Text("Appbar Title"),
          ),
          body: Stack(
            children: [
              Scrollbar(
                child: ListView.builder(
                  padding: EdgeInsets.all(0.0),
                  itemCount: 30,
                  itemBuilder: (context, i) 
                    return Container(
                      height: 100,
                      width: double.infinity,
                      color: Colors
                          .primaries[Random().nextInt(Colors.primaries.length)],
                    );
                  ,
                ),
              ),
              Align(
                alignment: Alignment.bottomLeft,
                child: AnimatedPadding(
                    padding: MediaQuery.of(context).viewInsets,
                    // You can change the duration and curve as per your requirement:
                    duration: const Duration(milliseconds: 200),
                    curve: Curves.decelerate,
                    child: InputField()),
              )
            ],
          )),
    );
  


class InputField extends StatefulWidget 
  InputField(Key key) : super(key: key);

  @override
  _InputFieldState createState() => _InputFieldState();


class _InputFieldState extends State<InputField> 
  @override
  Widget build(BuildContext context) 
    return Container(
      color: Colors.grey[100],
      padding: const EdgeInsets.symmetric(vertical: 6),
      child: Row(
        children: [
          SizedBox(
            width: 60,
            child: Icon(Icons.add_a_photo),
          ),
          Flexible(
            child: TextField(
              style: Theme.of(context).textTheme.bodyText1,
              decoration: InputDecoration(
                border: InputBorder.none,
                hintText: 'Enter text...',
              ),
            ),
          ),
          SizedBox(
            width: 60,
            child: Icon(Icons.send),
          ),
        ],
      ),
    );
  

输出 ->

【讨论】:

【参考方案3】:

我使用类似的东西:

AnimatedPadding(
        padding: MediaQuery.of(context).viewInsets,
        duration: const Duration(milliseconds: 100),
        curve: Curves.decelerate,
        child: ....
    )

这会根据 viewInsets(软件键盘高度)为填充设置动画。

【讨论】:

虽然这段代码可以解决问题,但including an explanation 确实有助于提高帖子的质量。请记住,您是在为将来的读者回答问题,而这些人可能不知道您提出代码建议的原因【参考方案4】:

您可以使用此包keyboard_visibility 并收听keyboard visibility。然后你可以给出你的逻辑来实现你的功能,就像你可以缩短主页container 高度一样。那并不完美。但我认为这是目前唯一的方法。

【讨论】:

【参考方案5】:

您应该尝试像这样设置resizeToAvoidBottomPadding: false

return Scaffold(
      key: _scaffoldKey,
      resizeToAvoidBottomPadding: false,

【讨论】:

以上是关于如何在键盘外观上为 Flutter 布局设置动画的主要内容,如果未能解决你的问题,请参考以下文章

Flutter 中的动画布局更改

如何在曲线上为实体球体设置动画

flutter-动画

如何在画布上为路径设置动画 - android

Flutter:如何在 google_maps_flutter 中为标记图标设置动画?

如何在 Android 中的另一个视图上为文本设置动画?