Flutter 开发问题总结

1.软键盘遮挡问题

场景:界面上有许多输入框 当软键盘弹起的使用遮挡 影响用户直观的输入
解决方式:android 端不要设置为全屏 全屏模式下 android:windowSoftInputMode="adjustResize"是失效的
让可滑动组件作为widget的父节点如listView或者SingleChildScrollView

2.SafeArea

Android 和 IOS都有状态栏,正确的使用SafeArea可以解决状态栏高度的问题

  color: Colors.white,
  child: SafeArea(
    child: Container(),
  ),
)  

3.Button使用问题

在Flutter中存在多个已经存在好的button这对于我们一般的开发使用无疑是方便的 但同时也带来了一些不方便的地方 。
(1)每个button的大小是固定的 没有宽高属性可以直接设置
解决:一种是外面嵌套一盒Sizebox box 用来固定大小 或者使用Theme来修改button默认的宽高
(2)使用FlatButtonIcon的时候Icon和child的间距是固定的没有办法修改
源码是这样的

    child: Row(
           mainAxisSize: MainAxisSize.min,
           children: <Widget>[
             icon,
              SizedBox(width: 8,),
             label,
           ],
         ),

关于FlatButton默认的padding问题 没有padding是要设置默认padding为0
建议修改:我们看一下button的源码
追踪的Button的实现层 发现是这样的

final Widget result = ConstrainedBox(
      constraints: widget.constraints,
      child: Material(
        elevation: _effectiveElevation,
        textStyle: widget.textStyle?.copyWith(color: effectiveTextColor),
        shape: effectiveShape,
        color: widget.fillColor,
        type: widget.fillColor == null ? MaterialType.transparency : MaterialType.button,
        animationDuration: widget.animationDuration,
        clipBehavior: widget.clipBehavior,
        child: InkWell(
          focusNode: widget.focusNode,//焦点
          canRequestFocus: widget.enabled,
          onFocusChange: _handleFocusedChanged,
          autofocus: widget.autofocus,
          onHighlightChanged: _handleHighlightChanged,//高亮改变时间 点下去的时候出发
          splashColor: widget.splashColor,
          highlightColor: widget.highlightColor,
          focusColor: widget.focusColor,
          hoverColor: widget.hoverColor,// 鼠标放置到上面的效果window有效
          onHover: _handleHoveredChanged,//鼠标放置到上面的回调
          onTap: widget.onPressed,
          onLongPress: widget.onLongPress,
          enableFeedback: widget.enableFeedback,
          customBorder: effectiveShape,
          child: IconTheme.merge(
            data: IconThemeData(color: effectiveTextColor),
            child: Container(
              padding: widget.padding,
              child: Center(
                widthFactor: 1.0,
                heightFactor: 1.0,
                child: widget.child,
              ),
            ),
          ),
        ),
      ),
    );

我们发现button其实就是对Material+InkWell做了封装完成了一个button的实现效果 我们完全可以使用Material+InkWell封装自己的button来实现自己的效果这可我么不仅可以随心的去除添加水波纹效果也可以不收系统参数的约束了

4.InputFormatters

简单使用
inputFormatters: [
BlacklistingTextInputFormatter(RegExp("[\u4e00-\u9fa5]")) //密码的时候去除中文字符
LengthLimitingTextInputFormatter(5)//长度限制
],
(1)在使用长度是碰到一个问题 Ios输入使用自带输入法输入文本的时候 文字会先在文本框中显示 然后在转成中文显示,导致到达长度中文不能输入问题(暂未解决) 建议在文本提交的时候判断 文本长度去做 不要显示中文长度 当然数字和密码还是可以的
(2)可以使用自定义InputFormatters来做一下简单限制
如限制小数点位数 或者限制输入的最大值

// 限制小数点位数和不能输入两个点
class LimitTextFormatter extends TextInputFormatter {
  //正常键盘限制几位小数
  int limitNum;

  LimitTextFormatter(this.limitNum);

  TextEditingValue formatEditUpdate(
      TextEditingValue oldValue, TextEditingValue newValue) {
    if (newValue.text.length == 0) {
      return newValue.copyWith(text: '');
    } else if (newValue.text.compareTo(oldValue.text) != 0) {
      if(newValue.text.contains(".")){
        if(newValue.text.indexOf(".") == newValue.text.length - limitNum -2){
          return oldValue;
        }
        //判断是否只有一个点
        if(newValue.text.indexOf(".")!=newValue.text.lastIndexOf(".")){
          return oldValue;
        }
      }
      return newValue;
    } else {
      return newValue;
    }
  }
}

5.依赖版本问题

在Flutter依赖版本时不建议使用^符号


image.png

^符号意味着你可以使用此插件的最新版本(大于等于当前版本)。这会导致什么问题呢?可能你前一天代码还能跑起来,今天就编译出错了。因为这些插件中包括Android、IOS的所用依赖环境配置,常见的就是新版本使用了AndroidX的依赖,但是还有些插件并没有使用AndroidX,导致了两者的冲突。
现在一般建议统一使用androidX版,或者统一不使用androidX版本

6.Flutter Android 打包

正常可以使用flutter build apk 发包
但是一般公司的项目都是分环境的 线上 测试 预发等等,在android是我们可以通过gradle的配置项把信息写入gradle的配置文件,线上打包是可以直接通过命令完成不同环境的打包,防止因为认为忘记修改带来的失误,那么在Flutter中我们可以通过配置不同的入口文件的方式,来通过不同命令是成不同环境的包,
下面试使用方式
(1).创建你需要环境的入口文件
如生产环境和测试环境

image.png

(2).重写入口文件
以main_test.dart为例
void main() {
AppConfigure.URL= TESTURL;//修改网络请求环境为测试
runApp(MyApp());
}
(3).这么修改完了以后我们的项目明显就运行不起来的 要想运行起来我们修改配置文件
添加IDE配置项
image.png

添加完配置项我们就可以直接运行不同环境的代码了(切换也很方便哦)
(4).配置打包命令
dev环境命令:flutter build apk -t lib/main_dev.dart
test环境命令:flutter build apk -t lib/main_test.dart
prod环境命令:flutter build apk -t lib/main_prod.dart
这样我们就可以随心的打包不同的环境了

7.setState() 问题

我们从开始的时候setState()真好用 这就更新了啊 ,到后面的这是啥子问题啊 ,为什么这样啊的一些问题
(1)setState() called after dispose()
这是在控制台偶然发现的 也并没有引发实质性的问题
产生原因:widget已经在dispose方法时销毁了,但在这之后却调用了setState方法,那么会发生此错误。比如定时器或动画回调调用setState(),但此时页面已关闭时,就会发生此错误。这个错误一般并不会程序崩溃,只是会造成内存的泄露。
解决方式:
1.及时停止计时器

  @override
  void dispose() {
    timer?.cancel();
    timer= null;
    super.dispose();
  }

2.在试听setState的时候 使用mounted

 if (mounted){
      setState(() {
        ...
      });

我么看一下mount而的源码

  BuildContext get context => _element;
  StatefulElement _element;

  /// Whether this [State] object is currently in a tree.
  ///
  /// After creating a [State] object and before calling [initState], the
  /// framework "mounts" the [State] object by associating it with a
  /// [BuildContext]. The [State] object remains mounted until the framework
  /// calls [dispose], after which time the framework will never ask the [State]
  /// object to [build] again.
  ///
  /// It is an error to call [setState] unless [mounted] is true.
  bool get mounted => _element != null;

BuildContext是Element的抽象类,你可以认为mounted 就是 context 是否存在。所以我们一般在使用带Context的地方也可以加一层mounted 的判断
8.关于loading的问题
先说一下过于loading实现的两种方式
1.Stack的方式实现
在我们的基类里面这么写

    return new Scaffold(
      backgroundColor: Colors.transparent,
      body: Stack(
        children: <Widget>[
          buildBody(context),
          Offstage(
            offstage: !isLoading,
            child: getLoadingWidget(""),
          )
        ],
      ),
    );

优势:(1)简单方便 只需要修改isLoading的值然后setState(){}就想了
(2)不需要context 任何时候都可以直接使用
缺点:(1)在已封装的控件中要使用loading是需要给父控件传值显示界面loading
(2) 每个界面都需要多渲染一层
2.loading dialog实现
做一个loading弹窗 时候弹出

  void showDialog() {
    /// 避免重复弹出
    if (mounted && !_isShowDialog){
      _isShowDialog = true;
      showDialog(
        context: context,
        barrierDismissible: false,
        builder:(_) {
          return WillPopScope(
            onWillPop: () async {
              // 拦截到返回键,证明dialog被手动关闭
              _isShowDialog = false;
              return Future.value(true);
            },
            child: ProgressDialog(hintText: "正在加载..."),
          );
        }
      );
    }

遇到的问题
(1)在initState()中请求接口需要弹出loading是的时候使用loadingdialog会抛出异常
原因:弹出一个DIalog的showDialog方法会调用Theme.of(context, shadowThemeOnly: true),而这个方法会通过inheritFromWidgetOfExactType来跨组件获取Theme对象。、
但是在_StateLifecycle为created 和 defunct 时是无法跨组件拿到数据的,也就是initState()时和dispose()后。所以错误信息提示我们在 didChangeDependencies 调用。
解决方案:使用 addPostFrameCallback
addPostFrameCallback回调方法在Widget渲染完成时触发,所以一般我们在获取页面中的Widget大小、位置时使用到。
在这里面使用loadingdialog

  @override
  void initState() {
    WidgetsBinding.instance.addPostFrameCallback((_){
      /// 接口请求
    });
  }

8.软键盘问题

获取软键盘状态

时候弹起 MediaQuery.of(context).viewInsets.bottom > 0
viewInsets.bottom就是键盘的顶部距离底部的高度,也就是弹起的键盘高度。如果你想实时过去键盘的弹出状态,配合使用didChangeMetrics。下面是实时获取

  @override
  void initState() {
    WidgetsBinding.instance.addObserver(this);
    super.initState();
  }

  @override
  void didChangeMetrics() {
    super.didChangeMetrics();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      print(MediaQuery.of(context).viewInsets.bottom);
      setState(() {
        widget.keyboardShowCallback
            ?.call(MediaQuery.of(context).viewInsets.bottom > 0);
      });
    });
  }

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

弹出软键盘

if (MediaQuery.of(context).viewInsets.bottom == 0){
  final focusScope = FocusScope.of(context);
  focusScope.requestFocus(FocusNode());
  Future.delayed(Duration.zero, () => focusScope.requestFocus(_focusNode));
}

关闭软件盘

FocusScope.of(context).requestFocus(FocusNode());
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容