Flutter 中文网
Flutter 文档
Dart 文档
Pub 插件包
Github Flutter
可以使用 Fluro
这个路由库的传参方式好像只有一种,不太灵活;传个中文的参数,必须编码,否则报错;路由的切换动画使用方便。
框架的路由功能:命名路由不能传参;路由栈的实例化方式可以传中文,路由退栈时也可以传数据。
// 框架的路由功能,还有很多...
Navigator.push(context, ...).then((params){...});
Navigator.pop(context, params);
dependencies:
fluro: ^1.4.0
// 常用的方法
final router = Router();
var usersHandler = Handler(handlerFunc: (BuildContext context, Map params) {
return UsersScreen(params["id"][0]);
});
void defineRoutes(Router router) {
router.define("users/:id", handler: usersHandler, transitionType: TransitionType.inFromLeft);
}
router.navigateTo(context, "/users/1234", transition: TransitionType.fadeIn);
// 具体项目的用法
// application.dart 文件
class Application {
static Router router;
}
// route_handlers.dart 文件
var demoRouteHandler = new Handler(
handlerFunc: (BuildContext context, Map> params) {
String message = params["message"]?.first;
return new DemoSimpleComponent(message: message);
});
// routes.dart 文件
class Routes {
static String root = "/";
static String demoSimple = "/demo";
static void configureRoutes(Router router) {
router.notFoundHandler = new Handler(
handlerFunc: (BuildContext context, Map> params) {
print("ROUTE WAS NOT FOUND !!!");
});
router.define(root, handler: rootHandler);
router.define(demoSimple, handler: demoRouteHandler, transitionType: TransitionType.inFromLeft);
}
}
// app_component.dart 文件 代码有省略
class AppComponentState extends State {
AppComponentState() {
final router = new Router();
Routes.configureRoutes(router);
Application.router = router;
}
@override
Widget build(BuildContext context) {
final app = new MaterialApp(
title: 'Fluro',
debugShowCheckedModeBanner: false,
theme: new ThemeData(
primarySwatch: Colors.blue,
),
onGenerateRoute: Application.router.generator, // 生成路由
);
print("initial route = ${app.initialRoute}");
return app;
}
}
// 手动调用路由功能
String route = "/demo?message=$message&color_hex=$hexCode";
TransitionType transitionType = TransitionType.native; // TransitionType.inFromLeft fadeIn
Application.router
.navigateTo(context, route, transition: transitionType)
.then((result) {
if (key == "pop-result") {
Application.router.navigateTo(context, "/demo/func?message=$result");
}
});
可以使用 flutter_screenutil
根据设计图的尺寸进行适配,可以适配宽度、高度、字体大小,传入整型数值,按照比例换算后返回浮点数值。
框架的 Flex/Row/Column -> Expended 可以设置 flex 。
dependencies:
flutter:
sdk: flutter
# 添加依赖
flutter_screenutil: ^0.5.0
import 'package:flutter_screenutil/flutter_screenutil.dart';
// 默认 width : 1080px , height:1920px , allowFontScaling:false
ScreenUtil.instance = ScreenUtil(width: 750, height: 1334, allowFontScaling: true)..init(context);
// 根据屏幕宽度适配 width:
ScreenUtil.getInstance().setWidth(540);
ScreenUtil.getInstance().setHeight(200);
ScreenUtil().setHeight(200);
// 传入字体大小
ScreenUtil.getInstance().setSp(28) ;
ScreenUtil(allowFontScaling: true).setSp(28);
// 相关的属性
ScreenUtil.pixelRatio //设备的像素密度
ScreenUtil.screenWidth //设备宽度
ScreenUtil.screenHeight //设备高度
ScreenUtil.bottomBarHeight //底部安全区距离,适用于全面屏下面有按键的
ScreenUtil.statusBarHeight //状态栏高度 刘海屏会更高 单位px
ScreenUtil.textScaleFactory //系统字体缩放比例
ScreenUtil.getInstance().scaleWidth // 实际宽度的dp与设计稿px的比例
ScreenUtil.getInstance().scaleHeight // 实际高度的dp与设计稿px的比例
可以使用 fluttertoast
fluttertoast: ^3.0.3
flutter packages get
import 'package:fluttertoast/fluttertoast.dart';
Fluttertoast.showToast(
msg: "This is a Toast 这是测试",
toastLength: Toast.LENGTH_SHORT, // Toast.LENGTH_LONG
gravity: ToastGravity.BOTTOM, // TOP CENTER
timeInSecForIos: 1,
backgroundColor: Color(0x99000000),
textColor: Colors.white,
fontSize: 16.0
);
Fluttertoast.cancel();
可以使用 dio
详细的中文文档,还有丰富的例子,忍不住点了 star 。
dependencies:
dio: ^2.1.x // 请使用pub上2.1分支的最新版本
import 'package:dio/dio.dart';
Response response;
Dio dio = new Dio();
// 配置 dio 实例
dio.options.baseUrl = "https://www.xx.com/api"; // 如果请求的路径以 http 开头,则忽略
dio.options.connectTimeout = 5000; // 5s
dio.options.receiveTimeout = 3000;
dio.options.contentType = ContentType.parse("application/x-www-form-urlencoded");
// 或者通过传递一个 `options` 来创建 dio 实例
Options options = new BaseOptions(
baseUrl: "https://www.xx.com/api",
connectTimeout: 5000,
receiveTimeout: 3000,
);
// Dio dio = new Dio(options);
response = await dio.get("/test?id=12&name=wendu");
print(response.data.toString());
// 请求参数也可以通过对象传递,上面的代码等同于:
response = await dio.get("/test", queryParameters: {"id": 12, "name": "wendu"});
print(response.data.toString());
response = await dio.post("/test", data: {"id": 12, "name": "wendu"});
response = await dio.download("https://www.google.com/", "./xx.html");
FormData formData = new FormData.from({
"name": "wendux",
"age": 25,
});
response = await dio.post("/info", data: formData);
// Dio 实例的核心 API 是 :
Future request(String path, {data, Map queryParameters, Options options, CancelToken cancelToken,
ProgressCallback onSendProgress, ProgressCallback onReceiveProgress);
response = await request(
"/test",
data: {"id": 12, "name": "xx"},
options: Options(method: "GET"),
);
// 请求配置
// method baseUrl headers path contentType responseType validateStatus queryParameters
// 响应数据 Response
// data headers request statusCode isRedirect
// 泛型支持 指定响应数据类型
dio.options.responseType=ResponseType.plain;
Response response = await dio.get("/test", options: Options(responseType: ResponseType.plain));
Response response = await dio.get("/test");
// 拦截器
dio.interceptors.add(InterceptorsWrapper(
onRequest:(RequestOptions options){
return options; // continue
// 如果你想完成请求并返回一些自定义数据,可以返回一个`Response`对象或返回`dio.resolve(data)`
// 如果你想终止请求并触发一个错误,你可以返回一个`DioError`对象,或返回`dio.reject(errMsg)`
return dio.resolve("fake data");
},
onRequest:(Options options) async {
Response response = await dio.get("/token");
options.headers["token"] = response.data["data"]["token"];
// 需要串行化请求/响应的场景中
dio.interceptors.requestLock.lock(); // dio.lock() dio.clear()
dio.interceptors.requestLock.unlock();
return options; // continue
},
onResponse:(Response response) {
return response; // continue
},
onError: (DioError e) {
return e; // continue
}
));
// 日志 拦截器队列的执行顺序是 FIFO 建议把 log 拦截添加到队尾
dio.interceptors.add(LogInterceptor(responseBody: false)); // 开启请求日志
// Cookie 管理
// dio 默认使用 CookieJar , 它会将cookie保存在内存中。
// 如果您想对 cookie 进行持久化, 请使用 PersistCookieJar
dio.interceptors.add(CookieManager(CookieJar()));
// FormData
FormData formData = new FormData.from({
"name": "wendux",
"age": 25,
"file": new UploadFileInfo(new File("./example/upload.txt"), "upload.txt")
});
response = await dio.post("/info", data: formData);
// 转换器
// 强烈建议 json 的解码通过 compute 方法在后台进行
_parseAndDecode(String response) {
return jsonDecode(response);
}
parseJson(String text) {
return compute(_parseAndDecode, text);
}
void main() {
(dio.transformer as DefaultTransformer).jsonDecodeCallback = parseJson;
runApp(MyApp());
}
// 执行流
// 请求拦截器 >> 请求转换器 >> 发起请求 >> 响应转换器 >> 响应拦截器 >> 最终结果
// HttpClientAdapter
// Dio 和 HttpClient 之间的桥梁
// 设置 Http 代理
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
client.findProxy = (uri) {
//proxy all request to localhost:8888
return "PROXY localhost:8888";
};
// you can also create a new HttpClient to dio
// return new HttpClient();
};
// Https 证书校验 请求取消 其它的可以看官方的
可以使用 fluttericon
图标的素材也可以使用 iconfont
选择好图标,或者上传好图标后,设置名字,点击下载按钮,可以下载转换后的字体文件和代码,就可以在项目中使用了。
不需要的图标库要删除,还有框架自带的图标 uses-material-design: false
。
可以使用高德地图的 Flutter 插件包,文档 ,API
调用原生功能获取图片或视频,可以使用 image_picker
dependencies:
image_picker: ^0.5.3+1
$ flutter packages get
import 'package:image_picker/image_picker.dart';
Future getImage() async {
var image = await ImagePicker.pickImage(source: ImageSource.camera);
setState(() {
_image = image; // File _image; Image.file(_image);
});
}
日期选择可以使用 flutter_picker
一个通过滚动或滑动选择的功能,可以用来选择数字、日期、数组或关联的数组。
flutter_picker:
git: git://github.com/yangyxd/flutter_picker.git
import 'package:flutter_picker/flutter_picker.dart';
final GlobalKey _scaffoldKey = GlobalKey();
showPicker(BuildContext context) {
Picker(
cancelText: "取消",
confirmText: "保存",
adapter: DateTimePickerAdapter(type: 7, isNumberMonth: true),
title: Text("选择日期"),
textAlign: TextAlign.left,
textStyle: const TextStyle(color: Colors.blue),
onConfirm: (Picker picker, List value) {
DateTime time = (picker.adapter as DateTimePickerAdapter).value;
String year = time.year.toString();
String month = time.month > 9 ? time.month.toString() : "0" + time.month.toString();
String day = time.day > 9 ? time.day.toString() : "0" + time.day.toString();
setState(() {
_value = "${year+'-'+month+'-'+day}";
});
},
).show(_scaffoldKey.currentState);
Picker(
changeToFirst: true,
adapter: PickerDataAdapter(data: dataList),
title: Text("选择城市"),
cancelText: "取消",
confirmText: "保存",
textAlign: TextAlign.left,
textStyle: const TextStyle(color: Colors.blue),
onConfirm: (Picker picker, List value) {
var arry = picker.getSelectedValues();
provinceGuid = arry[0];
provinceName = dataDicList.firstWhere((c) => c[arry[0]] != null)[arry[0]];
cityGuid = arry[1];
cityName = dataDicList.firstWhere((c) => c[arry[1]] != null)[arry[1]];
regionGuid = arry[2];
regionName = dataDicList.firstWhere((c) => c[arry[2]] != null)[arry[2]];
setState(() {
userInfo["provinceGuid"] = provinceGuid;
userInfo["provinceName"] = provinceName;
userInfo["cityGuid"] = cityGuid;
userInfo["cityName"] = cityName;
userInfo["regionGuid"] = regionGuid;
userInfo["regionName"] = regionName;
});
}
).show(_scaffoldKey.currentState);
}
手机的权限授权插件,permission_handler
使用框架的功能开发的。
import 'dart:io';
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:path_provider/path_provider.dart';
/// ### 记录项目日志、捕获项目错误、缓存所有记录、写入文件、上传服务器
/// * 初始化:
/// ```dart
/// ErrorLog.log = new ErrorLog(
/// reportZone: () async {
/// runApp(new MyApp());
/// },
/// debugMode: true,
/// uploadFile: (file) async {},
/// minutesWait: 30,
/// [fileName: 'error_log.txt']
/// );
/// ```
///
/// ### 项目日志
/// 默认标记的项目日志可以使用相应的方法;自定义标记的项目日志可以使用基础方法。
/// * 使用方式:
/// ```dart
/// ErrorLog.log.debug('msg'*8);
/// ErrorLog.log.info('msg'*8);
/// ErrorLog.log.warn('msg'*8);
/// ErrorLog.log.error('msg'*8);
/// ErrorLog.log.fatal('msg'*8);
/// ErrorLog.log.collectLog('msg'*8, 'error'); // 都是调用这个基础方法
/// ```
/// * 输出格式:
/// #### [2019-04-18 11:50:29.844858][error] msgmsgmsgmsgmsgmsgmsgmsg
///
/// ### 错误报告
/// 错误报告的信息比较多,标记为`report`。
/// * 使用方式:
/// 自动捕获错误,不包含 `try/catch`,不包含 `print`。
/// * 输出格式:
/// #### [2019-04-18 14:05:03.578755][report]
/// #### 所有错误信息
///
/// ### 写入文件
/// 所有记录都缓存在一个数组里,如果`debugMode`为真,打印到控制台;
/// 否则根据数组索引异步写入文件`error_log.txt`,在初始化时可传参`fileName`。
/// * 使用方式:
/// ```dart
/// ErrorLog.log.printBuffer(); // 打印记录缓存
/// ErrorLog.log.clearFile(); // 清空记录文件
/// ErrorLog.log.printFile(); // 打印文件内容
/// ```
///
/// ### 上传服务器
/// 打开应用时上传一次,然后设置计时器,建议30分钟上传一次。
/// * 使用方式:
/// 初始化时传参`uploadFile`和`minutesWait`,获取记录的文件`ErrorLog.log.logFile`。
///
class ErrorLog {
/// 实例,静态属性
static ErrorLog log;
/// 捕获错误的区域
Function _reportZone;
/// 是否调试模式
bool _debugMode;
/// 缓存记录的数组
List _logBuffer;
/// 文件记录
File _logFile;
File get logFile => _logFile;
/// 文件记录的起点
int _startIndex;
/// 文件记录的终点
int _endIndex;
/// 上传记录文件
Function _uploadFile;
/// 上传时间间隔
int _minutesWait;
/// 记录文件是否变化
bool _fileChange;
/// 记录文件的名称
String fileName;
ErrorLog({
@required Function reportZone,
@required bool debugMode,
@required Function uploadFile,
@required int minutesWait,
this.fileName = 'error_log.txt'
}) {
_reportZone = reportZone;
_debugMode = debugMode;
_uploadFile = uploadFile;
_minutesWait = minutesWait;
init();
}
/// 初始化
void init() async {
_logBuffer = [];
_startIndex = 0;
_endIndex = 0;
_fileChange = false;
FlutterError.onError = (FlutterErrorDetails details) {
reportError(details);
};
runZoned(
_reportZone,
onError: (Object obj, StackTrace stack) {
var details = makeDetails(obj, stack);
reportError(details);
}
);
_logFile = await _getLocalFile();
_uploadFile(_logFile);
Timer.periodic(Duration(minutes: _minutesWait), (timer) async {
if (_fileChange) {
await _uploadFile(_logFile);
_fileChange = false;
}
});
}
/// 错误报告
void reportError(FlutterErrorDetails details) {
String errorMeta = '[' + (new DateTime.now().toString()) + '][report]';
_logBuffer.add(errorMeta + '\n' + details.toString());
if (_debugMode) {
print(errorMeta);
print(details.toString());
} else {
_writeFile();
}
}
/// 项目日志
collectLog(String line, String label) {
String contents = '[' + (new DateTime.now().toString()) + '][' + label + '] ' + line;
_logBuffer.add(contents + '\n');
if (_debugMode) {
print(contents);
} else {
_writeFile();
}
}
/// 打印文件
Future printFile() async {
_readLocalFile().then((contents) {
print(contents);
});
}
/// 打印缓存
Future printBuffer() async {
print( _logBuffer.toString() );
}
/// 清空文件
Future clearFile() async {
await _logFile.writeAsString('', mode: FileMode.write);
}
/// 实时写入文件,防止意外
Future _writeFile() async {
int len = _logBuffer.length;
if (len > _endIndex) {
_startIndex = _endIndex;
_endIndex = len;
Iterable range = _logBuffer.getRange(_startIndex, _endIndex);
await _writeLocalFile( range.join('\n') );
_fileChange = true;
}
}
/// 获取文件
Future _getLocalFile() async {
String dir = (await getApplicationDocumentsDirectory()).path;
return new File('$dir/' + fileName);
}
/// 读取文件
Future _readLocalFile() async {
String contents = await _logFile.readAsString();
return contents;
}
/// 写入文件
Future _writeLocalFile(String contents) async {
await _logFile.writeAsString(contents, mode: FileMode.append, flush: false);
}
/// 构建错误信息
FlutterErrorDetails makeDetails(Object obj, StackTrace stack) {
return FlutterErrorDetails(exception: obj, stack: stack);
}
/// 调试
void debug(String msg) {
collectLog(msg, 'debug');
}
/// 信息
void info(String msg) {
collectLog(msg, 'info');
}
/// 警告
void warn(String msg) {
collectLog(msg, 'warn');
}
/// 错误
void error(String msg) {
collectLog(msg, 'error');
}
/// 致命错误
void fatal(String msg) {
collectLog(msg, 'fatal');
}
}
1、安卓模拟器在调试的时候,可能会出现旧的组件,可以停止调试,卸载模拟器上的项目,再重新调试;
2、定义一个类时,写好注释方便使用,注释符号使用 ///
[]
*
``
,解释一下,每行的开头符号、引用其它类名、列表项符号、引用代码;
3、使用组件时,多用用右键的重构功能,减少被晕的次数;
4、组件更新后,想要真正渲染到界面需要修改 key
,因为更新组件的条件是判断新旧组件的 runtimeType
和 key
是否有变化;