Flutter + 鸿蒙 | 服务卡片开发干货(一)

320 阅读3分钟

主要解决问题

  • Flutter中如何把数据传递到原生侧再传递给服务卡片 FormKit?(这篇)

  • 如何响应服务卡片的一些特定指令如快捷跳转到特定的Flutter界面?(第二篇)

  • Flutter应用内如何将服务卡片添加到桌面?(第二篇)

如果你也有相关的困惑,可以看这篇文章哦!

一、什么是服务卡片

如下图,服务卡片就是下面的展示出来的小组件,鸿蒙官方的名称是服务卡片。

f5bd99f839b6e9c90b381f3154ecdc10_720.jpg

二、创建服务卡片

对着鸿蒙项目的目录下的entry/src/ets单击右键,选择 Service Widget 中的 Dynamic Widget(见下两张图)。

image.png

image.png

三、新增的主要文件介绍

resource/base/profile/form_config.json:这是服务卡片的配置文件,设置服务卡片的名字,对外显示的名字等。具体说明见下,更详细的介绍可见官方文档:developer.huawei.com/consumer/cn…

{
  "forms": [
    {
      "name": "anniversary", //卡片名字,用于代码等开发操作 
      "displayName": "$string:anniversary_display_name", // 对用户显示的名字
      "description": "$string:anniversary_desc", // 对用户显示的卡片描述
      "src": "./ets/anniversary/pages/AnniversaryCard.ets", // 界面文件
      "uiSyntax": "arkts", //开发的语言
      "window": {
        "designWidth": 720,
        "autoDesignWidth": true
      },
      "formConfigAbility": "ability://EntryAbility", // 配置卡片的ability,主要就是用户在桌面长按卡片的时候可以长按进行编辑卡片
      "colorMode": "auto",
      "isDynamic": true,
      "isDefault": true,
      "updateEnabled": false,
      "scheduledUpdateTime": "00:00",
      "updateDuration": 1,
      "defaultDimension": "2*2",
      "supportDimensions": [
        "2*2"
      ]
    }
  ]
}

\ets\anniversary\pages\AnniversaryCard.ets 界面文件,编写服务卡片的UI \ets\EntryFormAbility.ets 服务卡片的管理类,所有服务卡片的逻辑处理都在这一个文件中编写逻辑,不管你不同类型的卡片有多少个。

四、Flutter 怎么把数据传递给服务卡片?

4.1 Flutter端

编写一个工具类,主要负责创建Flutter与鸿蒙原生之间的通信。

class HomeWidgetUtils {

  static pinCard(context, String name, String date, String image) async {
    const String channelPath =
        "plugins.flutter.io/home_widget"; //这儿要与MethodChannel(flutterEngine?.dartExecutor, CHANNEL)中CHANNEL名称一致
    const MethodChannel channel = MethodChannel(channelPath);
    Map map = {
      "name": name,
      "date": date,
      "image": image
    };
    await channel
        .invokeMethod("setPin", {"card": map}); //TODO 这里传数据有问题
    printLog("已发送到桌面");
  }
}

4.2 鸿蒙端

主要就是要写一下channel的代码,然后在EntryAbility.ets那里注册一下。

configureFlutterEngine(flutterEngine: FlutterEngine) {
  super.configureFlutterEngine(flutterEngine)
  GeneratedPluginRegistrant.registerWith(flutterEngine)
  this.registerWith(flutterEngine)
}

// 加入自己的引擎
registerWith(flutterEngine: FlutterEngine) {
  try {
    flutterEngine.getPlugins()?.add(new HomeWidgetPlugin());
  } catch (e) {

  }
}
export class HomeWidgetPlugin implements FlutterPlugin, AbilityAware {
  private methodChannel: MethodChannel | null = null
  private methodCallHandler: HomeWidgetHandlerImpl | null = null
  private context: common.UIAbilityContext | null = null

  onAttachedToEngine(binding: FlutterPluginBinding): void {
    this.setUpMethodChannel(binding.getBinaryMessenger(), binding.getApplicationContext());
  }

  onDetachedFromEngine(binding: FlutterPluginBinding): void {
    this.teardownMethodChannel();
  }

  onAttachedToAbility(binding: AbilityPluginBinding): void {
    this.context = binding.getAbility().context
    this.methodCallHandler?.setContext(this.context)
  }

  onDetachedFromAbility(): void {
    this.methodCallHandler?.setContext(null)
  }

  getUniqueClassName(): string {
    return TAG
  }

  setUpMethodChannel(messenger: BinaryMessenger, context: common.Context) {
    this.methodChannel = new MethodChannel(messenger, "plugins.flutter.io/home_widget");
    this.methodCallHandler =
      new HomeWidgetHandlerImpl(context, this.methodChannel);
    this.methodChannel.setMethodCallHandler(this.methodCallHandler);
  }

  teardownMethodChannel() {
    this.methodChannel?.setMethodCallHandler(null);
    this.methodChannel = null;
    this.methodCallHandler = null;
    this.context = null;
  }
}

值得注意的是,这里是要把拿到的数据用鸿蒙端的Sharepreference存起来。以供\ets\EntryFormAbility.ets 服务卡片的管理类使用。

export class HomeWidgetHandlerImpl implements MethodCallHandler {
  private context: common.UIAbilityContext | null = null
  private methodChannel: MethodChannel

  constructor(context: common.Context, methodChannel: MethodChannel) {
    this.methodChannel = methodChannel
  }

  setContext(context: common.UIAbilityContext | null) {
    this.context = context
  }

  async onMethodCall(call: MethodCall, result: MethodResult): Promise<void> {
    switch (call.method) {
      case "setPin": // 这里的名字要与Flutter端调用的对应
        await preferenceUtils.loadPreference(this.context,'pin')
        let pinDeed: Map<string, string> = call.argument('card') // 这里获取参数名以获得参数
        await preferenceUtils.delPreferenceValue('pin', 'name')
        await preferenceUtils.delPreferenceValue('pin', 'date')
        await preferenceUtils.delPreferenceValue('pin', 'image')
        await preferenceUtils.putPreferenceValue('pin', 'name', pinDeed.get('name'))
        await preferenceUtils.putPreferenceValue('pin', 'date', pinDeed.get('date'))
        await preferenceUtils.putPreferenceValue('pin', 'image', pinDeed.get('image'))
        result.success(null);
        break;
      default:
        result.notImplemented();
    }
  }
}

在管理类所在的EntryFormAbility.ets文件中的几个重要函数 onUpdateFormonFormEvent, onAddForm 中获取刚刚的数据data并填入到formData = formBindingData.createFormBindingData(data)中。示例代码如下:

async onUpdateForm(formId: string): Promise<void> {
  // 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要重写该方法以支持数据更新
  hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] onUpdateForm formId: ' + formId);
  let formData: formBindingData.FormBindingData
  let data = await this.getPinData(formId) // 这里就是从sharepreference中获取数据
  formData = formBindingData.createFormBindingData(data)

  formProvider.updateForm(formId, formData).catch((error: BusinessError) => {
    hilog.info(DOMAIN_NUMBER, TAG, '[EntryFormAbility] updateForm, error:' + JSON.stringify(error));
  });
}