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..."