Flutter Plugin簡單開發

個人博客:
http://www.milovetingting.cn

新建項目

image-20230302134808397.png
image-20230302135317486.png

項目結構

image-20230302135906299.png

創建完成后的目錄如圖所示,其中example是測試工程,用來測試我們寫的插件。lib目錄下的文件,就是需要具體實現的。

flutter_plugin_platform_interface.dart文件就是我們定義接口的地方,flutter_plugin_method_channel.dart是對應AndoidIOS的文件,flutter_plugin_web.dart是對應web平臺。

方法實現

AndroidIOS平臺要分別實現flutter_plugin_platform_interface.dart定義的方法。這里以AndroidWeb為例,實現接口中的方法。

Android端

1、在android目錄上點擊右鍵,選擇Flutter菜單下的Open Android module in Android Studio

3381990-64a2e818780d689a.png

2、打開后的界面如下

image-20230302141536195.png

我們主要在FlutterPlugin這個文件的onMethodCall方法中做具體實現

無參方法的調用

1、在flutter_plugin_platform_interface.dart類中增加方法

Future<String?> hello(){
  throw UnimplementedError('hello() has not been implemented.');
}

2、在flutter_plugin_method_channel.dart類中實現上面的方法

@override
Future<String?> hello() async {
  final msg = await methodChannel.invokeMethod<String>('hello');
  return msg;
}

3、在flutter_plugin.dart中調用

Future<String?> hello() {
  return FlutterPluginPlatform.instance.hello();
}

4、Android端實現

FlutterPlugin.kt

override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
  when (call.method) {
    "getPlatformVersion" -> {
      result.success("Android ${android.os.Build.VERSION.RELEASE}")
    }
    "hello" -> {
      result.success("Android invoke==>hello()")
    }
    else -> {
      result.notImplemented()
    }
  }
}

5、在Example中測試

main.dart

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:async';

import 'package:flutter/services.dart';
import 'package:flutter_plugin/flutter_plugin.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  String _platformVersion = 'Unknown';
  String? _msg;
  final _flutterPlugin = FlutterPlugin();

  @override
  void initState() {
    super.initState();
    initPlatformState();
  }

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initPlatformState() async {
    String platformVersion;
    // Platform messages may fail, so we use a try/catch PlatformException.
    // We also handle the message potentially returning null.
    try {
      platformVersion = await _flutterPlugin.getPlatformVersion() ?? 'Unknown platform version';
    } on PlatformException {
      platformVersion = 'Failed to get platform version.';
    }

    // If the widget was removed from the tree while the asynchronous platform
    // message was in flight, we want to discard the reply rather than calling
    // setState to update our non-existent appearance.
    if (!mounted) return;

    setState(() {
      _platformVersion = platformVersion;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Center(
          child: Column(
            children: [
              Text('Running on: $_platformVersion\n'),
              Text('msg: ${_msg ?? ""}\n'),
              ElevatedButton(
                onPressed: () async {
                  var msg = await _flutterPlugin.hello();
                  if (kDebugMode) {
                    print('msg from android:$msg');
                    setState(() {
                      _msg = msg;
                    });
                  }
                },
                child: const Text("調用hello")),
            ],
          ),
        ),
      ),
    );
  }
}

6、測試結果

image-20230302142948784.png

可以看出,成功調用到了Android端的方法

有參方法的調用

1、在flutter_plugin_platform_interface.dart類中增加方法

Future<String?> hi(String message){
  throw UnimplementedError('hi() has not been implemented.');
}

2、在flutter_plugin_method_channel.dart類中實現上面的方法

@override
Future<String?> hi(String message) async {
  Map<String, dynamic> param = <String, dynamic>{};
  param["message"] = message;
  final msg = await methodChannel.invokeMethod<String>('hi', param);
  return msg;
}

3、在flutter_plugin.dart中調用

Future<String?> hi(String message) {
  return FlutterPluginPlatform.instance.hi(message);
}

4、Android端實現

FlutterPlugin.kt

override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
  when (call.method) {
    "getPlatformVersion" -> {
      result.success("Android ${android.os.Build.VERSION.RELEASE}")
    }
    "hello" -> {
      result.success("Android invoke==>hello()")
    }
    "hi" -> {
      val param = call.arguments as Map<String, String>
      result.success("Android invoke==>hi(${param["message"]})")
    }
    else -> {
      result.notImplemented()
    }
  }
}

5、在Example中測試

main.dart

@override
Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
      appBar: AppBar(
        title: const Text('Plugin example app'),
      ),
      body: Center(
        child: Column(
          children: [
            Text('Running on: $_platformVersion\n'),
            Text('msg: ${_msg ?? ""}\n'),
            ElevatedButton(
              onPressed: () async {
                var msg = await _flutterPlugin.hello();
                if (kDebugMode) {
                  print('msg from android:$msg');
                  setState(() {
                    _msg = msg;
                  });
                }
              },
              child: const Text("調用hello")),
            ElevatedButton(
              onPressed: () async {
                var msg = await _flutterPlugin.hi("hi");
                if (kDebugMode) {
                  print('msg from android:$msg');
                  setState(() {
                    _msg = msg;
                  });
                }
              },
              child: const Text("調用hi(msg)")),
          ],
        ),
      ),
    ),
  );
}
Map參數方法調用

1、在flutter_plugin_platform_interface.dart類中增加方法

Future<String?> hey(String message){
  throw UnimplementedError('hey() has not been implemented.');
}

2、在flutter_plugin_method_channel.dart類中實現上面的方法

@override
Future<String?> hey(String message) async {
  Map<String, dynamic> param = <String, dynamic>{};
  param["message"] = message;
  final msg =
    await methodChannel.invokeMethod<Map<dynamic, dynamic>>('hey', param);
  return msg?["message"];
}

3、在flutter_plugin.dart中調用

Future<String?> hey(String message) {
  return FlutterPluginPlatform.instance.hey(message);
}

4、Android端實現

FlutterPlugin.kt

override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
  when (call.method) {
    "getPlatformVersion" -> {
      result.success("Android ${android.os.Build.VERSION.RELEASE}")
    }
    "hello" -> {
      result.success("Android invoke==>hello()")
    }
    "hi" -> {
      val param = call.arguments as Map<String, String>
      result.success("Android invoke==>hi(${param["message"]})")
    }
    "hey" -> {
      val param = call.arguments as Map<String, String>
      val response = mutableMapOf<String, String>()
      response["message"] = "Android invoke==>hey(${param["message"]})"
      result.success(response)
    }
    else -> {
      result.notImplemented()
    }
  }
}

5、在Example中測試

main.dart

@override
Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
      appBar: AppBar(
        title: const Text('Plugin example app'),
      ),
      body: Center(
        child: Column(
          children: [
            Text('Running on: $_platformVersion\n'),
            Text('msg: ${_msg ?? ""}\n'),
            ElevatedButton(
              onPressed: () async {
                var msg = await _flutterPlugin.hello();
                if (kDebugMode) {
                  print('msg from android:$msg');
                  setState(() {
                    _msg = msg;
                  });
                }
              },
              child: const Text("調用hello")),
            ElevatedButton(
              onPressed: () async {
                var msg = await _flutterPlugin.hi("hi");
                if (kDebugMode) {
                  print('msg from android:$msg');
                  setState(() {
                    _msg = msg;
                  });
                }
              },
              child: const Text("調用hi(msg)")),
            ElevatedButton(
              onPressed: () async {
                var msg = await _flutterPlugin.hey("hey");
                if (kDebugMode) {
                  print('msg from android:$msg');
                  setState(() {
                    _msg = msg;
                  });
                }
              },
              child: const Text("調用hey(msg)")),
          ],
        ),
      ),
    ),
  );
}
事件處理

1、在flutter_plugin_platform_interface.dart類中增加方法

Future<String?> event(){
  throw UnimplementedError('event() has not been implemented.');
}

2、在flutter_plugin_method_channel.dart類中實現上面的方法

late StreamSubscription<dynamic> _eventSubscription;

MethodChannelFlutterPlugin() {
  _initEvent();
}

void _initEvent() {
  _eventSubscription = _eventChannel()
    .receiveBroadcastStream()
    .listen((event) {
      final Map<dynamic,dynamic> map = event;
      switch(map["event"]){
        case "demoEvent":{
          String message = map["message"];
          if (kDebugMode) {
            print("demo event:$message");
          }
        }
      }
    }, onDone: () {}, onError: (err) {
      final PlatformException e = err;
      throw e;
    });
}

EventChannel _eventChannel() {
  return const EventChannel("flutter_plugin_demo_event");
}

@override
Future<String?> event() async{
  final msg =
    await methodChannel.invokeMethod<Map<dynamic, dynamic>>('event');
  return msg?["message"];
}

3、在flutter_plugin.dart中調用

Future<String?> event() {
  return FlutterPluginPlatform.instance.event();
}

4、Android端實現

FlutterPlugin.kt

// 事件派發對象
private var eventSink: (EventChannel.EventSink)? = null

// 事件流
private val streamHandler = object : EventChannel.StreamHandler {
  override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
    eventSink = events
  }

  override fun onCancel(arguments: Any?) {
    eventSink = null
  }

}

override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
  channel = MethodChannel(flutterPluginBinding.binaryMessenger, "flutter_plugin_demo")
  channel.setMethodCallHandler(this)

  //初始化事件
  val eventChannel =
  EventChannel(flutterPluginBinding.binaryMessenger, "flutter_plugin_demo_event")
  eventChannel.setStreamHandler(streamHandler)
}

override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
  when (call.method) {
    "getPlatformVersion" -> {
      result.success("Android ${android.os.Build.VERSION.RELEASE}")
    }
    "hello" -> {
      result.success("Android invoke==>hello()")
    }
    "hi" -> {
      val param = call.arguments as Map<String, String>
      result.success("Android invoke==>hi(${param["message"]})")
    }
    "hey" -> {
      val param = call.arguments as Map<String, String>
      val response = mutableMapOf<String, String>()
      response["message"] = "Android invoke==>hey(${param["message"]})"
      result.success(response)
    }
    "event" -> {
      if (eventSink != null) {
        val response = mutableMapOf<String, String>()
        response["event"] = "demoEvent"
        response["message"] = "Android invoke==>event"
        eventSink?.success(response)
      }
      val response = mutableMapOf<String, String>()
      response["message"] = "Android invoke==>event"
      result.success(response)
    }
    else -> {
      result.notImplemented()
    }
  }
}

5、在Example中測試

main.dart

@override
Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
      appBar: AppBar(
        title: const Text('Plugin example app'),
      ),
      body: Center(
        child: Column(
          children: [
            Text('Running on: $_platformVersion\n'),
            Text('msg: ${_msg ?? ""}\n'),
            ElevatedButton(
              onPressed: () async {
                var msg = await _flutterPlugin.hello();
                if (kDebugMode) {
                  print('msg from android:$msg');
                  setState(() {
                    _msg = msg;
                  });
                }
              },
              child: const Text("調用hello")),
            ElevatedButton(
              onPressed: () async {
                var msg = await _flutterPlugin.hi("hi");
                if (kDebugMode) {
                  print('msg from android:$msg');
                  setState(() {
                    _msg = msg;
                  });
                }
              },
              child: const Text("調用hi(msg)")),
            ElevatedButton(
              onPressed: () async {
                var msg = await _flutterPlugin.hey("hey");
                if (kDebugMode) {
                  print('msg from android:$msg');
                  setState(() {
                    _msg = msg;
                  });
                }
              },
              child: const Text("調用hey(msg)")),
            ElevatedButton(
              onPressed: () async {
                var msg = await _flutterPlugin.event();
                if (kDebugMode) {
                  print('msg from android:$msg');
                  setState(() {
                    _msg = msg;
                  });
                }
              },
              child: const Text("調用event()")),
          ],
        ),
      ),
    ),
  );
}

Web端

Alert

1、在flutter_plugin_platform_interface.dart類中增加方法

Future<String?> alert(String message){
  throw UnimplementedError('alert() has not been implemented.');
}

2、在項目根目錄下新增assets文件夾,然后增加plugin.css,plugin.js文件,在plugin.js文件里加入以下代碼

function showAlert(msg) {
  alert(msg);
}

3、在pubspec.yaml中增加以下配置

  assets:
    - assets/

4、在flutter_plugin_web.dart類中實現上面的方法

FlutterPluginWeb(){
  injectCssAndJSLibraries();
}

@override
Future<String?> alert(String message) {
  _showAlert(msg: message);
  return Future(() => message);
}

/// [injectCssAndJSLibraries] which add the JS and CSS files into DOM
Future<void> injectCssAndJSLibraries() async {
  final List<Future<void>> loading = <Future<void>>[];
  final List<html.HtmlElement> tags = <html.HtmlElement>[];

  final html.LinkElement css = html.LinkElement()
    ..id = 'plugin-css'
    ..attributes = {"rel": "stylesheet"}
  ..href = 'assets/packages/flutter_plugin/assets/plugin.css';
  tags.add(css);

  final html.ScriptElement script = html.ScriptElement()
    ..async = true
    ..src = "assets/packages/flutter_plugin/assets/plugin.js";
  loading.add(script.onLoad.first);
  tags.add(script);
  html.querySelector('head')!.children.addAll(tags);

  await Future.wait(loading);
}

_showAlert({String msg = ""}) {
  _insertAlertJs(msg);
}

_insertAlertJs(String msg) {
  String m = msg.replaceAll("'", "\\'").replaceAll("\n", "<br />");
  html.Element? ele = html.querySelector("#alert-js");
  String content = """
          showAlert('$m');
        """;
  if (html.querySelector("#alert-js") != null) {
    ele!.remove();
  }
  final html.ScriptElement scriptText = html.ScriptElement()
    ..id = "alert-js"
    ..innerHtml = content;
  html.querySelector('head')!.children.add(scriptText);
}

5、在flutter_plugin.dart中調用

Future<String?> alert(String message) {
  return FlutterPluginPlatform.instance.alert(message);
}

6、在Example中測試

main.dart

@override
Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
      appBar: AppBar(
        title: const Text('Plugin example app'),
      ),
      body: Center(
        child: Column(
          children: [
            //...
            ElevatedButton(
              onPressed: () async {
                var msg =
                  await _flutterPlugin.alert("hello,web!");
                if (kDebugMode) {
                  print('msg from web:$msg');
                  setState(() {
                    _msg = msg;
                  });
                }
              },
              child: const Text("調用alert()")),
          ],
        ),
      ),
    ),
  );
}
Copy

1、在flutter_plugin_platform_interface.dart類中增加方法

Future<bool?> copy(String message){
  throw UnimplementedError('copy() has not been implemented.');
}

2、在plugin.js文件里加入以下代碼

function copy(msg) {
  const input = document.getElementById('input_cp');
  // 選擇需要復制的文本
  input.focus();
  if (input.setSelectionRange) {
    input.setSelectionRange(0, input.value.length);
  } else {
    input.select();
  }
  try {
    const result = document.execCommand('copy');
  } catch (e) {
    console.err("復制失敗,請重試~");
  }
  // 讓輸入框失去焦點
  input.blur();
  // 讓移動端的輸入鍵盤自動收回
  document.activeElement.blur();
}

3、在plugin.css文件中加入以下代碼

.main {
  position: relative;
}

.input_wrap {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  opacity: 0;
  overflow: hidden;
  user-select: none;
}

.input_wrap input {
  width: 1px;
  resize: none;
  border: none;
  outline: none;
  user-select: none;
  color: transparent;
  background: transparent;
}

4、在flutter_plugin_web.dart類中實現上面的方法

@override
Future<bool?> copy(String message) {
  _copy(msg: message);
  return Future(() => true);
}

_copy({String msg = ""}) {
  _insertCopyHtml(msg);
  _insertCopyJs(msg);
}

_insertCopyHtml(String msg) {
  String m = msg.replaceAll("'", "\\'").replaceAll("\n", "<br />");
  html.Element? ele = html.querySelector("#copy-html");
  String content = """
     <div class="input_wrap">
        <input id="input_cp" type="text" readonly="true" value='$m'>
     </div>
        """;
  if (html.querySelector("#copy-html") != null) {
    ele!.remove();
  }
  final html.BodyElement scriptText = html.BodyElement()
    ..id = "copy-html"
    ..innerHtml = content;
  html.querySelector('body')!.children.add(scriptText);
}

_insertCopyJs(String msg) {
  String m = msg.replaceAll("'", "\\'").replaceAll("\n", "<br />");
  html.Element? ele = html.querySelector("#copy-js");
  String content = """
          copy('$m');
        """;
  if (html.querySelector("#copy-js") != null) {
    ele!.remove();
  }
  final html.ScriptElement scriptText = html.ScriptElement()
    ..id = "copy-js"
    ..innerHtml = content;
  html.querySelector('head')!.children.add(scriptText);
}

5、在flutter_plugin.dart中調用

Future<bool?> copy(String message) {
  return FlutterPluginDemoPlatform.instance.copy(message);
}

6、在Example中測試

main.dart

@override
Widget build(BuildContext context) {
  return MaterialApp(
    home: Scaffold(
      appBar: AppBar(
        title: const Text('Plugin example app'),
      ),
      body: Center(
        child: Column(
          children: [
            //...
            ElevatedButton(
              onPressed: () async {
                var result =
                  await _flutterPlugin.copy("hello,web!!!");
                if (kDebugMode) {
                  print('copy result:$result');
                  setState(() {
                    _msg = "copy result:$result";
                  });
                }
              },
              child: const Text("調用copy()"))
          ],
        ),
      ),
    ),
  );
}
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,401評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,069評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,635評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,128評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,365評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,881評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,733評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,935評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,172評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,595評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,908評論 2 372

推薦閱讀更多精彩內容