Flutter踩坑日记(持续更新...)

5,380 阅读5分钟

标题后面的时间是我用来记录自己更新上去的时间,不用在意

设置组件为中文(2020.05.20)

刚开始去使用一些弹窗组件(例如时间选择器什么的),发现里面的确定和取消都是英文,就去百度了一下方法。

改配置文件

在pubspec.yaml文件的对应位置加上

  flutter_localizations:
    sdk: flutter

修改主入口

在主入口函数添加(这里还需要引入package:flutter_localizations/flutter_localizations.dart)

import 'package:flutter_localizations/flutter_localizations.dart'; 
      //设置组件为中文
      localizationsDelegates: [
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
      ],
      supportedLocales: [
        //此处
        const Locale('zh', 'CH'),
        const Locale('en', 'US'),
      ],
      locale: Locale('zh'),

图片上传(2020.05.20)

这个坑,花了我整整一天的时间(其实就是电脑卡,10分钟一小卡,30分钟一大卡,不好调试)。

插件

image_picker

开始

直接贴代码

import 'dart:collection';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:image_picker/image_picker.dart';
import 'package:flutter/material.dart';
import '../../base/Http.dart';//这个是自己封装后的http
import 'package:http_parser/http_parser.dart';

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  File _image;

    //照相
  Future getImage() async {
    var image = await ImagePicker.pickImage(source: ImageSource.camera);
    setState(() {
      uploadImgFunc(image);
      _image = image;
    });
  }
    //从相机选择
  Future openGallery() async {
    var image = await ImagePicker.pickImage(source: ImageSource.gallery);
    setState(() {
      uploadImgFunc(image);
      _image = image;
    });
  }

  uploadImgFunc(File image) async {
    String path = image.path;//文件路径
    String name = path.substring(path.lastIndexOf("/") + 1, path.length);//文件名
    String suffix = name.substring(name.lastIndexOf(".") + 1, name.length);//文件后缀
    FormData formData = FormData.fromMap({
      "uploadFile":await MultipartFile.fromFile(
        path,
        filename: name,
        contentType:MediaType('image',suffix)//contentType这个参数看情况,下面会讲到
      ),
    });
    Dio dio = new Dio();
    var result = await dio.post<String>("接口", data: formData);
    print(result);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Image Picker Example'),
      ),
      body: Center(
        child: _image == null ? Text('No image selected.') : Image.file(_image),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: getImage,
        tooltip: 'Pick Image',
        child: Icon(Icons.add_a_photo),
      ),
    );
  }
}

上面就是一个例子了,现在开始讲解。

首先,我们需要引入image_picker这个包,在pubspec.yaml文件里面添加对应项

然后在对应文件引入

import 'dart:io';//io操作
import 'package:dio/dio.dart';
import 'package:image_picker/image_picker.dart';

写调用方法(这里以拍照为例)

  Future getImage() async {
    var image = await ImagePicker.pickImage(source: ImageSource.camera);//这里用到了对应插件
    setState(() {
      uploadImgFunc(image);
      _image = image;//_image需要在外层定义
    });
  }

写上传方法(重点)

  uploadImgFunc(File image) async {
    String path = image.path;
    String name = path.substring(path.lastIndexOf("/") + 1, path.length);
    String suffix = name.substring(name.lastIndexOf(".") + 1, name.length);
    FormData formData = FormData.fromMap({
      "uploadFile":await MultipartFile.fromFile(//这里记得加await
        path,
        filename: name,
        contentType:MediaType('image',suffix)
      ),
    });
    Dio dio = new Dio();
    var result = await dio.post<String>('接口', data: formData);
    print(result);
  }

dio3.0之后就使用MultipartFile不再使用UploadFileInfo。

MediaType这个需要额外引一个头文件,不然会报错

import 'package:http_parser/http_parser.dart';

假如,你们的后端是可以用二进制的方法来上传的话,

contentType:MediaType('image',suffix)

上面那一行可以去掉,不然的话一定要加上,不然后端如果是判断文件类型的话可能就过不了。我就是因为这个卡了很久......

ios真机调试的时候用相机(我同事说的)好像会闪退,因为我没有mac系统,也没钱,这里找个了文章。大家看看吧。

官网文档好像也有说,之前没看仔细。需要在配置文件里面加东西

获取元素位置(2020.05.20)

现在,我要获取某个元素的位置,然后按照位置来定义一个弹窗(showMenu)的位置,例如下图

定义一个key

GlobalKey positionKey = GlobalKey();

在需要定位的元素上使用

Text(
      '其他',
      key: positionKey,
      textAlign: TextAlign.center,
      style: TextStyle(
        height: 2.5,
        color: Color.fromRGBO(255, 106, 0, 1),
        fontSize: ScreenAdaper.size(30),
   )

使用key来获取位置

在函数内去获取

    RenderBox renderBox =
        positionKey.currentContext.findRenderObject();
    var offset = renderBox.localToGlobal(Offset.zero);
    print(offset.dx)//横坐标
    print(offset.dx)//纵坐标

ios数字软键盘没有'完成'按钮(2020.05.20)

当我们把输入框(TextField)的类型(keyboardType)设置number时,Android和ios就会弹出数字键盘而不是英文键盘,但是ios数字软键盘是没有所谓的完成按钮(就是点击后软键盘会收回去)。这时候,我们就需要用到一个插件了

keyboard_actions

这是一个可以解决上面问题的插件,如何引入同上面提过的。

使用

在需要用到的组件内先定义一个基础设置,这里的actions里面KeyboardAction有很多设置,推荐去看一下官方文档

  KeyboardActionsConfig _buildConfig(BuildContext context) {
    return KeyboardActionsConfig(
      keyboardActionsPlatform: KeyboardActionsPlatform.ALL,
      keyboardBarColor: Colors.grey[200],
      nextFocus: true,
      actions: [
        KeyboardAction(
          focusNode: _nodeNumberNode,//下面会提到
          toolbarButtons: [
            (node) {
              return GestureDetector(
                onTap: () => node.unfocus(),
                child: Container(
                  padding: EdgeInsets.fromLTRB(8, 8, 16, 8),
                  child: Text(
                    "完成",
                    style: TextStyle(color: Colors.black),
                  ),
                ),
              );
            },
          ],
        ),
      ],
    );
  }

然后,我们需要定义一个FocusNode来对应设置和输入框

final FocusNode _nodeNumberNode = FocusNode();

输入框设置,需要包一层KeyboardActions

KeyboardActions(
    config: _buildConfig(context),//上面定义的那个基础设置
    chlid:Container(
        child:TextField(
            focusNode: _nodeNumberNode,
            keyboardType: TextInputType.number,
            textInputAction: TextInputAction.done,
            decoration: InputDecoration(
                hintText: '请输入',
                border: InputBorder.none,
            ),
            autofocus: false,
            onChanged: (value) {
                setState(() {
                    //赋值操作
                });
            },
        )
    )
)

这样就可以了,不过有个要注意的点,然后你的输入框是放在ListView里面的话,需要给ListView加一个属性

shrinkWrap: false,

showDialog视图数据刷新不及时(2020.05.21)

使用showDialog后,通过setState()无法更新当前dialog。因为dialog其实是另一个页面,准确地来说是另一个路由,因为dialog的关闭也是通过navigator来pop的,所以它的地位跟你当前主页面一样,所以我们需要使用另外的方式来实现。

我们先看看showDialog的最基本写法

showDialog(
    context: context,
    builder: (context) {
      return AlertDialog(
        ...
      );
    }
);

第一种方法

解决问题后的写法

showDialog(
    context: context,
    builder: (context) {
      return StatefulBuilder(//这里需要先使用StatefulBuilder
        builder: (context, state) {//多了个state
          return AlertDialog(
            title: Text('弹窗标题'),
            content: Container(
              height: ScreenAdaper.height(200),//ScreenAdaper这个是自己的屏幕适配,大家可以直接使用数字的
              child: Column(
                children: <Widget>[
                  InkWell(
                    child: Row(
                      children: <Widget>[
                       Image.asset(
                            'images/task_leadicon.png',
                            width:ScreenAdaper.height(60),
                        )
                      ],
                    ),
                    onTap: () async {
                      state(() {//这个是重点
                        //...这里进行赋值操作
                      });
                    },
                  ),
                ],
              ),
            ),
            actions: <Widget>[
              FlatButton(
                child: Text('确认'),
                onPressed: () async {
                  Navigator.pop(context);
                },
              ),
              FlatButton(
                child: Text('取消'),
                onPressed: () {
                  Navigator.pop(context);
                },
              ),
            ],
          );
        },
      );
    });

第二种方法

这个还有另外一种写法,就是自己再去定义一个弹窗,不过这种我没有试过,这里直接贴别人的代码

showDialog(
    context: context,
    builder: (context) {
        String label = 'test';
        return DialogContent(
            label: label,
        );
    });
class DialogContent extends StatefulWidget {
  final String label;
  DialogContent({Key key, this.label}) : super(key: key);
  @override
  State<StatefulWidget> createState() => DialogContentState();
}
class DialogContentState extends State<DialogContent> {
  String label = '';
  @override
  void initState() {
    super.initState();
    label = widget.label;
  }
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      child: Text(label),
      onTap: () {
        setState(() {
          label = 'test9';
        });
      },
    );
  }
}

上面的坑都是我自己在开发遇到并且解决了的,如果你也遇到这些坑,而且我提的方法也有用的话,给个赞吧!!!如果没有起到作用,能否在评论区留下有用的方法