• flutter简单的本地草稿箱功能


    请添加图片描述

    需求1:发帖退出时提示是否保存草稿
    需求2:每条草稿中可以保存多张图片(最多9张)或一条视频及三十来个其它参数
    需求3:每条草稿都是可以被覆盖的、可以点击删除
    需求4:草稿页面可以一键清空
    需求5:草稿随app删除一起没掉

    看到需求第一时间想到的就是存轻量级SharedPreferences中;
    行动:将图片转为base64,然后将base64转json从而实现将图片存入轻量级

    /*
      * Asset类型图片转base64(其它类型替换能获取字节的对象)
      * */
      imageToUint8List(List<Asset> imageList) async {
        Completer<List<String>> _completer = Completer<List<String>>();
        List<String> dataList = [];
        if(imageList.length > 0){
          for (int i = 0; i < imageList.length; i++) {
            Asset asset = imageList[i];
            ByteData byteData = await asset.getByteData();
            var imageBase64 = base64.encode(Uint8List.view(byteData.buffer));
            dataList.add(imageBase64);
          }
          _completer.complete(dataList);
        }
        return _completer.future;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    结果:行不通。转换加储存极易造成内存泄漏且耗时太多(一张图片都有可能10mb以上而需求更是多图)
    方法:
    1.在app的file目录下新建一个文件夹保存草稿箱时把图片、视频文件复制进去。
    2.把普通的字符串、数字等参数和图片、视频的文件路径封装转成json字符串一起存入轻量级
    封装工具

    import 'dart:convert';
    import 'dart:io';
    
    import 'package:test/common/sp_util.dart';
    import 'package:test/generated/l10n.dart';
    import 'package:test/jade/bean/DraftsBean.dart';
    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    import 'package:path_provider/path_provider.dart';
    
    class DraftsUtil{
      /*
      * 保存草稿箱提示框
      * */
      draftsSaveDialog(context, {Function callback,Function cancelCallback}) async {
        showDialog(
            context: context,
            barrierDismissible: false,
            builder: (BuildContext context) {
              return CupertinoAlertDialog(
                content: SingleChildScrollView(
                  child: ListBody(
                    children: <Widget>[
                      Padding(
                        child: Text(
                          S.current.tuichu,
                          style: TextStyle(fontSize: 15, color: Color(0xffea2020)),
                        ),
                        padding: EdgeInsets.only(top: 15),
                      ),
                      SizedBox(height: 10),
                      Padding(
                        child: Text(
                          '是否保存编辑到草稿箱?',
                          style: TextStyle(fontSize: 15, color: Color(0xff666666)),
                        ),
                        padding: EdgeInsets.only(bottom: 15),
                      )
                    ],
                  ),
                ),
                actions: <Widget>[
                  CupertinoDialogAction(
                    child: Text(
                      S.current.quxiao,
                      style: TextStyle(fontSize: 14, color: Color(0xff999999)),
                    ),
                    onPressed: () {
                      if(cancelCallback != null){
                        cancelCallback();
                      }
                      Navigator.pop(context, false);
                    },
                  ),
                  CupertinoDialogAction(
                    child: Text(
                      '保存并退出',
                      style: TextStyle(fontSize: 14, color: Color(0xff4ec6cc)),
                    ),
                    onPressed: () {
                      if (callback != null) {
                        callback();
                      }
                      Navigator.pop(context, false);
                    },
                  )
                ],
              );
            });
      }
    
      /*
      * 读取草稿列表数据
      * */
      readDraftsList(var userId){
        List<DraftsBean> draftsList = [];
        List<Map<dynamic, dynamic>> getDraftsList = SpUtil.getObjectList('${userId}draftsList');
    
        if (getDraftsList != null && getDraftsList.length > 0) {
          getDraftsList.forEach((element) {
            draftsList.add(DraftsBean(type: element['type'],paramsData: element['paramsData'],time: element['time']));
          });
        }
        return draftsList;
      }
    
      /*
      * 删除某一条草稿
      * */
      deleteDrafts(var userId,int index,{List<DraftsBean> draftsList}){
        if(draftsList == null){
          draftsList = [];
          List<Map<dynamic,dynamic>> _getDraftsList = SpUtil.getObjectList('${userId}draftsList');
          if (_getDraftsList != null && _getDraftsList.length > 0) {
            _getDraftsList.forEach((element) {
              draftsList.add(DraftsBean(
                  type: element['type'],
                  paramsData: element['paramsData'],
                  time: element['time']
              ));
            });
          }
        }
        var _paramsData = json.decode(draftsList[index].paramsData);
        List <dynamic> imagePathList = _paramsData['selectImageList'];
        String videoPath = _paramsData['selectVideoPath'];
        if(imagePathList != null && imagePathList.length>0){
          imagePathList.forEach((element) {
            var imageFile = File(element);
            if(imageFile.existsSync()){
              imageFile.deleteSync();
            }
          });
        }
        if(videoPath != null){
          var videoFile = File(videoPath);
          if(videoFile.existsSync()){
            videoFile.deleteSync();
          }
        }
        draftsList.removeAt(index);
        SpUtil.putObjectList('${userId}draftsList',draftsList);
      }
    
      //清空草稿箱
      clearDrafts(var userId) async {
        Directory libDir = await getExternalStorageDirectory();
        String draftsPath = libDir.path + '/draftsFiles';
        var directory = Directory(draftsPath);
        try{
          bool exists = await directory.exists();
          if(exists){
            directory.deleteSync(recursive: true);
          }
        }catch(e){
          print('删除文件夹出错:${e.toString()}');
        }
        SpUtil.remove('${userId}draftsList');
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140

    草稿箱参数对象

    class DraftsBean {
      int type; //草稿类型: 0普通发帖 1 A型发帖 2 B型发帖
      String paramsData;//保存的发帖参数 json字符串
      String time; //日期
    
      DraftsBean({this.type, this.paramsData,this.time});
    
      DraftsBean.fromJson(dynamic json) {
        type = json['type'];
        paramsData = json['paramsData'];
        time = json['time'];
      }
    
      Map<String, dynamic> toJson() {
        final map = <String, dynamic>{};
        map['type'] = type;
        map['paramsData'] = paramsData;
        map['time'] = time;
        return map;
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    引用

    //草稿箱提示框
      _backDrafts(context){
        if(logic.canSaveDrafts()){
          DraftsUtil().draftsSaveDialog(context,
              cancelCallback: (){
                Navigator.of(context).pop();
              },
              callback: () async {
                print('========点击了保存草稿');
                //当前时间
                DateTime nowTime = DateTime.now();
                var _dateFormatStr = DateUtil.formatDate(nowTime, format: DateFormats.y_mo_d);
    
                state.selectImagePathList.clear();
                state.selectVideoPath = null;
                List<DraftsBean> draftsList = DraftsUtil().readDraftsList(logic.userInfo.user.id);
    
                if(state.selectImageList.length>0){
                  state.selectImagePathList = await Utils().copyFile(state.selectImageList);
                }else if(state.videoPath != null){
                  List<String> videoPathList = await Utils().copyFile([File(state.videoPath)]);
                  state.selectVideoPath = videoPathList.first;
                }
    
                if(widget.draftsIndex != null ){
                  //从草稿箱进来的,传递了草稿的index,先删除再插入用来覆盖该条
                  DraftsUtil().deleteDrafts(logic.userInfo.user.id, widget.draftsIndex,draftsList: draftsList);
                  List<DraftsBean> latestDraftsList = DraftsUtil().readDraftsList(logic.userInfo.user.id);
                  latestDraftsList.insert(widget.draftsIndex, DraftsBean(
                      type: logic.state.type == 0? 1:2,
                      paramsData: json.encode(logic.paramsData()),
                      time: '$_dateFormatStr',
                  ));
                  SpUtil.putObjectList('${logic.userInfo.user.id}draftsList',latestDraftsList);
                }else{
                  draftsList.insert(0, DraftsBean(
                      type: logic.state.type == 0? 1:2,
                      paramsData: json.encode(logic.paramsData()),
                      time: '$_dateFormatStr',
                  ));
                  SpUtil.putObjectList('${logic.userInfo.user.id}draftsList',draftsList);
                }
                ATuiEventBusTool.shared().fire(CommonConfig.draftsPageRefresh);
                Navigator.of(context).pop();
              });
        }else{
          Navigator.of(context).pop();
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    草稿箱列表页面

    import 'dart:convert';
    import 'dart:io';
    import 'package:test/common/sp_util.dart';
    import 'package:test/jade/bean/DraftsBean.dart';
    import 'package:test/jade/configs/CommonConfig.dart';
    import 'package:test/jade/configs/PathConfig.dart';
    import 'package:test/jade/customWidget/EmptyWidget.dart';
    import 'package:test/jade/homePage/wantHave/WantPost.dart';
    import 'package:test/jade/homePage/wantHave/wantHaveLogic/WantHaveLogic.dart';
    import 'package:test/jade/homePage/wantHave/wantHaveLogic/WantHavePostBinding.dart';
    import 'package:test/jade/mine/drafts/DraftsUtil.dart';
    import 'package:test/jade/mine/draftsVideoItemView.dart';
    import 'package:test/jade/utils/JadeColors.dart';
    import 'package:test/jade/utils/Utils.dart';
    import 'package:test/model/user/user_info_model.dart';
    import 'package:test/pages/user_share/edit/push_share_edit.dart';
    import 'package:test/util/eventbustool.dart';
    import 'package:test/util/navigator_util.dart';
    import 'package:test/widget/custom_appbar.dart';
    import 'package:city_pickers/city_pickers.dart';
    import 'package:flutter/material.dart';
    import 'package:flutter_screenutil/flutter_screenutil.dart';
    import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
    import 'package:get/get.dart';
    import 'package:provider/provider.dart';
    
    class DraftsPage extends StatefulWidget{
      
      State<StatefulWidget> createState() {
        // TODO: implement createState
        return _DraftsPage();
      }
    }
    
    class _DraftsPage extends State<DraftsPage>{
    
      String _userId;
      List<DraftsBean> _draftsList = [];
      var _eventBus;
      
      void initState() {
        // TODO: implement initState
        super.initState();
        _userId = Provider.of<UserInfoModel>(context, listen: false).user?.id;
        _draftsList = DraftsUtil().readDraftsList(_userId);
        _eventBus = ATuiEventBusTool.shared().on().listen((event) {
          if(event == CommonConfig.draftsPageRefresh){
            _draftsList = DraftsUtil().readDraftsList(_userId);
            setState(() {});
          }
        });
      }
    
      
      void dispose() {
        // TODO: implement dispose
        if(_eventBus != null){
          _eventBus.cancel();
          _eventBus = null;
        }
        super.dispose();
      }
    
      
      Widget build(BuildContext context) {
        // TODO: implement build
        return Scaffold(
            backgroundColor: JadeColors.lightGrey,
            appBar: CustomAppBar(
              elevation: 0.5,
              leading: GestureDetector(
                onTap: () {
                  Navigator.of(context).pop();
                },
                child: Icon(
                  Icons.arrow_back_ios,
                  color: Colors.black,
                ),
              ),
              title: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Container(),
                  Container(
                    margin: EdgeInsets.only(right: 34.w),
                    child: Text(
                      '草稿箱',
                      style: TextStyle(color: Colors.black,fontSize: 38.sp,fontWeight: FontWeight.w400),
                    ),
                  ),
                  GestureDetector(
                    child: Container(
                      color: Colors.transparent,
                      padding: EdgeInsets.only(left: 25.w,top: 6.w,bottom: 6.w),
                      child: Text('清空',style: TextStyle(color: JadeColors.grey_2,fontSize:32.sp),),
                    ),
                    onTap: () async {
                      var bool = await Utils.commonDialog(context,"是否清空草稿?");
                      if (bool) {
                        DraftsUtil().clearDrafts(_userId);
                        setState(() {
                          _draftsList.clear();
                        });
                      }
                    },
                  )
                ],
              )
            ),
            body: _draftsList.length>0?_body(): EmptyLayout(description: '没有草稿~',),
        );
      }
    
      _body(){
        return Column(
          children: [
            Container(
              margin: EdgeInsets.only(left: 20.w,right: 20.w,top: 24.w,bottom: 40.w),
              alignment: Alignment.centerLeft,
              child: Text('草稿在APP卸载后会被删除,请及时发布。',style: TextStyle(color: JadeColors.grey_2,fontSize: 24.sp,),)
            ),
            Expanded(child: Container(
                margin: EdgeInsets.symmetric(horizontal: 20.w,),
                padding: EdgeInsets.only(bottom: 20.w),
                child: MasonryGridView.count(
                  crossAxisCount: 2,
                  itemBuilder: (BuildContext context, int index){
                    var data = _draftsList[index].paramsData;
                    var decodeData = json.decode(data);
                    return _itemView(index,decodeData);
                  },
                  mainAxisSpacing: 6.0,
                  crossAxisSpacing: 6.0,
                  itemCount: _draftsList.length,
                  shrinkWrap: true,
                )
            ))
          ],
        );
      }
    
    
      _itemView(int index,var paramsData){
        return GestureDetector(
          child: Container(
              decoration: BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.circular(10)
              ),
              child: Column(
                children: [
                  if(paramsData['selectImageList'] != null && paramsData['selectImageList'].isNotEmpty)
                    ClipRRect(
                      borderRadius: BorderRadius.only(topLeft: Radius.circular(10),topRight: Radius.circular(10)),
                      child: Image.file(
                          File(paramsData['selectImageList'][0]),
                          width: double.infinity,
                          height: index % 2 == 1?500.w : 400.w,
                          fit: BoxFit.cover,
                          frameBuilder: (context, child, frame, wasSynchronouslyLoaded){
                            if(wasSynchronouslyLoaded){
                              return child;
                            }
                            return AnimatedOpacity(
                              child: child,
                              opacity: frame == null ? 0 : 1,
                              duration: const Duration(seconds: 4),
                              curve: Curves.easeOut,
                            );
                          }
                      ),
                    ),
                  if(paramsData['selectVideoPath'] != null)
                    DraftsVideoItemView(mediaUrl: paramsData['selectVideoPath'],currentIndex: index,),
                  Container(
                      margin: EdgeInsets.only(left: 10.w,right: 10.w,top: 20.w,bottom: 20.w),
                      alignment: Alignment.centerLeft,
                      child: Text('${paramsData['articleContent']??''}',
                          style: TextStyle(fontSize: 26.sp,fontWeight: FontWeight.w600),maxLines: 2,overflow: TextOverflow.ellipsis)
                  ),
                  Container(
                      margin: EdgeInsets.only(left: 20.w,right: 20.w,bottom: 20.w),
                      child: Row(
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: [
                          Text('${_draftsList[index].time}',style: TextStyle(fontSize: 20.sp,color: JadeColors.grey)),
                          GestureDetector(
                            child: Container(
                              width: 32.w,
                              height: 32.w,
                              color: Colors.transparent,
                              padding: EdgeInsets.all(2),
                              child: Image.asset(PathConfig.iconDeleteGrey),
                            ),
                            onTap: () async {
                              var bool = await Utils.commonDialog(context,"是否删除草稿?");
                              if (bool) {
                                DraftsUtil().deleteDrafts(_userId,index,draftsList:_draftsList);
                                setState(() {});
                              }
                            },
                          )
                        ],
                      )
                  )
                ],
              )
          ),
          onTap: (){
            switch(_draftsList[index].type){
              case 0:
                NavigatorUtil.push(PushShareEdit(
                  draftsJson: _draftsList[index].paramsData,
                  draftsIndex: index,
                ));
                break;
              case 1:
                Get.to(() => WantPost(),
                    binding: WantHavePostBinding(), arguments: {
                      'type':0,
                      'draftsParamsJson': _draftsList[index].paramsData, //草稿箱json数据
                      'draftsIndex': index //草稿index
                    });
                break;
              case 2:
                Get.to(() => WantPost(),
                    binding: WantHavePostBinding(), arguments: {
                      'type':1,
                      'draftsParamsJson': _draftsList[index].paramsData, //草稿箱json数据
                      'draftsIndex': index //草稿index
                    });
                break;
            }
          },
        );
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
  • 相关阅读:
    对Transformer中的Attention(注意力机制)的一点点探索
    slambook2+ubuntu20.04(第九章-第十章)
    常用git 打tag命令
    24--Excel图表与数据看板
    LeetCode47-全排列II-剪枝逻辑
    复习单片机:矩阵按键(内含1 矩阵按键介绍+2 硬件设计+3 软件设计+4原始代码+5 实验现象)
    Servlet | HttpServletRequest接口、通过request接口获取请求参数
    JDK、JRE、JVM三者之间的关系
    云备份客户端——目录遍历模块以及文件备份模块
    PL/SQL 集合
  • 原文地址:https://blog.csdn.net/androidhyf/article/details/132875872