Flutter+鸿蒙 | 调用原生模态窗

4 阅读1分钟

相信很多小伙伴都觉得鸿蒙原生的模态窗很好看,但困惑的是其需要绑定组件:

  Button('Open Sheet').width('90%').height('80vp')
  .onClick(() => {
      this.isShowSheet = !this.isShowSheet;
  })
  .bindSheet($$this.isShowSheet, this.SheetBuilder(), {
      detents: [SheetSize.MEDIUM, SheetSize.LARGE, 600],
      preferType: SheetType.BOTTOM,
      // 请将$r('app.string.tSheetBuilder_text2')替换为实际资源文件,在本示例中该资源文件的value值为"嵌套滚动场景"
      title: { title: $r('app.string.tSheetBuilder_text2') },
  })

那是否意味着Flutter开发的程序无法调用?

可以找到Flutter的鸿蒙工程中的Index.ets, 在原来的column背后加上.bindSheet即可。

Column() {
  FlutterPage({ viewId: this.viewId })
}.bindSheet($$this.isShowSheet, this.SheetBuilder(), {
  detents: [SheetSize.LARGE],
  preferType: SheetType.BOTTOM,
  onDisappear: () => {
    AppStorage.setOrCreate<boolean>('isShowSheet', false);
  }
})

也是在这个文件里,在Index这个struct中加上

@StorageLink('isShowSheet') isShowSheet: boolean = false;

private webController: webview.WebviewController = new webview.WebviewController();

@Builder
SheetBuilder() {
  Column() {
    Web({ src: 'https://baidu.com', controller: this.webController })
      .width('100%')
      .height('100%')
      .nestedScroll({
        scrollForward: NestedScrollMode.PARENT_FIRST,
        scrollBackward: NestedScrollMode.SELF_FIRST,
      })
  }
  .width('100%')
  .height('100%')
}

这个builder即可控制控制显示的内容,这里我们显示了一个网页。

到这里,我们的Flutter鸿蒙混合App是可以调用原生的模态窗口了,但需要有调用的地方。于是需要写一个插件:

// entry/src/main/ets/plugin/SheetPlugin.ets
import {
  FlutterPlugin,
  FlutterPluginBinding,
  MethodChannel,
  MethodCallHandler,
  MethodCall,
  MethodResult,
  AbilityPluginBinding,
} from '@ohos/flutter_ohos';
import UIAbility from '@ohos.app.ability.UIAbility';

export class SheetPlugin implements FlutterPlugin, MethodCallHandler {
  private channel: MethodChannel | null = null;
  private ability: UIAbility;

  constructor(ability: UIAbility) {
    this.ability = ability;
  }

  getUniqueClassName(): string {
    return 'SheetPlugin';
  }

  onAttachedToEngine(binding: FlutterPluginBinding): void {
    this.channel = new MethodChannel(
      binding.getBinaryMessenger(),
      'com.xxx/sheet'  // 与 Flutter 侧一致
    );
    this.channel.setMethodCallHandler(this);
  }

  onDetachedFromEngine(binding: FlutterPluginBinding): void {
    this.channel?.setMethodCallHandler(null);
    this.channel = null;
  }

  onMethodCall(call: MethodCall, result: MethodResult): void {
    if (call.method === 'openSheet') {
      this.triggerSheet();
      result.success(null);
    } else {
      result.notImplemented();
    }
  }

  private triggerSheet(): void {
    // 通过 AppStorage 驱动状态变更
    AppStorage.setOrCreate<boolean>('isShowSheet', true);
  }
}

然后在EntryAbility注册插件:

flutterEngine.getPlugins()?.add(new SheetPlugin(this));

最后在Flutter端写调用的代码:

static Future<void> openSheet() async {
  const String channelPath =
      "com.xxx/sheet"; //这儿要与MethodChannel(flutterEngine?.dartExecutor, CHANNEL)中CHANNEL名称一致
  const MethodChannel channel = const MethodChannel(channelPath);
  try {
    await channel.invokeMethod('openSheet');
  } on PlatformException catch (e) {
    print('调用 Sheet 失败: ${e.message}');
  }
}

然后在UI中调用:

GestureDetector(
    onTap: () {
      LaunchUtils.openSheet();
    },
    child: UniversalWidget(context)),

大工告成,假如你也是Flutter+ArkTS混合开发,也来试试吧!