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: [
             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: [
          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());

你可能感兴趣的:(Flutter 开发问题总结)