使用fluro配置路由
1、安装
fluro: ^1.5.1
2 、配置
首页的id设置成'/'即可,其它界面的就无所谓了
import 'package:fluro/fluro.dart';
import 'package:ui_test_no4/page_one.dart';
import 'package:ui_test_no4/page_two.dart';
class Routes {
static Router router;//静态声明代表这是一个类成员不是对象成员
static String page1 = '/';
static String page2 = '/page2';
static void configureRoutes(Router router) {
//两个参数,第一个是跳转路由的id,第二个是一个handler,用于接收参数、创建函数
router.define(
page1, handler: Handler(handlerFunc: (context, params) => Page1()));
router.define(
page2, handler: Handler(handlerFunc: (context, params) {
var message = params['message']?.first;//取出传参
return Page2(message);
}));
Routes.router = router;
}
}
3、设置主函数初始化
import 'package:flutter/material.dart';
import 'package:fluro/fluro.dart';
import 'package:ui_test_no4/routes.dart';
///启动
void main() {
///初始化并配置路由
final router = new Router();
Routes.configureRoutes(router);
runApp(
MaterialApp(
onGenerateRoute: Routes.router.generator
)
);
}
4、界面跳转,A跳转到B界面
///跳转到第二个界面
intentTo(BuildContext context) {
///字符串编码
var json = jsonEncode(Utf8Encoder().convert('来自第一个界面'));
Routes.router.navigateTo(
context, '${Routes.page2}?message=$json',//跳转路径,多个参数类似url?a=1&b=2传值
transition: TransitionType.inFromRight//过场效果
).then((result) {//回传值
if (result != null) {
message = result;
}
});
}
5、B返回A界面传值
RaisedButton(onPressed: () => Navigator.pop(context, '来自第二个界面'),
child: Text('返回上一个界面'))
6 为什么这么写路由?
为了路由同一管理,之后修改,例如想加上用户登陆时长限制,直接Routes里面修改就好
使用scoped_model
什么是scoped_model
Scoped_model是一个dart第三方库,提供了让您能够轻松地将数据模型从父Widget传递到它的后代的功能。此外,它还会在模型更新时重新渲染使用该模型的所有子项。,而且无论该widget是不是有状态的都可以进行更新,再一次build
实现原理
Scoped model使用了观察者模式,将数据模型放在父代,后代通过找到父代的model进行数据渲染,最后数据改变时将数据传回,父代再通知所有用到了该model的子代去更新状态。
用途
可以进行全局公共数据共享,除了使用scoped_model也可以使用shared_preferences、或者新建一个类将共享数据设置成static类的成员。
- 使用sp的缺点是效率低,需要读写数据库,同时不可以在状态改变后主动发起更新
- 使用static类成员的缺点也是不可以主动发起类更新
- 使用scoped_model的速度上更快,并且可以监听变换后自动发起setState变化
具体使用
1 添加依赖
scoped_model: ^0.3.0
2 、创建Model
import 'package:scoped_model/scoped_model.dart';
class CountModel extends Model{
int _count = 0;
get count => _count;
void increment(){
_count++;
notifyListeners();
}
}
3、将model放在顶层
//创建顶层状态
CountModel countModel = CountModel();
@override
Widget build(BuildContext context) {
return ScopedModel<CountModel>(
model: countModel,
child: new MaterialApp(
home: TopScreen(),
),
);
}
4、在子界面中获取Model
@override
Widget build(BuildContext context) {
return ScopedModelDescendant<CountModel>(
builder: (context,child,model){
return Scaffold(
body: Center(
child: Text(
model.count.toString(),
style: TextStyle(fontSize: 48.0),
),
),
);
},
);
}
5 一个简单的demo可以直接运行
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
class CountModel extends Model{
int _count = 0;
get count => _count;
void increment()
{
_count++;
notifyListeners();
}
}
void main()
{
CountModel countModel = CountModel();
runApp(new ScopedModel(model: countModel, child: MaterialApp(
home: PageA(),
)));
}
class PageA extends StatefulWidget{
@override
State<StatefulWidget> createState() {
// TODO: implement createState
return new _PageA();
}
}
class _PageA extends State<PageA>
{
@override
Widget build(BuildContext context) {
// TODO: implement build
return ScopedModelDescendant<CountModel>(
builder: (context,child,model)
{
return Scaffold(
appBar: AppBar(
title: Text(model.count.toString()),
),
body: RaisedButton(
onPressed: (){model.increment();},
),
);
},
);
}
}
dart优雅避空
语法糖 ?.
它的意思是左边如果为空返回 null,否则返回右边的值。
A?.B
如果 A 等于 null,那么 A?.B 为 null如果 A 不等于 null,那么 A?.B 等价于 A.B
语法糖 ??
它的意思是左边如果为空返回右边的值,否则不处理。
A??B如果 A 等于 null,那么 A??B 为 B如果 A 不等于 null,那么 A??B 为 A
BoxShadow
下面给出每个参数的具体含义
/** 阴影效果
const BoxShadow({
Color color = const Color(0xFF000000),//阴影默认颜色,不能与父容器同时设置color
Offset offset = Offset.zero,//延伸的阴影,向右下偏移的距离
double blurRadius = 0.0,//延伸距离,会有模糊效果
this.spreadRadius = 0.0 //延伸距离,不会有模糊效果
})
*/
举例
boxShadow: [BoxShadow(color: Color(0xFF908F90),offset: Offset(2,2),spreadRadius: 5)],
BottomSheet的使用
参数解释
基本使用
showModalBottomSheet(
backgroundColor: Colors.transparent,
context: curContext,
builder: (BuildContext context){
return Container();
});
Image_picker
安装
image_picker: ^0.6.2+1
配置
只有苹果的需要配置
我的配置如下
<key>NSCameraUsageDescription</key>
<string>it is used to write one question and answer one question</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>it is used to write one question and answer one question</string>
<key>NSMicrophoneUsageDescription</key>
<string>it is used to record a video for publish in the third part</string>
使用
均返回路径
/*拍照*/
_takePhoto() async {
var image = await ImagePicker.pickImage(source: ImageSource.camera);
print('拍照返回:' + image.toString());
}
/*相册*/
_openGallery() async {
var image = await ImagePicker.pickImage(source: ImageSource.gallery);
print('相册返回:' + image.toString());
}
/*选取视频*/
_getVideo() async {
var image = await ImagePicker.pickVideo(source: ImageSource.gallery);
print('选取视频:' + image.toString());
}
/*拍摄视频*/
_takeVideo() async {
var image = await ImagePicker.pickVideo(source: ImageSource.camera);
print('拍摄视频:' + image.toString());
}
flutter阿里云oss对象存储使用dio上传
工具类文件夹,uploadToOss.dart
import 'package:dio/dio.dart';
import 'dart:io';
import 'dart:convert';
import 'package:crypto/crypto.dart';
//使用oss进行文件上传
class OssUpLoad
{
//验证文本域
static String policyText =
'{"expiration": "2020-01-01T12:00:00.000Z","conditions": [["content-length-range", 0, 1048576000]]}';
//进行utf8编码
static List<int> policyText_utf8 = utf8.encode(policyText);
//进行base64编码
static String policy_base64 = base64.encode(policyText_utf8);
//再次进行utf8编码
static List<int> policy = utf8.encode(policy_base64);
static String accesskey= '你的accessKey';
static String accessId = "你的accessId";
//进行utf8 编码
static List<int> key2 = utf8.encode(accesskey);
//通过hmac,使用sha1进行加密
static List<int> signature_pre = new Hmac(sha1, key2).convert(policy).bytes;
//最后一步,将上述所得进行base64 编码
String signature = base64.encode(signature_pre);
void uploadFile(File file,String fileName )async
{
//要上传的文件,此处从参数传入
//fileName是oss对象的文件名
File imageFile = file;
//dio的请求配置,这一步非常重要!
BaseOptions options = new BaseOptions();
options.responseType = ResponseType.plain;
//创建dio对象
Dio dio = new Dio(options);
//创建一个formdata,作为dio的参数
FormData data = new FormData.from({
'Filename': fileName,//文件名
'key' : fileName,//key是("文件夹名(对应于oss服务中的文件夹)/" + fileName)
'policy': policy_base64,
'OSSAccessKeyId': accessId,//accessId认证身份
'success_action_status' : '200', //让服务端返回200,不然,默认会返回204
'signature': signature,
'file': new UploadFileInfo(imageFile, "imageFileName")
});
try {
//域名修改为自己的,注意使用https协议
Response response = await dio.post("https://xxxxxxxxx.aliyuncs.com",data: data);
print(response.headers);
print(response.data);
} on DioError catch(e) {
print(e.message);
print("00000000000000000000000000000");
print(e.response.data);
print(e.response.headers);
print(e.response.request);
}
}
}
三种App目录之间的区别与flutter的获取方式
- 临时目录:可以使用 getTemporaryDirectory() 来获取临时目录; 系统可随时清除的临时目录(缓存)。在iOS上,这对应于NSTemporaryDirectory() 返回的值。在Android上,这是getCacheDir()返回的值。
- 文档目录:可以使用getApplicationDocumentsDirectory()来获取应用程序的文档目录,该目录用于存储只有自己可以访问的文件。只有当应用程序被卸载时,系统才会清除该目录。在iOS上,这对应于NSDocumentDirectory。在Android上,这是AppData目录。
- 外部存储目录:可以使用getExternalStorageDirectory()来获取外部存储目录,如SD卡;由于iOS不支持外部目录,所以在iOS下调用该方法会抛出UnsupportedError异常,而在Android下结果是android SDK中getExternalStorageDirectory的返回值。
word文档实现预览功能
给出链接如下https://blog.csdn.net/qxianx/article/details/81317894
我们尝试过XDOC云服务,链接https://blog.csdn.net/sqlhub/article/details/84885334
可以直接使用webview展示,但是发现展示效果不好,也不可以放大缩小。
最后,采用的方案是后端转成pdf,前端flutter使用flutter_full_pdf_viewer预览
手机选取pdf doc docx文件
一开始使用的是评分99的file_picker插件,但是经常会闪退,后面经过比较使用了flutter_file_picker插件
里面需要填写的参数有
allowedFileExtensions:选择文件后插件根据这个参数进行文件类型检查
allowedUtiTypes:苹果上Uti类型,插件会根据这个参数决定使得iphone手机文件哪些是可以选择的(enabled),其余(disabled),参考链接
allowedMimeTypes:安卓上Mime类型,插件会根据这个参数决定android手机文件哪些是可以选择的(enabled),其余(disabled),参考链接
invalidFileNamesSymbols:如果文件名中含有这些字符,插件会使用'_'替换
我们的需求是选择pdf\doc\docx类型文件,参数设置为
allowedFileExtensions: ['doc','docx','pdf'],
allowedUtiTypes: ['com.adobe.pdf','com.microsoft.word.doc','org.openxmlformats.wordprocessingml.document'],
allowedMimeTypes: ['application/pdf','application/msword','application/vnd.openxmlformats-officedocument.wordprocessingml.document'],
invalidFileNameSymbols: ['/'],
上传文件使用通知显示进度条
功能需求是上传视频到阿里oss存储,但是由于视频本身较大,让用户等待上传过程很不友好,需要放在后台运行,并且使用通知显示上传进度
需要解决的第一个问题:如何获取上传进度?
上传oss使用的是dio框架,具体代码参考本文前部分
经查资料,dio可以实时获取上传进度,使用onSendProgress代码为
Response response = await dio.post("https://beiweijia-bucket-1.oss-cn-beijing.aliyuncs.com",data: data,onSendProgress: (int send,int total)
{
});
需要解决的第二个问题:如果在通知中显示上传进度
使用插件flutter_local_notifications,但是无法更新某个通知的body,后面发现无需去更改,只要使用同一个通知id,一直发拥有新的body的通知即可,新的通知会覆盖使用相同id的旧的通知。
需要解决的第三个问题:进度条通知取消震动
因为进度条的通知会不断发送更新,如果每发一次震动一次很不友好
更改方法为增加enableVibration:false,代码如下
var androidPlatformChannelSpecifics = AndroidNotificationDetails(
'10000', 'showUploadProgree', 'showUploadProgress',
importance: Importance.Low, priority: Priority.Low, ticker: 'ticker',
enableVibration: false,
);
对于android8.0+,还需要重新指定新的channel id,因为如果channel id不变,发送通知是否震动的设置会和该channel第一发送通知设置相同并之后保持不变。
需要解决的第四个问题:通知显示多行文字
发现插件flutter_local_notification的通知只会显示一行文字,无法满足需求,解决方法是添加
style:AndroidNotificationStyle.BigText属性,代码如下
'10000', 'showUploadProgree', 'showUploadProgress',
importance: Importance.Low, priority: Priority.Low, ticker: 'ticker',
enableVibration: false,
style: AndroidNotificationStyle.BigText
);
使用推送服务
在线推送比较好搞,极光用起来也方便,参考链接
但是离线的极光需要vip,最低版本的一年三万,计划使用mob推送
android平台app后台保活
medium上发现大神写的教程,链接
github对应的demo链接