
使用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"。