一. 調用原生功能
1.1. Camera
某些應用程序可能需要使用移動設備進行拍照或者選擇相冊中的照片,Flutter官方提供了插件:image_picker
1.1.1. 添加依賴
添加對image_picker的依賴:
????https://pub.dev/packages/image_picker
dependencies:
? image_picker: ^0.6.5
1.1.2. 平臺配置
對iOS平臺,想要訪問相冊或者相機,需要獲取用戶的允許:
????依然是修改info.plist文件:/ios/Runner/Info.plist
????添加對相冊的訪問權限:Privacy - Photo Library Usage Description
????添加對相機的訪問權限:Privacy - Camera Usage Description ? ?
之后選擇相冊或者訪問相機時,會彈出如下的提示框:
1.1.3. 代碼實現
image_picker的核心代碼是pickImage方法:
????可以傳入數據源、圖片的大小、質量、前置后置攝像頭等
????數據源是必傳參數:ImageSource枚舉類型
????????camera:相機
????????gallery:相冊
? static Future<File> pickImage(
? ? ? {@required ImageSource source,
? ? ? double maxWidth,
? ? ? double maxHeight,
? ? ? int imageQuality,
? ? ? CameraDevice preferredCameraDevice = CameraDevice.rear}) async
案例演練:
import 'package:flutter/material.dart';
import 'dart:io';
import 'package:image_picker/image_picker.dart';
class HYCameraScreen extends StatefulWidget {
? static const String routeName = "/camera";
? @override
? _HYCameraScreenState createState() => _HYCameraScreenState();
}
class _HYCameraScreenState extends State<HYCameraScreen> {
? File _image;
? @override
? Widget build(BuildContext context) {
? ? return Scaffold(
? ? ? appBar: AppBar(
? ? ? ? title: Text("Camera"),
? ? ? ),
? ? ? body: Center(
? ? ? ? child: Column(
? ? ? ? ? mainAxisAlignment: MainAxisAlignment.center,
? ? ? ? ? children: <Widget>[
? ? ? ? ? ? _image == null ? Text("未選擇圖片"): Image.file(_image),
? ? ? ? ? ? RaisedButton(
? ? ? ? ? ? ? child: Text("選擇照片"),
? ? ? ? ? ? ? onPressed: _pickImage,
? ? ? ? ? ? )
? ? ? ? ? ],
? ? ? ? ),
? ? ? ),
? ? );
? }
? void _pickImage() async {
? ? File image = await ImagePicker.pickImage(source: ImageSource.gallery);
? ? setState(() {
? ? ? _image = image;
? ? });
? }
}
1.2. 電池信息
某些原生的信息,如果沒有很好的插件,我們可以通過platform channels(平臺通道)來獲取信息。
1.2.1. 平臺通過介紹
平臺通過是如何工作的呢?
????消息使用platform channels(平臺通道)在客戶端(UI)和宿主(平臺)之間傳遞;
????消息和響應以異步的形式進行傳遞,以確保用戶界面能夠保持響應;
調用過程大致如下:
????1.客戶端(Flutter端)發送與方法調用相對應的消息
????2.平臺端(iOS、Android端)接收方法,并返回結果;
????????iOS端通過FlutterMethodChannel做出響應;
????????Android端通過MethodChannel做出響應;
Flutter、iOS、Android端數據類型的對應關系:
1.2.2. 創建測試項目
我們這里創建一個獲取電池電量信息的項目,分別通過iOS和Android原生代碼來獲取對應的信息:
創建方式一:默認創建方式
????目前默認創建的Flutter項目,對應iOS的編程語言是Swift,對應Android的編程語言是kotlin????
flutter create batterylevel
創建方式二:指定編程語言
????如果我們希望指定編程語言,比如iOS編程語言為Objective-C,Android的編程語言為Java
flutter create -i objc -a java batterylevel2
1.2.3. 編寫Dart代碼
在Dart代碼中,我們需要創建一個MethodChannel對象:
????創建該對象時,需要傳入一個name,該name是區分多個通信的名稱
????可以通過調用該對象的invokeMethod來給對應的平臺發送消息進行通信
????????該調用是異步操作,需要通過await獲取then回調來獲取結果
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
? @override
? Widget build(BuildContext context) {
? ? return MaterialApp(
? ? ? title: 'Flutter Demo',
? ? ? theme: ThemeData(
? ? ? ? ? primarySwatch: Colors.blue, splashColor: Colors.transparent),
? ? ? home: HYBatteryScreen(),
? ? );
? }
}
class HYBatteryScreen extends StatefulWidget {
? static const String routeName = "/battery";
? @override
? _HYBatteryScreenState createState() => _HYBatteryScreenState();
}
class _HYBatteryScreenState extends State<HYBatteryScreen> {
? // 核心代碼一:
? static const platform = const MethodChannel("coderwhy.com/battery");
? int _result = 0;
? @override
? Widget build(BuildContext context) {
? ? return Scaffold(
? ? ? appBar: AppBar(
? ? ? ? title: Text("Battery"),
? ? ? ),
? ? ? body: Center(
? ? ? ? child: Column(
? ? ? ? ? children: <Widget>[
? ? ? ? ? ? Text("當前電池信息: $_result"),
? ? ? ? ? ? RaisedButton(
? ? ? ? ? ? ? child: Text("獲取電池信息"),
? ? ? ? ? ? ? onPressed: getBatteryInfo,
? ? ? ? ? ? )
? ? ? ? ? ],
? ? ? ? ),
? ? ? ),
? ? );
? }
? void getBatteryInfo() async {
? ? // 核心代碼二
? ? final int result = await platform.invokeMethod("getBatteryInfo");
? ? setState(() {
? ? ? _result = result;
? ? });
? }
}
當我們通過 platform.invokeMethod 調用對應平臺方法時,需要在對應的平臺實現其操作:
????iOS中可以通過Objective-C或Swift來實現
????Android中可以通過Java或者Kotlin來實現
1.2.4. 編寫iOS代碼
1.2.4.1. Swift代碼實現
代碼相關的操作步驟如下:
????1.獲取FlutterViewController(是應用程序的默認Controller)
????2.獲取MethodChannel(方法通道)
????????注意:這里需要根據我們創建時的名稱來獲取
????3.監聽方法調用(會調用傳入的回調函數)
????????iOS中獲取信息的方式
????????如果沒有獲取到,那么返回給Flutter端一個異常
????????通過result將結果回調給Flutter端
????????????3.1.判斷是否是getBatteryInfo的調用,告知Flutter端沒有實現對應的方法
????????????3.2.如果調用的是getBatteryInfo的方法, 那么通過封裝的另外一個方法實現回調
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
? override func application(
? ? _ application: UIApplication,
? ? didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
? ) -> Bool {
? ? // 1.獲取FlutterViewController(是應用程序的默認Controller)
? ? let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
? ? // 2.獲取MethodChannel(方法通道)
? ? let batteryChannel = FlutterMethodChannel(name: "coderwhy.com/battery",
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? binaryMessenger: controller.binaryMessenger)
? ? // 3.監聽方法調用(會調用傳入的回調函數)
? ? batteryChannel.setMethodCallHandler({
? ? ? [weak self] (call: FlutterMethodCall, result: FlutterResult) -> Void in
? ? ? // 3.1.判斷是否是getBatteryInfo的調用,告知Flutter端沒有實現對應的方法
? ? ? guard call.method == "getBatteryInfo" else {
? ? ? ? result(FlutterMethodNotImplemented)
? ? ? ? return
? ? ? }
? ? ? // 3.2.如果調用的是getBatteryInfo的方法, 那么通過封裝的另外一個方法實現回調
? ? ? self?.receiveBatteryLevel(result: result)
? ? })
? ? GeneratedPluginRegistrant.register(with: self)
? ? return super.application(application, didFinishLaunchingWithOptions: launchOptions)
? }
? private func receiveBatteryLevel(result: FlutterResult) {
? ? // 1.iOS中獲取信息的方式
? ? let device = UIDevice.current
? ? device.isBatteryMonitoringEnabled = true
? ? // 2.如果沒有獲取到,那么返回給Flutter端一個異常
? ? if device.batteryState == UIDevice.BatteryState.unknown {
? ? ? result(FlutterError(code: "UNAVAILABLE",
? ? ? ? ? ? ? ? ? ? ? ? ? message: "Battery info unavailable",
? ? ? ? ? ? ? ? ? ? ? ? ? details: nil))
? ? } else {
? ? ? // 3.通過result將結果回調給Flutter端
? ? ? result(Int(device.batteryLevel * 100))
? ? }
? }
}
1.2.4.2. Objective-C代碼實現
實現思路和上面是一致的,只是使用了Objective-C來實現:
????可以參考注釋內容
#import <Flutter/Flutter.h>
#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
? // 1.獲取FlutterViewController(是應用程序的默認Controller)
? FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
? // 2.獲取MethodChannel(方法通道)
? FlutterMethodChannel* batteryChannel = [FlutterMethodChannel
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? methodChannelWithName:@"coderwhy.com/battery"
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? binaryMessenger:controller.binaryMessenger];
? // 3.監聽方法調用(會調用傳入的回調函數)
? __weak typeof(self) weakSelf = self;
? [batteryChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
? ? // 3.1.判斷是否是getBatteryInfo的調用
? ? if ([@"getBatteryInfo" isEqualToString:call.method]) {
? ? ? // 1.iOS中獲取信息的方式
? ? ? int batteryLevel = [weakSelf getBatteryLevel];
? ? ? // 2.如果沒有獲取到,那么返回給Flutter端一個異常
? ? ? if (batteryLevel == -1) {
? ? ? ? result([FlutterError errorWithCode:@"UNAVAILABLE"
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? message:@"Battery info unavailable"
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? details:nil]);
? ? ? } else {
? ? ? ? // 3.通過result將結果回調給Flutter端
? ? ? ? result(@(batteryLevel));
? ? ? }
? ? } else {
? ? ? // 3.2.如果調用的是getBatteryInfo的方法, 那么通過封裝的另外一個方法實現回調
? ? ? result(FlutterMethodNotImplemented);
? ? }
? }];
? [GeneratedPluginRegistrant registerWithRegistry:self];
? return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (int)getBatteryLevel {
? // 獲取信息的方法
? UIDevice* device = UIDevice.currentDevice;
? device.batteryMonitoringEnabled = YES;
? if (device.batteryState == UIDeviceBatteryStateUnknown) {
? ? return -1;
? } else {
? ? return (int)(device.batteryLevel * 100);
? }
}
@end
1.2.5. 編寫Android代碼
1.2.5.1. Kotlin代碼實現
實現思路和上面是一致的,只是使用了Kotlin來實現:
????可以參考注釋內容
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
class MainActivity: FlutterActivity() {
? ? private val CHANNEL = "coderwhy.com/battery"
? ? override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
? ? ? ? // 1.創建MethodChannel對象
? ? ? ? val methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
? ? ? ? // 2.添加調用方法的回調
? ? ? ? methodChannel.setMethodCallHandler {
? ? ? ? ? ? // Note: this method is invoked on the main thread.
? ? ? ? ? ? call, result ->
? ? ? ? ? ? // 2.1.如果調用的方法是getBatteryInfo,那么正常執行
? ? ? ? ? ? if (call.method == "getBatteryInfo") {
? ? ? ? ? ? ? ? // 2.1.1.調用另外一個自定義方法回去電量信息
? ? ? ? ? ? ? ? val batteryLevel = getBatteryLevel()
? ? ? ? ? ? ? ? // 2.1.2. 判斷是否正常獲取到
? ? ? ? ? ? ? ? if (batteryLevel != -1) {
? ? ? ? ? ? ? ? ? ? // 獲取到返回結果
? ? ? ? ? ? ? ? ? ? result.success(batteryLevel)
? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? // 獲取不到拋出異常
? ? ? ? ? ? ? ? ? ? result.error("UNAVAILABLE", "Battery level not available.", null)
? ? ? ? ? ? ? ? }
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? // 2.2.如果調用的方法是getBatteryInfo,那么正常執行
? ? ? ? ? ? ? ? result.notImplemented()
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ? private fun getBatteryLevel(): Int {
? ? ? ? val batteryLevel: Int
? ? ? ? if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
? ? ? ? ? ? val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
? ? ? ? ? ? batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
? ? ? ? } else {
? ? ? ? ? ? val intent = ContextWrapper(applicationContext).registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
? ? ? ? ? ? batteryLevel = intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
? ? ? ? }
? ? ? ? return batteryLevel
? ? }
}
1.2.5.1. Java代碼實現
實現思路和上面是一致的,只是使用了Java來實現:
????可以參考注釋內容
package com.example.batterylevel2;
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugins.GeneratedPluginRegistrant;
import io.flutter.plugin.common.MethodChannel;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
public class MainActivity extends FlutterActivity {
? private static final String CHANNEL = "coderwhy.com/battery";
? @Override
? public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
? ? // 1.創建MethodChannel對象
? ? MethodChannel methodChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL);
? ? // 2.添加調用方法的回調
? ? methodChannel.setMethodCallHandler(
? ? ? ? (call, result) -> {
? ? ? ? ? // 2.1.如果調用的方法是getBatteryInfo,那么正常執行
? ? ? ? ? if (call.method.equals("getBatteryInfo")) {
? ? ? ? ? ? // 2.1.1.調用另外一個自定義方法回去電量信息
? ? ? ? ? ? int batteryLevel = getBatteryLevel();
? ? ? ? ? ? // 2.1.2. 判斷是否正常獲取到
? ? ? ? ? ? if (batteryLevel != -1) {
? ? ? ? ? ? ? // 獲取到返回結果
? ? ? ? ? ? ? result.success(batteryLevel);
? ? ? ? ? ? } else {
? ? ? ? ? ? ? // 獲取不到拋出異常
? ? ? ? ? ? ? result.error("UNAVAILABLE", "Battery level not available.", null);
? ? ? ? ? ? }
? ? ? ? ? } else {
? ? ? ? ? ? // 2.2.如果調用的方法是getBatteryInfo,那么正常執行
? ? ? ? ? ? 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;
? }
}
二. 嵌入原有項目
首先,我們先明確一點:Flutter設計初衷并不是為了和其它平臺進行混合開發,它的目的是為了打造一個完整的跨平臺應用程序。
但是,實際開發中,原有項目完全使用Flutter進行重構并不現實,對于原有項目我們更多可能采用混合開發的方式。
2.1. 創建Flutter模塊
對于需要進行混合開發的原有項目,Flutter可以作為一個庫或者模塊,繼承進現有項目中。
????模塊引入到你的Android或iOS應用中,以使用Flutter渲染一部分的UI,或者共享的Dart代碼。
????在Flutter v1.12中,添加到現有應用的基本場景已經被支持,每個應用在同一時間可以集成一個全屏幕的Flutter實例。
但是,目前一些場景依然是有限制的:
????運行多個Flutter實例,或在屏幕局部上運行Flutter可能會導致不可以預測的行為;
????在后臺模式使用Flutter的能力還在開發中(目前不支持);
????將Flutter庫打包到另一個可共享的庫或將多個Flutter庫打包到同一個應用中,都不支持;
????添加到應用在Android平臺的實現基于 FlutterPlugin 的 API,一些不支持 FlutterPlugin 的插件可能會有不可預知的行為。
創建Flutter Module
flutter create --template module my_flutter
創建完成后,該模塊和普通的Flutter項目一直,可以通過Android Studio或VSCode打開、開發、運行;
目錄結構如下:
????和之前項目不同的iOS和Android項目是一個隱藏文件,并且我們通常不會單獨打開它們再來運行;
????它們的作用是將Flutter Module進行編譯,之后繼承到現有的項目中;
my_flutter/
├── .ios/
├── .android/
├── lib/
│? └── main.dart
├── test/
└── pubspec.yaml
2.2. 嵌入iOS項目
嵌入到現有iOS項目有多種方式:
????可以使用 CocoaPods 依賴管理和已安裝的 Flutter SDK ;
????也可以通過手動編譯 Flutter engine 、你的 dart 代碼和所有 Flutter plugin 成 framework ,用 Xcode 手動集成到你的應用中,并更新編譯設置;
目前iOS項目幾乎都已經使用Cocoapods進行管理,所以推薦使用第一種CocoaPods方式;
我們按照如下的方式,搭建一個需要繼承的iOS項目:
1.為了進行測試,我們這里創建一個默認的iOS項目:使用Xcode創建即可
2.將項目加入CocoaPods進行管理
????電腦上需要已經安裝了CocoaPods
初始化CocoaPods:
pod init
安裝CocoaPods的依賴:
pod install
編譯Podfile文件:
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
# 添加模塊所在路徑
flutter_application_path = '../my_flutter'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')
target 'ios_my_test' do
? # Comment the next line if you don't want to use dynamic frameworks
? use_frameworks!
? # 安裝Flutter模塊
? install_all_flutter_pods(flutter_application_path)
? # Pods for ios_my_test
end
重新執行安裝CocoaPods的依賴:
pod install
2.2.1. Swift代碼
為了在既有的iOS應用中展示Flutter頁面,需要啟動 Flutter Engine和 FlutterViewController。
通常建議為我們的應用預熱一個 長時間存活 的FlutterEngine:
????我們將在應用啟動的 app delegate 中創建一個 FlutterEngine,并作為屬性暴露給外界。
import UIKit
import FlutterPluginRegistrant
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
? // 1.創建一個FlutterEngine對象
? ? lazy var flutterEngine = FlutterEngine(name: "my flutter engine")
? ? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
? ? ? // 2.啟動flutterEngine
? ? ? ? flutterEngine.run()
? ? ? ? return true
? ? }
}
在啟動的ViewController中,創建一個UIButton,并且點擊這個Button時,彈出FlutterViewController
import UIKit
import Flutter
class ViewController: UIViewController {
? ? override func viewDidLoad() {
? ? ? ? super.viewDidLoad()
? ? ? // 1.創建一個按鈕
? ? ? ? let button = UIButton(type: UIButton.ButtonType.custom)
? ? ? ? button.addTarget(self, action: #selector(showFlutter), for: .touchUpInside)
? ? ? ? button.setTitle("Show Flutter", for: .normal)
? ? ? ? button.frame = CGRect(x: 80, y: 210, width: 160, height: 40)
? ? ? ? button.backgroundColor = UIColor.blue
? ? ? ? self.view.addSubview(button)
? ? }
? ? @objc func showFlutter() {
? ? ? ? // 2.創建FlutterViewController對象(需要先獲取flutterEngine)
? ? ? ? let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine;
? ? ? ? let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil);
? ? ? ? navigationController?.pushViewController(flutterViewController, animated: true);
? ? }
}
我們也可以省略預先創建的 FlutterEngine :
????不推薦這樣來做,因為在第一針圖像渲染完成之前,可能會出現明顯的延遲。
func showFlutter() {
? let flutterViewController = FlutterViewController(project: nil, nibName: nil, bundle: nil)
? present(flutterViewController, animated: true, completion: nil)
}
2.2.2. Objective-C代碼
如果上面的代碼希望使用Objective-C也是可以實現的:
????代碼的邏輯是完成一直的
AppDelegate.h代碼:
@import UIKit;
@import Flutter;
@interface AppDelegate : FlutterAppDelegate
@property (nonatomic,strong) FlutterEngine *flutterEngine;
@end
AppDelegate.m代碼:
#import <FlutterPluginRegistrant/GeneratedPluginRegistrant.h> // Used to connect plugins.
#import "AppDelegate.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
? ? didFinishLaunchingWithOptions:(NSDictionary<UIApplicationLaunchOptionsKey, id> *)launchOptions {
? self.flutterEngine = [[FlutterEngine alloc] initWithName:@"my flutter engine"];
? [self.flutterEngine run];
? [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];
? return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end
ViewController.m代碼:
@import Flutter;
#import "AppDelegate.h"
#import "ViewController.h"
@implementation ViewController
- (void)viewDidLoad {
? ? [super viewDidLoad];
? ? // Make a button to call the showFlutter function when pressed.
? ? UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
? ? [button addTarget:self
? ? ? ? ? ? ? action:@selector(showFlutter)
? ? forControlEvents:UIControlEventTouchUpInside];
? ? [button setTitle:@"Show Flutter!" forState:UIControlStateNormal];
? ? button.backgroundColor = UIColor.blueColor;
? ? button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
? ? [self.view addSubview:button];
}
- (void)showFlutter {
? ? FlutterEngine *flutterEngine =
? ? ? ? ((AppDelegate *)UIApplication.sharedApplication.delegate).flutterEngine;
? ? FlutterViewController *flutterViewController =
? ? ? ? [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
? ? [self presentViewController:flutterViewController animated:YES completion:nil];
}
@end
2.3.嵌入Android項目
嵌入到現有Android項目有多種方式:
????編譯為AAR文件(Android Archive)
????????通過Flutter編譯為aar,添加相關的依賴
????依賴模塊的源碼方式,在gradle進行配置
這里我們采用第二種方式
1.創建一個Android的測試項目
????使用Android Studio創建
2.添加相關的依賴
????修改Android項目中的settings.gradle文件:
// Include the host app project.
include ':app'? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // assumed existing content
setBinding(new Binding([gradle: this]))? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // new
evaluate(new File(? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // new
? settingsDir.parentFile,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? // new
? 'my_flutter/.android/include_flutter.groovy'? ? ? ? ? ? ? ? ? ? ? ? ? // new
))?
另外,我們需要在Android項目工程的build.gradle中添加依賴:
dependencies {
? implementation project(':flutter')
}
編譯代碼,可能會出現如下錯誤:
????這是因為從Java8開始才支持接口方法
????Flutter Android引擎使用了該Java8的新特性
解決辦法:通過設置Android項目工程的build.gradle配置使用Java8編譯:
? compileOptions {
? ? sourceCompatibility 1.8
? ? targetCompatibility 1.8
? }
接下來,我們這里嘗試添加一個Flutter的screen到Android應用程序中
Flutter提供了一個FlutterActivity來展示Flutter界面在Android應用程序中,我們需要先對FlutterActivity進行注冊:
在AndroidManifest.xml中進行注冊
<activity
? android:name="io.flutter.embedding.android.FlutterActivity"
? android:theme="@style/AppTheme"
? android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
? android:hardwareAccelerated="true"
? android:windowSoftInputMode="adjustResize"
? />
2.3.1. Java代碼
package com.coderwhy.testandroid;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import io.flutter.embedding.android.FlutterActivity;
public class MainActivity extends AppCompatActivity {
? ? @Override
? ? protected void onCreate(Bundle savedInstanceState) {
? ? ? ? super.onCreate(savedInstanceState);
//? ? ? ? setContentView(R.layout.activity_main);
? ? ? ? startActivity(
? ? ? ? ? ? FlutterActivity.createDefaultIntent(this)
? ? ? ? );
? ? }
}
也可以在創建時,傳入默認的路由:
package com.coderwhy.testandroid;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import io.flutter.embedding.android.FlutterActivity;
public class MainActivity extends AppCompatActivity {
? ? @Override
? ? protected void onCreate(Bundle savedInstanceState) {
? ? ? ? super.onCreate(savedInstanceState);
//? ? ? ? setContentView(R.layout.activity_main);
? ? ? ? startActivity(
? ? ? ? ? FlutterActivity
? ? ? ? ? .withNewEngine()
? ? ? ? ? .initialRoute("/my_route")
? ? ? ? ? .build(currentActivity)
? ? ? ? );
? ? }
}
將Flutter頁面嵌入到Android項目
2.3.2. Kotlin代碼
package com.coderwhy.test_demo_a_k
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import io.flutter.embedding.android.FlutterActivity
class MainActivity : AppCompatActivity() {
? ? override fun onCreate(savedInstanceState: Bundle?) {
? ? ? ? super.onCreate(savedInstanceState)
//? ? ? ? setContentView(R.layout.activity_main)
? ? ? ? startActivity(
? ? ? ? ? ? FlutterActivity.createDefaultIntent(this)
? ? ? ? )
? ? }
}
也可以在創建時指定路由:
package com.coderwhy.test_demo_a_k
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import io.flutter.embedding.android.FlutterActivity
class MainActivity : AppCompatActivity() {
? ? override fun onCreate(savedInstanceState: Bundle?) {
? ? ? ? super.onCreate(savedInstanceState)
//? ? ? ? setContentView(R.layout.activity_main)
? ? ? ? startActivity(
? ? ? ? ? ? FlutterActivity
? ? ? ? ? ? ? ? .withNewEngine()
? ? ? ? ? ? ? ? .initialRoute("/my_route")
? ? ? ? ? ? ? ? .build(this)
? ? ? ? );
? ? }
}
三. Flutter模塊調試
一旦將Flutter模塊繼承到你的項目中,并且使用Flutter平臺的API運行Flutter引擎或UI,那么就可以先普通的Android或者iOS一樣來構建自己的Android或者iOS項目了
但是Flutter的有一個非常大的優勢是其快速開發,也就是hot reload。
那么對應Flutter模塊,我們如何使用hot reload加速我們的調試速度呢?
????可以使用flutter attach
# --app-id是指定哪一個應用程序
# -d是指定連接哪一個設備
flutter attach --app-id com.coderwhy.ios-my-test -d 3D7A877C-B0DD-4871-8D6E-0C5263B986CD