亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Android開發(fā)Flutter?桌面應(yīng)用窗口化實(shí)戰(zhàn)示例

 更新時(shí)間:2022年09月27日 14:30:33   作者:Karl_wei  
這篇文章主要為大家介紹了Android開發(fā)Flutter?桌面應(yīng)用窗口化實(shí)戰(zhàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

通過(guò)此篇文章,你可以編寫出一個(gè)完整桌面應(yīng)用的窗口框架。

你將了解到:

  • Flutter在開發(fā)windows和Android桌面應(yīng)用初始階段,應(yīng)用窗口的常規(guī)配置;
  • windows平臺(tái)特定交互的實(shí)現(xiàn),如:執(zhí)行控制臺(tái)指令,windows注冊(cè)表,應(yīng)用單例等;
  • 桌面應(yīng)用的交互習(xí)慣,如:交互點(diǎn)擊態(tài),不同大小的頁(yè)面切換,獲取系統(tǒng)喚起應(yīng)用的參數(shù)等。

在使用Flutter開發(fā)桌面應(yīng)用之前,筆者之前都是開發(fā)移動(dòng)App的,對(duì)于移動(dòng)應(yīng)用的交互比較熟悉。開始桌面應(yīng)用開發(fā)后,我發(fā)現(xiàn)除了技術(shù)棧一樣之外,其他交互細(xì)節(jié)、用戶行為習(xí)慣以及操作系統(tǒng)特性等都有很大的不同。

我將在windows和android桌面設(shè)備上,從0到1親自搭建一個(gè)開源項(xiàng)目,并且記錄實(shí)現(xiàn)細(xì)節(jié)和技術(shù)難點(diǎn)。

一、應(yīng)用窗口的常規(guī)配置

眾所周知,F(xiàn)lutter目前最大的應(yīng)用是在移動(dòng)app上,在移動(dòng)設(shè)備上都是以全屏方式展示,因此沒(méi)有應(yīng)用窗口這個(gè)概念。而桌面應(yīng)用是窗口化的,需求方一般都會(huì)對(duì)窗口外觀有很高的要求,比如:自定義窗口導(dǎo)航欄、設(shè)置圓角、陰影;同時(shí)還有可能要禁止系統(tǒng)自動(dòng)放大的行為。

應(yīng)用窗口化

Flutter在windows桌面平臺(tái),是依托于Win32Window承載engine的,而Win32Windows本身就是窗口化的,無(wú)需再做過(guò)多的配置。(不過(guò)也正因?yàn)橐劳性翱?,作為UI框架的flutter完全沒(méi)辦法對(duì)Win32Window的外觀做任何配置)

// win32_window.cpp
bool Win32Window::CreateAndShow(const std::wstring& title,
                                const Point& origin,
                                const Size& size) {
 // ...此處省略代碼...
 // 這里創(chuàng)建了win32接口的句柄
  HWND window = CreateWindow(
      window_class, title.c_str(), WS_POPUP | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,
      Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
      Scale(size.width, scale_factor), Scale(size.height, scale_factor),
      nullptr, nullptr, GetModuleHandle(nullptr), this);
  UpdateWindow(window);
  if (!window) {
    return false;
  }
  return OnCreate();
}
bool FlutterWindow::OnCreate() {
  if (!Win32Window::OnCreate()) {
    return false;
  }
  // GetClientArea獲取創(chuàng)建的win32Window區(qū)域
  RECT frame = GetClientArea();
  // 綁定窗口和flutter engine
  flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
      frame.right - frame.left, frame.bottom - frame.top, project_);
  if (!flutter_controller_->engine() || !flutter_controller_->view()) {
    return false;
  }
  RegisterPlugins(flutter_controller_->engine());
  SetChildContent(flutter_controller_->view()->GetNativeWindow());
  return true;
}

應(yīng)用窗口化主要是針對(duì)Android平臺(tái),F(xiàn)lutter應(yīng)用是依托于Activity的,Android平臺(tái)上Activity默認(rèn)是全屏,且出于安全考慮,當(dāng)一個(gè)Activity展示的時(shí)候,是不允許用戶穿透點(diǎn)擊的。所以想要讓Flutter應(yīng)用在Android大屏桌面設(shè)備上展示出windows上的效果,需要以下步驟:

  • 將底層承載的FlutterActivity的主題樣式設(shè)置為Dialog,同時(shí)全屏窗口的背景色設(shè)置為透明,點(diǎn)擊時(shí)Dialog不消失;
<!-- android/app/src/main/res/values/styles.xml -->
<style name="Theme.DialogApp" parent="Theme.AppCompat.Light.Dialog"> 
    <item name="android:windowBackground">@drawable/launch_application</item> 
    <item name="android:windowIsTranslucent">true</item> 
    <item name="android:windowContentOverlay">@null</item> 
    <item name="android:backgroundDimEnabled">false</item> 
    <item name="windowActionBar">false</item> 
    <item name="windowNoTitle">true</item> 
</style>
<!-- android/app/src/main/AndroidManifest.xml -->
<activity
android:name=".MainActivity"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:theme="@style/Theme.DialogApp"
android:windowSoftInputMode="adjustResize"> 
    <meta-data
        android:name="io.flutter.embedding.android.NormalTheme"
        android:resource="@style/Theme.DialogApp" /> 
    <intent-filter> 
        <action android:name="android.intent.action.MAIN" /> 
        <category android:name="android.intent.category.LAUNCHER" /> 
    </intent-filter>
</activity>
// android/app/src/main/kotlin/com/maxhub/upgrade_assistant/MainActivity.kt
class MainActivity : FlutterActivity() {
    override fun getTransparencyMode(): TransparencyMode {
        // 設(shè)置窗口背景透明
        return TransparencyMode.transparent
    }
    override fun onResume() {
        super.onResume()
        setFinishOnTouchOutside(false) // 點(diǎn)擊外部,dialog不消失
        // 設(shè)置窗口全屏
        var lp = window.attributes
        lp.width = -1
        lp.height = -1
        window.attributes = lp
    }
}
  • 至此Android提供了一個(gè)全屏的透明窗口,F(xiàn)lutter runApp的時(shí)候,我在MaterialApp外層套了一個(gè)盒子控件,這個(gè)控件內(nèi)部主要做邊距、陰影等一系列窗口化行為。
class GlobalBoxManager extends StatelessWidget {
  GlobalBoxManager({Key? key, required this.child}) : super(key: key);
  final Widget child;
  @override
  Widget build(BuildContext context) {
    return Container(
        width: ScreenUtil().screenWidth,
        height: ScreenUtil().screenHeight,
        // android偽全屏,加入邊距
        padding: EdgeInsets.symmetric(horizontal: 374.w, vertical: 173.h),
        child: child,
    );
  }
}
// MyApp下的build構(gòu)造方法
GlobalBoxManager(
  child: GetMaterialApp(
    locale: Get.deviceLocale,
    translations: Internationalization(),
    // 桌面應(yīng)用的頁(yè)面跳轉(zhuǎn)習(xí)慣是無(wú)動(dòng)畫的,符合用戶習(xí)慣
    defaultTransition: Transition.noTransition,
    transitionDuration: Duration.zero,
    theme: lightTheme,
    darkTheme: darkTheme,
    initialRoute: initialRoute,
    getPages: RouteConfig.getPages,
    title: 'appName'.tr,
  ),
),
  • 效果圖

自定義窗口導(dǎo)航欄

主要針對(duì)Windows平臺(tái),原因上面我們解析過(guò):win32Window是在windows目錄下的模板代碼創(chuàng)建的默認(rèn)是帶系統(tǒng)導(dǎo)航欄的(如下圖)。

很遺憾Flutter官方也沒(méi)有提供方法,pub庫(kù)上對(duì)窗口操作支持的最好的是window_manager,由國(guó)內(nèi)Flutter桌面開源社區(qū)leanFlutter所提供。

  • yaml導(dǎo)入window_manager,在runApp之前執(zhí)行以下代碼,把win32窗口的導(dǎo)航欄去掉,同時(shí)配置背景色為透明、居中顯示;
dependencies:
  flutter:
    sdk: flutter
  window_manager: ^0.2.6
// runApp之前運(yùn)行
WindowManager w = WindowManager.instance;
await w.ensureInitialized();
WindowOptions windowOptions = WindowOptions(
  size: normalWindowSize,
  center: true,
  titleBarStyle: TitleBarStyle.hidden // 該屬性隱藏導(dǎo)航欄
);
w.waitUntilReadyToShow(windowOptions, () async {
  await w.setBackgroundColor(Colors.transparent);
  await w.show();
  await w.focus();
  await w.setAsFrameless();
});
  • 此時(shí)會(huì)發(fā)現(xiàn)應(yīng)用打開時(shí)在左下角閃一下再居中。這是由于原生win32窗口默認(rèn)是左上角顯示,而后在flutter通過(guò)插件才居中;
  • 處理方式建議在原生代碼中先把窗口設(shè)為默認(rèn)不顯示,通過(guò)上面的window_manager.show()展示出來(lái);
// windows/runner/win32_window.cpp
HWND window = CreateWindow(
    // 去除WS_VISIBLE屬性
    window_class, title.c_str(), WS_OVERLAPPEDWINDOW,
    Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
    Scale(size.width, scale_factor), Scale(size.height, scale_factor),
    nullptr, nullptr, GetModuleHandle(nullptr), this);

美化應(yīng)用窗口

通過(guò)前面的步驟,我們?cè)赼ndroid和windows平臺(tái)上都得到了一個(gè)安全透明的窗口,接下來(lái)的修飾Flutter就可以為所欲為了。

  • 窗口陰影、圓角

上面介紹過(guò)在MaterialApp外套有盒子控件,直接在Container內(nèi)加入陰影和圓角即可,不過(guò)Android和桌面平臺(tái)還是需要區(qū)分下的;

import 'dart:io';
import 'package:flutter/material.dart';
class GlobalBoxManager extends StatelessWidget {
  const GlobalBoxManager({Key? key, required this.child}) : super(key: key);
  final Widget child;
  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      height: double.infinity,
      // android偽全屏,加入邊距
      padding: Platform.isAndroid
          ? const EdgeInsets.symmetric(horizontal: 374, vertical: 173)
          : EdgeInsets.zero,
      child: Container(
        clipBehavior: Clip.antiAliasWithSaveLayer,
        margin: const EdgeInsets.all(10),
        decoration: const BoxDecoration(
            borderRadius: BorderRadius.all(Radius.circular(8)),
            boxShadow: [
              BoxShadow(color: Color(0x33000000), blurRadius: 8),
            ]),
        child: child,
      ),
    );
  }
}

  • 自定義導(dǎo)航欄

回歸Scaffold的AppBar配置,再加上導(dǎo)航拖拽窗口事件(僅windows可拖拽)

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: PreferredSize(
      preferredSize: const Size.fromHeight(64),
      child: GestureDetector(
        behavior: HitTestBehavior.translucent,
        onPanStart: (details) {
          if (Platform.isWindows) windowManager.startDragging();
        },
        onDoubleTap: () {},
        child: AppBar(
          title: Text(widget.title),
          centerTitle: true,
          actions: [
            GestureDetector(
              behavior: HitTestBehavior.opaque,
              child: const Padding(
                padding: EdgeInsets.symmetric(horizontal: 16),
                child: Icon(
                  Icons.close,
                  size: 24,
                ),
              ),
            ),
          ],
        ),
      ),
    ),
    body: Center(),
  );
}

到這里多平臺(tái)的窗口就配置好了,接下來(lái)可以愉快的編寫頁(yè)面啦。

可能有些小伙伴會(huì)說(shuō):窗口的效果本就應(yīng)該由原生去寫,為啥要讓Flutter去做這么多的事情?

答案很簡(jiǎn)單:

跨平臺(tái)! 要跨平臺(tái)就勢(shì)必需要繞一些,通過(guò)這種方式你會(huì)發(fā)現(xiàn)任何平臺(tái)的應(yīng)用,都可以得到相同效果的窗口,而代碼只需要Flutter寫一次,這才是Flutter存在的真正意義。

二、windows平臺(tái)特定交互

在開發(fā)windows的過(guò)程中,我發(fā)現(xiàn)跟移動(dòng)app最大的不同在于:桌面應(yīng)用需要頻繁的去與系統(tǒng)做一些交互。

注冊(cè)表操作

應(yīng)用開發(fā)過(guò)程中,經(jīng)常需要通過(guò)注冊(cè)表來(lái)做數(shù)據(jù)存儲(chǔ);在pub上也有一個(gè)庫(kù)提供這個(gè)能力,但是我沒(méi)有使用,因?yàn)?strong>dart已經(jīng)提供了win32相關(guān)的接口,我認(rèn)為這個(gè)基礎(chǔ)的能力沒(méi)必要引用多一個(gè)庫(kù),所以手?jǐn)]了一個(gè)工具類來(lái)操作注冊(cè)表。(值得注意的是部分注冊(cè)表的操作是需要管理員權(quán)限的,所以應(yīng)用提權(quán)要做好)

import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:win32/win32.dart';
const maxItemLength= 2048;
class RegistryKeyValuePair {
  final String key;
  final String value;
  const RegistryKeyValuePair(this.key, this.value);
}
class RegistryUtil {
  /// 根據(jù)鍵名獲取注冊(cè)表的值
  static String? getRegeditForKey(String regPath, String key,
      {int hKeyValue = HKEY_LOCAL_MACHINE}) {
    var res = getRegedit(regPath, hKeyValue: hKeyValue);
    return res[key];
  }
  /// 設(shè)置注冊(cè)表值
  static setRegeditValue(String regPath, String key, String value,
      {int hKeyValue = HKEY_CURRENT_USER}) {
    final phKey = calloc<HANDLE>();
    final lpKeyPath = regPath.toNativeUtf16();
    final lpKey = key.toNativeUtf16();
    final lpValue = value.toNativeUtf16();
    try {
      if (RegSetKeyValue(hKeyValue, lpKeyPath, lpKey, REG_SZ, lpValue,
              lpValue.length * 2) !=
          ERROR_SUCCESS) {
        throw Exception("Can't set registry key");
      }
      return phKey.value;
    } finally {
      free(phKey);
      free(lpKeyPath);
      free(lpKey);
      free(lpValue);
      RegCloseKey(HKEY_CURRENT_USER);
    }
  }
  /// 獲取注冊(cè)表所有子項(xiàng)
  static List<String>? getRegeditKeys(String regPath,
      {int hKeyValue = HKEY_LOCAL_MACHINE}) {
    final hKey = _getRegistryKeyHandle(hKeyValue, regPath);
    var dwIndex = 0;
    String? key;
    List<String>? keysList;
    key = _enumerateKeyList(hKey, dwIndex);
    while (key != null) {
      keysList ??= [];
      keysList.add(key);
      dwIndex++;
      key = _enumerateKeyList(hKey, dwIndex);
    }
    RegCloseKey(hKey);
    return keysList;
  }
  /// 刪除注冊(cè)表的子項(xiàng)
  static bool deleteRegistryKey(String regPath, String subPath,
      {int hKeyValue = HKEY_LOCAL_MACHINE}) {
    final subKeyForPath = subPath.toNativeUtf16();
    final hKey = _getRegistryKeyHandle(hKeyValue, regPath);
    try {
      final status = RegDeleteKey(hKey, subKeyForPath);
      switch (status) {
        case ERROR_SUCCESS:
          return true;
        case ERROR_MORE_DATA:
          throw Exception('An item required more than $maxItemLength bytes.');
        case ERROR_NO_MORE_ITEMS:
          return false;
        default:
          throw Exception('unknown error');
      }
    } finally {
      RegCloseKey(hKey);
      free(subKeyForPath);
    }
  }
  /// 根據(jù)項(xiàng)的路徑獲取所有值
  static Map<String, String> getRegedit(String regPath,
      {int hKeyValue = HKEY_CURRENT_USER}) {
    final hKey = _getRegistryKeyHandle(hKeyValue, regPath);
    final Map<String, String> portsList = <String, String>{};
    /// The index of the value to be retrieved.
    var dwIndex = 0;
    RegistryKeyValuePair? item;
    item = _enumerateKey(hKey, dwIndex);
    while (item != null) {
      portsList[item.key] = item.value;
      dwIndex++;
      item = _enumerateKey(hKey, dwIndex);
    }
    RegCloseKey(hKey);
    return portsList;
  }
  static int _getRegistryKeyHandle(int hive, String key) {
    final phKey = calloc<HANDLE>();
    final lpKeyPath = key.toNativeUtf16();
    try {
      final res = RegOpenKeyEx(hive, lpKeyPath, 0, KEY_READ, phKey);
      if (res != ERROR_SUCCESS) {
        throw Exception("Can't open registry key");
      }
      return phKey.value;
    } finally {
      free(phKey);
      free(lpKeyPath);
    }
  }
  static RegistryKeyValuePair? _enumerateKey(int hKey, int index) {
    final lpValueName = wsalloc(MAX_PATH);
    final lpcchValueName = calloc<DWORD>()..value = MAX_PATH;
    final lpType = calloc<DWORD>();
    final lpData = calloc<BYTE>(maxItemLength);
    final lpcbData = calloc<DWORD>()..value = maxItemLength;
    try {
      final status = RegEnumValue(hKey, index, lpValueName, lpcchValueName,
          nullptr, lpType, lpData, lpcbData);
      switch (status) {
        case ERROR_SUCCESS:
          {
            // if (lpType.value != REG_SZ) throw Exception('Non-string content.');
            if (lpType.value == REG_DWORD) {
              return RegistryKeyValuePair(lpValueName.toDartString(),
                  lpData.cast<Uint32>().value.toString());
            }
            if (lpType.value == REG_SZ) {
              return RegistryKeyValuePair(lpValueName.toDartString(),
                  lpData.cast<Utf16>().toDartString());
            }
            break;
          }
        case ERROR_MORE_DATA:
          throw Exception('An item required more than $maxItemLength bytes.');
        case ERROR_NO_MORE_ITEMS:
          return null;
        default:
          throw Exception('unknown error');
      }
    } finally {
      free(lpValueName);
      free(lpcchValueName);
      free(lpType);
      free(lpData);
      free(lpcbData);
    }
    return null;
  }
  static String? _enumerateKeyList(int hKey, int index) {
    final lpValueName = wsalloc(MAX_PATH);
    final lpcchValueName = calloc<DWORD>()..value = MAX_PATH;
    try {
      final status = RegEnumKeyEx(hKey, index, lpValueName, lpcchValueName,
          nullptr, nullptr, nullptr, nullptr);
      switch (status) {
        case ERROR_SUCCESS:
          return lpValueName.toDartString();
        case ERROR_MORE_DATA:
          throw Exception('An item required more than $maxItemLength bytes.');
        case ERROR_NO_MORE_ITEMS:
          return null;
        default:
          throw Exception('unknown error');
      }
    } finally {
      free(lpValueName);
      free(lpcchValueName);
    }
  }
}

執(zhí)行控制臺(tái)指令

windows上,我們可以通過(guò)cmd指令做所有事情,dart也提供了這種能力。我們可以通過(guò)io庫(kù)中的Progress類來(lái)運(yùn)行指令。如:幫助用戶打開網(wǎng)絡(luò)連接。

Process.start('ncpa.cpl', [],runInShell: true);

剛接觸桌面開發(fā)的小伙伴,真的很需要這個(gè)知識(shí)點(diǎn)。

實(shí)現(xiàn)應(yīng)用單例

應(yīng)用單例是windows需要特殊處理,android默認(rèn)是單例的。而windows如果不作處理,每次點(diǎn)擊都會(huì)重新運(yùn)行一個(gè)應(yīng)用進(jìn)程,這顯然不合理。Flutter可以通過(guò)windows_single_instance插件來(lái)實(shí)現(xiàn)單例。在runApp之前執(zhí)行下這個(gè)方法,重復(fù)點(diǎn)擊時(shí)會(huì)讓用戶獲得焦點(diǎn)置頂,而不是多開一個(gè)應(yīng)用。

/// windows設(shè)置單實(shí)例啟動(dòng)
static setSingleInstance(List<String> args) async {
  await WindowsSingleInstance.ensureSingleInstance(args, "desktop_open",
      onSecondWindow: (args) async {
    // 喚起并聚焦
    if (await windowManager.isMinimized()) await windowManager.restore();
    windowManager.focus();
  });
}

三、桌面應(yīng)用的交互習(xí)慣

按鈕點(diǎn)擊態(tài)

按鈕點(diǎn)擊交互的狀態(tài),其實(shí)在移動(dòng)端也存在。但不同的是移動(dòng)端的按鈕基本上水波紋的效果就能滿足用戶使用,但是桌面應(yīng)用顯示區(qū)域大,而點(diǎn)擊的鼠標(biāo)卻很小,很多時(shí)候點(diǎn)擊已經(jīng)過(guò)去但水波紋根本就沒(méi)顯示出來(lái)。

正常交互是:點(diǎn)擊按鈕馬上響應(yīng)點(diǎn)擊態(tài)的顏色(文本和背景都能編),松開恢復(fù)。

TextButton(
  clipBehavior: Clip.antiAliasWithSaveLayer,
  style: ButtonStyle(
    animationDuration: Duration.zero, // 動(dòng)畫延時(shí)設(shè)置為0
    visualDensity: VisualDensity.compact,
    overlayColor: MaterialStateProperty.all(Colors.transparent),
    padding: MaterialStateProperty.all(EdgeInsets.zero),
    textStyle:
        MaterialStateProperty.all(Theme.of(context).textTheme.subtitle1),
    // 按鈕按下的時(shí)候的前景色,會(huì)讓文本的顏色按下時(shí)變?yōu)榘咨?
    foregroundColor: MaterialStateProperty.resolveWith((states) {
      return states.contains(MaterialState.pressed)
          ? Colors.white
          : Theme.of(context).toggleableActiveColor;
    }),
    // 按鈕按下的時(shí)候的背景色,會(huì)讓背景按下時(shí)變?yōu)樗{(lán)色
    backgroundColor: MaterialStateProperty.resolveWith((states) {
      return states.contains(MaterialState.pressed)
          ? Theme.of(context).toggleableActiveColor
          : null;
    }),
  ),
  onPressed: null,
  child: XXX),
)

獲取應(yīng)用啟動(dòng)參數(shù)

由于我們的桌面設(shè)備升級(jí)自研的整機(jī),因此在開發(fā)過(guò)程經(jīng)常遇到其他軟件要喚起Flutter應(yīng)用的需求。那么如何喚起,又如何拿到喚起參數(shù)呢?

1. windows:其他應(yīng)用通過(guò)Procress.start啟動(dòng).exe即可運(yùn)行Flutter的軟件;傳參也非常簡(jiǎn)單,直接.exe后面帶參數(shù),多個(gè)參數(shù)使用空格隔開,然后再Flutter main函數(shù)中的args就能拿到參數(shù)的列表,非常方便。

其實(shí)cmd執(zhí)行的參數(shù),是被win32Window接收了,只是Flutter幫我們做了這層轉(zhuǎn)換,通過(guò)engine傳遞給main函數(shù),而Android就沒(méi)那么方便了。

2. Android:Android原生啟動(dòng)應(yīng)用是通過(guò)Intent對(duì)應(yīng)包名下的Activity,然后再Activity中通過(guò)Intent.getExtra可以拿到參數(shù)。我們都知道Android平臺(tái)下Flutter只有一個(gè)Activity,因此做法是先在MainActivity中拿到Intent的參數(shù),然后建立Method Channel通道;

``` kotlin class MainActivity : FlutterActivity() { private var sharedText: String? = null private val channel = "app.open.shared.data"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val intent = intent
        handleSendText(intent) // Handle text being sent
    }
    override fun onRestart() {
        super.onRestart()
        flutterEngine!!.lifecycleChannel.appIsResumed()
    }
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channel)
            .setMethodCallHandler { call: MethodCall, result: MethodChannel.Result ->
                when (call.method) {
                    "getSharedText" -> {
                        result.success(sharedText)
                    }
                }
            }
    }
    private fun handleSendText(intent: Intent) {
        sharedText = intent.getStringExtra("params")
    }
}
```
Flutter層在main函數(shù)中通過(guò)Method Channel的方式取到MainActivity中存儲(chǔ)的參數(shù),繞多了一層鏈路。
```dart
const platform = MethodChannel('app.open.shared.data');
String? sharedData = await platform.invokeMethod('getSharedText');
if (sharedData == null) return null;
return jsonDecode(sharedData);
```

四、寫在最后

通過(guò)上面這么多的實(shí)現(xiàn),我們已經(jīng)完全把一個(gè)應(yīng)用窗體結(jié)構(gòu)搭建起來(lái)了。長(zhǎng)篇幅的實(shí)戰(zhàn)記錄,希望可以切實(shí)的幫助到大家??傮w來(lái)說(shuō),桌面開發(fā)雖然還有很多缺陷,但是能用,性能尚佳,跨平臺(tái)降低成本。

以上就是Android開發(fā)Flutter 桌面應(yīng)用窗口化實(shí)戰(zhàn)示例的詳細(xì)內(nèi)容,更多關(guān)于Android Flutter 桌面應(yīng)用窗口化的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Android自定義view之太極圖的實(shí)現(xiàn)教程

    Android自定義view之太極圖的實(shí)現(xiàn)教程

    這篇文章主要給大家介紹了關(guān)于Android自定義view之太極圖的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01
  • Android冷啟動(dòng)優(yōu)化的3個(gè)小案例分享

    Android冷啟動(dòng)優(yōu)化的3個(gè)小案例分享

    為了提高App的冷啟動(dòng)耗時(shí),除了在常規(guī)的業(yè)務(wù)側(cè)進(jìn)行耗時(shí)代碼優(yōu)化之外,為了進(jìn)一步縮短啟動(dòng)耗時(shí),需要在純技術(shù)測(cè)做一些優(yōu)化探索,本期我們從類預(yù)加載、Retrofit 、ARouter方面進(jìn)行了進(jìn)一步的優(yōu)化,感興趣的同學(xué)跟著小編一起來(lái)看看吧
    2023-07-07
  • PowerManagerService之喚醒鎖的使用獲取創(chuàng)建示例解析

    PowerManagerService之喚醒鎖的使用獲取創(chuàng)建示例解析

    這篇文章主要為大家介紹了PowerManagerService之喚醒鎖的使用獲取創(chuàng)建示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • Android RIL使用詳解

    Android RIL使用詳解

    這篇文章主要介紹了Android RIL使用詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • Android中的指紋識(shí)別demo開發(fā)實(shí)例

    Android中的指紋識(shí)別demo開發(fā)實(shí)例

    這篇文章主要介紹了Android中的指紋識(shí)別demo開發(fā)實(shí)例的相關(guān)知識(shí),非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2016-09-09
  • 詳解android在mob平臺(tái)實(shí)現(xiàn)qq登陸和分享

    詳解android在mob平臺(tái)實(shí)現(xiàn)qq登陸和分享

    這篇文章主要介紹了詳解android在mob平臺(tái)實(shí)現(xiàn)qq登陸和分享,對(duì)接入第三方平臺(tái)SDK感興趣的同學(xué)們,可以參考下
    2021-04-04
  • Android UI設(shè)計(jì)系列之自定義Dialog實(shí)現(xiàn)各種風(fēng)格的對(duì)話框效果(7)

    Android UI設(shè)計(jì)系列之自定義Dialog實(shí)現(xiàn)各種風(fēng)格的對(duì)話框效果(7)

    這篇文章主要介紹了Android UI設(shè)計(jì)系列之自定義Dialog實(shí)現(xiàn)各種風(fēng)格的對(duì)話框效果,具有一定的實(shí)用性和參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-06-06
  • 自定義GridView并且實(shí)現(xiàn)拖拽(附源碼)

    自定義GridView并且實(shí)現(xiàn)拖拽(附源碼)

    本文實(shí)現(xiàn)了GridView的拖拽功能,原理很簡(jiǎn)單只是在交換位置上記錄了X軸的相關(guān)坐標(biāo),計(jì)算了X軸的相關(guān)變量,實(shí)例代碼如下,感興趣的額朋友可以參考下哈
    2013-06-06
  • Android自定義View Flyme6的Viewpager指示器

    Android自定義View Flyme6的Viewpager指示器

    這篇文章主要為大家詳細(xì)介紹了Android自定義View Flyme6的Viewpager指示器,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-01-01
  • android實(shí)現(xiàn)滾動(dòng)文本效果

    android實(shí)現(xiàn)滾動(dòng)文本效果

    這篇文章主要為大家詳細(xì)介紹了android實(shí)現(xiàn)滾動(dòng)文本效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-05-05

最新評(píng)論