1.安卓开发环境的基本配置
首先修改你创建的项目中的/android/build.gradle
文件,修改buildscript
以及allprojects
的仓库地址,否则编译打包会卡住不动,其次就是package get
会卡住,原因是因为连接不上谷歌的服务,有点蛋疼
buildscript {
repositories {
// google()
// jcenter()
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'https://maven.aliyun.com/nexus/content/groups/public' }
}
}
allprojects {
repositories {
// google()
// jcenter()
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'https://maven.aliyun.com/nexus/content/groups/public' }
}
2.SignleChildScrollView页面不满一屏无法撑满全屏
可以换个布局组件或者将其设置为Container
子组件,设置alignment
,如下
Container(
alignment: Alignment.topLeft,
child: SingleChildScrollView(),
),
3.自定义AppBar
AppBar
这个组件可拓展性还是比较低的,尤其是它的高度都是写死的,这种情况可以换SliverAppBar
或者自定义AppBar
,换句话说自定义组件,这里就不详诉
4.push页面返回,保持上一次的页面位置状态
使用 StatefulWidget
混入 AutomaticKeepAliveClientMixin
,覆盖wantKeepAlive
属性
class TesPage extends StatefulWidget {
@override
createState() => TesPageState();
}
class TesPageState extends State<TesPage> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
}
5.路由跳转方式的选择
通常路由跳转会用到Navigator.push
推入路由栈,Navigator.pop
就移出、返回上一个的状态。如果在页面上全部用这两个方法,你会发现你的页面间的跳转仿佛乱了一样,原因就是Navigator.push
是每次将你的路由推入栈,也就是你可以一直做返回操作、手势来获取之前的页面状态。这个时候就要考虑用其他方式,如
pushAndRemoveUntil
:跳转到指定路由,删除先前的路由栈
pushNamed
:跳转到指定命名路由,路由栈不会删除。这种方式需要配合命名路由使用,即在入口文件MaterialApp
配置好routes
pushNamedAndRemoveUntil
:跳转到指定路由,删除先前的路由栈
pushReplacement
:路由替换
pushReplacementNamed
:跳转到指定命名路由,并删除最后的路由
6.页面初始化时拿到context
有时候,可能会在页面初始化使用context
,比如说路由相关的Navigator.of(context)
。解决方法很简单,在initState
方法中,this
指向上下文,即使用this.context
即可以保存页面初始的context
7.键盘弹出时将布局打乱,或者说将元素顶起来了
在 Scafold
里设置 resizeToAvoidBottomInset: false
,键盘会遮住布局,而不是顶起布局。
8.无法设置虚线边框
说实话当时发现Flutter
无法设置虚线边框的时候我是很震惊的,我感觉这算是基本的不能在基本的属性了,解决方法,参考官方issues
中大佬的解法,目前只能自己实现paint
方法,自己用Canvas
画
import 'package:flutter/material.dart';
import 'package:path_drawing/path_drawing.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
decoration: BoxDecoration(
border: DashPathBorder.all(
dashArray: CircularIntervalList<double>(<double>[5.0, 2.5]),
),
),
padding: const EdgeInsets.all(20.0),
child: const Text('You have pushed the button this many times:'),
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
class DashPathBorder extends Border {
DashPathBorder({
@required this.dashArray,
BorderSide top = BorderSide.none,
BorderSide left = BorderSide.none,
BorderSide right = BorderSide.none,
BorderSide bottom = BorderSide.none,
}) : super(
top: top,
left: left,
right: right,
bottom: bottom,
);
factory DashPathBorder.all({
BorderSide borderSide = const BorderSide(),
@required CircularIntervalList<double> dashArray,
}) {
return DashPathBorder(
dashArray: dashArray,
top: borderSide,
right: borderSide,
left: borderSide,
bottom: borderSide,
);
}
final CircularIntervalList<double> dashArray;
@override
void paint(
Canvas canvas,
Rect rect, {
TextDirection textDirection,
BoxShape shape = BoxShape.rectangle,
BorderRadius borderRadius,
}) {
if (isUniform) {
switch (top.style) {
case BorderStyle.none:
return;
case BorderStyle.solid:
switch (shape) {
case BoxShape.circle:
assert(borderRadius == null,
'A borderRadius can only be given for rectangular boxes.');
canvas.drawPath(
dashPath(Path()..addOval(rect), dashArray: dashArray),
top.toPaint(),
);
break;
case BoxShape.rectangle:
if (borderRadius != null) {
final RRect rrect =
RRect.fromRectAndRadius(rect, borderRadius.topLeft);
canvas.drawPath(
dashPath(Path()..addRRect(rrect), dashArray: dashArray),
top.toPaint(),
);
return;
}
canvas.drawPath(
dashPath(Path()..addRect(rect), dashArray: dashArray),
top.toPaint(),
);
break;
}
return;
}
}
assert(borderRadius == null,
'A borderRadius can only be given for uniform borders.');
assert(shape == BoxShape.rectangle,
'A border can only be drawn as a circle if it is uniform.');
// TODO(dnfield): implement when borders are not uniform.
}
}
9.使用Dio时,设置相应数据的媒体类型
使用Dio
封装网络请求,普通的请求最好设置相应类型dio.options.responseType = ResponseType.json
,否则可能接收到的响应可能是字符串。
10.build配置
1.安卓的SHA1码获取方式
很多第三方应用都需要用到安卓的SHA1码,在Flutter
应用中,首先用Andriod Studio
打开项目,随意选择一个文件,点击右上角Open for Editing in Android Studio
打开Terminal
,输入gradlew signingReport
,mac环境需要指定当前目录
最后控制台会打印出应用的各种签名信息,复制SHA1
码即可
2.安卓打包release
配置
首先打开/android/build.gradle
文件,在buildscript
中指定kotlin_version
,否则debug
、release
编译会不通过
buildscript {
ext.kotlin_version = '1.3.50'
repositories {
// google()
// jcenter()
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'https://maven.aliyun.com/repository/jcenter' }
maven { url 'https://maven.aliyun.com/nexus/content/groups/public' }
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
然后生成key
,同样打开Andriod Studio
,打开Terminal
,输入
keytool -genkey -v -keystore ~/key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key
生成过程中会让你输入存储密码和文件密码,生成完key
后记住文件地址
然后在在android
目录中创建一个文件,文件名为:key.properties
添加内容
storePassword = // 和生成key输入的存储密码一致
keyPassword = // 和生成key输入的密码一致
keyAlias = key //
storeFile = // key文件的存储路径
找到android/app/build.gradle
文件,添加签名配置singingConfig
以及打包配置buildTypes
android {
.....
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
ndk {
abiFilters 'armeabi-v7a','x86' // 指定打包平台
}
}
}
}
然后进入项目根目录,执行flutter build apk
,即可以打包一个安卓apk包
3.iOS调试打包注意事项
iOS
模拟器调试体验比安卓好的多,但是真机调试,首先你得有个苹果ID,然后用Xcode
打开项目,点击Signing & Capabilities
,找到Team
设置,设置一个开发账号,可以是未付费注册的苹果ID,设置好开发者证书
然后flutter run
的时候,选择一个真机设备,即可在真机上调试app
了,但是未付费注册的苹果ID证书有效期只有七天,七天后你手机上的app
就不可用了。
未付费注册的账号也无法打包上传App Store
,不过配置完了开发团队、账号后、开发证书后,可以直接flutter build ios
,这样会生成一个文件。这时候创建一个名为Payload
的文件夹,将打包生成的文件放进去、压缩,然后将压缩文件后缀改为ipa
,即可以生成一个ipa
文件了。然后借助第三方工具,如itunes(貌似已经不能用)、xxx助手
即可将这个文件安装在终端上运行。
11.总结
断断续续做这个小项目已经有几个月了,因为平常很忙,所以拖了这么久。Flutter
给我的感觉优点就是流程、动画较为细腻,相对于其它跨平台方案,使用JsBrigde
与native
端通信,有较大的优势,毕竟它是自己的引擎绘制的页面。但是它的官方组件太少,自定义组件所需精力较多(可能是我不熟悉),iOS
风格组件虽然样式和原生的差不多,但具体效果还是有差距。这种跨平台框架终于还是得要和原生开发人员一起操作才玩的溜。