Google 跨平臺方案 Flutter 從入門到實戰

本文由 玉剛說寫做平臺 提供寫做贊助java

原做者:楊哲git

版權聲明:本文版權歸微信公衆號 玉剛說 全部,未經許可,不得以任何形式轉載github

前言

2018年2月27日,在2018世界移動大會上,Google發佈了Flutter的第一個Beta版本。Flutter是Google用以幫助開發者在 Android/IOS 兩個平臺開發高質量原生應用的全新移動UI框架。編程

  1. 熱重載(Hot Reload),做爲一個安卓開發者,能熱重載真的太舒服了,利用Android Studio直接一個ctrl+s就能夠保存並重載,模擬器立馬就能夠看見效果。
  2. 一切皆爲Widget的理念,對於Flutter來講,手機應用裏的全部東西都是Widget,經過可組合的空間集合、豐富的動畫庫以及分層課擴展的架構實現了富有感染力的靈活界面設計。
  3. 藉助可移植的GPU加速的渲染引擎以及高性能本地代碼運行時以達到跨平臺設備的高質量用戶體驗。

這段介紹是直接抄下來的,雖然我並不知道什麼叫可移植的GPU加速的渲染引擎,可是最終結果就是利用Flutter構建的應用在運行效率上會和原生應用差很少,那麼咱們開始走進 Flutter 的世界吧。swift

本文章的結構以下:api

  • 如何搭建 Flutter 開發環境
  • Dart的優勢和基本用法
  • 如何使用平臺特性API
  • 除去UI部分,代碼如何在 Android/IOS 複用
  • 實現一個案例

Flutter進行開發 Android/IOS

tips: 本文在蘋果筆記上開發,由於須要調試 IOS 和 Android數組

這裏我用 Android Studio 開發 Flutter ,下邊咱們來看一下開發步驟。安全

準備工做

  1. 安裝Android Studio 中 Flutter 和 Dart 兩個插件(設置中Plugin搜索便可)
  2. 官網下載 fluter sdk(tip: 國內下載地址
  3. 建立工程
  4. 安裝Xcode ,啓動一個IOS 模擬器
  5. 啓動一個 Android 模擬器

我這裏也是大概描述一下大概流程,爲了節省篇幅,官網有更加詳細的步驟:bash

我首先推薦官網: Flutter 官網 若是你想快速入門,這裏有 中文官網微信

Dart 語法

Dart 的優點

當時我學習 Dart 語言的時候,一直思考 Dart 有什麼優點?只有 Google 這個親爹的緣由嗎?我帶着這個思考查了很多資料,發現 Dart 的優點有以下幾點:

  • Dart 支持AOT編譯和JIT編譯兩種方式
  • Dart 是單線程的,這意味着它根本不容許搶佔

JIT編譯

JIT編譯在開發過程當中使用,編譯器速度特別快。而後,當一個應用程序準備發佈時,它被AOT編譯。所以,藉助先進的工具和編譯器,Dart具備一箭雙鵰的優點:極快的開發週期、快速的執行速度和極短啓動時間。

咱們討論過一個有助於保持順暢的特性,那就是Dart能AOT編譯爲本地機器碼。預編譯的AOT代碼比JIT更具可預測性,由於在運行時不須要暫停執行JIT分析或編譯。

AOT編譯

然而,AOT編譯代碼還有一個更大的優點,那就是避免了「JavaScript橋樑」。當動態語言(如JavaScript)須要與平臺上的本地代碼互操做時,它們必須經過橋進行通訊,這會致使上下文切換,從而必須保存特別多的狀態(可能會存儲到輔助存儲)。這些上下文切換具備雙重打擊,由於它們不只會減慢速度,還會致使嚴重的卡頓。

Dart 的 基本語法

若是你有 java 語言的基礎,發現dart裏邊的 API 幾乎 90% 以上相同, 幾乎很快能上手,這裏我就特別指出他們的不一樣點。這裏我想最快的入門方法,應該是查看官網的 quick start, 快速正版放心,並且不會過期,一直在更新。 這裏我簡單的介紹一下:

  • Hello World
  • 變量的聲明
  • 方法的定義
  • 基本類型
  • 條件判斷
  • 循環語句

因爲篇幅有限,我這裏列舉一下我認爲特別的地方,剩下的能夠仔細閱讀 官方文檔

Hello World

咱們萬年老套路 Hello World,熟悉他語言的運行機制。

// 定義一個函數
        printNumber(num aNumber) {
          print('The number is $aNumber.'); //控制檯打印
        }

        // 啓動方法,相似於 java 的main函數
        main() {
          var number = 42; 
          printNumber(number); 
        }
複製代碼

咱們能夠以看到一下幾點:

  • 能賦值給變量的因此東西都是對象,包括 numbers, null, function, 都是繼承自 Object 內置類
  • 儘可能給變量定義一個類型,會更安全,沒有顯示定義類型的變量在 debug 模式下會類型會是 dynamic(動態的)
  • dart 在 running 以前解析你的全部代碼,指定數據類型和編譯時的常量,能夠提升運行速度
  • dart 提供了頂級函數(如:main())
  • dart 沒有 public、private、protected 這些關鍵字,變量名以"_"開頭意味着對它的 lib 是私有的

變量聲明

沒有初始化的變量都會被賦予默認值 null

var name = 'Bob';
var unInitializeValue1;   //未給初值的變量,默認值爲 null
Int unInitializeValue2;   //即便是Int 型,默認值也是 null

//相似於 Kotlin, 能夠推導出 name 爲字符串類型
var name = 'Bob';
// 若是不想 推導出類型,下邊兩種寫法
dynamic name = 'Bob';
Object name = 'Bob';
複製代碼

程序中只當數據類型是爲了指出本身的使用意圖,並幫助語言進行語法檢查。可是,指定類型不是必須的,相似於Kotlin 會進行類型推導。

基本類型

number 取值範圍:-2^53 to 2^53

// String -> int
var one = int.parse('1');

// String -> double
var onePointOne = double.parse('1.1');

// int -> String
String oneAsString = 1.toString();

// double -> String 注意括號中要有小數點位數,不然報錯
String piAsString = 3.14159.toStringAsFixed(2);
複製代碼

string

  • '''...''',"""..."""表示多行字符串
  • r'...',r"..."表示「raw」字符串
  • 或{} 來計算字符串中變量的值

示例代碼:

var s = 'Android Developer';

  print ('A Commpany has a $s, which is good idea.' ==
      'A Commpany has a Android Developer,' +
          ' which is good idea.');
  print('I am a ' +
      '${s.toUpperCase()} is very hornor!' ==
      'I am a ' +
          'ANDROID DEVELOPER is very hornor!');
複製代碼

bool 布爾類型

Dart 是強 bool 類型檢查,只有bool 類型的值是true 才被認爲是true

list 列表

var vegetables = new List();

// 或者簡單的用List來賦值
var fruits = ['apples', 'oranges'];

// 添加元素
fruits.add('kiwis');

// 添加多個元素
fruits.addAll(['grapes', 'bananas']);

// 獲取第一個元素
fruits.first;

// 獲取元素最後一個元素
fruits.last;

// 查找某個元素的索引號
assert(fruits.indexOf('apples') == 0);

// 刪除指定位置的元素,返回刪除的元素
fruits.removeAt(index);

// 刪除指定元素,成功返回true,失敗返回false
fruits.remove('apples');

// 刪除最後一個元素,返回刪除的元素
fruits.removeLast();

// 刪除指定範圍元素,含頭不含尾,成功返回null
fruits.removeRange(start,end);

// 刪除指定條件的元素,成功返回null
fruits.removeWhere((item) => item.length >6);

// 刪除全部的元素
fruits.clear();

// sort()對元素進行排序,傳入一個函數做爲參數,return <0表示由小到大, >0表示由大到小
fruits.sort((a, b) => a.compareTo(b));
複製代碼

map 散列表

// Map的聲明
var hawaiianBeaches = {
    'oahu' : ['waikiki', 'kailua', 'waimanalo'],
    'big island' : ['wailea bay', 'pololu beach'],
    'kauai' : ['hanalei', 'poipu']
};
var searchTerms = new Map();

// 指定鍵值對的參數類型
var nobleGases = new Map<int, String>();

// Map的賦值,中括號中是Key,這裏可不是數組
nobleGase[54] = 'dart';

//Map中的鍵值對是惟一的
//同Set不一樣,第二次輸入的Key若是存在,Value會覆蓋以前的數據
nobleGases[54] = 'xenon';
assert(nobleGases[54] == 'xenon');

// 檢索Map是否含有某Key
assert(nobleGases.containsKey(54));

//刪除某個鍵值對
nobleGases.remove(54);
assert(!nobleGases.containsKey(54));
複製代碼

條件判斷和循環

  • if...else
  • for
  • while do-while
  • break continue
  • switch...case 若是 case 後面有表達式可是沒有 break,會拋出異常
  • assert(僅在checked模式有效),若是條件爲假,拋出異常

這裏我介紹了一下基本語法,還有函數、異常、單線程的操做,因爲篇幅有限,並且咱們也是一個入門教程,我這裏就介紹到這裏,若是想具體查看,能夠點擊我推薦的官網教程,用的 dart2 的方式。

Flutter如何使用Android和iOS的平臺特性

Flutter使用了一個靈活的系統,容許您調用特定平臺的API,不管在Android上的Java或Kotlin代碼中,仍是iOS上的ObjectiveC或Swift代碼中都可用。

Flutter平臺特定的API支持不依賴於代碼生成,而是依賴於靈活的消息傳遞的方式:

應用的Flutter部分經過平臺通道(platform channel)將消息發送到其應用程序的所在的宿主(iOS或Android)。

宿主監聽的平臺通道,並接收該消息。而後它會調用特定於該平臺的API(使用原生編程語言)並將響應發送回客戶端,即應用程序的Flutter部分。

調用流程以下:

跨端調用流程

電池電量的 banerry

(1)建立一個新的應用程序項目

首先建立一個新的應用程序: 方式一: 在終端運行中:

flutter create batterylevel
複製代碼

默認狀況下,模板支持使用Java編寫Android代碼,或使用Objective-C編寫iOS代碼。要使用Kotlin或Swift,請使用-i和/或-a標誌:

在終端中運行:

flutter create -i swift -a kotlin batterylevel
複製代碼

方式二: 也能夠經過項目new Flutter Project 來創造項目

(2) 建立Flutter平臺客戶端

該應用的State類擁有當前的應用狀態。咱們須要延長這一點以保持當前的電量

首先,咱們構建通道。咱們使用MethodChannel調用一個方法來返回電池電量。

通道的客戶端和宿主經過通道構造函數中傳遞的通道名稱進行鏈接。單個應用中使用的全部通道名稱必須是惟一的;

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
...
class _MyHomePageState extends State<MyHomePage> {
  static const platform = const MethodChannel('samples.flutter.io/battery');

  // Get battery level.
}
複製代碼

接下來,咱們調用通道上的方法,指定經過字符串標識符調用方法getBatteryLevel。 該調用可能失敗。

例如,若是平臺不支持平臺API(例如在模擬器中運行時),因此咱們將invokeMethod調用包裝在try-catch語句中。

咱們使用返回的結果,在setState中來更新用戶界面狀態batteryLevel。

// Get battery level.
  String _batteryLevel = 'Unknown battery level.';

  Future<Null> _getBatteryLevel() async {
    String batteryLevel;
    try {
      final int result = await platform.invokeMethod('getBatteryLevel');
      batteryLevel = 'Battery level at $result % .';
    } on PlatformException catch (e) {
      batteryLevel = "Failed to get battery level: '${e.message}'.";
    }

    setState(() {
      _batteryLevel = batteryLevel;
    });
  }
複製代碼

最後,咱們在build建立包含一個小字體顯示電池狀態和一個用於刷新值的按鈕的用戶界面。

override
Widget build(BuildContext context) {
  return new Material(
    child: new Center(
      child: new Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          new RaisedButton(
            child: new Text('Get Battery Level'),
            onPressed: _getBatteryLevel,
          ),
          new Text(_batteryLevel),
        ],
      ),
    ),
  );
}
複製代碼
(3) 在Android平臺的代碼實現

接下來,在 ManActivity 中 的 onCreate裏建立MethodChannel並設置一個MethodCallHandler。確保使用與在Flutter客戶端使用的通道名稱相同。

public class MainActivity extends FlutterActivity {
    private static final String CHANNEL = "samples.flutter.io/battery";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
                new MethodCallHandler() {
                    @Override
                    public void onMethodCall(MethodCall call, Result result) {
                        if (call.method.equals("getBatteryLevel")) {
                            int batteryLevel = getBatteryLevel();

                            if (batteryLevel != -1) {
                                result.success(batteryLevel);
                            } else {
                                result.error("UNAVAILABLE", "Battery level not available.", null);
                            }
                        } else {
                            result.notImplemented();
                        }
                    }
                });
    }

    private int getBatteryLevel() {
        int batteryLevel = -1;
        if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
            BatteryManager batteryManager = (BatteryManager) getSystemService(BATTERY_SERVICE);
            batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
        } else {
            Intent intent = new ContextWrapper(getApplicationContext()).
                    registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
            batteryLevel = (intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100) /
                    intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
        }

        return batteryLevel;
    }
}
複製代碼

到此爲止,咱們介紹完如何使用兩個平臺的特殊 API ,若是你在使用flutter 開發的時候,碰到官方沒有支持的api,你能夠本身去實現兩個平臺的代碼,來實現你想要的效果。

除去UI部分,代碼如何在 Android/IOS 複用

若是您但願在多個Flutter應用程序中使用特定於平臺的代碼,將代碼分離爲位於主應用程序以外的目錄中,作一個平臺插件會頗有用。這樣就能夠把 UI 部分刨除掉,複用代碼部分。 咱們能夠開發插件來來實現咱們要的通用的部分,如何開發一個插件呢?這裏我就不班門弄斧了,你能夠直接查看官網提升的如何開發一個插件

實例展現

經過上邊的介紹,你們對於Flutter 有必定的理解,下面咱們實現一個demo項目,如何咱們開始進入實戰階段,咱們具體實現的效果如圖下: 主頁(Tab欄+Banner輪播圖+ViewPaper):

主頁
抽屜:

建立一個項目

經過Android Studio new 一個flutter Project 項目,刪除lib/main.dart代碼,咱們開始本身實現代碼。

添加 MaterialAPP

MaterialAPP 是一個方便的widget,它封裝了應用程序實現Material Design所須要的一些widget。Material 風格是咱們一直想實現的風格,這裏放到最外層就能實現咱們想要的效果是否是很 Happy?

// 導入用的依賴
import 'package:flutter/material.dart';
// main函數使用了(=>)符號, 這是Dart中單行函數或方法的簡寫
void main() => runApp(new MyApp());
// 該應用程序繼承了 StatelessWidget,這將會使應用自己也成爲一個widget。
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'home', // 默認標題
      home: new HomePage(), // 返回的界面
    );
  }
}

複製代碼

StatelessWidget 和 StatefulWidget 的區別

細心的同窗已經發現,咱們用到的 widget 發現有,StatelessWidget 和 StatefulWidget , 他們的區別以下:

Stateless widgets 是不可變的, 這意味着它們的屬性不能改變 - 全部的值都是最終的.

Stateful widgets 持有的狀態可能在widget生命週期中發生變化. 實現一個 stateful widget 至少須要兩個類:

  • 一個 StatefulWidget類。
  • 一個 State類。 StatefulWidget類自己是不變的,可是 State類在widget生命週期中始終存在.
class HomePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => new _HomePageState();
}

class _HomePageState extends State<HomePage> {
    
}
複製代碼

添加 Scaffold 頁面框架組件

Scaffold 是 Material library 中提供的一個widget, 它提供了默認的導航欄、標題和包含主屏幕widget樹的body屬性

class _HomePageState extends State<HomePage> {
    @override
  Widget build(BuildContext context) {
    return new DefaultTabController(
      child: new Scaffold(
        appBar: , // 標題
        body: ,//主屏幕
        drawer: ,//抽屜
        bottomNavigationBar: ,// Tab欄 
      ),
    );
  } 
}
複製代碼

添加 標題

添加標題比較簡單,他的屬性很少,我這裏只添加 appbar 的名稱屬性,由於咱們後邊須要添加導航欄,標題名稱會發生改變,我這裏實現代碼以下:

class _HomePageState extends State<HomePage> {
  
    @override
    Widget build(BuildContext context) {
    return new DefaultTabController(
      child: new Scaffold(
        appBar: new AppBar(
          title: _getTitle(),// 抽取成方法
        ), // 標題
        body: ,//主屏幕
        drawer: ,//抽屜
        bottomNavigationBar: ,// Tab欄 
      ),
    );
    } 
    // 方法
    _getTitle() {
    switch (index) {
      case 0:
        return _forMatchTitle('電影');
      case 1:
        return _forMatchTitle('圖書');
      case 2:
        return _forMatchTitle('音樂');
    }
    }
    
    //獲取標題的樣式
    _forMatchTitle(String data) {
    return new Text(data);
    }
}

複製代碼

添加 抽屜

添加抽屜,就直接在 中對應的屬相添加組件便可。

class _HomePageState extends State<HomePage> {
  
    @override
    Widget build(BuildContext context) {
    return new DefaultTabController(
      child: new Scaffold(
        appBar: new AppBar(
          title: _getTitle(),// 抽取成方法
        ), // 標題
        body: ,//主屏幕
        drawer: ,//抽屜
        bottomNavigationBar:  bottomNavigationBar: new BottomNavigationBar(
            onTap: _selectPosition,
            currentIndex: index,
            type: BottomNavigationBarType.fixed,
            iconSize: 24.0,
            items: new List<BottomNavigationBarItem>.generate(3, (index) {
              switch (index) {
                case 0:
                  return new BottomNavigationBarItem(
                      icon: new Icon(Icons.movie), title: new Text('電影'));
                case 1:
                  return new BottomNavigationBarItem(
                      icon: new Icon(Icons.book), title: new Text('圖書'));
                case 2:
                  return new BottomNavigationBarItem(
                      icon: new Icon(Icons.music_note), title: new Text('音樂'));
              }
            })),// Tab欄 
      ),
    );
    } 
  
   //獲取選中的tab 索引
    _selectPosition(int index) {
    if (this.index == index) return;
    setState(() {
      this.index = index;
    });
  }
}
複製代碼

因爲篇幅,還剩下主內容、抽屜、輪播圖的實現,我就不一一說明了,具體內容我放到了github上,搜索 studylifetime/flutter_demo 就不往文章上貼代碼了。具體實現詳情能夠查看源碼,裏邊註釋比較清楚。

總結

對於 Android 開發人員來講,入門比較簡單,dart 與java 很是相似,語言這一關很好過,熟悉一下界面開發,即可快速上手開發了。可是Flutter 如今還不適合商業項目的開發, 平時使用的微信支付、登陸,推送消息,bugly 錯誤上報,這些都須要國內的廠商來適配,推送、錯誤上報、分享若是從頭作一遍的話,會牽扯公司很大精力。

參考引用

歡迎關注微信公衆號,接收第一手技術乾貨
相關文章
相關標籤/搜索