1. 介绍
元数据绑定框架是基于HarmonyOS SDK开发的一套提供UI和数据源绑定能力的框架。通过使用元数据绑定框架,HarmonyOS应用开发者无需开发繁琐重复的代码即可实现绑定UI和数据源。
在本篇Codelab中,您将学会使用元数据绑定框架提供的接口,通过简单少量的代码实现UI和数据源绑定。我们提供了绑定简单UI组件、绑定UI容器组件、绑定自定义UI组件和绑定自定义数据源四种场景的实现。
2. 搭建HarmonyOS环境 我们首先需要完成HarmonyOS开发环境搭建,可参照如下步骤进行。
- 安装DevEco Studio,详情请参考下载和安装软件。
- 设置DevEco Studio开发环境,DevEco Studio开发环境依赖于网络环境,需要连接上网络才能确保工具的正常使用,可以根据如下两种情况来配置开发环境:
- 开发者可以参考以下链接,完成设备调试的相关配置:
您可以利用如下设备完成Codelab:
3. 代码结构解读 本篇Codelab只对核心代码进行讲解,对于完整代码,会在10 参考中提供下载方式,接下来会讲解整个工程的关键代码结构:
- alarm/simple_ui:该路径下是实现简单UI绑定场景的代码。
- alarm/list_ui:该路径下是实现UI容器组件绑定场景的代码。
- custom_data_source:该目录下是实现绑定自定义数据源场景的代码。
- custom_ui:该目录下是实现绑定自定义UI组件场景的代码。
- MyApplication和MainAbility:项目应用程序的入口,在MyApplication中初始化元数据绑定框架。
- MainAbilitySlice:显示了四个按钮,分别是四个例子的入口。
- resources/base/layout:该路径下是各个界面的布局文件。
- resources/rawfile/jsonschema:该路径下是各个元数据的Json Schema文件。
4. 引入元数据绑定框架 在项目中引入元数据绑定框架在模块的build.gradle文件中的dependencies中添加对元数据绑定框架的引用:
- implementation 'com.huawei.middleplatform:ohos-metadata-annotation:1.0.0.0'
- implementation 'com.huawei.middleplatform:ohos-metadata-binding:1.0.0.0'
- annotationProcessor 'com.huawei.middleplatform:ohos-metadata-processor:1.0.0.0'
复制代码并添加编译选项,开启注解处理器:
- ohos {
- compileOptions {
- annotationEnabled true
- }
- }
复制代码 使用元数据绑定框架并初始化在MyApplication的类的声明上使用元数据绑定框架提供的注解@MetaDataApplication,其中requireData设为true,exportData设为false,表示该application需要获取数据,不对外提供数据。
- @MetaDataApplication(requireData = true, exportData = false)
- public class MyApplication extends AbilityPackage {
- ...
- }
复制代码在MyApplication类的初始化方法onInitialize方法中,调用框架的初始化方法,并将MyApplication.this上下文传入。这一行代码使框架在应用启动时进行初始化。
- [url=home.php?mod=space&uid=2735960]@Override[/url]
- public void onInitialize() {
- super.onInitialize();
- context = this.getContext();
- MetaDataFramework.init(this);
- }
复制代码至此环境准备好了,也完成了元数据绑定框架的引入和初始化。下面开始进行不同场景下的业务的开发。
5. 实现简单UI组件的绑定 定义元数据结构在alarm_schema.json中定义元数据的结构:
- {
- "id": "com.example.meta-data.alarm",
- "title": "alarm schema",
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "alarm description",
- "type": "object",
- "properties": {
- "id": {
- "type": "integer"
- },
- "hour": {
- "type": "integer"
- },
- "minutes": {
- "type": "integer"
- },
- "daysofweek": {
- "type": "integer"
- },
- "enabled": {
- "type": "integer"
- },
- "message": {
- "type": "string"
- }
- }
- }
复制代码
元数据包括id、小时、分钟、星期几、是否开启和闹钟信息。其中星期几用七位的二进制整数表示,将整数转换为二进制后,第n位如果是1表示在星期n开启,否则为在星期n关闭。
说明
Json Schema定义了一套词汇和规则,这套词汇和规则用来定义Json元数据。关于Json Schema的定义请参考Json Schema官网。Json Schema文件一般由数据源一方提供。需要将得到的元数据Json文件放到resource/rawfile.jsonschema路径下。
在布局文件中使用元数据来看一下要实现的界面效果:
创建布局文件single_alarm.xml,在xml布局文件中的最外层UI组件的定义中加入元数据绑定框架的命名空间:
- xmlns:metaDataBinding="http://schemas.huawei.com/res/metaDatabinding"
复制代码创建元数据实体:
- <request-meta-data
- name="ClockMetaData"
- schema="com.example.meta-data.alarm"
- uri="dataability:///com.huawei.metadatabindingdemo.db.AlarmsDataAbility"/>
复制代码
说明
元数据实体包含两类,和;前者表示需要主动请求数据源的数据进行绑定,后者表示将一个元数据和UI进行绑定,不需要在绑定的开始向数据源请求数据。name表示元数据的名称,schema表示元数据的id(与Json Schema文件中的id一致),uri表示取得元数据的数据源的地址。
在组件中使用元数据:
- <TimePicker
- ohos:id="$+id:time_picker"
- ...
- metaDataBinding:hour="@={ClockMetaData.hour}"
- metaDataBinding:minute="@={ClockMetaData.minutes}"/>
- <TextField
- ohos:id="$+id:clock_name_text"
- ...
- metaDataBinding:text="@={ClockMetaData.message}"/>
- <Text
- ohos:id="$+id:show_alarm_name"
- ohos:align_parent_left="true"
- ...
- metaDataBinding:text="@{ClockMetaData.message}"/>
- <Text
- ohos:id="$+id:show_alarm_time"
- ohos:align_parent_right="true"
- ...
- metaDataBinding:text="@{ClockMetaData.hour} + ':' + @{ClockMetaData.minutes}"/>
复制代码
在代码中进行绑定在alarm/simple_ui/SingleAlarmSlice.java中的onStart方法中请求绑定:
- // 创建元数据请求对象
- MetaDataRequestInfo request = new MetaDataRequestInfo.Builder()
- .setMetaDataClass("ClockMetaData", ClockRowMetaData.class)
- .setSyncRequest("ClockMetaData", true)
- .build();
- MetaDataBinding binding;
- Component mainComponent;
- try {
- // 请求绑定
- binding = SinglealarmMetaDataBinding.requestBinding(this, request, null);
- // 获得绑定的界面组件
- mainComponent = binding.getLayoutComponent();
- } catch (DataSourceConnectionException e) {
- mainComponent = LayoutScatter.getInstance(this)
- .parse(ResourceTable.Layout_default_error, null, false);
- }
- setUIContent((ComponentContainer) mainComponent);
复制代码
说明
"ClockMetaData"是元数据的名字,对应xml布局文件中元数据的name。ClockRowMetaData类继承自MetaData类,我们可以在其中创建一些操作元数据内容的方法,例如将元数据内容转换成json字符串等。如果没有自定义的方法,则不需要创建继承自MetaData的类。
这样就完成了数据源和简单UI之间的绑定。运行之后效果如下:
6. 实现UI容器组件的绑定 创建布局文件创建一个布局文件alarm_list_slice.xml,内容如下:
- <?xml version="1.0" encoding="utf-8"?>
- <DirectionalLayout
- xmlns:ohos="http://schemas.huawei.com/res/ohos"
- ohos:height="match_parent"
- ohos:width="match_parent"
- ohos:background_element="$color:colorAppBackground"
- ohos:orientation="vertical">
- <request-meta-data
- class="com.huawei.metadatabindingdemo.alarm.metadata.ClockRowMetaData"
- name="ClockMetaData"
- schema="com.example.meta-data.alarm"/>
- <!-- title -->
- <DependentLayout...>
- <!-- list -->
- <ListContainer
- ohos:id="$+id:list_view"
- ...
- ohos:top_padding="18vp"/>
- </DirectionalLayout>
复制代码
获取数据源中的元数据在AlarmListSlice类中请求数据源绑定。
- MetaDataRequestInfo request = new MetaDataRequestInfo.Builder()
- .setRequestSource("ClockMetaData",
- "dataability:///com.huawei.metadatabindingdemo.db.AlarmsDataAbility")
- .setSyncRequest("ClockMetaData", false)
- .build();
- Component mainComponent;
- try {
- binding = AlarmlistsliceMetaDataBinding.requestBinding(this, request, this);
- mainComponent = binding.getLayoutComponent();
- } catch (DataSourceConnectionException e) {
- mainComponent = LayoutScatter.getInstance(this)
- .parse(ResourceTable.Layout_default_error, null, false);
- }
- setUIContent((ComponentContainer) mainComponent);
复制代码
这里设置同步请求为false,表示请求是异步的。在requestBinding方法中dataCallback参数传入this,因为AlarmListSlice类实现了IMetaDataObserver接口。
说明
setSyncRequest传入false表示是异步的请求,IMetaDataObserver.onDataLoad方法会异步执行;如果传入true,则是同步的,IMetaDataObserver.onDataLoad方法执行之后requestBinding方法才会返回。
两个回调函数实现如下:
- @Override
- public void onDataLoad(List<MetaData> metaDatas, MetaDataRequestInfo.RequestItem requestItem) {
- if (metaDatas == null || requestItem == null) { return;}
- if (listContainer != null) {
- itemProvider.initData(createAlarms(this, metaDatas));
- listContainer.setItemProvider(itemProvider);
- }
- }
- @Override
- public void onDataChange(List<MetaData> addedMetaData, List<MetaData> updatedMetaData,
- List<MetaData> deletedMetaData, MetaDataRequestInfo.RequestItem requestItem) {
- if (addedMetaData == null) { return;}
- itemProvider.addItems(createAlarms(this, addedMetaData));
- }
- private List<AlarmRow> createAlarms(AbilitySlice context, List<MetaData> dataList) {
- List<AlarmRow> list = new ArrayList<>();
- for (MetaData metaData : dataList) {
- AlarmRow item = new AlarmRow(context, metaData);
- list.add(item);
- }
- return list;
- }
复制代码
将列表条目与元数据进行绑定列表条目的展示效果:
为列表条目创建布局文件alarm_row.xml:
- <?xml version="1.0" encoding="utf-8"?><DependentLayout
- xmlns:ohos="http://schemas.huawei.com/res/ohos"
- xmlns:metaDataBinding="http://schemas.huawei.com/res/metaDatabinding"
- ...
- ohos:orientation="vertical">
- <using-meta-data
- class="com.huawei.metadatabindingdemo.alarm.metadata.ClockRowMetaData"
- name="ClockMetaData"
- schema="com.example.meta-data.alarm"/>
- <DependentLayout
- ohos:id="$+id:double_line_text_area"...>
- <Text
- ohos:id="$+id:timezone"
- ...
- metaDataBinding:text="*{ClockMetaData.getTimeZone(@{ClockMetaData.hour})}"/>
- <Text
- ohos:id="$+id:time"
- ...
- metaDataBinding:text="@{ClockMetaData.hour} + ':' + @{ClockMetaData.minutes}"/>
- <Text
- ohos:id="$+id:message"
- ...
- metaDataBinding:text="*{ClockMetaData.toMessage(@{ClockMetaData.message},@{ClockMetaData.daysofweek})}"/>
- </DependentLayout>
- <DependentLayout
- ohos:id="$+id:double_line_switch_hot_area"...>
- <Image
- ohos:id="$+id:switch_enable_button"
- ...
- metaDataBinding:image_src="@{ClockMetaData.enabled} == 1 ? ${Media_icon_switch_enabled} : ${Media_icon_switch_disabled}"
- metaDataBinding:onClick="#{ClockMetaData.enabled = (@{ClockMetaData.enabled} == 1 ? 0 : 1)}"/>
- </DependentLayout>
- </DependentLayout>
复制代码
这里用到了多种引用元数据的表达式,具体的用法如下:
标识符
| 意义
| 示例
|
@
| 取得原数据属性的值赋值到UI属性的单项绑定
| metaDataBinding:text="@{ClockMetaData.message}"
|
@=
| 元数据属性的值和UI属性双向绑定
| metaDataBinding:text="@={ClockMetaData.message}"
|
*
| 绑定自定义函数
| metaDataBinding:text="*{ClockMetaData.getTimeZone(@{ClockMetaData.hour})}"
|
$
| 绑定资源文件
| metaDataBinding:image_src="@{ClockMetaData.enabled} == 1 ? ${Media_icon_switch_enabled} : ${Media_icon_switch_disabled}"
|
#
| 点击事件触发给元数据赋值
| metaDataBinding:onClick="#{ClockMetaData.enabled = (@{ClockMetaData.enabled} == 1 ? 0 : 1)}"
|
创建AlarmRow类表示列表的一个条目。它持有一个元数据对象,提供了获取UI组件,绑定UI组件和响应点击事件三个方法:
- public class AlarmRow {
- private final AbilitySlice context;
- private final ClockRowMetaData clockMeta;
- public AlarmRow(AbilitySlice context, MetaData clockMeta) {
- this.context = context;
- this.clockMeta = (ClockRowMetaData) clockMeta;
- }
- public Component createComponent() {
- AlarmrowMetaDataBinding metaBinding = AlarmrowMetaDataBinding.createBinding(context, clockMeta);
- Component comp = metaBinding.getLayoutComponent();
- comp.setTag(metaBinding);
- return comp;
- }
- public void bindComponent(Component component) {
- AlarmrowMetaDataBinding metaBinding = (AlarmrowMetaDataBinding) component.getTag();
- metaBinding.reBinding(component, clockMeta);
- }
- public void onClick() {
- context.present(new AlarmEditSlice(clockMeta), new Intent());
- }
- }
复制代码
创建AlarmListProvider类,重写getComponent方法,将列表条目组件和元数据进行绑定:
- public Component getComponent(int position, Component component, ComponentContainer componentContainer) {
- AlarmRow alarm = alarmList.get(alarmIndex);
- if (component == null) {
- Component newComponent = alarm.createComponent();
- return newComponent;
- } else {
- alarm.bindComponent(component);
- return component;
- }
- }
复制代码
在AlarmEditSlice类中将传入的元数据和现在的界面进行绑定;并监听"返回"按钮和"保存"按钮的点击事件。当点击"返回"按钮的时候使用元数据的rollback方法进行操作回滚, 点击"保存"按钮的时候进行提交和备份:
- metaBinding = AlarmdetailMetaDataBinding.createBinding(this, clockMeta);
- Component comp = metaBinding.getLayoutComponent();
- DependentLayout dependentLayout = (DependentLayout) comp.findComponentById(ResourceTable
- .Id_title_area_back_icon_hot_area);
- dependentLayout.setClickedListener(component -> {
- clockMeta.rollback();
- this.terminate();
- });
- comp.findComponentById(ResourceTable.Id_save).setClickedListener(component -> {
- clockMeta.commit();
- clockMeta.stash();
- this.terminate();
- });
复制代码
添加一条记录给界面右上方的'+‘号按钮添加点击事件监听,点击后会添加一条数据,该数据会保存在列表和数据源中。添加的数据默认表示将闹铃设置为开启,设定在周三和周四的12时12分。添加记录的代码如下:
- public static void insertAnAlarm(MetaDataBinding binding) {
- MetaDataRequestInfo.RequestItem requestItem = binding.getRequestInfo().getRequestItem("ClockMetaData");
- MetaData metaData = AlarmlistsliceMetaDataBinding.createMetaData(requestItem);
- metaData.put(COL_HOUR, DEFAULT_HOUR);
- metaData.put(COL_MINUTE, DEFAULT_MINUTES);
- metaData.put(COL_DAYS, DEFAULT_DAYS_OF_WEEK);
- metaData.put(COL_ENABLE, DEFAULT_ENABLE);
- metaData.put("message", "count" + count);
- binding.addMetaData(metaData, requestItem);
- count++;
- }
复制代码
说明
这里使用了MetaDataBinding的addMetaData方法添加元数据对象,您可以尝试使用deleteMetaData方法实现删除操作。
效果如下:
7. 绑定自定义UI组件 目前元数据绑定框架支持绑定的组件和属性如下:
组件
| 属性
|
所有组件
| alpha,enabled,focusable,visibility,onClick
|
Button
| text,text_color,text_size
|
Checkbox
| checked
|
Image
| image_src
|
ProgressBar
| progress
|
RadioContainer
| marked
|
Slider
| progress,
|
Switch
| checked
|
TabList
| orientation
|
TextField
| hint
|
Text
| text, text_color, text_size
|
TimePicker
| hour, minute, second
|
针对自定义UI组件,元数据绑定框架提供了BindingComponent注解、BindingTag注解和Operator接口。
在custom_ui目录下,创建一个正方形的组件,将它的颜色和数据源进行绑定,还有一个自定义的RadioButton组件,它的颜色也和该数据源进行绑定。
关于正方形组件的绘制代码我们不再赘述,请查看源码。
正方形组件提供了一个设置颜色的方法:
- public void setColor(boolean isGreen) {
- this.isGreen = isGreen;
- if (isGreen) {
- rectPaint.setColor(Color.GREEN);
- } else {
- rectPaint.setColor(Color.YELLOW);
- }
- invalidate();
- }
复制代码
RadioButton组件继承RadioButton类,重写构造方法,其余什么都不做。
使用一个Switch组件和元数据的bool_attr元素进行双向绑定,意图通过Switch控件来给元数据赋值。元数据的Json Schema文件是custom_schema.json。
布局文件custom_operator.xml如下:
- <?xml version="1.0" encoding="utf-8"?>
- <DirectionalLayout
- xmlns:ohos="http://schemas.huawei.com/res/ohos"
- xmlns:metaDataBinding="http://schemas.huawei.com/res/metaDatabinding"
- ...
- >
- <using-meta-data
- class="com.huawei.metadatabindingdemo.custom_ui.metadata.CustomMetaData"
- name="MetaData"
- schema="com.example.meta-data.custom"/>
- <Switch
- ohos:id="$+id:switch_color"
- ...
- metaDataBinding:checked="@={MetaData.bool_attr}"/>
- <com.hauwei.metadatabindingdemo.custom_ui.component.MyRadioButton
- ohos:id="$+id:test_my_radiobt"
- ...
- metaDataBinding:text_color_off2="*{MetaData.getColorOff(@{MetaData.bool_attr})}"
- metaDataBinding:text_color_on2="*{MetaData.getColorOn(@{MetaData.bool_attr})}"/>
- <com.huawei.metadatabindingdemo.custom_ui.component.MySquare
- ohos:id="$+id:test_my_comp"
- ...
- metaDataBinding:enabled2="@{MetaData.bool_attr}"/>
- </DirectionalLayout>
复制代码
在CustomOperatorSlice中创建一个元数据对象,然后将元数据对象和UI进行绑定。
- metaData = new CustomMetaData();
- metaData.setPrimaryKey("id");
- metaData.setName("MetaData");
- metaData.put("bool_attr", false);
- MetaDataBinding binding;
- binding = CustomoperatorMetaDataBinding.createBinding(this, metaData);
- Component mainComponent;
- mainComponent = binding.getLayoutComponent();
- setUIContent((ComponentContainer) mainComponent);
复制代码
适配正方形组件:
- @BindingComponent(component ="com.huawei.metadatabindingdemo.custom_ui.component.MySquare")
- public class Custom1Operator {
- @BindingTag(attr = "enabled2", type = "java.lang.Boolean")
- public static Operator<Boolean> SetColorOperator = new Operator<Boolean>() {
- @Override
- public void operate(Component component, Boolean value) {
- ((MySquare) component).setColor(value);
- }
- };
- }
复制代码
适配自定义RadioButton组件:
- @BindingComponent(component ="com.huawei.metadatabindingdemo.custom_ui.component.MyRadioButton")
- public class Custom2Operator {
- @BindingTag(attr = "text_color_on2", type = "ohos.agp.utils.Color")
- public static Operator<Color> SetOnColor = new Operator<Color>() {
- @Override
- public void operate(Component component, Color value) {
- ((MyRadioButton)component).setTextColorOn(value);
- }
- };
- @BindingTag(attr = "text_color_off2", type = "ohos.agp.utils.Color")
- public static Operator<Color> SetOffColor = new Operator<Color>() {
- @Override
- public void operate(Component component, Color value) {
- ((MyRadioButton)component).setTextColorOff(value);
- }
- };
- }
复制代码
现在元数据绑定框架可以识别自定义的两个组件了。
8. 绑定自定义数据源 元数据绑定框架对自定义数据源的支持是通过两个接口,CustomDao.ICustomMetaDataHandler和SimpleDao.ISimpleMetaDataHandler来实现的。由于ICustomMetaDataHandler接口支持多个元数据的获取,所以这里使用它来实现一个列表容器。布局文件不再赘述。数据源复用了闹钟列表的数据库,在数据库中添加了一个新的表,具体实现请阅读custom_data_source路径下的源码。这里只展示CustomDao.ICustomMetaDataHandler的接口实现:
- public class MyDataHandler implements CustomDao.ICustomMetaDataHandler {
- private DataAbilityHelper mDbHelper = null;
- private MetaDataRequestInfo.RequestItem mReqItem = null;
- @Override
- public boolean onConnect(CustomDao dao) {
- Context mCtx = MetaDataFramework.appContext;
- this.mDbHelper = DataAbilityHelper.creator(mCtx);
- this.mReqItem = dao.getRequestItem();
- return true;
- }
- @Override
- public List<MetaData> onQuery(CustomDao dao, boolean isSync, String[] properties, PacMap predicates) {
- ArrayList<MetaData> list = new ArrayList<>();
- try {ResultSet resultSet = mDbHelper.query(NotesDataAbility.NOTE_URI, null, null);
- if (resultSet != null) {
- boolean hasData = resultSet.goToFirstRow();
- if (!hasData) {
- return list;
- }
- do {
- list.add(getQueryResults(resultSet)); // ResultSet to MetaData
- }while (resultSet.goToNextRow());
- }
- } catch (DataAbilityRemoteException e) {
- MyLog.error("ohosTest: SimpleHandler onQuery error");
- }
- return list;
- }
- @Override
- public void onAdd(MetaData metaData) {}
- @Override
- public void onDelete(MetaData metaData) {}
- @Override
- public void onChange(MetaData metaData, String key, Object value) {}
- @Override
- public void onFlush(MetaData metaData) {}
- @Override
- public void onDisconnect(CustomDao dao) {}
- }
复制代码
CustomDao.ICustomMetaDataHandler接口提供了连接、断连和增删改查元数据的操作。只需要实现这几个接口,在其中实现对数据源的操作,这样元数据绑定框架就会将数据源和UI绑定。
9. 恭喜你 现在你已经成功完成了本篇CodeLab并且学到了:
- 将元数据绑定框架引入工程和初始化。
- 将简单UI组件和数据源绑定。
- 将UI容器组件和数据源绑定。
- 将自定义UI组件和数据源绑定。
- 将UI组件和自定义数据源绑定。
10. 参考