原生轉flutter快速入門

本文是給第一次接觸flutter的原生開發iOS/android同窗快速入門的攻略,高手請繞路,輕拍哈。前端

對於原生開發的同窗,對於flutter會比較感興趣,也許會從網上零星得到一些學習資源,可是比較零散,不構成學習路徑,可能也會踩一些坑,爲了不少走彎路,又能快速的入門flutter,現將我的的一些實踐經歷分享一下,供你們批評指正補充。android

1. 安裝flutter

1.1 先下載flutter編輯器 android studio,最新版本,解壓,安裝。。。(如下簡稱AS)ios

1.2 安裝flutter 已iOS爲例git

cd ~/development
 unzip ~/Downloads/flutter_macos_v1.9.1+hotfix.2-stable.zip
複製代碼
  • 設置PATH環境變量

先編輯bash_profile文件(默認狀況下,macOS Mojave(及更早版本)使用Bash shell,所以編輯$HOME/.bashrc)github

$vim ~/.bash_profile
複製代碼

添加如下路徑shell

export PATH="$PATH:`pwd`/flutter/bin"macos

完整的以下編程

export PATH="/bin:/usr/bin:/usr/local/bin:$PATH"
export PATH="/Users/boob/Documents/flutter/bin:$PATH"
export PATH="/usr/local/opt/openssl/bin:$PATH"
export PATH="/Users/boob/Documents/depot_tools:$PATH"
export PATH="/Users/boob/Library/Android/sdk/platform-tools:$PATH"
export PATH
複製代碼

編輯完成使用wq退出,便可執行flutter命令了swift

  • 完成了,能夠試試在終端執行 flutter --version看看版本號, which flutter能夠查看flutter安裝的路徑

2. 建立工程並運行

ios的同窗默認安裝了xcode,沒有的話去安裝一個吧,安卓同窗須要下載android studio,後續開發都是須要用as進行開發和調試的。 爲了第一次能直接運行flutter,咱們先開一個模擬器,而且把其餘真機設備移除,防止後面操做找不到運行目標,或不知道如何選擇設備vim

前提操做 打開模擬器命令

open -a Simulator
複製代碼

檢測pod的版本號是否高於1.6.0

pod --version
複製代碼

flutter默認最低支持的pod版本是1.6.0,若是使用到plugin時就會提示版本太低,致使pod失敗了

建立工程的命令

flutter create testflutter
複製代碼

注意工程名字都要小寫,不然會提示你命名出錯。

運行

$ cd myapp
$ flutter run
複製代碼

此時flutter會編譯dart代碼,而且簽名運行

3. flutter產物介紹

咱們進入到flutter源碼目錄

ios目錄存放ios工程,android則存放android工程 對於ios來講,編譯的產物在ios/Flutter文件夾中 包含了

  • App.framework 這是flutter工程編譯出來的ios產物,對於debug編譯來講,flutter_assets包含了全部的可執行產物和資源 對於release編譯來講,可執行的部分在APP文件中,資源存放在flutter_assets中

  • flutter.framework 俗稱flutter engine/flutter 引擎,支持上層flutter運行的底層庫。

  • xxx.xcconfig 工程配置,flutter命令自動生成的,爲了配置flutter路徑,flutterframework的路徑

  • flutter_export_environment.sh 1.9新增的腳本,配置flutter經常使用的環境變量

4. 熱重載 hot reload

這是flutter的吹噓的幾大特性之一,跨平臺一致性,熱重載。。。 即寫完代碼能夠當即執行。 編寫flutter代碼咱們使用android studio,官方推薦3.0以上的版本 developer.android.com/studio

咱們可使用最新的,由於已經使用了最新的flutter插件功能,包括斷點調試,attach,性能查看分析等。

用AS打開testflutter工程

找到lib路徑,這是咱們dart代碼存放的路徑,flutter項目是用dart語言開發。

如今能夠點運行按鈕️,直接啓動flutter,這個跟在終端啓動效果同樣。

修改一下源碼,把標題改爲個人第一個flutter項目,以下

而後按下 cmd+s 保存,便可在模擬器上看到運行結果

5. flutter的默認UI庫

默認提供兩套ui庫,一套是android風格的Material Design 和ios風格的 cupertino (連接是傳送門)

下面感覺一下差異

咱們使用一個button試一下 在main.dart的scaffold的中添加代碼

CupertinoButton(
              child: Text('CLICK ME'),
              color: Theme.of(context).accentColor,
              onPressed: (){
                print("點擊了按鈕");
              },
              disabledColor: Theme.of(context).disabledColor,
            )
複製代碼

6. 開發思路的轉變

到了源碼級別,原生的編程思路就須要開始轉變了,因爲原生開發都是命令式編程,然而前端和flutter是聲明式編程的。

對於命令式,是指若是咱們要對一個文本內容和文本顏色改變,咱們就去取到這個文本的text和textcolor 而後對text和textcolor進行賦值。

然而對於聲明式,要改文本內容,須要將文本的內容text和文本的控件分別先聲明

全部佈局的控件都寫到 Widget build(BuildContext context) { ... } 方法中,可是 可是控件須要用到是內容而且可能改變的,則使用一個變量記下來.

  • 聲明寫法
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}
複製代碼

final String title; 就是聲明瞭一個title的字符串。

或者

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
    ...
}
複製代碼
  • 控件寫法與使用
AppBar(
        title: Text(widget.title),
      ),
複製代碼
Text(
      '$_counter',
      style: Theme.of(context).textTheme.display1,
    ),
複製代碼
  • 改變文本內容 setState

這是和原生最大的差異,須要改變文本的內容則須要使用setState中生效,告訴flutter這時候狀態變化了,須要從新刷新。

例子中單擊+ 數字+1,能夠看到界面上的數字當即更新了

void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }
複製代碼

值得注意的是,state頻繁刷新也會帶來性能問題,不可濫用哦。 其餘代碼你們能夠自行研究,都是聲明式編程的運用。

7. 萬物皆widget

widget在ui層面至關於原生的uiview,可是不只僅侷限於顯示視圖UIView,也有用於佈局相關的。

  • 基礎 Widgets

Container、Button、Row和Column、Text、Scaffold、Icon、Image、Stack、TabBar+TabBarView、Widget-輸入框TextField

  • 用於佈局的 Widgets

Align、Center、Expended、LayoutBuilder、Padding、Wrap

  • 可滾動 Widgets

CustomScrollView、GridView、ListView、PageView、SingleChildScrollView

  • 裝飾 Widgets

BoxDecoration、Clip系列、Opacity、SafeArea、高斯模糊BackdropFilter

參考: github.com/chenBingX/C…

8. FLEX佈局

咱們知道橫向佈局使用Row 縱向佈局使用Column Wiget 佈局對其方式分爲主軸和交叉軸,若是是Row佈局主軸mainAxisAlignment就是橫向,而其交叉軸就是縱軸, 主軸排列方式有6中,start,end,center,spaceAround,spaceBetween,spaceEvenly

  • spaceBetween 左右item靠邊,中間等間距
Padding(
              padding: const EdgeInsets.all(0.0),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  Container(color: Colors.red, width: 50, height: 50,),
                  Container(color: Colors.blue, width: 50, height: 50,),
                  Container(color: Colors.red, width: 50, height: 50,),
                  Container(color: Colors.blue, width: 50, height: 50,)
                ],
              ),
            )
            
複製代碼

效果以下

  • spaceEvenly 表示全部item間距都相等,包括左右item距離邊界
Padding(
              padding: const EdgeInsets.all(0.0),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  Container(color: Colors.red, width: 50, height: 50,),
                  Container(color: Colors.blue, width: 50, height: 50,),
                  Container(color: Colors.red, width: 50, height: 50,),
                  Container(color: Colors.blue, width: 50, height: 50,)
                ],
              ),
複製代碼

效果圖以下

  • spaceAround 表示將可用空間均勻地放在孩子之間,以及其中一半,第一個和最後一個孩子以前和以後的空間。
Row(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  Container(color: Colors.red, width: 50, height: 50,),
                  Container(color: Colors.blue, width: 50, height: 50,),
                  Container(color: Colors.red, width: 50, height: 50,),
                  Container(color: Colors.blue, width: 50, height: 50,)
                ],
              ),
複製代碼

效果圖以下

start 表示將子級放置在儘量靠近主軸起點的位置。若是此值沿水平方向使用,則[TextDirection]必須爲可用於肯定起點是左側仍是右側。 若是在垂直方向上使用此值,則[VerticalDirection]必須爲可用於肯定起點是頂部仍是底部。

Row(
               mainAxisAlignment: MainAxisAlignment.start,
               crossAxisAlignment: CrossAxisAlignment.center,
               children: <Widget>[
                 Container(color: Colors.red, width: 50, height: 50,),
                 Container(color: Colors.blue, width: 50, height: 50,),
                 Container(color: Colors.red, width: 50, height: 50,),
                 Container(color: Colors.blue, width: 50, height: 50,)
               ],
             ),
複製代碼

效果圖以下:

  • center 居中
Row(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  Container(color: Colors.red, width: 50, height: 50,),
                  Container(color: Colors.blue, width: 50, height: 50,),
                  Container(color: Colors.red, width: 50, height: 50,),
                  Container(color: Colors.blue, width: 50, height: 50,)
                ],
              ),
複製代碼

效果以下

  • end 橫向佈局則表示靠近主軸的終點,縱向佈局則表示靠近縱軸的終點
Row(
                mainAxisAlignment: MainAxisAlignment.end,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  Container(color: Colors.red, width: 50, height: 50,),
                  Container(color: Colors.blue, width: 50, height: 50,),
                  Container(color: Colors.red, width: 50, height: 50,),
                  Container(color: Colors.blue, width: 50, height: 50,)
                ],
              ),
複製代碼

9 原生工程如何支持flutter

混合工程方案網上有很多,可是真正的核心只有一個,就是將flutter工程的構建產物加入到原生工程中來。

咱們已經知道了flutter的構建產物有App.framework 其餘的就是Flutter.framework,還有就是dart依賴的第三方庫,多是plugin也多是dart庫,能夠在.symbol文件夾中找到(.xxx開頭的文件默認是隱藏的,使用shift+cmd+. 將其顯示出來)

要想加入到主工程,沒法就是將三個東西作成pod的形式,在pod install/update的時候將他pod進來。這樣就能作原生開發人員無感知的使用flutter工程。

業界有一個主流的方向,安裝產物的本地和遠程來劃分,本地依賴方式和遠程依賴方式。沒法仍是把產物放哪的問題。

提供另外一個視角看混合工程可能更好。咱們按照角色劃分混合方案,最多三種角色

  • 1、原生開發
  • 2、flutter開發
  • 3、混合開發

這三種角色的訴求分別是,

原生開發無需安裝flutter,可是會用到flutter的產物。

其餘人員都須要安裝flutter,因此需求差很少。 a、安裝了flutter的同窗,可能也不想編譯flutter產物而是直接使用,b、flutter開發的同窗可能只想編譯debug的產物,可是不想上傳,他們想debug構建並attach到 c、對於構建的需求是須要使用最新的代碼構建release的包。

使用官方的混合方式沒法解決以上全部的需求。本身開發混合工程腳本,須要從以上角度考慮。

然而使用方法僅僅是簡單的一句話

flutter_application_path = 'xxxflutter'
load File.join(flutter_application_path,  'IOSFlutterConfig', 'start.rb') 
def GirFlutter     
    puts "自動檢測flutter是否存在,自動執行不一樣的腳本" 
    install_flutter_engine_pod
end
複製代碼

以後pod update便可!

10 混合工程與原生工程通訊

分爲兩個部分,flutter調用native的代碼、native調用flutter的功能 官方提供了兩種方式 methodchannel、eventchannel。

流程圖以下,

method channel

另外plugin就是一種native和flutter通訊的最好的例子,咱們能夠直接看他給的例子。

終端執行

flutter create --org com.example --template=plugin myplugin

複製代碼

進入myplugin/ios/Classes目錄 咱們看到 SwiftMypluginPlugin.swift

import Flutter
import UIKit

public class SwiftMypluginPlugin: NSObject, FlutterPlugin {
  public static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(name: "myplugin", binaryMessenger: registrar.messenger())
    let instance = SwiftMypluginPlugin()
    registrar.addMethodCallDelegate(instance, channel: channel)
  }

  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    result("iOS " + UIDevice.current.systemVersion)
  }
}

複製代碼

能夠看到這個插件建立了一個名爲myplugin的FlutterMethodChannel,而且經過registar註冊到了methodcalldelegate裏面了。

在dart那邊可使用調起該方法。

class Myplugin {
  static const MethodChannel _channel =
      const MethodChannel('myplugin');

  static Future<String> get platformVersion async {
    final String version = await _channel.invokeMethod('getPlatformVersion');
    return version;
  }
}
複製代碼

那麼對於native如何調用flutter的代碼呢 在methodchannel這個例子中,咱們看到已經給到了一個result過來了,咱們能夠拿到這個result result: @escaping FlutterResult,若是有新的事件想發出去,就不斷回調這個result也能夠。

self.result("第二次回調給flutter")
複製代碼

eventchannel

使用流程相似,先註冊-> 發送事件

FlutterEventChannel *evenChannal =  [FlutterEventChannel eventChannelWithName:@"flutter_hummer_event" binaryMessenger:[registrar messenger]];
    FlutterEventChannelHandel *handle = [FlutterEventChannelHandel new];
    instance.eventHandel = handle;
    [evenChannal setStreamHandler:handle];
    
。。。
 self.eventHandel.eventsBlock(dic);
複製代碼

dart層使用方式

先註冊一個通知--> 監聽回調

// 註冊一個通知
  static const EventChannel eventChannel =
      const EventChannel('flutter_hummer_event');
    eventChannel
        .receiveBroadcastStream(12345)
        .listen(_onEvent, onError: _onError);
        

  // 回調事件
  void _onEvent(Object event) {
      ...
  }
  
  // 錯誤返回
  void _onError(Object error) {}

複製代碼

!! 提示,plugin默認生成swift版的plugin,若是想要選擇ios可使用-i objc選項,其餘配置選項能夠-h查詢

flutter create --org com.example --template=plugin -i objc  myplugin22 
複製代碼

原創聲明

歡迎你們批評指正補充,爭取作最好最精的入門教程,持續更新中...

原創不易,如需轉載請註明來源,共田君

相關文章
相關標籤/搜索