Flutter開(kāi)發(fā)總結(jié)

Flutter

使用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是我本地的路徑镶骗,需要替換。

  1. 參考https://flutter.dev/community/china上的描述滓窍,將flutter從下載到本地卖词。git clone -b stable https://github.com/flutter/flutter.git
  2. 執(zhí)行flutter doctor進(jìn)行dart SDK的下載。
  3. vim .bash_profile中添加export PATH=~/Public/Flutter/flutter/bin:$PATH吏夯。
  4. source .bash_profile 讓PATH生效此蜈。
  5. 在android studio和VS code安裝flutter插件。
  6. flutter upgrade 在/Users/arthurwang/Public/Flutter/flutter 安裝flutter路徑中進(jìn)行噪生。

三裆赵、 常用布局

  1. Container: 只有一個(gè)子Widget。默認(rèn)充滿跺嗽,包含了padding战授、margin、color桨嫁、寬高植兰、decoration等。
  2. Padding: 只有一個(gè)子Widget璃吧。只用于設(shè)置Padding楣导,常用于嵌套child,給child設(shè)置padding畜挨。
  3. Center:只有一個(gè)子Widget筒繁。只用于居中顯示,常用于嵌套child巴元,給child設(shè)置居中毡咏。
  4. Stack:可以有多個(gè)子Widget。子Widget堆疊在一起逮刨。
  5. Column:可以有多個(gè)子Widget呕缭。垂直布局。
  6. Row: 可以有多個(gè)子Widget。水平布局臊旭。
  7. Expanded:只有一個(gè)子Widget落恼。在Column和Row中充滿。(不會(huì)超出父視圖)
  8. ListView:可以有多個(gè)子Widget离熏。
  9. Align: 只有一個(gè)子Widget佳谦。 絕對(duì)布局,可以設(shè)置位置滋戳。
  10. SizeBox: 強(qiáng)制設(shè)置它的孩子寬度或者高度為指定值钻蔑。傳width、height奸鸯、child咪笑。

明白這些布局的使用,基本能夠滿足頁(yè)面的布局娄涩。

四窗怒、組件

  1. MaterailApp:作為APP頂層的主要入口,可配置主題蓄拣,多語(yǔ)言扬虚,路由等。
  2. Scaffold: 用戶頁(yè)面承載Widget球恤,包含appbar辜昵、snackbar、drawer等material design設(shè)定咽斧。
  3. AppBar: 用于Scaffold的appbar堪置,內(nèi)有標(biāo)題、二級(jí)頁(yè)面返回按鍵等张惹。
  4. Text:顯示文本舀锨,可通過(guò)style設(shè)置TextStyle來(lái)設(shè)置字體樣式等。
  5. RichText: 福文本宛逗。設(shè)置TextSpan坎匿,可以拼接出富文本場(chǎng)景。
  6. TextField: 文本輸入框拧额。
  7. Image: 圖片加載碑诉。
  8. 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"侧蘸。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末柿扣,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子闺魏,更是在濱河造成了極大的恐慌未状,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件析桥,死亡現(xiàn)場(chǎng)離奇詭異司草,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)泡仗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)埋虹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人娩怎,你說(shuō)我怎么就攤上這事搔课。” “怎么了截亦?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵爬泥,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我崩瓤,道長(zhǎng)袍啡,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任却桶,我火速辦了婚禮境输,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘颖系。我一直安慰自己嗅剖,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布嘁扼。 她就那樣靜靜地躺著信粮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪偷拔。 梳的紋絲不亂的頭發(fā)上蒋院,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音莲绰,去河邊找鬼欺旧。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蛤签,可吹牛的內(nèi)容都是我干的辞友。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼震肮,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼称龙!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起戳晌,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鲫尊,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后沦偎,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體疫向,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年豪嚎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了搔驼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡侈询,死狀恐怖舌涨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情扔字,我是刑警寧澤囊嘉,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站革为,受9級(jí)特大地震影響哗伯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜篷角,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一焊刹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧恳蹲,春花似錦虐块、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至错忱,卻和暖如春儡率,著一層夾襖步出監(jiān)牢的瞬間挂据,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工儿普, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留崎逃,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓眉孩,卻偏偏與公主長(zhǎng)得像个绍,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子浪汪,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容