【HarmonyOS】低代码开发之FA卡片开发流程

发布时间 2023-06-28 10:44:53作者: Mayism123

1开发准备

1.1FA卡片开发注意事项

参考文档:

https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ability-service-widget-provider-js-0000001150602175

1、只定义一个FA卡片

首先通过DevEco Studio创建一个工程,创建完成之后,找到src/main/config.json文件,

在config.json配置文件中,在module--->abilities--->forms节点下,只有一条数据。

forms节点下的isDefault字段值为true,表示该卡片为默认卡片,每个Ability有且只有一个默认卡片。

备注:forms节点的数据结构是数组,这里的要求是内部只有一个{}对象数据。

cke_2508.png

2、提供多种尺寸

在forms节点下的supportDimensions字段中配置多种尺寸,比如这里配置了1*2和2*2两种尺寸:

cke_3523.png

3、对应卡片的js相关资源

“js”模块中的name字段要与“forms”模块中的jsComponentName字段的值一致,为js资源的实例名。

cke_5033.png

4、添加相应权限

例如:卡片需要请求网络数据,需要添加相应的网络权限,打开config.json文件,在module节点下添加reqPermissions节点,如下图所示:

cke_6764.png

5、快照

需要在元服务开发态默认将快照图定义成和2*2卡片相同的内容,用于对新用户展示。

 

1.2低码SDK初始化

基于景区模板开发可以跳过此步骤,低码模板中已经有低码SDK。

在项目工程的根目录下build.gradle文件中的dependencies闭包中添加如下依赖:

cke_8867.png

在entry的根目录下的build.gradle文件的头部添加以下内容:

apply plugin: 'com.huawei.agconnect'

同时在dependencies闭包中添加低码SDK的依赖:

cke_13280.png

在entry/src/main/java/包名/MyApplication.java文件中进行SDK初始化:

cke_16275.png

 

1.3数据模型初始化

基于景区模板开发可以跳过此步骤,此步骤主要介绍如何在云侧创建数据模型。

登录AppGallery Connect控制台,找到对应的项目,然后选中低代码平台中的数据模型,点击新建数据模型:

cke_20447.png

之后点击编辑,为该数据模型添加所需的字段:

cke_24112.png

所需的数据字段创建完成之后,点击保存。

回到数据模型列表所在页面,点击管理数据,之后点击新建,创建以上数据模型所需的数据:

cke_28187.png

在所需的数据全部添加完成之后,数据模型就创建完成了。

 

1.4差异化内容展示方案

第一种:负一屏通过AGC自定义参数

在云侧数据模型中创建了extraParam这个预留参数字段(注意:此处可以创建多个字段,需要和AGC管理台位置感应服务中创建的字段保持一致),通过在AGC管理台---位置感应服务---服务申请中创建相应的字段,端侧获取该字段值,通过对不同值进行判断处理,控制端侧卡片展示不同样式内容。

第二种:小艺建议通过不同POI ID

在云侧数据模型中创建了poi_id字段(字段名称需要根据实际创建),端侧获取该字段值,通过对不同值进行判断处理,小艺建议通过不同POI ID实现控制端侧卡片展示不同的样式内容。

AGC管理台定义的数据和POI ID的数据通过onCreateForm(Intent intent)方法中的intent获取,比如:String poi_id = intent.getStringParam(“poi_id”);

 

2开发卡片

2.1卡片目录结构

卡片的典型开发目录结构如下图所示,详细介绍参见文件组织

cke_32730.png

目录中主要文件夹如下:

  • pages:用于存放卡片的页面。
  • common:用于存放公共资源文件,比如:图片资源。
  • resource:用于存放资源配置文件,比如:多分辨率加载配置文件。
  • i18n:用于配置不同语言场景下资源内容,比如:应用文本词条、图片路径等。

目录中文件分类如下:

  • .hml结尾的HML模板文件:用于描述卡片页面的布局结构。
  • .css结尾的CSS样式文件:用于描述页面的样式。
  • .json结尾的JSON文件:用于配置卡片中使用的变量action事件。

 

2.2布局卡片

在index.hml文件中实现卡片的页面布局。最外层使用div组件和stack组件。stack组件内部使用了两个div组件分别布局1x2和2x2两种不同大小的卡片样式,并且绑定click事件。1x2格式卡片为左右结构,左侧text组件显示标题,右侧Image组件显示播放按钮,绑定消息事件。2x2格式卡片为上下结构,上方text组件显示标题,下方Image组件显示播放按钮,并且绑定消息事件。

<div class="container">
<stack style="height: 100%;width: 100%;">
<div class="item-container-small" show="{{smallCard}}" "routerEvent">
  <text class="title-small">{{title}}</text>
<image src="{{btnSrc}}" class="btn-img-small" "messageEvent"></image>
</div>
<div class="item-container-normal" show="{{normalCard}}" "routerEvent">
<text class="title-normal">{{title}}</text>
<image src="{{btnSrc}}" class="btn-img-normal" "messageEvent"></image>
</div>
</stack>
</div>

2.3设置卡片样式

在index.css文件中定义页面样式,除通用样式外,各组件还可以定义自己特有的样式。例如:text组件设置font-size。组件样式的设置参见各组件的详细介绍。

.container {
flex-direction: column;
justify-content: center;
align-items: center;
}
.title-small {
font-size: 12fp;
margin-start: 15px;
}
.title-normal {
margin-top: 30px;
font-size: 20fp;
}
.item-container-small {
   width: 100%;
   height: 100%;
align-items: center;
}
.item-container-normal {
   width: 100%;
   height: 100%;
align-items: center;
flex-direction: column;
}
.btn-img-small {
margin-start: 25px;
   width: 32px;
   height: 32px;
}
.btn-img-normal {
margin-top: 20px;
   width: 64px;
   height: 64px;
}

2.4配置卡片事件

在index.json文件中配置卡片使用的变量和事件,在data字段中声明变量,在actions字段中声明事件。

本示例在action中定义了名为“routerEvent”的事件,事件类型为“router”,用于跳转到MainAbility。更多用户自定义事件可以使用action:"message"实现。

{
 "data": {
   "smallCard": false,
   "normalCard": true,
   "type": "",
   "title": "",
   "btnSrc": ""
 },
 "actions": {
   "routerEvent": {
     "action": "router",
     "bundleName": "com.lowcode.scenicarea",
     "abilityName": "com.lowcode.scenicarea.MainAbility",
     "params": {
       "message": "add detail"
     }
   },
   "messageEvent": {
     "action": "message",
     "params": {
       "message": "play"
     }
   }
 }
}

除了通过页面主动触发事件,还可以通过卡片各生命周期的回调函数来实现业务处理。卡片生命周期回调在卡片对应的PageAbility中,本示例对应的pageAbility为com.lowcode.scenicarea.MainAbility。在MainAbility的卡片创建回调函数onCreateForm中通过Intent获取卡片的ID、name等信息:

cke_60217.png

public class MainAbility extends AceAbility {
   public static final int DEFAULT_DIMENSION_2X2 = 2;
   private static final int INVALID_FORM_ID = -1;
   private static final HiLogLabel TAG = new HiLogLabel(HiLog.DEBUG, 0x0, MainAbility.class.getName());
   private String topWidgetSlice;
   @Override
   protected ProviderFormInfo onCreateForm(Intent intent) {
       HiLog.info(TAG, "onCreateForm");
       String formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY);
       int dimension = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, DEFAULT_DIMENSION_2X2);
       HiLog.info(TAG, "onCreateForm: formId=" + formId + ",formName=" + formName);
       FormControllerManager formControllerManager = FormControllerManager.getInstance(this);
       FormController formController = formControllerManager.getController(formId);
       formController = (formController == null) ? formControllerManager.createFormController(formId,
               formName, dimension) : formController;
       if (formController == null) {
           HiLog.error(TAG, "Get null controller. formId: " + formId + ", formName: " + formName);
           return null;
       }
       return formController.bindFormData(formId);
   }
   ...
}

2.5低码获取数据模型数据

创建数据模型端侧数据实体CardInfo.java:

public class CardInfo {
private String id;
private String type;
private String title;
private String btnSrc;
private String extraParam;
private String poi_id;
      setter(); // 定义各个变量的setter方法
      getter(); // 定义各个变量的getter方法
}

创建获取数据模型端侧数据工具类DataUtil.java:

public class DataUtil {
private static final HiLogLabel TAG = new HiLogLabel(HiLog.INFO, 0x00201, "jscard");

public static List<CardInfo> getData() {
List<CardInfo> mCardInfos = new ArrayList<>();
DataModelRequest request = new DataModelRequest();
request.setModelId("1179644081647666113");
request.setStatus(0);
request.setMethodName("list");
DataModelReqParams params = new DataModelReqParams();
params.setOrderBy("id");
params.setOrderType("asc");
request.setParams(params);
HarmonyTask<DataModelResponse> task = AGConnectLowCode.getInstance().callDataModel(request);
task.addOnCompleteListener(new OnHarmonyCompleteListener<DataModelResponse>() {
@Override
public void onComplete(HarmonyTask<DataModelResponse> harmonyTask) {
               if (harmonyTask.isSuccessful()) {
                   DataModelResponse response = harmonyTask.getResult();
                   List<Map<String, Object>> list = response.getData().getRecords();
                   mCardInfos.clear();
                   for (Map<String, Object> map : list) {
                       CardInfo cardInfo = new CardInfo();
cardInfo.setId(map.get("id").toString());
cardInfo.setType(map.get("type").toString());
cardInfo.setTitle(map.get("title").toString());
cardInfo.setBtnSrc(map.get("btnSrc").toString());
cardInfo.setExtraParam(map.get("extraParam").toString());
cardInfo.setPoi_id(map.get("poi_id").toString());
                       mCardInfos.add(cardInfo);
                   }
                   HiLog.info(TAG, "success");
               } else {
                   HiLog.info(TAG, "fail");
               }
}
});
return mCardInfos;
   }
}

创建单例管理类DataInstance.java用来管理数据和Player播放器:

public class DataInstance {
private static class SingletonHolder {
private static final DataInstance INSTANCE = new DataInstance();
   }

private DataInstance () {}

public static final DataInstance getInstance() {
return SingletonHolder.INSTANCE;
   }

private List<CardInfo> list;
private Player player;

private boolean isPlay= false;

public boolean isPlay() {
return isPlay;
   }

public void setPlay(boolean play) {
isPlay = play;
   }

public Player getPlayer() {
return player;
   }

public void setPlayer(Player player) {
this.player = player;
   }

public List<CardInfo> getList() {
return list;
   }

public void setList(List<CardInfo> list) {
this.list = list;
   }
}

2.6初始化卡片

首先在MainAbility.java类的onStart()方法中初始化数据和播放器:

public class MainAbility extends AceAbility {
public static final int DEFAULT_DIMENSION_2X2 = 2; //System-defined constant. Do not change its value.
private static final int INVALID_FORM_ID = -1;
private static final HiLogLabel TAG = new HiLogLabel(HiLog.DEBUG, 0x0, "jscard");
private String topWidgetSlice;
private Player mPlayer;

@Override
public void onStart(Intent intent) {
super.onStart(intent);
DataInstance.getInstance().setList(DataUtil.getData());
initPlayer();
   }

@Override
public void onStop() {
super.onStop();
   }

private void initPlayer() {
if (mPlayer == null) {
mPlayer = new Player(this);
}
DataInstance.getInstance().setPlayer(mPlayer);
Source source = new Source("https://www.cambridgeenglish.org/images/153149-movers-sample-listening-test-vol2.mp3");
DataInstance.getInstance().getPlayer().setSource(source);
DataInstance.getInstance().getPlayer().prepare();
   }

@Override
protected ProviderFormInfo onCreateForm(Intent intent) {
HiLog.info(TAG, "onCreateForm");
long formId = intent.getLongParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, INVALID_FORM_ID);
String formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY);
int dimension = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, DEFAULT_DIMENSION_2X2);
HiLog.info(TAG, "onCreateForm: formId=" + formId + ",formName=" + formName);
FormControllerManager formControllerManager = FormControllerManager.getInstance(this);
FormController formController = formControllerManager.getController(formId);
formController = (formController == null) ? formControllerManager.createFormController(formId,
               formName, dimension) : formController;
if (formController == null) {
HiLog.error(TAG, "Get null controller. formId: " + formId + ", formName: " + formName);
return null;
}
if (DataInstance.getInstance().getList()==null || DataInstance.getInstance().getList().size()==0){
DataInstance.getInstance().setList(DataUtil.getData());
initPlayer();
}
return formController.bindFormData(formId);
	}
	……
}

在WidgetImpl.java类的bindFormData()方法中初始化卡片展示:

private Context mContext;
public WidgetImpl(Context context, String formName, Integer dimension) {
super(context, formName, dimension);
HiLog.info(TAG, "WidgetImpl");
this.mContext = context;
   }

@Override
   public ProviderFormInfo bindFormData(long formId) {
HiLog.info(TAG, "bind form data");
ZSONObject object = new ZSONObject();
if (dimension == DIMENSION_1X2) {
object.put("smallCard", true);
object.put("normalCard", false);
object.put("type", DataInstance.getInstance().getList().get(0).getType());
object.put("title", DataInstance.getInstance().getList().get(0).getTitle());
if (!DataInstance.getInstance().isPlay()){
       object.put("btnSrc", DataInstance.getInstance().getList().get(0).getBtnSrc());
}else {
               object.put("btnSrc", DataInstance.getInstance().getList().get(1).getBtnSrc());
}
} else if (dimension == DIMENSION_2X2) {
object.put("smallCard", false);
object.put("normalCard", true);
object.put("type", DataInstance.getInstance().getList().get(2).getType());
object.put("title", DataInstance.getInstance().getList().get(2).getTitle());
if (!DataInstance.getInstance().isPlay()){
               object.put("btnSrc", DataInstance.getInstance().getList().get(2).getBtnSrc());
}else {
               object.put("btnSrc", DataInstance.getInstance().getList().get(3).getBtnSrc());
}
}
FormBindingData bindingData = new FormBindingData(object);
ProviderFormInfo formInfo = new ProviderFormInfo();
formInfo.setJsBindingData(bindingData);
return formInfo;
   }

2.7卡片效果展示

cke_212422.png​​

2.8音乐播放/暂停事件

此部分功能同样在WidgetImpl.java类中实现:

创建音乐播放/暂停的方法:

// 暂停音乐
private void pause() {
	DataInstance.getInstance().getPlayer().pause();
}

// 播放音乐
private void play() {
	DataInstance.getInstance().getPlayer().play();
}

音乐播放/暂停时更新播放按钮的图片资源:

// 更新数据
private void updateForm(long formId, String btnSrc) {
ZSONObject object = new ZSONObject();
object.put("btnSrc", btnSrc);
try {
if (mContext instanceof Ability) {
         ((Ability) mContext).updateForm(formId, new FormBindingData(object));
}
} catch (FormException e) {
HiLog.error(TAG, e.getMessage());
}
}

在2.4中配置了卡片的消息事件,通过消息事件触发音乐播放,在onTriggerFormEvent()方法中接收事件并处理播放/暂停及按钮的样式更新逻辑:

@Override
public void onTriggerFormEvent(long formId, String message) {
HiLog.info(TAG, "onTriggerFormEvent");
if (!TextUtils.isEmpty(message)){
ZSONObject zsonObject = ZSONObject.stringToZSON(message);
   String msg = zsonObject.getString("message");
if (!TextUtils.isEmpty(msg) && "play".equals(msg)){
               if (!DataInstance.getInstance().isPlay()){
DataInstance.getInstance().setPlay(true);
                   if (dimension==DIMENSION_1X2){
updateForm(formId,DataInstance.getInstance().getList().get(1).getBtnSrc());
                   }else if (dimension==DIMENSION_2X2){
updateForm(formId,DataInstance.getInstance().getList().get(3).getBtnSrc());
                   }
                   play();
               }else {
DataInstance.getInstance().setPlay(false);
                   if (dimension == DIMENSION_1X2){
                       updateForm(formId,DataInstance.getInstance().getList().get(0).getBtnSrc());
                   }else if (dimension==DIMENSION_2X2){
updateForm(formId,DataInstance.getInstance().getList().get(2).getBtnSrc());
                   }
           pause();
               }
}
}
}

最后来看一下播放的效果:

cke_225846.png​​