概述
- 調(diào)用原生功能
- 嵌入原有項(xiàng)目
- Flutter模塊調(diào)試
一、調(diào)用原生功能
-
1.1、Camera
某些應(yīng)用程序可能需要使用移動(dòng)設(shè)備進(jìn)行拍照或者選擇相冊(cè)中的照片,F(xiàn)lutter官方提供了插件:image_picker-
1.1.1常拓、添加依賴
添加對(duì)image_picker的依賴:https://pub.dev/packages/image_picker掐禁,在項(xiàng)目的pubspec.ymal
里面添加下面的依賴即可怜械,然后執(zhí)行右上角的Pub get
dependencies: image_picker: ^0.6.7+1
-
1.1.2、平臺(tái)配置
對(duì)iOS平臺(tái)傅事,想要訪問(wèn)相冊(cè)或者相機(jī)缕允,需要獲取用戶的允許:
依然是修改info.plist
文件:/ios/Runner/Info.plist
添加對(duì)相冊(cè)的訪問(wèn)權(quán)限:Privacy - Photo Library Usage Description
添加對(duì)相機(jī)的訪問(wèn)權(quán)限:Privacy - Camera Usage Description
-
拓展
:其他的權(quán)限 - 相機(jī)權(quán)限:
Privacy - Camera Usage Description
是否允許此App使用你的相機(jī)? - 相冊(cè)權(quán)限:
Privacy - Photo Library Usage Description
是否允許此App訪問(wèn)你的媒體資料庫(kù)蹭越? - 通訊錄權(quán)限:
Privacy - Contacts Usage Description
是否允許此App訪問(wèn)你的通訊錄障本? - 藍(lán)牙權(quán)限:
Privacy - Bluetooth Peripheral Usage Description
是否許允此App使用藍(lán)牙? - 使用期間定位權(quán)限:
Privacy - Location When In Use Usage Description
是否允許此App使用定位服務(wù)响鹃? - 始終定位權(quán)限:
Privacy - Location Always Usage Description
是否允許此App始終使用定位服務(wù)驾霜? - 語(yǔ)音轉(zhuǎn)文字權(quán)限:
Privacy - Speech Recognition Usage Description
是否允許此App使用語(yǔ)音識(shí)別? - 日歷權(quán)限:
Privacy - Calendars Usage Description
是否允許此App使用日歷买置? - 健康—讀取數(shù)據(jù):
Privacy - Health Share Usage Description
是否允許此App讀取健康數(shù)據(jù)粪糙? - 健康—寫入數(shù)據(jù):
Privacy - Health Share Usage Description
是否允許此App寫入健康數(shù)據(jù)? - 讀取HomeKit:
Privacy - HomeKit Usage Description
是否允許此App訪問(wèn)HomeKit忿项? - 麥克風(fēng):
Privacy - Microphone Usage Description
是否允許此App訪問(wèn)麥克風(fēng)蓉冈? - 提醒事項(xiàng):
Privacy - Reminders Usage Description
是否允許此App訪問(wèn)提醒事項(xiàng)城舞? - 運(yùn)動(dòng)與健身:
Privacy - Motion Usage Description
是否允許此App訪問(wèn)運(yùn)動(dòng)與健身? - 面部ID權(quán)限:
Privacy - Face ID Usage Description
是否允許此App訪問(wèn)Face ID寞酿?
之后選擇相冊(cè)或者訪問(wèn)相機(jī)時(shí)家夺,會(huì)彈出如下的提示框:
-
-
1.1.3、代碼實(shí)現(xiàn)
image_picker
的核心代碼是getImage
方法:
可以傳入數(shù)據(jù)源伐弹、圖片的大小拉馋、質(zhì)量、前置后置攝像頭等
數(shù)據(jù)源是必傳參數(shù):ImageSource
枚舉類型:camera:相機(jī)
惨好、gallery:相冊(cè)
Future<PickedFile> getImage({ @required ImageSource source, double maxWidth, double maxHeight, int imageQuality, // 默認(rèn)后置攝像頭 CameraDevice preferredCameraDevice = CameraDevice.rear, }) { return platform.pickImage( source: source, maxWidth: maxWidth, maxHeight: maxHeight, imageQuality: imageQuality, preferredCameraDevice: preferredCameraDevice, ); }
案例演練:
import 'dart:io'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; class JKCameraScreen extends StatefulWidget { @override _JKCameraScreenState createState() => _JKCameraScreenState(); } class _JKCameraScreenState extends State<JKCameraScreen> { PickedFile _imageFile; final ImagePicker _picker = ImagePicker(); @override Widget build(BuildContext context) { return Center( child: Column( children: [ RaisedButton( child: Text('選擇一個(gè)相冊(cè)'), onPressed: _pickImage ), _imageFile == null ? Text('請(qǐng)選擇一張照片') : Image.file(File(_imageFile.path)) ], ), ); } void _pickImage() async { print('選擇相冊(cè)'); PickedFile pickedFile = await _picker.getImage(source: ImageSource.gallery); setState(() { _imageFile = pickedFile; }); } }
-
-
1.2煌茴、電池信息
某些原生的信息,如果沒(méi)有很好的插件昧狮,我們可以通過(guò)景馁、platform channels(平臺(tái)通道)
來(lái)獲取信息。-
1.2.1逗鸣、平臺(tái)通過(guò)介紹
平臺(tái)通過(guò)是如何工作的呢合住?- 消息使用platform channels(平臺(tái)通道)在客戶端(UI)和宿主(平臺(tái))之間傳遞;
- 消息和響應(yīng)以異步的形式進(jìn)行傳遞撒璧,以確保用戶界面能夠保持響應(yīng)透葛;
調(diào)用過(guò)程大致如下:
- 1.客戶端(Flutter端)發(fā)送與方法調(diào)用相對(duì)應(yīng)的消息
- 2.平臺(tái)端(iOS、Android端)接收方法卿樱,并返回結(jié)果僚害;
- iOS端通過(guò)FlutterMethodChannel做出響應(yīng);
- Android端通過(guò)MethodChannel做出響應(yīng)繁调;
Flutter萨蚕、iOS、Android端數(shù)據(jù)類型的對(duì)應(yīng)關(guān)系:
-
1.2.2、創(chuàng)建測(cè)試項(xiàng)目
我們這里創(chuàng)建一個(gè)獲取電池電量信息的項(xiàng)目裕寨,分別通過(guò)iOS和Android原生代碼來(lái)獲取對(duì)應(yīng)的信息:-
創(chuàng)建方式一:默認(rèn)創(chuàng)建方式浩蓉,目前默認(rèn)創(chuàng)建的Flutter項(xiàng)目,對(duì)應(yīng)iOS的編程語(yǔ)言是Swift宾袜,對(duì)應(yīng)Android的編程語(yǔ)言是kotlin
flutter create batterylevel
-
創(chuàng)建方式二:指定編程語(yǔ)言捻艳,如果我們希望指定編程語(yǔ)言,比如iOS編程語(yǔ)言為Objective-C庆猫,Android的編程語(yǔ)言為Java
flutter create -i objc -a java batterylevel2
-
提示:
i
代表iOS
认轨,a
代表android
-
-
-
1.2.3、寫Dart代碼
在Dart代碼中月培,我們需要?jiǎng)?chuàng)建一個(gè)MethodChannel對(duì)象:- 創(chuàng)建該對(duì)象時(shí)好渠,需要傳入一個(gè)name昨稼,該name是區(qū)分多個(gè)通信的名稱
- 可以通過(guò)調(diào)用該對(duì)象的invokeMethod來(lái)給對(duì)應(yīng)的平臺(tái)發(fā)送消息進(jìn)行通信
-
該調(diào)用是異步操作,需要通過(guò)await獲取then回調(diào)來(lái)獲取結(jié)果
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( // 啟動(dòng)要顯示的界面 home: HomePage(), ); } } class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("原生電池的調(diào)用"), ), body: JKBatteryLevel(), ); } } class JKBatteryLevel extends StatefulWidget { @override _JKBatteryLevelState createState() => _JKBatteryLevelState(); } class _JKBatteryLevelState extends State<JKBatteryLevel> { // 定義一個(gè)平臺(tái)通道 static const platform1 = const MethodChannel('com.jk/battery'); int _batterylevel = 0; @override Widget build(BuildContext context) { return Center( child: Column( children: [ RaisedButton( child: Text('獲取剩余電量'), onPressed: _buildLevelInfo ), Text('電量:${_batterylevel}') ], ), ); } void _buildLevelInfo() async { // 調(diào)用原生的電池信息 final result = await platform1.invokeMethod('getBatteryInfo'); setState(() { _batterylevel = result; }); } }
當(dāng)我們通過(guò)
platform.invokeMethod
調(diào)用對(duì)應(yīng)平臺(tái)方法時(shí)拳锚,需要在對(duì)應(yīng)的平臺(tái)實(shí)現(xiàn)其操作:
iOS
中可以通過(guò)Objective-C
或Swift
來(lái)實(shí)現(xiàn)
Android
中可以通過(guò)Java
或者Kotlin
來(lái)實(shí)現(xiàn)
-
-
1.2.4、編寫iOS代碼
-
<1>寻行、Swift 代碼霍掺,在
AppDelegate.swift
里面寫代碼import UIKit import Flutter @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { // 實(shí)現(xiàn)獲取電量信息的功能 // 1、獲取FlutterViewController let flutterController: FlutterViewController = window.rootViewController as! FlutterViewController // 2拌蜘、創(chuàng)建 FlutterMethodChannel /** name:static const platform1 = const MethodChannel('com.jk/battery'); 的 com.jk/battery杆烁,名字自己定義: 域名/名字 binaryMessenger: 二進(jìn)制消息 */ let channel = FlutterMethodChannel(name: "com.jk/battery", binaryMessenger: flutterController.binaryMessenger); // 3.監(jiān)聽(tīng)channnel方法 channel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) in guard call.method == "getBatteryInfo" else { // 找不到該方法 result(FlutterMethodNotImplemented) return; } let device = UIDevice.current // 電池電量的探測(cè),設(shè)置為true简卧,才能更好的獲取電量 device.isBatteryMonitoringEnabled = true if device.batteryState == .unknown { result(FlutterError(code: "Unknown", message: "Battery is unknown", details: nil)) } else { result(Int(device.batteryLevel * 100)) } } GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } }
-
<2>兔魂、OC 代碼
#import "AppDelegate.h" #import "GeneratedPluginRegistrant.h" @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // 1.獲取FlutterViewController(是應(yīng)用程序的默認(rèn)Controller) FlutterViewController *flutterController = (FlutterViewController *)self.window.rootViewController; // 2.獲取MethodChannel(方法通道) FlutterMethodChannel *batteryChannel = [FlutterMethodChannel methodChannelWithName:@"com.jk/battery" binaryMessenger:flutterController.binaryMessenger]; // 3.監(jiān)聽(tīng)方法調(diào)用(會(huì)調(diào)用傳入的回調(diào)函數(shù)) __weak typeof(self) weakSelf = self; [batteryChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult _Nonnull result) { // 3.1.判斷是否是getBatteryInfo的調(diào)用 if ([@"getBatteryInfo" isEqualToString:call.method]) { // 1.iOS中獲取信息的方式 int batteryLevel = [weakSelf getBatteryLevel]; // 2.如果沒(méi)有獲取到,那么返回給Flutter端一個(gè)異常 if (batteryLevel == -1) { result([FlutterError errorWithCode:@"UNAVAILABLE" message:@"Battery info unavailable" details:nil]); } else { // 3.通過(guò)result將結(jié)果回調(diào)給Flutter端 result(@(batteryLevel)); } } else { // 3.2.如果調(diào)用的是getBatteryInfo的方法, 那么通過(guò)封裝的另外一個(gè)方法實(shí)現(xiàn)回調(diào) result(FlutterMethodNotImplemented); } }]; [GeneratedPluginRegistrant registerWithRegistry:self]; // Override point for customization after application launch. 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>举娩、Ktolin 代碼
package com.example.batterylevel import android.content.Context import android.content.ContextWrapper import android.content.Intent import android.content.IntentFilter import android.os.BatteryManager import android.os.Build import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel class MainActivity: FlutterActivity() { private val CHANNEL = "com.jk/battery" override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) // 1.創(chuàng)建MethodChannel對(duì)象 val methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL) // 2.添加調(diào)用方法的回調(diào) methodChannel.setMethodCallHandler { call, result -> if (call.method == "getBatteryInfo") { // 2.1.1.調(diào)用另外一個(gè)自定義方法回去電量信息 val batteryLevel = getBatteryLevel() // 2.1.2. 判斷是否正常獲取到 if (batteryLevel != -1) { // 獲取到返回結(jié)果 result.success(batteryLevel) } else { // 獲取不到拋出異常 result.error("UNAVAILABLE", "Battery level not available.", null) } } else { // 2.2.如果調(diào)用的方法是getBatteryInfo,那么正常執(zhí)行 result.notImplemented() } } } private fun getBatteryLevel(): Int { val batteryLevel: Int if (Build.VERSION.SDK_INT > Build.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 } }
-
<2>析校、Java 代碼
實(shí)現(xiàn)思路和上面是一致的,只是使用了Java來(lái)實(shí)現(xiàn):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 = "com.jk/battery"; @Override public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { // 1.創(chuàng)建MethodChannel對(duì)象 MethodChannel methodChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL); // 2.添加調(diào)用方法的回調(diào) methodChannel.setMethodCallHandler( (call, result) -> { // 2.1.如果調(diào)用的方法是getBatteryInfo,那么正常執(zhí)行 if (call.method.equals("getBatteryInfo")) { // 2.1.1.調(diào)用另外一個(gè)自定義方法回去電量信息 int batteryLevel = getBatteryLevel(); // 2.1.2. 判斷是否正常獲取到 if (batteryLevel != -1) { // 獲取到返回結(jié)果 result.success(batteryLevel); } else { // 獲取不到拋出異常 result.error("UNAVAILABLE", "Battery level not available.", null); } } else { // 2.2.如果調(diào)用的方法是getBatteryInfo,那么正常執(zhí)行 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; } }
-
-
二铜涉、嵌入原有項(xiàng)目
首先智玻,我們先明確一點(diǎn):Flutter設(shè)計(jì)初衷并不是為了和其它平臺(tái)進(jìn)行混合開(kāi)發(fā),它的目的是為了打造一個(gè)完整的跨平臺(tái)應(yīng)用程序芙代。
但是吊奢,實(shí)際開(kāi)發(fā)中,原有項(xiàng)目完全使用Flutter進(jìn)行重構(gòu)并不現(xiàn)實(shí)纹烹,對(duì)于原有項(xiàng)目我們更多可能采用混合開(kāi)發(fā)的方式页滚。
-
2.1、創(chuàng)建Flutter模塊
-
對(duì)于需要進(jìn)行混合開(kāi)發(fā)的原有項(xiàng)目铺呵,F(xiàn)lutter可以作為一個(gè)庫(kù)或者模塊裹驰,繼承進(jìn)現(xiàn)有項(xiàng)目中。
- 模塊引入到你的Android或iOS應(yīng)用中陪蜻,以使用Flutter渲染一部分的UI邦马,或者共享的Dart代碼。
- 在Flutter v1.12中宴卖,添加到現(xiàn)有應(yīng)用的基本場(chǎng)景已經(jīng)被支持滋将,每個(gè)應(yīng)用在同一時(shí)間可以集成一個(gè)全屏幕的Flutter實(shí)例。
-
但是症昏,目前一些場(chǎng)景依然是有限制的:
- 運(yùn)行多個(gè)Flutter實(shí)例随闽,或在屏幕局部上運(yùn)行Flutter可能會(huì)導(dǎo)致不可以預(yù)測(cè)的行為;
- 在后臺(tái)模式使用Flutter的能力還在開(kāi)發(fā)中(目前不支持)肝谭;
- 將Flutter庫(kù)打包到另一個(gè)可共享的庫(kù)或?qū)⒍鄠€(gè)Flutter庫(kù)打包到同一個(gè)應(yīng)用中掘宪,都不支持蛾扇;
- 添加到應(yīng)用在Android平臺(tái)的實(shí)現(xiàn)基于 FlutterPlugin 的 API,一些不支持 FlutterPlugin 的插件可能會(huì)有不可預(yù)知的行為魏滚。
-
創(chuàng)建 Flutter Module
flutter create --template module my_flutter
創(chuàng)建完成后镀首,該模塊和普通的Flutter項(xiàng)目一直,可以通過(guò)Android Studio或VSCode打開(kāi)鼠次、開(kāi)發(fā)更哄、運(yùn)行;
- 目錄結(jié)構(gòu)如下:
和之前項(xiàng)目不同的iOS和Android項(xiàng)目是一個(gè)隱藏文件腥寇,并且我們通常不會(huì)單獨(dú)打開(kāi)它們?cè)賮?lái)運(yùn)行成翩;
-
它們的作用是將Flutter Module進(jìn)行編譯,之后繼承到現(xiàn)有的項(xiàng)目中赦役;
my_flutter/ ├── .iOS/ ├── .android/ ├── lib/ │ └── main.dart ├── test/ └── pubspec.yaml
- 目錄結(jié)構(gòu)如下:
-
-
2.2麻敌、嵌入iOS項(xiàng)目
-
嵌入到現(xiàn)有iOS項(xiàng)目有多種方式:
- 可以使用 CocoaPods 依賴管理和已安裝的 Flutter SDK ;
- 也可以通過(guò)手動(dòng)編譯 Flutter engine 掂摔、你的 dart 代碼和所有 Flutter plugin 成 framework 术羔,用 Xcode 手動(dòng)集成到你的應(yīng)用中,并更新編譯設(shè)置棒呛;
目前iOS項(xiàng)目幾乎都已經(jīng)使用Cocoapods進(jìn)行管理聂示,所以推薦使用第一種CocoaPods方式;
-
我們按照如下的方式簇秒,搭建一個(gè)需要繼承的iOS項(xiàng)目:我們暫且起名字:
testdemoios
-
1鱼喉、為了進(jìn)行測(cè)試,我們這里創(chuàng)建一個(gè)默認(rèn)的iOS項(xiàng)目:使用Xcode創(chuàng)建即可
-
2趋观、將項(xiàng)目加入CocoaPods進(jìn)行管理扛禽,電腦上需要已經(jīng)安裝了CocoaPods,直接百度輸入 CocoaPods即可搜到很多的教程皱坛,按著教程來(lái)就好
初始化CocoaPods:
cd 進(jìn)入剛才創(chuàng)建的 testdemoios pod init
編譯Podfile文件:
# platform :ios, '9.0' # 添加模塊所在路徑编曼,記得 `command + s` 保存 flutter_application_path = '../../my_flutter/my_flutter' load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb') target 'testdemoios' do use_frameworks! # 安裝Flutter模塊 install_all_flutter_pods(flutter_application_path) end
提示:
flutter_application_path = '../../my_flutter/my_flutter'
后面的路徑,我們可以放一個(gè)統(tǒng)一的位置剩辟,方便團(tuán)隊(duì)開(kāi)發(fā)
安裝CocoaPods的依賴
pod install
-
-
2.2.1掐场、Swift代碼里面嵌入 上面 my_flutter 包
為了在既有的iOS應(yīng)用中展示Flutter頁(yè)面,需要啟動(dòng) Flutter Engine和 FlutterViewController贩猎。
通常建議為我們的應(yīng)用預(yù)熱一個(gè) 長(zhǎng)時(shí)間存活 的FlutterEngine:
我們將在應(yīng)用啟動(dòng)的AppDelegate.swift
中創(chuàng)建一個(gè)FlutterEngine
熊户,并作為屬性暴露給外界。import UIKit import Flutter @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? lazy var flutterEngine = FlutterEngine(name: "my flutter engine") func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // 開(kāi)啟引擎 flutterEngine.run() return true } }
在啟動(dòng)的ViewController中吭服,創(chuàng)建一個(gè)UIButton嚷堡,并且點(diǎn)擊這個(gè)Button時(shí),彈出FlutterViewController
import UIKit import Flutter class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .green let button = UIButton(frame: CGRect(x: 0, y: 0, width: 200, height: 50)) button .setTitle("進(jìn)入 Flutter 界面", for: .normal) button.backgroundColor = .brown button.center = view.center button.addTarget(self, action: #selector(click), for: .touchUpInside) view.addSubview(button) } @objc func click() { let flutterVC = FlutterViewController(engine: (UIApplication.shared.delegate as! AppDelegate).flutterEngine, nibName: nibName, bundle: nil) self .present(flutterVC, animated: true, completion: nil) } }
提示:我當(dāng)時(shí)運(yùn)行代碼報(bào)錯(cuò):
framework not found FlutterPluginRegistrant
艇棕,我進(jìn)行了一下pod update
就好了\-
我們也可以省略預(yù)先創(chuàng)建的 FlutterEngine :不推薦這樣來(lái)做蝌戒,因?yàn)樵诘谝会槇D像渲染完成之前串塑,可能會(huì)出現(xiàn)明顯的延遲。
func showFlutter() { let flutterViewController = FlutterViewController(project: nil, nibName: nil, bundle: nil) present(flutterViewController, animated: true, completion: nil) }
-
2.2.2北苟、Objective-C代碼
如果上面的代碼希望使用Objective-C也是可以實(shí)現(xiàn)的:代碼的邏輯是完成一致的-
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]; 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
-
擴(kuò)展:在原生項(xiàng)目開(kāi)啟 Flutter的熱更新和熱加載桩匪,也就是flutter代碼修改完,原生項(xiàng)目不用再啟動(dòng)粹淋,就可以直接看到flutter代碼的修改內(nèi)容
步驟如下:
1吸祟、關(guān)閉模擬器開(kāi)啟的項(xiàng)目,殺死模擬器的APP
2桃移、進(jìn)入module項(xiàng)目的根目錄,終端執(zhí)行下面的代碼葛碧,選擇對(duì)用的設(shè)備flutter attach
3借杰、打開(kāi)app,在flutter修改完項(xiàng)目后进泼,在終端輸入對(duì)應(yīng)的指令蔗衡,app界面跟著變化
-
-
2.3.嵌入Android項(xiàng)目
嵌入到現(xiàn)有Android項(xiàng)目有多種方式:- 編譯為AAR文件(Android Archive):通過(guò)Flutter編譯為aar,添加相關(guān)的依賴
- 依賴模塊的源碼方式乳绕,在gradle進(jìn)行配置
這里我們采用第二種方式
-
1>绞惦、創(chuàng)建一個(gè)Android的測(cè)試項(xiàng)目,使用Android Studio創(chuàng)建
-
2>济蝉、添加相關(guān)的依賴
-
修改Android項(xiàng)目中的settings.gradle文件:
include ':app' rootProject.name = "testdemoandroid" setBinding(new Binding([gradle: this])) // new evaluate(new File( // new settingsDir.parentFile, // new '../my_flutter/my_flutter/.android/include_flutter.groovy' // new ))
提示:
File()
后面的路徑是my_flutter
項(xiàng)目的路徑,我放置的和上面iOS那個(gè)圖一樣 -
我們需要在Android項(xiàng)目工程的build.gradle中添加依賴:
dependencies { implementation project(':flutter') }
編譯代碼菠发,可能會(huì)出現(xiàn)如下錯(cuò)誤: 1王滤、這是因?yàn)閺腏ava8開(kāi)始才支持接口方法;2滓鸠、Flutter Android引擎使用了該Java8的新特性
解決辦法:通過(guò)設(shè)置Android項(xiàng)目工程的build.gradle配置使用Java8編譯:
compileOptions { sourceCompatibility 1.8 targetCompatibility 1.8 }
接下來(lái)雁乡,我們這里嘗試添加一個(gè)Flutter的screen到Android應(yīng)用程序中
Flutter提供了一個(gè)FlutterActivity來(lái)展示Flutter界面在Android應(yīng)用程序中,我們需要先對(duì)FlutterActivity進(jìn)行注冊(cè):-
在AndroidManifest.xml中進(jìn)行注冊(cè)
<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.jk.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) ); } }
也可以在創(chuàng)建時(shí)踱稍,傳入默認(rèn)的路由:
package com.jk.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) ); } }
-
2.3.2、Kotlin代碼
package com.jk.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) ) } }
也可以在創(chuàng)建時(shí)指定路由:
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模塊調(diào)試
一旦將Flutter模塊繼承到你的項(xiàng)目中珠月,并且使用Flutter平臺(tái)的API運(yùn)行Flutter引擎或UI,那么就可以先普通的Android或者iOS一樣來(lái)構(gòu)建自己的Android或者iOS項(xiàng)目了
但是Flutter的有一個(gè)非常大的優(yōu)勢(shì)是其快速開(kāi)發(fā)锌钮,也就是hot reload桥温。
那么對(duì)應(yīng)Flutter模塊,我們?nèi)绾问褂胔ot reload加速我們的調(diào)試速度呢梁丘?
-
可以使用
flutter attach
# --app-id是指定哪一個(gè)應(yīng)用程序 # -d是指定連接哪一個(gè)設(shè)備 flutter attach --app-id com.coderwhy.ios-my-test -d 3D7A877C-B0DD-4871-8D6E-0C5263B986CD