flutter开发的一些技巧

  • gradle国内下载

http://mirrors.cloud.tencent.com/gradle/gradle-7.5-all.zip
  • 注意事项

- 请不要在 initState()  dispose() 里面 setState(),因为这里 context 不存在

- 引用的下拉刷新插件 pull_to_refresh 同样存在 1 的问题 源码: https://github.com/peng8350/flutter_pulltorefresh/blob/master/lib/src/smart_refresher.
  dart
static RefreshConfiguration of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(RefreshConfiguration);
}
- 只要是使用了 controler 句柄的页面,请在 dispose() 里面销毁

- 请尽量使用实体数据模型

- 请尽量定义页面参数附上默认值

- 组件/文本/元素/图片 不要因为默认跟设计稿一致就不设置值,必须设置设计稿上的默认值,否则不同机型显示效果不一致

- 关于打包说明

谷歌flutter build apk命令运行时会自动使用R8压缩,解决方法是加上参数--no-shrink,目前暂时的解决方法。

flutter build apk --release --no-shrink
  • 关于退出 app

只能在安卓上使用 SystemChannels.platform.invokeMethod('SystemNavigator.pop'); 退出程序

ios 不支持此操作: https://github.com/flutter/flutter/issues/18331
  • 关于 ios pod install 失败

删除ios文件夹文件:

Podfile.lock
Pods
Flutter\App.framework
Flutter\Flutter.framework
Flutter\Flutter.podspec

# 然后在执行 强制安装
pod install --verbose
  • 关于支付宝支付

# 使用 ios 版本的 tobias 插件会和 UTDID 依赖冲突

# 解决办法:
# 先运行项目,自动下载依赖(运行会失败,报错冲突UTDID,不用管)
# 然后,下载兼容版本的sdk https://opendocs.alipay.com/open/54/104509 覆盖到目录
ios/Pods/AlipaySDK-iOS
# 重修运行 即可
  • 读取粘贴板

/// 混入 WidgetsBindingObserver
/// 实现 didChangeAppLifecycleState()
@override
void didChangeAppLifecycleState(AppLifecycleState state) async {
    print("---app state: ${state}");
    switch (state) {
        case AppLifecycleState.inactive: // 处于这种状态的应用程序应该假设它们可能在任何时候暂停。
        break;
        case AppLifecycleState.resumed: // 应用程序可见,前台
        /// 检测剪切板
        copySearchShowDo(context: context);
        break;
        case AppLifecycleState.paused: // 应用程序不可见,后台
        break;
        case AppLifecycleState.detached:
        break;
    }
}
/// 读取粘贴板的注意 copySearchShowDo() 核心代码
/// 测试有时候一次无法读取数据,这里多次读取数据
import 'package:flutter/services.dart';

for (var i = 0; i < 12; i++) {
    ClipboardData data = await Clipboard.getData('text/plain');
    if (data?.text != null) {
        print('copy num ${i}');
        thisText = data?.text.toString();
        break;
    }
}
  • webweiew交互 使用flutter_webview_plugin兼容性更高

import 'dart:async';
import 'dart:io';
import 'dart:convert' as convert;

import 'package:flutter/material.dart';
import 'package:flutter_tk/common.dart';
import 'package:flutter_tk/models/MoldEn.dart';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
import 'package:webview_flutter/webview_flutter.dart' as webview_flutter;

// webview 页
class WebViewWid extends StatefulWidget {
  /// 这里的参数有:
  /// 跳转地址 params['webUrl'][0]
  final params;
  WebViewWid({this.params}) : super();

  @override
  State<StatefulWidget> createState() {
    return WebViewWidState();
  }
}

class WebViewWidState extends State<WebViewWid> with WidgetsBindingObserver {
  FlutterWebviewPlugin flutterWebviewPlugin = new FlutterWebviewPlugin();
  StreamSubscription<WebViewStateChanged> _onStateChanged;
  String title = '加载...';
  WebViewWidState() : super();
  @override
  void dispose() {
    _onStateChanged?.cancel();
    // flutterWebviewPlugin?.hide();
    flutterWebviewPlugin?.close();
    flutterWebviewPlugin?.dispose();
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void initState() {
    super.initState();
    _onStateChanged = flutterWebviewPlugin.onStateChanged
        .listen((WebViewStateChanged state) async {
      if (mounted) {
        String _title =
            await flutterWebviewPlugin.evalJavascript('document.title');
        setState(() {
          title = _title;
        });
      }
    });
    flutterWebviewPlugin.onProgressChanged.listen((progress) {
      print(progress);
      setState(() {
        lineProgress = progress;
      });
    });

    /// 检测生命周期
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) async {
    switch (state) {
      case AppLifecycleState.resumed: // 应用程序可见,前台
        try {
          flutterWebviewPlugin?.show();
        } catch (e) {}
        break;
    }
  }

  /// [js通讯通道] js调用
  ///
  /// let str = '';
  /// str = JSON.stringify({
  /// "mold": "list",
  /// "info": "taobao",
  /// });
  /// Print.postMessage(str);
  Set<JavascriptChannel> getJsChannel({@required BuildContext context}) {
    final Set<JavascriptChannel> jsChannels = [
      JavascriptChannel(
          name: 'Print',
          onMessageReceived: (JavascriptMessage message) async {
            print('---message.message');
            print(message.message);
            // 转为实体
            Map<String, dynamic> _map = convert.json.decode(message.message);
            MoldEn ret = MoldEn.fromJson(_map);
            print('---ret');
            print(ret);

            /// 执行js
            // WebViewWidState des = WebViewWidState();
            // String jscode = 'alert("${ret.mold}");';
            // print('---jscode');
            // print(jscode);
            // des.flutterWebviewPlugin.evalJavascript(jscode);
            if (ret?.mold != null && ret?.info != null) {
              flutterWebviewPlugin?.hide();
              await Common.toJumpPage(context: context, mod: {
                "mold": ret.mold,
                "info": ret.info,
              });
              flutterWebviewPlugin?.show();
            }
          }),
    ].toSet();
    return jsChannels;
  }

  double lineProgress = 0.0;
  @override
  Widget build(BuildContext context) {
    try {
      flutterWebviewPlugin?.show();
    } catch (e) {}
    String url = Uri.decodeComponent(widget.params['webUrl'][0] ?? '');
    print("---url:${url}");
    return Scaffold(
      appBar: AppBar(
        title: Text("${title == null ? '' : title}"),
        // actions: [Text('关闭')],
        elevation: 0,
        brightness: Brightness.light,
        backgroundColor: Colors.white,
        bottom: PreferredSize(
          child: _progressBar(lineProgress, context),
          preferredSize: Size.fromHeight(3.0),
        ),
        actions: [
          GestureDetector(
            behavior: HitTestBehavior.translucent,
            onTap: () {
              Common.toJumpPage(context: context, url: '');
            },
            child: Container(
              alignment: Alignment.center,
              padding: EdgeInsets.only(right: Common.width(26)),
              child: Container(
                alignment: Alignment.center,
                width: Common.width(100),
                height: Common.width(95),
                child: Text(
                  '关闭',
                  style: TextStyle(fontSize: Common.getFontSize(26)),
                ),
              ),
            ),
          )
        ],
      ),
      body: WillPopScope(
        onWillPop: () async {
          try {
            flutterWebviewPlugin?.show();
          } catch (e) {}
          if (await flutterWebviewPlugin.canGoBack()) {
            flutterWebviewPlugin.goBack();
            return false;
          } else {
            return true;
          }
        },
        child: true // Platform.isAndroid ==
            ? WebviewScaffold(
                url: url,
                javascriptChannels: getJsChannel(context: context),
              )
            : webview_flutter.WebView(
                initialUrl: url,
                javascriptMode: webview_flutter.JavascriptMode.unrestricted,
              ),
      ),
    );
  }

  _progressBar(double progress, BuildContext context) {
    return LinearProgressIndicator(
      backgroundColor: Colors.white70.withOpacity(0),
      value: progress == 1.0 ? 0 : progress,
      valueColor: new AlwaysStoppedAnimation<Color>(Colors.blue),
    );
  }
}
  • 安卓端下载和升级 r_upgrade 插件

import 'dart:convert' show json;

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_custom_dialog/flutter_custom_dialog.dart';
import 'package:flutter_tk/common.dart';
import 'package:flutter_tk/models/BaseEn.dart';
import 'package:flutter_tk/models/UpdateAppEn.dart';
import 'package:flutter_tk/service/home.dart';
import 'package:flutter_tk/store/StoreConfig.dart';
import 'package:giffy_dialog/giffy_dialog.dart';
import 'package:package_info/package_info.dart';
import 'package:provider/provider.dart';
import 'package:r_upgrade/r_upgrade.dart';

/// 检测app升级,先请求下载地址是不是有新版,对比当前版本号
/// 如果有添加监听下载/开始下载安装包
/// 会在状态管理里面新增一个 下载状态,通过有一个下载就不提示了
Future<UpdateAppEnData> checkAppInfo({
  @required BuildContext context,
  bool backGroundUpload = false, // 静默升级,在首页使用
}) async {
  /// 获取升级数据,如果有新版本对比升级版本就提示,点击下载时候
  print('---检测升级数据_data');
  if (Platform.isAndroid == false) {
    print('---非安卓平台不升级');
    return null;
  }
  UpdateAppEnData _data = await getVersion();
  print(_data);
  if (_data?.app_version != null &&
      _data?.download_url != null &&
      _data?.app_version != "" &&
      _data?.download_url != "") {
  } else {
    if (backGroundUpload == false) {
      Common.toast('暂未更新');
    }
    return null;
  }

  /// 检测版本号
  DownloadInfo _doloadInfo =
      Provider.of<StoreConfig>(context, listen: false).doloadInfo;
  if (_doloadInfo != null) {
    // 如果暂停下载请继续下载
    if (_doloadInfo.status == DownloadStatus.STATUS_PAUSED) {
      await RUpgrade.upgradeWithId(_doloadInfo.id);
      Common.toast('继续下载');
      return null;
    }
    if (_doloadInfo.status == DownloadStatus.STATUS_RUNNING) {
      Common.toast('正在下载中');
      return null;
    }
    if (_doloadInfo.status == DownloadStatus.STATUS_PENDING) {
      Common.toast('等待下载');
      return null;
    }
    if (_doloadInfo.status == DownloadStatus.STATUS_FAILED) {
      Common.toast('下载失败');
      Provider.of<StoreConfig>(context, listen: false).setDownloadInfo(null);
      return null;
    }
    if (_doloadInfo.status == DownloadStatus.STATUS_CANCEL) {
      Provider.of<StoreConfig>(context, listen: false).setDownloadInfo(null);
      return null;
    }
    if (_doloadInfo.status == DownloadStatus.STATUS_SUCCESSFUL) {
      Common.toast('下载成功');
      if (_doloadInfo.maxLength != _doloadInfo.currentLength) {
        Common.toast('下载失败 请尝试重新打开app');
        return null;
      }
      Provider.of<StoreConfig>(context, listen: false).setDownloadInfo(null);
      RUpgrade.install(_doloadInfo.id);
      return null;
    }
    Common.toast('请重新打开app升级');
    return null;
  }
  PackageInfo _packageInfo = await PackageInfo.fromPlatform();

  /// 比较大小,值保留小数点后面2位
  List<String> _temp = _data.app_version.split('.'); // 0.1|1|3.2.2|4.44
  double app_version = 0;
  if (_temp.length <= 2) {
    app_version = double.parse(_data.app_version);
  } else {
    app_version = double.parse("${_temp[0]}.${_temp[1]}");
  }
  //
  double package_version = 0;
  List<String> _temp2 = _packageInfo.version.split('.');
  print('length: ${_temp2.length}');
  if (_temp2.length <= 2) {
    package_version = double.parse(_packageInfo.version);
  } else {
    package_version = double.parse("${_temp2[0]}.${_temp2[1]}");
  }
  // DEBUG: 测试升级
  // app_version = 99.0;
  print('升级版本: ${app_version}-${package_version}');
  if (app_version <= package_version) {
    if (backGroundUpload == false) {
      Common.toast('暂未更新');
    }
    return null;
  }

  /// 强制静默更新
  if (backGroundUpload == true && _data?.is_force != null) {
    Common.toast('新版本升级后台下载中');
    beginUpgrade(context: context, updateData: _data);
    // 进度显示弹窗
    showLoading(context: context, updateData: _data);
    return _data;
  }

  /// 弹窗提示
  YYDialog().build(context)
    ..width = Common.width(600)
    ..borderRadius = Common.width(24)
    ..barrierDismissible = false
    ..dismissCallBack = () {
      if (_data?.is_force != null && _data?.is_force == 1) {
        Common.toast('下载中 下载完成即可安装');
        beginUpgrade(context: context, updateData: _data);
      }
    }
    ..text(
      padding: EdgeInsets.all(Common.width(18)),
      text: '有新版本! ${_data.app_version}',
      color: Colors.black,
      fontSize: 18.0,
      fontWeight: FontWeight.w500,
    )
    ..widget(Container(
      height: Common.width(300),
      padding: EdgeInsets.only(right: Common.width(18), left: Common.width(18)),
      child: ListView(
        children:

            /// 更新内容
            _data.update_logs.length > 0
                ? _data.update_logs.map((e) {
                    return Container(
                      child: Text('${e.content}'),
                    );
                  }).toList()
                : [
                    Container(
                      child: Text('修复部分已知bug.'),
                    )
                  ],
      ),
    ))
    ..doubleButton(
      withDivider: true,
      height: Common.width(112),
      padding: EdgeInsets.only(top: Common.width(18)),
      gravity: Gravity.center,
      text1: _data?.is_force != null && _data?.is_force == 1 ? '强制更新' : '取消',
      color1: Colors.black38,
      fontSize1: Common.getFontSize(28),
      isClickAutoDismiss: false,
      onTap1: () {
        Navigator.of(context, rootNavigator: true).pop();
        if (_data?.is_force != null && _data?.is_force == 1) {
          beginUpgrade(context: context, updateData: _data);
        }
      },
      text2: '下载并更新',
      color2: Color(0xffF15343),
      fontSize2: Common.getFontSize(28),
      onTap2: () async {
        Navigator.of(context, rootNavigator: true).pop();
        beginUpgrade(context: context, updateData: _data);
      },
    )
    ..show();
  return _data;
}

/// 执行升级操作,提交下载任务
Future<void> beginUpgrade(
    {@required BuildContext context, @required UpdateAppEnData updateData}) {
  /// 监听升级,当升级完成,调用安装
  RUpgrade.stream.listen((DownloadInfo info) async {
    /// 同步数据到状态
    Provider.of<StoreConfig>(context, listen: false).setDownloadInfo(info);
    // 下载完成
    if (info.status == DownloadStatus.STATUS_SUCCESSFUL) {
      if (info.maxLength != info.currentLength) {
        Common.toast('下载失败 请尝试重新打开app');
        return;
      }
      // 安装
      Provider.of<StoreConfig>(context, listen: false).setDownloadInfo(null);
      RUpgrade.install(info.id);
    }
    if (info.status == DownloadStatus.STATUS_PAUSED) {
      Common.toast('暂停下载');
    }
    if (info.status == DownloadStatus.STATUS_FAILED) {
      Common.toast('下载失败');
      Provider.of<StoreConfig>(context, listen: false).setDownloadInfo(null);
    }
    if (info.status == DownloadStatus.STATUS_CANCEL) {
      Common.toast('取消下载');
      Provider.of<StoreConfig>(context, listen: false).setDownloadInfo(null);
    }
    if (info.status == DownloadStatus.STATUS_PENDING) {
      Common.toast('等待下载');
    }
    // print("percent:${info.percent}"); // 36.33 保留2位的百分比
  });

  /// 开始升级
  upgradeApk(
    context: context,
    app_version: updateData.app_version,
    download_url: updateData.download_url,
  );
  Common.toast('下载中更新中 下载完成即可安装');
  return null;
}

/// 获取新版的下载地址,如果没有新版本返回空
Future<UpdateAppEnData> getVersion() async {
  /// DEBUG: 测试升级数据
  // Map<String, dynamic> response = json.decode(
  //     '{"state":1,"data":{"is_force":0,"app_version":"1.2","download_url":"http:\/\/www.51qhs.com\/static\/app\/4.2.apk","app_type":"android","update_logs":[{"content":"测试升级"}]},"msg":""}');
  // print('------response');
  // print(response);
  // BaseEn ret = BaseEn.fromJson(response);

  dynamic response = await updateApp();
  BaseEn ret = BaseEn.fromJson(response);
  if (ret.state != 1) {
    return null;
  }
  if (ret.state == 1) {
    UpdateAppEn ret2 = UpdateAppEn.fromJson(response);
    if (ret2.state == 1 && ret2?.data?.download_url != null) {
      return ret2.data;
    }
  }
  return null;
}

/// 开始下载apk并安装
Future<num> upgradeApk({
  @required BuildContext context,
  @required String app_version,
  @required String download_url,
}) async {
  int id = await RUpgrade.upgrade(download_url,
      fileName: 'app_tk-${app_version}.apk', isAutoRequestInstall: true);
  return id;
}

/// 显示升级loading,显示倒计时和百分比
/// 允许关闭
Future<void> showLoading(
    {@required BuildContext context, @required UpdateAppEnData updateData}) {
  List<Update_logs> _updateLog = updateData.update_logs;
  String _updateLogstr = "";
  if (_updateLog.length > 0) {
    _updateLog.forEach((element) {
      _updateLogstr += '''
${element.content}
''';
    });
  }
  try {
    Provider.of<StoreConfig>(context, listen: false).addListener(() {
      if (Provider.of<StoreConfig>(context, listen: false)
              ?.doloadInfo
              ?.status ==
          DownloadStatus.STATUS_SUCCESSFUL) {
        /// 下载完成
        if (Provider.of<StoreConfig>(context, listen: false)
                ?.isShowDownloading ==
            true) {
          Navigator.of(context).pop(true);
        }
      }
    });
  } catch (e) {}
  showDialog(
    context: context,
    barrierDismissible: false,
    child: StatefulBuilder(builder: (context, setDialogState) {
      Provider.of<StoreConfig>(context, listen: false)?.isShowDownloading =
          true;
      return WillPopScope(
          onWillPop: () async {
            return false;
          },
          child: AssetGiffyDialog(
            image: Image.asset(
              'asset/icon/men_wearing_jacket.gif',
              fit: BoxFit.fitWidth,
              height: Common.width(400),
            ),
            title: Text(
              '正在下载升级包',
              style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.w600),
            ),
            buttonOkText: Text('后台下载'),
            onlyOkButton: updateData.is_force == 1, // 强制下载
            buttonCancelText: Text('取消下载'),
            height: Common.width(910),
            description: null,
            descriptionWidget:
                Provider.of<StoreConfig>(context)?.doloadInfo?.maxLength == -1
                    ? Text('抱歉 下载失败,请稍后再试')
                    : Container(
                        padding: EdgeInsets.only(
                            left: Common.width(20), right: Common.width(24)),
                        height: Common.width(300),
                        child: ListView(
                          children: [
                            Text(
                              '''新版本: ${updateData.app_version}
更新日志

${_updateLogstr}
当前速度: ${Provider.of<StoreConfig>(context)?.doloadInfo?.speed == null ? '-' : Provider.of<StoreConfig>(context)?.doloadInfo?.speed?.toStringAsFixed(2)} kb/s
下载进度: ${Provider.of<StoreConfig>(context)?.doloadInfo?.percent == null ? '-' : Provider.of<StoreConfig>(context)?.doloadInfo?.percent}%
            ''',
                              textAlign: TextAlign.left,
                              style:
                                  TextStyle(fontSize: Common.getFontSize(24)),
                            )
                          ],
                        ),
                      ),
            entryAnimation: EntryAnimation.TOP,
            onOkButtonPressed: () async {
              Common.toast('后台下载');
              Navigator.of(context).pop(true);
              Provider.of<StoreConfig>(context, listen: false)
                  ?.isShowDownloading = false;
            },
            onCancelButtonPressed: () async {
              // 取消下载
              num _id = Provider.of<StoreConfig>(context, listen: false)
                  ?.doloadInfo
                  ?.id;
              if (_id != null && _id != 0) {
                await RUpgrade.cancel(_id);
              }
              Navigator.of(context).pop(true);
              Provider.of<StoreConfig>(context, listen: false)
                  ?.isShowDownloading = false;
            },
          ));
    }),
  );
  return null;
}
  • 关于全局的跳转封装

/// 请使用async
  // 跳转页面
  static Future<dynamic> toJumpPage({
    @required BuildContext context,
    TransitionType transition = TransitionType.inFromRight, // 动画
    Map<String, dynamic> params, // 其他页面传递参数
    // 是否关闭上级页面
    bool replace: false,
    // 页面地址
    String url = '',
    // 是否清除路由栈
    bool clearStack: false,
    // 如果传入的参数为跳转模型
    Map mod,
    // 如果需要指定登录之后跳转回来的页面
    String loginBackUrl,
    // 附加操作字段
    String extAction,
  }) async {
    /// 模块跳转,请返回一个map数据,告知这里需不需要再进行处理
    if (mod != null) {
      ModuleLocation moduleLocation = ModuleLocation();
      Map<String, dynamic> info = await moduleLocation.toModLocation(mod,
          context: context, loginBackUrl: loginBackUrl);
      print('---info---');
      print(info);
      if (info != null) {
        // 将拿回来的参数重新赋值
        url = info['url'] ?? '';
        if (info['params'] != null) {
          // 需要转换一下
          params = new Map<String, dynamic>.from(info['params'] ?? null);
        }
        print('---数据返回');
      }
      if (info != null &&
          (info['action'] ?? '').isNotEmpty &&
          (info['action'] ?? '') == 'not_do') {
        // * 通知这里直接返回null,不做任何操作
        return null;
      }
      // 不支持的类型 不允许加载了
      if (info == null) {
        return null;
      }
    }

    // 后退
    if (url == '') {
      final ModalRoute<dynamic> parentRoute = ModalRoute.of(context);
      final bool canPop = parentRoute?.canPop ?? false;
      if (canPop) {
        // 如果页面栈为1,退出
        AppRuntime.router.pop(context);
        // 每次产生后退利用 did 重绘下数据
        AppRuntime.instance?.setState(() {});
        return;
      }
      // 如果当前首页,replace到首页
      await AppRuntime.router.navigateTo(
        context,
        '/home',
        replace: true,
      );
      return;
    }

    // 网页跳转
    if (ValidatorRule.isNetworkUrl(url)) {
      AppRuntime.router.navigateTo(
        context,
        // 路径编码,在webview页面进行路径解码
        generateRouteParam('/webview',
            {'webUrl': Uri.encodeComponent(url), 'extAction': extAction}),
        transition: transition,
        replace: replace,
      );
      return;
    }

    // 第三方跳转, 相对路径
    if (ValidatorRule.isOtherAppUrl(url)) {
      print('第三方app跳转: $url');
      return;
    }

    List<String> path = routesMap.keys.toList();
    for (int i = 0; i < path.length; i++) {
      String _url = url.split('?')[0];
      if (_url == path[i]) {
        return AppRuntime.router.navigateTo(
          context,
          generateRouteParam(url, params),
          transition: transition,
          replace: replace,
          clearStack: clearStack,
        );
      }
    }
    return AppRuntime.router
        .navigateTo(context, '/home', transition: transition, replace: false);
  }
  • 绘制不规则图形 TrianglePath

ClipPath(
    clipper: TrianglePath(),
    child: Container(
    alignment: Alignment.center,
    width: Common.width(256),
    height: Common.width(86),
    decoration: BoxDecoration(
        color: Color.fromRGBO(241, 85, 36, 1),
    ),
    child: Text(
        '导师微信',
        style: TextStyle(
            fontSize: Common.getFontSize(36),
            color: Color.fromRGBO(255, 255, 255, 1)),
    ),
    ),
),
/// 绘制不规则形状
class TrianglePath extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    var path = Path();
    // 反梯形
    path.moveTo(0, 0);
    path.lineTo(size.width * (1 / 4), size.height);
    path.lineTo(size.width * (3 / 4), size.height);
    path.lineTo(size.width, 0);
    // 梯形
    // path.moveTo(size.width / 4, 0);
    // path.lineTo(0, size.height);
    // path.lineTo(size.width, size.height);
    // path.lineTo(size.width * (3 / 4), 0);
    return path;
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) {
    return true;
  }
}
  • 安卓配置签名

// 文件 app\android\app\build.gradle
// 原文
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
    localPropertiesFile.withReader('UTF-8') { reader ->
        localProperties.load(reader)
    }
}

def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
    throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}

def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
    flutterVersionCode = '1'
}

def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
    flutterVersionName = '1.0'
}

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}

android {
    compileSdkVersion 28

    sourceSets {
        main.java.srcDirs += 'src/main/kotlin'
    }

    lintOptions {
        disable 'InvalidPackage'
    }

    defaultConfig {
        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
        applicationId "com.xxx.xxx.app"
        minSdkVersion 18
        targetSdkVersion 28
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
        multiDexEnabled true

        ndk {
            // 增加两个 x86模拟器使用
            abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
        }

        manifestPlaceholders = [
            JPUSH_PKGNAME : applicationId,
            JPUSH_APPKEY : "xxxx", //Portal上注册的包名对应的 appKey.
            JPUSH_CHANNEL : "developer-default", //暂时填写默认值即可.
        ]
    }

    signingConfigs {
        release {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile file(keystoreProperties['storeFile'])
            storePassword keystoreProperties['storePassword']
        }
        debug {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile file(keystoreProperties['storeFile'])
            storePassword keystoreProperties['storePassword']
        }
    }

    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
        debug {
            signingConfig signingConfigs.debug
        }
    }
}

flutter {
    source '../..'
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'cn.jiguang.sdk:jverification:2.6.4'  // 此处以2.6.4 版本为例。
    implementation 'cn.jiguang.sdk:jcore:2.3.4'  // 此处以JCore 2.3.4 版本为例。
    implementation 'com.android.support:multidex:1.0.3'

    //登陆
    implementation 'com.ali.auth.sdk:alibabauth_core:2.0.0.6@aar'
    implementation 'com.ali.auth.sdk:alibabauth_ui:2.0.0.6@aar'
    implementation 'com.ali.auth.sdk:alibabauth_ext:2.0.0.6@aar'
    //安全组件
    implementation 'com.taobao.android:securityguardaar3:5.4.171@aar'
    implementation 'com.taobao.android:securitybodyaar3:5.4.99@aar'
    implementation 'com.taobao.android:avmpaar3:5.4.36@aar'
    implementation 'com.taobao.android:sgmiddletieraar3:5.4.9@aar'
    //Mtop
    implementation 'com.taobao.android:mtopsdk_allinone_open:3.1.2.5@jar'
    //applink
    implementation 'com.alibaba.sdk.android:alibc_link_partner:4.1.15@aar'
    //ut
    implementation 'com.taobao.android:utdid4all:1.5.2'
    implementation 'com.alibaba.mtl:app-monitor-sdk:2.6.4.5_for_bc'
    // 电商基础组件
    implementation 'com.alibaba.sdk.android:AlibcTradeCommon:4.0.0.15@aar'
    implementation 'com.alibaba.sdk.android:AlibcTradeBiz:4.0.0.15@aar'
    implementation 'com.alibaba.sdk.android:nb_trade:4.0.0.15@aar'
    implementation 'com.alibaba:fastjson:1.1.71.android'
}
# 配置文件 app\android\key.properties
# 原文
storePassword=xx1234..
keyPassword=xx1234..
keyAlias=androidKey
# storeFile=F:/flutter2/app/keys/android-key.jks
storeFile=F:/flutterwork/flutter_tk_new/app/keys/android-key.jks
# 这里后期需要在gradle工程内通过环境变量载入(windows执行android-key.bat,mac以及unix执行android-key.sh)
# storeFile=/Users/candy/work/coding/flutter/app/keys/android-key.jks
  • 配置

关于应用图标替换
替换 asset/icon/icon.png
执行 flutter run
------------
应用名称
Android 是在 android ▸ app ▸ src ▸ main ▸ AndroidManifest.xml 中修改 android:label="XXX";
iOS 在 ios ▸ Runner ▸ Info.plist 中修改 CFBundleName 对应的 Value

------------
推送修改
android/app/build.gradle - 极光应用一键登录配置

---- 快速打开/安卓
android:scheme="schemename"
  • 缓存数据格式

// 保存对象为json字符串的时候请使用:
String jsonString = convert.jsonEncode(_cacheList);
// 读取的时候才能 读出来
convert.json.decode(temp);
  • cocoapods 配置

sudo gem uninstall cocoapods
sudo gem install cocoapods -v 1.7.5
# git clone 很慢耗时很久,大概800m,难受
pod setup
# pod setup手动安装,快很多
cd ~/.cocoapods/repos
git clone https://gitee.com/mirrors/CocoaPods-Specs.git master
# 再进行
pod setup
  • 广告图

import 'dart:async';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_swiper/flutter_swiper.dart';
import 'package:flutter_tk/common.dart';
import 'package:flutter_tk/lib/shared_preferences_util.dart';

/// 引导图和闪屏图 请使用默认图
/// 首次安装打开现实引导图,如果存在引导图,就不显示闪屏图,否则闪屏图1秒打开页面
/// 注意: 显示图片还是需要时间 目前使用的是菊花填充 已经 [假装预加载]
class SplashPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return new _SplashPageState();
  }
}

class _SplashPageState extends State<SplashPage> {
  /// 引导数据 多张图
  List<String> guideData = [
    'asset/startpic/1.png',
    'asset/startpic/2.png',
    'asset/startpic/3.png',
  ];

  /// 闪屏图 1张
  String flashScreenData = '';

  /// 轮播当前图片的index
  num _swiperIndex = 0;

  /// 倒计时秒数
  Timer _timer;
  int _count = 2;

  /// 是否是第一次打开app
  bool isFirstOpenApp = false;

  /// 轮播句柄
  SwiperController _swiperController = SwiperController();

  /// 引导图倒计时
  Timer timerInit;

  /// 引导图倒计时时间
  num timerNum = 3;
  @override
  void dispose() {
    _timer?.cancel();
    _timer = null;
    timerInit.cancel();
    timerInit = null;
    super.dispose();
  }

  Future initData() async {
    /// 获取数据
    // dynamic response = await startPictureData();
    // print('----startPictureData');
    // print(response);
    // BaseEn ret = BaseEn.fromJson(response);
    // if (ret.state == 1) {
    //   StartPictureEn ret2 = StartPictureEn.fromJson(response);
    //   if (ret2.state == 1) {
    //     if (ret2?.data?.open_picture != null &&
    //         ret2?.data?.open_picture != "") {
    //       flashScreenData = ret2?.data?.open_picture;
    //     }
    //     if (ret2?.data?.start_picture != null &&
    //         ret2.data.start_picture.length > 0) {
    //       guideData = ret2?.data?.start_picture;
    //     }
    //   }
    // } else {
    //   Common.toJumpPage(context: context, url: '/home', replace: true);
    // }

    /// 检测是不是第一次打开app
    String _check = await SharedPreferencesUtil.getData('isFirstOpenApp');
    // DEBUG: 测试首次打开App
    // _check = "";

    if (_check == null || _check == "" || _check != "yes") {
      isFirstOpenApp = true;
    } else {
      isFirstOpenApp = false;
    }

    /// 检测数据,筛选数据
    if (isFirstOpenApp == false) {
      // 不是第一次
      // INFO: 直接跳转 闪屏图使用安卓在源文件那个
      Common.toJumpPage(context: context, url: '/home', replace: true);
      return null;
      print('----flashScreenData');
      print(flashScreenData);
      if (flashScreenData == "" || flashScreenData.contains('http') == false) {
        // 数据有问题直接跳转
        Common.toJumpPage(context: context, url: '/home', replace: true);
        return null;
      }
    } else {
      // 第一次打开
      if (guideData == null || guideData.length == 0) {
        Common.toJumpPage(context: context, url: '/home', replace: true);
        return null;
      }
      // 筛选数据
      // List<String> _temp = [];
      // guideData.forEach((element) {
      //   if (element.contains('http')) {
      //     _temp.add(element);
      //   }
      // });
      // guideData = _temp;
      // print('----guideData');
      // print(guideData);
      // if (guideData == null || guideData.length == 0) {
      //   Common.toJumpPage(context: context, url: '/home', replace: true);
      //   return null;
      // }
    }

    setState(() {});
  }

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) async {
      initData();
    });
  }

  /// 闪屏数据
  @override
  Widget build(BuildContext context) {
    if (isFirstOpenApp == false) {
      return Scaffold(
        body: Container(
          alignment: Alignment.center,
          width: Common.width(750),
          child: flashScreenData.isNotEmpty
              ? Container(
                  child: Image.network(
                    flashScreenData,
                    loadingBuilder: (BuildContext context, Widget child,
                        ImageChunkEvent loadingProgress) {
                      if (loadingProgress == null) return child;
                      if (loadingProgress.cumulativeBytesLoaded ==
                          loadingProgress.expectedTotalBytes) {
                        print('----img加载完成');

                        /// 倒计时闪屏图
                        if (isFirstOpenApp == false) {
                          // 闪屏图,倒计时开始
                          _timer =
                              Timer.periodic(new Duration(seconds: 1), (timer) {
                            if (_count <= 1) {
                              _timer?.cancel();
                              _timer = null;
                              Common.toJumpPage(
                                  context: context,
                                  url: '/home',
                                  replace: true);
                            } else {
                              _count = _count - 1;
                            }
                          });
                        }
                      }
                      return Opacity(opacity: 0);
                    },
                    fit: BoxFit.fill,
                    width: double.infinity,
                    height: double.infinity,
                  ),
                )
              : Container(
                  width: Common.width(534),
                  alignment: Alignment.center,
                  child: CupertinoActivityIndicator(
                    radius: Common.width(20),
                  ),
                ),
        ),
      );
    }

    List<Widget> _imgList = [];
    guideData.asMap().keys.map((index) {
      _imgList.add(GestureDetector(
        onTap: () {
          if (index == guideData.length - 1) {
            SharedPreferencesUtil.saveData('isFirstOpenApp', 'yes');
            Common.toJumpPage(context: context, url: '/home', replace: true);
          }
        },
        child: Stack(
          children: [
            // 图片
            Image.asset(
              guideData[index],
              fit: BoxFit.fill,
              // loadingBuilder: (BuildContext context, Widget child,
              //     ImageChunkEvent loadingProgress) {
              //   if (loadingProgress == null) return child;
              //   if (loadingProgress.cumulativeBytesLoaded <=
              //       loadingProgress.expectedTotalBytes) {
              //     return CupertinoActivityIndicator(
              //       radius: Common.width(20),
              //     );
              //   }
              //   return Opacity(opacity: 0);
              // },
            ),
            index == guideData.length - 1
                ? Positioned(
                    right: Common.width(60),
                    top: Common.width(60),
                    child: Container(
                      padding: EdgeInsets.only(
                          top: Common.width(15),
                          bottom: Common.width(15),
                          right: Common.width(20),
                          left: Common.width(20)),
                      decoration: BoxDecoration(
                          color: Colors.white,
                          borderRadius:
                              BorderRadius.circular(Common.width(15))),
                      child: Text('倒计时${timerNum}'),
                    ),
                  )
                : Container(),
          ],
        ),
      ));
    }).toList();
    return Scaffold(
      body: Container(
        child: guideData.length > 0
            ? Container(
                alignment: Alignment.bottomCenter,
                width: Common.width(750),
                child: Swiper.children(
                  viewportFraction: 0.99999, // 假装预加载
                  onIndexChanged: (index) {
                    _swiperIndex = index;
                    print('${_swiperIndex}');
                    if (timerInit == null &&
                        _swiperIndex == guideData.length - 1) {
                      print('开始倒计时');
                      const oneSec = const Duration(seconds: 1);
                      timerInit = new Timer.periodic(
                        oneSec,
                        (Timer timerInit) => setState(
                          () {
                            if (timerNum < 1) {
                              print('倒计时完成');
                              timerInit.cancel();
                              SharedPreferencesUtil.saveData(
                                  'isFirstOpenApp', 'yes');
                              Common.toJumpPage(
                                  context: context,
                                  url: '/home',
                                  replace: true);
                            } else {
                              timerNum = timerNum - 1;
                            }
                          },
                        ),
                      );
                    }
                  },
                  children: guideData.length > 0 ? _imgList : [],
                  // pagination: SwiperPagination(),
                  // control: SwiperControl(),
                  loop: false,
                ),
              )
            : Container(
                width: Common.width(750),
                alignment: Alignment.center,
                child: CupertinoActivityIndicator(
                  radius: Common.width(20),
                ),
              ),
      ),
    );
  }
}
  • 指定宽度

/// 请在main文件初始化调用
static init(context) {
    ScreenUtil.init(context, width: 750, height: 1334, allowFontScaling: false);
}
/// 全局调用
static num width(double value) {
    return ScreenUtil().setWidth(value);
}
/// 获取文字大小
static double getFontSize(int fontsize) {
    final baseSize = 1;
    return ScreenUtil().setSp((fontsize * baseSize).toInt());
}
/// 获取时间戳
static int getTime() {
return new DateTime.now().millisecondsSinceEpoch;
}

// 将时间戳转换为日期
static String timeStampToString(int time) {
return formatDate(DateTime.fromMillisecondsSinceEpoch(time * 1000),
    [yyyy, '-', mm, '-', dd, ' ', HH, ':', nn, ':', ss]);
}
// 格式化时间戳
static String forMatTime(int timestamp,
    [String format = "yyyy-MM-dd HH:mm"]) {
if (timestamp is int && timestamp > 100000000) {
    return DateUtil.formatDateMs(timestamp * 1000, format: format);
}
return "";
}
  • 持久化数据

import 'package:shared_preferences/shared_preferences.dart';

/// 数据
class SharedPreferencesUtil {
  /// 保存数据
  static saveData<T>(String key, T value) async {
    SharedPreferences prefs = await SharedPreferences.getInstance();

    switch (T) {
      case String:
        prefs.setString(key, value as String);
        break;
      case int:
        prefs.setInt(key, value as int);
        break;
      case bool:
        prefs.setBool(key, value as bool);
        break;
      case double:
        prefs.setDouble(key, value as double);
        break;
    }
  }

  /// 读取数据
  static Future<T> getData<T>(String key) async {
    SharedPreferences prefs = await SharedPreferences.getInstance();

    T res;
    switch (T) {
      case String:
        res = prefs.getString(key) as T;
        break;
      case int:
        res = prefs.getInt(key) as T;
        break;
      case bool:
        res = prefs.getBool(key) as T;
        break;
      case double:
        res = prefs.getDouble(key) as T;
        break;
    }
    return res;
  }
}
  • 隐私协议弹窗 flutter_custom_dialog

YYDialog().build(context)
          ..width = Common.width(700)
          ..borderRadius = Common.width(24)
          ..barrierDismissible = false
          ..dismissCallBack = () {
            // 消失弹窗开始检测升级
            if (isAgree == true) {
              checkAppInfo(context: context, backGroundUpload: true);
            } else {
              // 退出
              if (Platform.isAndroid == false) {
                exit(0);
              }
              SystemChannels.platform.invokeMethod('SystemNavigator.pop');
            }
          }
          ..text(
            padding: EdgeInsets.all(Common.width(18)),
            text: '用户协议和隐私政策',
            color: Colors.black,
            fontSize: 18.0,
            fontWeight: FontWeight.w500,
          )
          ..text(
            padding: EdgeInsets.only(
                right: Common.width(18), left: Common.width(18)),
            text: '使用本软件即同意用户协议和隐私政策',
            color: Colors.grey[500],
            fontSize: 15.0,
          )
          ..widget(Container(
            height: Common.width(560),
            padding: EdgeInsets.only(
                right: Common.width(18), left: Common.width(18)),
            child: ListView(
              children: [
                // Text('用户协议'),
                Html(
                  data: _text1,
                ),
                // Text('隐私政策'),
                Html(
                  data: _text2,
                ),
              ],
            ),
          ))
          ..doubleButton(
            withDivider: true,
            height: Common.width(112),
            padding: EdgeInsets.only(top: Common.width(18)),
            gravity: Gravity.center,
            text1: '不同意退出',
            color1: Colors.black38,
            fontSize1: Common.getFontSize(28),
            onTap1: () {
              isAgree = false;
            },
            text2: '同意并继续',
            color2: Color(0xffF15343),
            fontSize2: Common.getFontSize(28),
            onTap2: () async {
              isAgree = true;
              await SharedPreferencesUtil.saveData('user_is_agree', 'yes');
            },
          )
          ..show();
  • 其他插件

  shared_preferences: 0.5.3

  # 路由
  fluro: 1.6.3
  # 屏幕适配
  flutter_screenutil: 1.1.0
  # 网路请求
  dio: 3.0.9
  # 弹窗
  oktoast: 2.3.2
  # 常用工具类
  flustars: 0.2.6+1
  # 选择器
  flutter_picker: 1.1.3
  # dio请求缓存
  # dio_http_cache: 0.2.6
  #下拉刷新和上拉加载类
  flutter_easyrefresh: 2.1.1
  # 图片上传选择器
  image_picker: 0.6.6+1
  # loding
  floading: ^1.0.0
  # 获取原生软件,版本号,软件名称等
  package_info: 0.4.0+17
  # 询问弹窗 https://pub.dev/packages/flutter_custom_dialog#-readme-tab-
  flutter_custom_dialog: 1.0.19
  # 询问弹窗/提示/特效
  giffy_dialog:
    path: ./lib/sourcecode/giffy_dialog/
  #轮播图
  flutter_swiper: 1.1.6
  # 升级检测 https://pub.dev/packages/r_upgrade/install
  r_upgrade: ^0.3.1
  # 二维码扫码
  barcode_scan: ^3.0.1
  # 网页调用,包含sdk通信
  webview_flutter: 0.3.22+1
  flutter_webview_plugin: ^0.3.11
  # 极光一键登录
  jverify: 0.6.20
  modal_progress_hud: 0.1.3
  # html富文本渲染
  flutter_html: 1.0.2
  # flutter_widget_from_html: 0.4.2
  # flutter_html_view: 0.5.11

  # video 视频播放组件
  video_player: 0.10.11+2
  # 分享组件
  # jshare_flutter_plugin: 1.0.2
  fluwx: 2.2.0
  # 长按菜单
  focused_menu: 1.0.1
  # 展示更多
  show_more_text_popup: 0.2.0
  # 阿里百川SDK
  flutter_alibc: 0.0.15
  # 京东开普勒SDK
  # flutter_kepler: 0.0.1
  # 防抖节流函数
  debounce_throttle: 1.1.0
  # 第三方跳转
  url_launcher: 5.5.0
  # 相册存储
  image_gallery_saver: "^1.5.0"
  # 权限
  permission_handler: 5.0.1+1
  # 上拉加载下拉刷新
  pull_to_refresh: 1.6.1
  # 推送
  jpush_flutter: 0.5.9
  # 文件读写目录
  path_provider: 1.2.0
  # 图片预览
  photo_view: 0.9.2
  # 剪切板
  clipboard: ^0.1.2+8
  # store
  provider: ^4.3.2
  # 组件通信
  fbroadcast: ^1.0.0
  # radio选择器
  fradio: ^1.0.1
  # 设备信息
  device_info: ^0.4.2+7
  # ios登录
  sign_in_with_apple: ^2.5.2
  # 原生
  eyro_toast: ^0.1.0

windows flutter升级之后卡到BitsTransfer下载解决方案

  • 基本思路是: 修改update_dart_sdk.ps1文件,打印当前sdk的下载地址,自行下载国内dart-sdk(在此之前flutter-sdk也可以下载码云的镜像,很快)

  • 然后直接指定文件位置路径,直接解压即可

# D:\flutter\bin\internal\update_dart_sdk.ps1 主要修改

Write-Host "Downloading Dart SDK from Flutter engine $engineVersion..."
$dartSdkBaseUrl = $Env:FLUTTER_STORAGE_BASE_URL
$dartSdkBaseUrl = "https://storage.flutter-io.cn/"
$dartZipName = "dart-sdk-windows-x64.zip"
$dartSdkUrl = "$dartSdkBaseUrl/flutter_infra/flutter/$engineVersion/$dartZipName"
Write-Host "下载地址: $dartSdkUrl";

# try BitsTransfer 已经被删除了

# 直接命中数据
$dartSdkZip = "$cachePath\dart-sdk-windows-x64.zip"

Write-Host "直接命中数据: $dartSdkZip";

Write-Host "Unzipping Dart SDK..."