1.替换APP启动图标。
IOS的图标目录。
——————————
安卓启动图标的目录地址:
如果放入的图片自己重新命名了需要更改配置名称,不用写图片后缀格式:
——————————————————
2,应用程序启动名称
安卓的应用名称修改
————————————————————————
ios启动名称修改需要额外开启XCOD
————————————————
3,启动页面设置
3.1冷启动自带白屏处理,安卓版:
指向自己添加的图片名就可以启动的时候显示图片而不是白色底。注意下面各个分辨率显示文件都要建设一份,因为不知道你手机具体调用哪个的文件夹图片,按手机分辨率对应来。
还有这个文件也要处理一下:
插入的背景图片无法铺满白底可以加上这个设置
——————————————————————————————
具体解释如下:
这里加载了一个启动主题样式
这里是启动主题样式的定义,其中一句话
指向了下面文件的背景白色定义:如果为空指向背景色
释放下面的注释就可以指向图片启动了。
3.2冷启动自带白屏处理,ios版:
需要启动ios XCOD
点加号添加一个图片组件拖进去
设置属性就可以了
4,配置静态图片资源文件,本地目录
直接可以使用本地图片文件了:
设置android状态栏为透明的沉浸
SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle(statusBarColor: Colors.transparent);
SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle);
13,getX 基于第三方插件的数据共享,路由导航,弹窗等简化Provider 操作
中文文档https://github.com/jonataslaw/getx/blob/master/README.zh-cn.md#%E5%85%B3%E4%BA%8Eget
另一篇参考文档http://liujunmin.com/tags/getx/
采坑:他是数据层独立项目外,可以任意页面上传,也可以任意页面下载数据。所以不存在传统路由带值传输的需要、当然你可以结合之前传统路由传参综合使用,并不相互干扰。
13.1基本配置
引入插件后,首页开始的风格MaterialApp改成使用GetMaterialApp,它里面配置好了路由等依赖,省去了手动配置。
安装IDE插件
13.2生成GEXT代码模板
新建了个文件夹getx专门放状态数据。点击右键用插件生成代码文件。
13.3,编写全局数据层
13.4调用全局数据
案例直接写在生成的视图文件了,便于测试。
注意视图引用文件引入了库文件import ‘package:get/get.dart’;使其可以支持Get.put和 Get.to()路由跳转等。
首先引入全局依赖,
然后从依赖里调取数据,可以通过Obx()自动监控刷新
通过floatingActionButton按钮调用全局函数增加数值
函数取自之前GETX定义
13.5 GETX 自带信息提示框显示效果 Get.snackbar()
13.6 GETX 自带弹窗显示框显示效果 Get.defaultDialog()
13.7 GETX 呼出弹窗按钮事件触发 Get.bottomSheet()
13.8 GETX 页面路由传参跳转 Get.to(()=>CotrollerPage()
一般使用路由名称
,CotrollerPage()页面接受参数,页面传值也是同样接收
13.9 getX 命名路由 带参跳转
9.1定义路由
9.2使用路由带参
9.2调用参数
注意需要GET的引用要使用import ‘package:get/get.dart’;
9.3补充导航命令
导航到下一个页面
Get.toNamed("/NextScreen");
浏览并删除前一个页面。
Get.offNamed("/NextScreen");
浏览并删除所有以前的页面。
Get.offAllNamed("/NextScreen");
9.3.2使用异步路由回调传值 结合GETX路由
1.设置变量接收回值
2.发送
3.下面是返回上一页,对应的路由页面返回页面值
9.4 GETX 生命周期 数据初始化时按需执行事件监听
9.5 不需更新只需加上任意ID就可以
9.6GetUtils GETX监测数据格式
GetUtils是getx为我们提供一些常用的工具类库,包括值是否为空、是否是数字、是否是视频、图片、音频、PPT、Word、APK、邮箱、手机号码、日期、MD5、SHA1
参考资料http://liujunmin.com/flutter/getx/getx_newss.html
注意页面还是需要引入import ‘package:get/get.dart’;这个是使用GET . 的关键。
5,APP权限请求
权限包含文件储存权限,摄像头权限等根据需要配置。
5.1引入权限请求的库。
(不应该去官方库选择最新的版本号,应该搜索此版本库的教程文章https://www.jianshu.com/p/35b37c012351,找一篇写了最新版本库的对应学习引入,因为官网新版往往兼容有问题,所以这里没有加^版本号而是规定了9.2.0固定板)
5.2配置具体权限清单
具体字符看https://www.jianshu.com/p/35b37c012351
<!--
Internet permissions do not affect the `permission_handler` plugin, but are required if your app needs access to
the internet.
-->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- Permissions options for the `contacts` group -->
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<!-- Permissions options for the `storage` group -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- Permissions options for the `camera` group -->
<uses-permission android:name="android.permission.CAMERA"/>
<!-- Permissions options for the `sms` group -->
<uses-permission android:name="android.permission.SEND_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_WAP_PUSH"/>
<uses-permission android:name="android.permission.RECEIVE_MMS"/>
<!-- Permissions options for the `phone` group -->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.ADD_VOICEMAIL"/>
<uses-permission android:name="android.permission.USE_SIP"/>
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
<uses-permission android:name="android.permission.WRITE_CALL_LOG"/>
<uses-permission android:name="android.permission.BIND_CALL_REDIRECTION_SERVICE"/>
<!-- Permissions options for the `calendar` group -->
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<!-- Permissions options for the `location` group -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<!-- Permissions options for the `microphone` or `speech` group -->
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<!-- Permissions options for the `sensors` group -->
<uses-permission android:name="android.permission.BODY_SENSORS" />
<!-- Permissions options for the `accessMediaLocation` group -->
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
<!-- Permissions options for the `activityRecognition` group -->
<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
<!-- Permissions options for the `ignoreBatteryOptimizations` group -->
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<!-- Permissions options for the `bluetooth` group -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- Permissions options for the `manage external storage` group -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<!-- Permissions options for the `system alert windows` group -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<!-- Permissions options for the `request install packages` group -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<!-- Permissions options for the `access notification policy` group -->
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY"/>
5.3其他设置
在 gradle.properties 中添加
android.useAndroidX=true
android.enableJetifier=true
把android/app/build.gradle 改成31
`android { compileSdkVersion 31 ... }`
5.3.1判断网页版跳过加载
引用了工具文件定义如下,工具文件自己创建
import 'dart:io';
import 'package:flutter/foundation.dart';
判断是什么终端的设备打开地
class PlatformUtils {
static bool _isWeb() {
return kIsWeb == true;
}
static bool _isAndroid() {
return _isWeb() ? false : Platform.isAndroid;
}
static bool _isIOS() {
return _isWeb() ? false : Platform.isIOS;
}
static bool _isMacOS() {
return _isWeb() ? false : Platform.isMacOS;
}
static bool _isWindows() {
return _isWeb() ? false : Platform.isWindows;
}
static bool _isFuchsia() {
return _isWeb() ? false : Platform.isFuchsia;
}
static bool _isLinux() {
return _isWeb() ? false : Platform.isLinux;
}
static bool get isWeb => _isWeb();
static bool get isAndroid => _isAndroid();
static bool get isIOS => _isIOS();
static bool get isMacOS => _isMacOS();
static bool get isWindows => _isWindows();
static bool get isFuchsia => _isFuchsia();
static bool get isLinux => _isLinux();
}
5.3.2下面紧跟权限申请页面PermissionRequestWidget调用,根据页面返回值判断操作
//开始权限申请判断
Future.delayed(Duration.zero, () {
//因为initState时间没有context,所以定义延时执行等待加载完成
Navigator.of(context).push(
PageRouteBuilder(
/打开装载了申请的页面
opaque: false,//路由透明
pageBuilder: (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return const PermissionRequestWidget(/装载了申请的页面
Permission.storage);
}),
).then((value) {
if (value == null || !value) {
//权限请求不通过
} else {
//权限请求通过
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) {
// return const HomePageWidget();
// },
// maintainState: false,
// ),
// );
// Get.offAll(const HomePageWidget());
Get.offAllNamed("/WelcomePage", arguments: '我是命名路由传过来的数据');
//命名路由方式Get在这里接受任何东西,无论是一个字符串,一个Map,一个List,甚至一个类的实例。
// 接受方可print(Get.arguments);其他参考print(Get.parameters['id']);
}
});
});
延时的秒和毫秒设置
Future.delayed(Duration.zero, () {。。。延时0秒
await Future.delayed(Duration(seconds: 1)).then((_){。。。。。延时1秒
await Future.delayed(Duration(milliseconds: 500)).then((_){。。。。。延时500毫秒
5.3.4PermissionRequestWidget页面的具体定义
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:permission_handler/permission_handler.dart';
//权限申请功能页
class PermissionRequestWidget extends StatefulWidget {
const PermissionRequestWidget(this.permission,
{this.isCloseApp = false, this.leftButtonText = "再考虑一下", Key? key})
: super(key: key);
final Permission permission;
final bool isCloseApp;
final String leftButtonText;
State<PermissionRequestWidget> createState() =>
_PermissionRequestWidgetState();
}
class _PermissionRequestWidgetState extends State<PermissionRequestWidget>
with WidgetsBindingObserver {
//WidgetsBindingObserver : 检测页面生命周期
//页面初始化函数
List<String> permissionList = [
"需要获取您的手机文件储存权限,以保存您的一些偏好设置",
"您已拒绝权限,所以无法保存您的一些偏好设置,将无法使用App",
"您已拒绝权限,请在设置中心中同意APP的权限请求",
"其他错误",
];
bool _isGoSetting = false;
void initState() {
super.initState();
checkPermisson();
WidgetsBinding.instance.addObserver(this); //注册观察者
}
//生命周期变化时回调
// resumed:应用可见并可响应用户操作
// inactive:用户可见,但不可响应用户操作
// paused:已经暂停了,用户不可见、不可操作
// suspending:应用被挂起,此状态IOS永远不会回调
void didChangeAppLifecycleState(AppLifecycleState state) {
//由后台返回主屏幕 数据刷新时
//根据挂起状态申请
super.didChangeAppLifecycleState(state);
//print(state);// ==resumed:应用可见并可响应用户操作
if (state == AppLifecycleState.resumed && _isGoSetting) {
checkPermisson(); //自定义的权限申请函数
}
}
void dispose() {
WidgetsBinding.instance.addObserver(this); //注销观察者
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.transparent,//设置背景透明
);
}
//关闭应用
void closeApp() {
//关闭应用的方法
SystemChannels.platform.invokeMethod("SystemNavigator.pop");
}
//函数 申请弹窗
void showPermissonAlert(
String message, String rightString, Permission permission,
{isGoSetting = false}) {
showCupertinoDialog(
//用于弹出ios风格对话框
builder: (BuildContext context) {
return CupertinoAlertDialog(
title: Text("温馨提示"),
content: Container(
padding: EdgeInsets.all(12),
child: Text(message),
),
actions: [
//左边按钮
CupertinoDialogAction(
child: Text("${widget.leftButtonText}"),
onPressed: () {
if (widget.isCloseApp) {
closeApp(); //关闭APP
} else {
Navigator.of(context).pop(false); //返回上一页面
}
},
),
//右边按钮
CupertinoDialogAction(
child: Text("$rightString"),
onPressed: () {
if (isGoSetting) {
_isGoSetting = true;
openAppSettings();
} else {
requestPermiss(permission);
} //关闭弹窗
Navigator.of(context).pop();
},
),
],
);
},
context: context);
}
//###############################################
//函数 申请权限
void requestPermiss(Permission permission) async {
//发起权限申请
PermissionStatus status =
await permission.request(); //呼出窗口让用户选择权限,返回所选择结果状态
checkPermisson(status: status);
}
//##############################################
//函数 权限申请审批
void checkPermisson({PermissionStatus? status}) async {
//权限读取
Permission permission =
widget.permission; // widget.permission== Permission.storage拿取储存权限
if (status == null) {
//权限状态
status = await permission.status;
}
if (status.isGranted) {
//通过
Navigator.of(context).pop(true);
print("通过了");
} else if (status.isLimited) {
//无效!似乎进入不了
//用户第一次申请拒绝
print("用户第一次申请拒绝");
showPermissonAlert(permissionList[1], "重试", permission);
} else if (status.isPermanentlyDenied) {
//第二次申请 用户拒绝
print("第二次申请申请拒绝");
showPermissonAlert(permissionList[2], "去设置中心", permission,
isGoSetting: true);
} else if (status.isDenied) {
//第一次申请
print("第1次申请");
showPermissonAlert(permissionList[0], "同意", permission);
}
}
}
5.4开始代码使用权限申请(补充完善解释)
下面是具体的函数定义
48行(48#) …=Permission.storage;//储存权限
此图片中的状态类型53行开始,不是很好用应该是插件版本不同判断类型变化了,第一次申请状态最好是isDenied建议参考下面更详细的操作代码
其中48#.storage就是权限清单中的storage
表示的是储存权限。
更详细的操作代码:
权限对象Permission.storage中自带状态属性.status
6,APP弹窗showCupertinoDialog,打开手机设置权限,关闭APP,返回上一页
showCupertinoDialog()弹窗
CupertinoAlertDialog()iOS风格的对话框
CupertinoDialogAction()按钮
详细资料参考https://www.jianshu.com/p/f2447dedb155
7,延时执行,任务延迟进行
await Future.delayed(const Duration(milliseconds: 1000));//这里等待了1秒
8,自定义路由,打开页面背景为透明,页面跳转
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return new DetailPage();
}))//页面跳转
9,检测用户操作:按下瞬间、按压、长按、轻击、快速滑屏、拖动
10,移除焦点
11, WidgetsBindingObserver : 监测页面生命周期
生命周期参考资料https://www.jianshu.com/p/ab3cd84f624c
11.1添加 with WidgetsBindingObserver
11.2添加观测者
11.3屏幕切换执行监测,输出状态。根据状态判断执行对应代码。
其他监测类型:用户本地设置变化时调用,如系统语言改变///低内存回调///应用尺寸改变时回调,例如旋转 //文字系数变化请参考资料https://www.jianshu.com/p/a556a7a46e4f
11.4销毁观察者
12,颜色和主题样式,设置整体样式主题
12.1第一种写法:
在线编辑生成网站https://m3.material.io/theme-builder#/custom
生成样式文件放入项目中
main函数的主引用首页RootApp()里添加了新样式属性:theme 白天主题,darkTheme夜晚主题,使整个后续页面都有了样式。切换手机系统的深色模式就会看到暗色调。
12.2第二种写法,按钮手动样式换肤
参考资料https://www.bilibili.com/video/BV1aT4y1S7pB/?spm_id_from=333.999.0.0&vd_source=27b88e2428d8b67a4013c8ec833c13d7
去掉darkTheme配置,保留theme。theme:里面的配置改成可以切换darkTheme的内容,就是两种切换。要是切换按钮全局存在,需要配合使用全局GetX储存。请查看上面参考资料。
12.3在全局色上特别使用自定义的:
参考资料https://book.flutterchina.club/chapter7/theme.html#_7-4-2-%E4%B8%BB%E9%A2%98-theme
14获取元素的大小SchedulerBinding.instance.addPostFrameCallback
参考资料https://www.jianshu.com/p/6c214a054f90
addPostFrameCallback 是 StatefulWidget 渲染结束的回调,只会被调用一次,之后 StatefulWidget 需要刷新 UI 也不会被调用,
addPostFrameCallback 的使用方法是在 initState 里添加回调:
void initState() {
super.initState();
SchedulerBinding.instance.addPostFrameCallback((_) => {});
}
这个方法在一帧的最后调用,并且只调用一次,使用这个方法就可以在判断渲染完成,并获取到元素的大小。
void didChangeDependencies() {
WidgetsBinding.instance.addPostFrameCallback(_onAfterRendering);
super.didChangeDependencies();
}
void didUpdateWidget(T oldWidget) {
WidgetsBinding.instance.addPostFrameCallback(_onAfterRendering);
super.didUpdateWidget(oldWidget);
}
void _onAfterRendering(Duration timeStamp){
//这里编写获取元素大小和位置的方法
}
获取元素大小和位置:
//获取元素大小
RenderObject renderObject = context.findRenderObject();
Size size = renderObject.paintBounds.size;
//获取元素位置:
Vector3 vector3 = renderObject.getTransformTo(null)?.getTranslation();
//位置(vector3.x,vector3.y)
关闭应用
15用户隐私与协议
下面需要引入几个插件分别是网页插件,本地数据储存插件,可以先看一下引入。另外插件需要更高版本的安卓SDK先这样设置一下:
检查一下SDK包
去掉之前的权限通过跳转,改成执行初始弹窗函数
下面是完成用户协议主体文本内容显示
利用手势识别监控点击打开网页
上面的定义如下:
打开网页链接,需要引入第三方插件,WebViewpage是自己定义的页面部件里面有读取网页展示部分
15.2用户隐私与协议,数据本地化保存通过签名(使用插件)
需要引入封装函数具体定义看码库
码库里展示具体封装定义SPUtil()
https://github.com/csliwenzhu/flutter-ho/blob/main/lib/src/utils/sp_utils.dart
用户协议弹窗部分修改认证
16设置打开页面透明
第一步:路由透明
第二步:打开的页面也设置背景透明
17Future无限调用的起始写法
await会阻塞流程,等待紧跟着的的Future执行完毕之后,再执行下一条语句,而如果用了Future.then这个api,那么就不会等待,直接执行下面的语句,等Future执行完了,再调用then这个方法。例如下面的代码是不等待继续往下执行代码 print(“_loadUserInfo:${new DateTime.now()}”);的:
Future _loadUserInfo() async{
print("_loadUserInfo:${new DateTime.now()}");
_getUserInfo().then((info){
print(info);
});
print("_loadUserInfo:${new DateTime.now()}");
}
18文本字段调用变量取值方法
label: Text("获取:${Get.arguments}"),