前言
在上一篇HarmonyOS DataBinding 使用指南中,有人提到了元数据绑定框架并提出了疑问,元数据绑定框架跟DataBinding有什么区别?功能上似乎也是做数据绑定,我查阅了官方文档,没有太多的资料,只有Codelabs上有个Demo教程,带着这种疑问,让我们一起来探索一下。
概述
根据官方Demo介绍,元数据绑定框架是基于HarmonyOS SDK开发的一套提供UI和数据源绑定能力的框架。通过使用元数据绑定框架,HarmonyOS应用开发者无需开发繁琐重复的代码即可实现绑定UI和数据源。这跟Databinding功能类似,接下来让我们再来看看它们有什么不同之处。
开始使用
简单UI组件绑定
1.首先,我们在模块的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
- }
- }
2.引用之后我们在MyApplication中对其进行初始化并添加对应注解,具体代码如下:
- /**
- * requireData = true 表示该application需要获取数据
- * exportData = false 表示该application不对外提供数据
- */
- @MetaDataApplication(requireData = true, exportData = false)
- public class MyApplication extends AbilityPackage {
- private static Context context;
- @Override
- public void onInitialize() {
- super.onInitialize();
- mContext = this.getContext();
- //初始化MetaDataFramework
- MetaDataFramework.init(this);
- }
- public static Context getApplication() {
- return context;
- }
- }
其中注解中的requireData表示该application是否需要获取数据,exportData表示该application是否外提供数据,大家可根据自己的需求进行配置。
3.接下来我们需要定义元数据,数据是以Json的格式,而DataBinding则是采用ActiveData对象绑定数据。我们简单的定义两个参数。Json数据采用得是Json Schema定义的一套词汇和规则,我们用这套词汇和规则用来定义Json元数据。最后我们需要将元数据Json文件放在resource/rawfile.jsonschema路径下。
- {
- "id": "com.example.meta-data.time",
- "title": "test",
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "test description",
- "type": "object",
- "properties": {
- "id": {
- "type": "integer"
- },
- "message": {
- "type": "string"
- }
- }
- }
4.在我们XML布局文件中,最外层的Layout中加入元数据绑定的框架的命名空间:xmlns:metaDataBinding,并创建元数据实体,作用跟我们Databinding的
- <?xml version="1.0" encoding="utf-8"?>
- <DirectionalLayout
- xmlns:ohos="http://schemas.huawei.com/res/ohos"
- xmlns:metaDataBinding="http://schemas.huawei.com/res/metaDatabinding"
- ohos:height="match_parent"
- ohos:width="match_parent"
- ohos:orientation="vertical">
- <request-meta-data
- name="TestData"
- schema="com.example.meta-data.time"
- uri="dataability:///com.example.time.db.TestDataAbility"/>
- <Text
- ohos:id="$+id:title_text"
- ohos:height="300"
- ohos:width="match_parent"
- metaDataBinding:text="@{TestData.message}"
- ohos:text_alignment="center"
- ohos:text_color="#FF555555"
- ohos:text_size="50"/>
- </DirectionalLayout>
这时候
言归正传,在
5.接下来需要在代码中请求绑定,我们在AbilitySlice中的onStart方法中添加如下代码:
- @Override
- public void onStart(Intent intent) {
- super.onStart(intent);
- Test alarm = TestOperation.queryFirst(this);
- if (alarm == null) {
- TestOperation.insert(this);
- }
- MetaDataRequestInfo request = new MetaDataRequestInfo.Builder()
- .setMetaDataClass("TestData", TestData.class)
- .setSyncRequest("TestData", true)
- .build();
- MetaDataBinding binding;
- Component mainComponent;
- try {
- // 请求绑定
- binding = AbilityindexpageMetaDataBinding.requestBinding(this, request, null);
- // 获得绑定的界面组件
- mainComponent = binding.getLayoutComponent();
- } catch (DataSourceConnectionException e) {
- mainComponent = LayoutScatter.getInstance(this)
- .parse(ResourceTable.Layout_error_layout, null, false);
- }
- setUIContent((ComponentContainer) mainComponent);
- }
刚才第4点说到,TestData为对应的XML中
- public class TestData extends DataAbilityMetaData {
- public String toMessage(String message) {
- return message + "元数据绑定";
- }
- }
6.配置部分基本完成,接下来就是配置数据库部分。数据库部分内容也比较多,这里只做简单的说明。关于数据库后续会有专门的文章进行详细讲解,欢迎大家订阅关注。
在我们上面XML布局中,
- public class TestDataAbility extends Ability {
- public static final Uri CLOCK_URI = Uri.parse(
- "dataability:///com.example.time.db.TestDataAbility");
- private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD001100, "Demo");
- private OrmContext ormContext = null;
- @Override
- public void onStart(Intent intent) {
- super.onStart(intent);
- HiLog.info(LABEL_LOG, "TestDataAbility onStart");
- DatabaseHelper manager = new DatabaseHelper(this);
- ormContext = manager.getOrmContext(
- TestOrmDatabase.DATABASE_NAME_ALIAS,
- TestOrmDatabase.DATABASE_NAME,
- TestOrmDatabase.class);
- }
- @Override
- public ResultSet query(Uri uri, String[] columns, DataAbilityPredicates predicates) {
- if (uri.equals(CLOCK_URI)) {
- OrmPredicates ormPredicates = DataAbilityUtils.createOrmPredicates(predicates, Test.class);
- return ormContext.query(ormPredicates, columns);
- }
- return null;
- }
- @Override
- public int insert(Uri uri, ValuesBucket value) {
- Test alarm = new Test();
- if (ormContext.insert(alarm.fromValues(value))) {
- ormContext.flush();
- DataAbilityHelper.creator(this, uri).notifyChange(uri);
- return (int) alarm.getRowId();
- }
- return -1;
- }
- @Override
- public int delete(Uri uri, DataAbilityPredicates predicates) {
- return 0;
- }
- @Override
- public int update(Uri uri, ValuesBucket value, DataAbilityPredicates predicates) {
- OrmPredicates ormPredicates;
- if (predicates == null) {
- Integer id = value.getInteger("id");
- if (id == null) {
- return -1;
- }
- value.delete("id");
- ormPredicates = new OrmPredicates(Test.class).equalTo("id", id);
- } else {
- ormPredicates = DataAbilityUtils.createOrmPredicates(predicates, Test.class);
- }
- int rst = ormContext.update(ormPredicates, value);
- DataAbilityHelper.creator(getContext(), uri).notifyChange(uri);
- return rst;
- }
- @Override
- public FileDescriptor openFile(Uri uri, String mode) {
- return null;
- }
- @Override
- public String[] getFileTypes(Uri uri, String mimeTypeFilter) {
- return new String[0];
- }
- @Override
- public PacMap call(String method, String arg, PacMap extras) {
- return null;
- }
- @Override
- public String getType(Uri uri) {
- return null;
- }
- @Override
- protected void onStop() {
- super.onStop();
- if (ormContext != null) {
- ormContext.close();
- }
- }
- }
TestOperation类,是一个数据库的操作类,负责数据库的查询或写入等操作。
- public class TestOperation {
- private static final String COL_MSG = "message";
- private static int idx = 0;
- private static int count = 0;
- public TestOperation() {
- }
- public static void insert(Context context) {
- try {
- int time = Math.abs((int) System.currentTimeMillis());
- ValuesBucket bucket = new ValuesBucket();
- bucket.putString(COL_MSG, "元数据绑定" + idx++);
- DataAbilityHelper.creator(context).insert(TestDataAbility.CLOCK_URI, bucket);
- } catch (DataAbilityRemoteException ex) {
- }
- }
- public static void insertAnAlarm(MetaDataBinding binding) {
- MetaDataRequestInfo.RequestItem requestItem = binding.getRequestInfo().getRequestItem("TestData");
- MetaData metaData = AbilityindexpageMetaDataBinding.createMetaData(requestItem);
- metaData.put(COL_MSG, "count" + count);
- binding.addMetaData(metaData, requestItem);
- count++;
- }
- public static Test queryFirst(Context context) {
- DataAbilityHelper helper = DataAbilityHelper.creator(context);
- ResultSet resultSet = null;
- try {
- resultSet = helper.query(
- TestDataAbility.CLOCK_URI,
- new String[]{COL_MSG},
- null);
- } catch (DataAbilityRemoteException e) {
- }
- Test test = null;
- if (resultSet != null) {
- boolean hasData = resultSet.goToFirstRow();
- if (!hasData) {
- return null;
- }
- test = getQueryResults(resultSet);
- }
- return test;
- }
- private static Test getQueryResults(ResultSet resultSet) {
- Test alarm = new Test();
- for (String column : resultSet.getAllColumnNames()) {
- int index = resultSet.getColumnIndexForName(column);
- alarm.setMessage(getFromColumn(resultSet, index).toString());
- }
- return alarm;
- }
- private static Object getFromColumn(ResultSet resultSet, int index) {
- ResultSet.ColumnType type = resultSet.getColumnTypeForIndex(index);
- switch (type) {
- case TYPE_INTEGER:
- return resultSet.getInt(index);
- case TYPE_FLOAT:
- return resultSet.getDouble(index);
- case TYPE_STRING:
- return resultSet.getString(index);
- case TYPE_BLOB:
- case TYPE_NULL:
- default:
- return null;
- }
- }
- }
TestOrmDatabase类,就是我们对象关系映射数据库的相关操作,具体可看官方文档。
- @Database(entities = {Test.class}, version = 1)
- public abstract class TestOrmDatabase extends OrmDatabase {
- public static final String DATABASE_NAME = "TestOrmDatabase.db";
- public static final String DATABASE_NAME_ALIAS = "TestOrmDatabase";
- }
到目前位置我们整个元数据绑定的开发流程就完整了,下面是展示页面:
Text显示的内容就是我们TestOperation类,在数据库添加的Message的数据( bucket.putString(COL_MSG, “元数据绑定” + idx++))。
UI容器组件绑定
接下来给大家说一下容器组件绑定,容器组件也就是我们的ListContainer,无处不列表,可以说是我们平时用的最多的组件,接下来给大家讲一下ListContainer如何进行绑定。(大致配置与简单UI差不多,下面只列出它们的区别之处)
1.首先我们需要在XML中添加ListContainer组件,我们直接沿用刚才的数据:
- <?xml version="1.0" encoding="utf-8"?>
- <DirectionalLayout
- xmlns:ohos="http://schemas.huawei.com/res/ohos"
- xmlns:metaDataBinding="http://schemas.huawei.com/res/metaDatabinding"
- ohos:height="match_parent"
- ohos:width="match_parent"
- ohos:orientation="vertical">
- <request-meta-data
- name="TestData"
- schema="com.example.meta-data.time"
- uri="dataability:///com.example.time.db.TestDataAbility"/>
- <Text
- ohos:id="$+id:title_text"
- ohos:height="300"
- ohos:width="match_parent"
- ohos:text="容器组件绑定"
- ohos:text_alignment="center"
- ohos:text_color="#FF555555"
- ohos:text_size="50"/>
- <ListContainer
- ohos:id="$+id:list_view"
- ohos:top_margin="10vp"
- ohos:height="match_parent"
- ohos:width="match_parent"
- />
- </DirectionalLayout>
2.跟正常使用一样,我们需要创建继承BaseItemProvider的Provider类:
- public class TestListProvider extends BaseItemProvider {
- private final Context mContext;
- private List<TestRow> mData;
- public TestListProvider(Context mContext) {
- this.mContext = mContext;
- }
- public void initData(List<TestRow> testList) {
- this.mData = testList;
- }
- public void addItems(List<TestRow> alarmList) {
- this.mData.addAll(alarmList);
- mContext.getUITaskDispatcher().asyncDispatch(this::notifyDataChanged);
- }
- @Override
- public int getCount() {
- return mData.size();
- }
- @Override
- public Object getItem(int i) {
- return mData.get(i);
- }
- @Override
- public long getItemId(int i) {
- return i;
- }
- @Override
- public Component getComponent(int i, Component component, ComponentContainer componentContainer) {
- TestRow testRow = mData.get(i);
- if (component == null) {
- Component newComponent = testRow.createComponent();
- testRow.bindComponent(newComponent);
- return newComponent;
- } else {
- testRow.bindComponent(component);
- return component;
- }
- }
- }
3.TestRow表示列表的条目,它持有一个元数据对象,我们对每个item进行数据绑定,获取UI组件及响应点击事件。
- public class TestRow {
- private final AbilitySlice context;
- private final TestData clockMeta;
- public TestRow(AbilitySlice context, MetaData clockMeta) {
- this.context = context;
- this.clockMeta = (TestData) clockMeta;
- }
- public Component createComponent() {
- TestlistitemlayoutMetaDataBinding metaBinding = TestlistitemlayoutMetaDataBinding.createBinding(context, clockMeta);
- Component comp = metaBinding.getLayoutComponent();
- comp.setTag(metaBinding);
- return comp;
- }
- public void bindComponent(Component component) {
- TestlistitemlayoutMetaDataBinding metaBinding = (TestlistitemlayoutMetaDataBinding) component.getTag();
- metaBinding.reBinding(component, clockMeta);
- }
- // public void onClick() {
- // context.present(new XXXSlice(clockMeta), new Intent());
- // }
- }
TestlistitemlayoutMetaDataBinding是我们定义布局后自动生成的MetaDataBinding类,通过createBinding方法将布局与数据进行绑定。
4.接下来看一下item的布局:
- <?xml version="1.0" encoding="utf-8"?>
- <DirectionalLayout
- xmlns:ohos="http://schemas.huawei.com/res/ohos"
- xmlns:metaDataBinding="http://schemas.huawei.com/res/metaDatabinding"
- ohos:height="match_content"
- ohos:width="match_parent"
- ohos:background_element="#000000"
- ohos:orientation="vertical">
- <using-meta-data
- class="com.example.time.bean.TestData"
- name="TestData"
- schema="com.example.meta-data.time"/>
- <DirectionalLayout
- ohos:height="match_content"
- ohos:width="match_parent"
- ohos:background_element="#3c3c3c"
- ohos:bottom_padding="15vp"
- ohos:end_padding="15vp"
- ohos:orientation="vertical"
- ohos:start_padding="15vp"
- ohos:top_padding="15vp">
- <Text
- ohos:id="$+id:title_tv"
- ohos:height="match_content"
- ohos:width="match_parent"
- metaDataBinding:text="@={TestData.message}"
- ohos:text_size="16fp"
- ohos:text_color="#ffffff"
- />
- <Text
- ohos:id="$+id:desc_tv"
- ohos:height="match_content"
- ohos:width="match_parent"
- ohos:top_margin="10vp"
- metaDataBinding:text="@={TestData.message}"
- ohos:text_size="12fp"
- ohos:text_color="#727272"
- />
- </DirectionalLayout>
- </DirectionalLayout>
这里需要注意的是,和普通布局区别在于item的元数据实体为
5.接下来就是在AbilitySlice中进行请求绑定:
- public class IndexPageAbilitySlice extends AbilitySlice implements IMetaDataObserver {
- private ListContainer mListContainer;
- private TestListProvider mTestListProvider;
- @Override
- public void onStart(Intent intent) {
- super.onStart(intent);
- initView();
- }
- private void initView() {
- Test alarm = TestOperation.queryFirst(this);
- if (alarm == null) {
- TestOperation.insert(this);
- }
- // 创建元数据请求对象
- MetaDataRequestInfo request = new MetaDataRequestInfo.Builder()
- .setMetaDataClass("TestData", TestData.class)
- .setSyncRequest("TestData", false)
- .build();
- MetaDataBinding binding;
- Component mainComponent;
- try {
- // 请求绑定
- binding = AbilityindexpageMetaDataBinding.requestBinding(this, request, this);
- // 获得绑定的界面组件
- mainComponent = binding.getLayoutComponent();
- } catch (DataSourceConnectionException e) {
- mainComponent = LayoutScatter.getInstance(this)
- .parse(ResourceTable.Layout_error_layout, null, false);
- }
- setUIContent((ComponentContainer) mainComponent);
- mListContainer = (ListContainer) findComponentById(ResourceTable.Id_list_view);
- mTestListProvider = new TestListProvider(this);
- }
- @Override
- public void onActive() {
- super.onActive();
- }
- @Override
- public void onForeground(Intent intent) {
- super.onForeground(intent);
- }
- @Override
- public void onDataLoad(List<MetaData> list, MetaDataRequestInfo.RequestItem requestItem) {
- if (list == null || requestItem == null) {
- return;
- }
- if (mListContainer != null) {
- mTestListProvider.initData(createAlarms(this, list));
- mListContainer.setItemProvider(mTestListProvider);
- }
- }
- private List<TestRow> createAlarms(AbilitySlice context, List<MetaData> dataList) {
- List<TestRow> list = new ArrayList<>();
- for (MetaData metaData : dataList) {
- TestRow item = new TestRow(context, metaData);
- list.add(item);
- }
- return list;
- }
- @Override
- public void onDataChange(List<MetaData> list, List<MetaData> list1, List<MetaData> list2, MetaDataRequestInfo.RequestItem requestItem) {
- if (list == null) {
- return;
- }
- mTestListProvider.addItems(createAlarms(this, list));
- }
- }
容器组件绑定的话,我们实现了IMetaDataObserver接口,主要用于数据的加载及数据更新,在onDataLoad将Provider跟ListContainer进行绑定,如数据有发生变化,则onDataChange对列表进行更新,而在setSyncRequest传参中我们改为false,表示为异步请求,因为IMetaDataObserver方法会异步执行,如果传Ture的话,会在onDataLoad方法执行之后requestBinding方法才会返回,之后在请求绑定requestBinding方法中第三个参数,dataCallback传入this进行监听。
6.最终实现效果
7.添加数据只需要调用我们之前的**TestOperation.insertAnAlarm(binding)**方法就可以进行数据添加:
元数据表达式
在xml文件中进行元数据绑定时 metaDataBinding会用到多种表达式,具体用法如下:
总结
元数据绑定的简单使用就介绍到这里,这里只跟大家展示了我们最常用的两种布局的绑定,我们还可以进行自定义UI的绑定、自定义数据源等等更多的用法等着大家一起来探索。
回到我们最初的问题,元数据绑定框架跟DataBinding有什么区别?我个人理解是,元数据绑定框架是基于元数据,而DataBinding则是绑定ActiveData(我们专栏有专门讲解ActiveData的文章,欢迎大家前去查阅。),两者的功能及数据源是不一样的,可以针对自己的业务需求进行选择。
但在Demo的编写过程中,也发现了一个问题,同一个页面普通UI组件和容器组件不能同事绑定,问题也时处在我们容器组件第5点所说的,实现了IMetaDataObserver接口进行异步请求,这点也希望跟大家一起继续探索,欢迎在评论区共同探讨。