使用Flutter一直是拒絕的路翻,感覺(jué)追不動(dòng)新技術(shù)了。但是當(dāng)看到用的人越來(lái)越多履羞,flutter的書(shū)籍已經(jīng)全面超過(guò)OC和Swift了峦萎。是時(shí)候低下還有幾許青絲的頭了,雖然學(xué)不精吧雹,但我學(xué)的廣。!_!
一涂身、準(zhǔn)備工作
找一些網(wǎng)上的免費(fèi)資料學(xué)起來(lái)雄卷,flutter中文網(wǎng)站看一個(gè)大概,一般感覺(jué)看不下去蛤售,先看個(gè)大概吧丁鹉。然后詳細(xì)的學(xué)習(xí)Flutter實(shí)戰(zhàn),將所有的代碼都敲一遍悴能。做到所有的功能自己實(shí)現(xiàn)揣钦。
建議使用Mac來(lái)開(kāi)發(fā),那么搭建環(huán)境會(huì)比較方便漠酿。Flutter使用Stable Channel冯凹,直接上最新的版本,雖然有一點(diǎn)坑炒嘲,碰到問(wèn)題后網(wǎng)上的解決方法有點(diǎn)少宇姚。但是辦法總比困難多匈庭,學(xué)習(xí)的太順利,記住的東西太少浑劳。
Dart語(yǔ)言的學(xué)習(xí)感覺(jué)花一點(diǎn)時(shí)間過(guò)一下就好阱持,簡(jiǎn)單的定義變量,函數(shù)魔熏,集合看一下衷咽,夠用。
二蒜绽、安裝及升級(jí)
注意:/Public/Flutter/flutter是我本地的路徑镶骗,需要替換。
- 參考https://flutter.dev/community/china上的描述滓窍,將flutter從下載到本地卖词。
git clone -b stable https://github.com/flutter/flutter.git
- 執(zhí)行
flutter doctor
進(jìn)行dart SDK的下載。 -
vim .bash_profile
中添加export PATH=~/Public/Flutter/flutter/bin:$PATH
吏夯。 -
source .bash_profile
讓PATH生效此蜈。 - 在android studio和VS code安裝flutter插件。
-
flutter upgrade
在/Users/arthurwang/Public/Flutter/flutter 安裝flutter路徑中進(jìn)行噪生。
三裆赵、 常用布局
- Container: 只有一個(gè)子Widget。默認(rèn)充滿跺嗽,包含了padding战授、margin、color桨嫁、寬高植兰、decoration等。
- Padding: 只有一個(gè)子Widget璃吧。只用于設(shè)置Padding楣导,常用于嵌套child,給child設(shè)置padding畜挨。
- Center:只有一個(gè)子Widget筒繁。只用于居中顯示,常用于嵌套child巴元,給child設(shè)置居中毡咏。
- Stack:可以有多個(gè)子Widget。子Widget堆疊在一起逮刨。
- Column:可以有多個(gè)子Widget呕缭。垂直布局。
- Row: 可以有多個(gè)子Widget。水平布局臊旭。
- Expanded:只有一個(gè)子Widget落恼。在Column和Row中充滿。(不會(huì)超出父視圖)
- ListView:可以有多個(gè)子Widget离熏。
- Align: 只有一個(gè)子Widget佳谦。 絕對(duì)布局,可以設(shè)置位置滋戳。
- SizeBox: 強(qiáng)制設(shè)置它的孩子寬度或者高度為指定值钻蔑。傳width、height奸鸯、child咪笑。
明白這些布局的使用,基本能夠滿足頁(yè)面的布局娄涩。
四窗怒、組件
- MaterailApp:作為APP頂層的主要入口,可配置主題蓄拣,多語(yǔ)言扬虚,路由等。
- Scaffold: 用戶頁(yè)面承載Widget球恤,包含appbar辜昵、snackbar、drawer等material design設(shè)定咽斧。
- AppBar: 用于Scaffold的appbar堪置,內(nèi)有標(biāo)題、二級(jí)頁(yè)面返回按鍵等张惹。
- Text:顯示文本舀锨,可通過(guò)style設(shè)置TextStyle來(lái)設(shè)置字體樣式等。
- RichText: 福文本宛逗。設(shè)置TextSpan坎匿,可以拼接出富文本場(chǎng)景。
- TextField: 文本輸入框拧额。
- Image: 圖片加載碑诉。
- FlatButton: 按鍵點(diǎn)擊彪腔。
明白基本組件侥锦,可以構(gòu)建一般頁(yè)面了。
五德挣、 網(wǎng)絡(luò)請(qǐng)求
有兩種思路(1)通過(guò)flutter和native交互恭垦,發(fā)送url和params到native,然后返回結(jié)果。(2)通過(guò)flutter和native交互番挺,從native獲取params然后在flutter進(jìn)行網(wǎng)絡(luò)請(qǐng)求唠帝。
考慮到Android的網(wǎng)絡(luò)第三方庫(kù)使用注解來(lái)實(shí)現(xiàn)請(qǐng)求,所以放棄了思路(1)玄柏,嘗試思路(2)并封裝請(qǐng)求襟衰。
網(wǎng)絡(luò)請(qǐng)求官方推薦第三方庫(kù)Dio。
class ApiService {
// 單例配置
static ApiService get apiService => _getInstance();
static ApiService _apiService = ApiService._internal();
// 網(wǎng)絡(luò)請(qǐng)求配置
static const _platform = const MethodChannel("******");
static Dio _dio = Dio();
String? _baseUrl;
static const int _GET = 0;
static const int _POST = 1;
// 初始化
factory ApiService() => _getInstance();
static ApiService _getInstance() {
return _apiService;
}
ApiService._internal() {
_dio.options.connectTimeout = 5000;
_dio.options.receiveTimeout = 3000;
_dio.options.contentType = Headers.formUrlEncodedContentType;
_dio.options.responseType = ResponseType.json;
_setBaseUrl();
}
Future<void> _setBaseUrl() async {
try {
String? baseUrl = await _platform.invokeMethod("***") as String?;
if (null != baseUrl) {
_dio.options.baseUrl = baseUrl;
_baseUrl = baseUrl;
}
} on PlatformException catch (e) {
// Do nothing
}
}
Future<Map<String, dynamic>?> _request(
int type, String url, Map<String, String>? params) async {
if (null == _baseUrl) {
await _setBaseUrl();
}
Map<String, String>? tokenParams;
try {
Map<String, String>? paramsMap;
if (null != params) {
paramsMap = Map();
paramsMap["params"] = json.encode(params);
}
tokenParams = Map<String, String>.from(
await _platform.invokeMethod("***", paramsMap));
} on PlatformException catch (e) {
// Do nothing
}
print("ApiService url is $url, params is $tokenParams");
Response<Map<String, dynamic>>? response;
try {
switch (type) {
case _GET:
response = await _dio.get(url, queryParameters: tokenParams);
break;
case _POST:
response = await _dio.post(url, queryParameters: tokenParams);
break;
}
} on DioError catch (e) {
print("$url 錯(cuò)誤 ${e.message}");
throw RequestError(kUnknownError, "服務(wù)器走失了粪摘,請(qǐng)稍后重試");
}
print("$url response is $response");
if (0 != response?.data?["status"]) {
throw RequestError(response?.data?["status"], response?.data?["msg"]);
} else {
print("ApiService response.data is ${response?.data}");
return response?.data;
}
}
// 網(wǎng)絡(luò)請(qǐng)求
// 獲取信息
Future<UserInfoEntity> getUserInfo() async {
Map<String, dynamic>? response =
await _request(_GET, "url/url/url", null);
if (null != response) {
return UserInfoEntity().fromJson(response);
} else {
throw RequestError(kUnknownError, "服務(wù)器走失了瀑晒,請(qǐng)稍后重試");
}
}
采用工廠模式創(chuàng)建一個(gè)網(wǎng)絡(luò)請(qǐng)求,在創(chuàng)建時(shí)設(shè)置好配置徘意。之后添加新的接口苔悦,只需要在底部依次添加。
Future<UserInfoEntity> getUserInfo() async {
Map<String, dynamic>? response =
await _request(_GET, "url/url/url", null);
if (null != response) {
return UserInfoEntity().fromJson(response);
} else {
throw RequestError(kUnknownError, "服務(wù)器走失了椎咧,請(qǐng)稍后重試");
}
}
采用處
try {
UserInfoEntity entity = await ApiService.apiService.getUserInfo();
userInfoData = entity.data;
notifyListeners();
} on RequestError catch (e) {
print("e.message is ${e.message}, e.code is ${e.code}");
}
其中的UserInfoEntity為定義的Model玖详,RequestError為自定錯(cuò)誤類型。
六勤讽、 Json轉(zhuǎn)Model
強(qiáng)烈推薦Android Studio的插件FlutterJsonBeanFactory蟋座,使用方法參照官網(wǎng)。如果要修改或重建Model地技,那么將之前的model刪除再創(chuàng)建一次蜈七。有一些小的修改,可以用alt+J來(lái)刷新莫矗。
注意:Json數(shù)據(jù)如果不能“Make”飒硅,那么可以試下將最后一個(gè)逗號(hào)刪除。
七作谚、 狀態(tài)共享
當(dāng)用戶信息此數(shù)據(jù)需要用在很多頁(yè)面三娩,那么考慮采用官方推薦的provider來(lái)實(shí)現(xiàn)刷新Widget。
void main() => runApp(ChangeNotifierProvider<PersonalCenterProviderModel>.value(
value: PersonalCenterProviderModel(),
child: MyApp(),
));
建議將Provider添加到頂部妹懒,方便在任何頁(yè)面中獲取UserInfo雀监。
class PersonalCenterProviderModel with ChangeNotifier {
UserInfoData? userInfoData;
void refreshUserInfo() async {
try {
UserInfoEntity entity = await ApiService.apiService.getUserInfo();
userInfoData = entity.data;
notifyListeners();
} on RequestError catch (e) {
print("e.message is ${e.message}, e.code is ${e.code}");
}
}
}
PersonalCenterProviderModel類中可以添加很多方法來(lái)更新數(shù)據(jù)。
PersonalCenterProviderModel _model =
Provider.of<PersonalCenterProviderModel>(context);
backgroundImage: NetworkImage(_model.userInfoData?.portrait ?? defaultImage),
使用方式眨唬,當(dāng)userInfo中的portrait發(fā)生改變時(shí)会前,那么頁(yè)面會(huì)重建。
PersonalCenterProviderModel _model =
Provider.of<PersonalCenterProviderModel>(context, listen: false);
_model.refreshUserInfo();
如果僅僅是獲取model來(lái)調(diào)用方法匾竿,那么將listen設(shè)置為false瓦宜。
八、Flutter和Native交互
參考module集成到iOS和Android岭妖。
iOS的使用方式
- (void)initialFlutterVC
{
FlutterEngine *flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"];
[flutterEngine run];
[GeneratedPluginRegistrant registerWithRegistry:flutterEngine];
self.flutterViewController = [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
[self initialFlutterMethods];
self.flutterViewController.modalPresentationStyle = UIModalPresentationFullScreen;
}
打開(kāi)Flutter頁(yè)面
- (void)personalButtonDidClick:(id)sender
{
[self presentViewController:self.flutterViewController animated:YES completion:nil];
}
記得提前初始化flutterEngine临庇,可以加快打開(kāi)的速度反璃。
Android的使用方式
flutterEngine = new FlutterEngine(this);
flutterEngine.getDartExecutor().executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault());
FlutterEngineCache.getInstance().put(engine_personal_center, flutterEngine);
提前初始化flutterEngine
btn_jump.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(FlutterActivity.
withCachedEngine(engine_personal_center).build(HomeActivity.this));
}
});
通過(guò)緩存打開(kāi)Flutter頁(yè)面。
添加交互方法
final String CHANNEL = "qji_flutter/getUserInfo";
MethodChannel methodChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL);
methodChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
switch (call.method) {
case "*":
result.success(API.SERVER_URL);
break;
case "*":
String paramsStr = call.argument("params");
Gson gson = new Gson();
Type jsonType = new TypeToken<HashMap<String, String>>() {
}.getType();
HashMap<String, String> params = gson.fromJson(paramsStr, jsonType);
result.success(ParamManager.Companion.token(params));
break;
case "*":
EventMessage<String> eventMessage = new EventMessage<>();
eventMessage.setTag(EVENT_LOGOUT);
EventBus.getDefault().post(eventMessage);
UserAccount.getInstance().logout();
break;
case "*":
String path = call.argument("imagePath");
if (null == path || path.isEmpty()) {
result.error("1", "上傳失敗", null);
} else {
uploadImageToOSS(path, result);
}
break;
case "*":
// DO Nothing
break;
default:
result.notImplemented();
break;
}
}
});
iOS與Android的用法類似假夺,注意必須是同一個(gè)flutterEngine淮蜈,不然會(huì)找不到。
九已卷、iOS打包
如果打包的證書(shū)不是debug或release梧田,那么需要FLUTTER_BUILD_MODE設(shè)置為Release才能進(jìn)行Ad-hoc和Release打包。
在../qiji_flutter/.ios/Flutter/flutter_export_environment.sh中添加
export "FLUTTER_BUILD_MODE=Release"
侧蘸。