您现在的位置是:亿华云 > IT科技
手把手教HDC2021趣味闯关赛平行视界服务流转
亿华云2025-10-03 06:22:58【IT科技】3人已围观
简介想了解更多内容,请访问:和华为官方合作共建的鸿蒙技术社区https://harmonyos.51cto.com一、前言上一篇 轻松玩转HDC2021趣味闯关赛平行视界服务流转 帖子里是基于Codela
想了解更多内容,手把手教视界请访问:
和华为官方合作共建的味闯鸿蒙技术社区
https://harmonyos.51cto.com
一、前言
上一篇 轻松玩转HDC2021趣味闯关赛平行视界服务流转 帖子里是关赛基于Codelabs里Java电影卡片,平行视界Sample集成的平行,此帖从空白项目开始,服务一步步教你如何在项目里,流转学到列表显示,手把手教视界平行视界,味闯服务卡片,关赛服务流转各知识点。平行下面大慨说一下项目的服务开发流程:
实现List显示, 点击某项跳转到RightAbility界面
添加JS服务卡片, 点击跳转到List列表
平行视界, 点击List列表, 左边显示列表,右边显示详情图片
服务流转, 在详情,点击连接图标, 选择流转设备; 再点击流转图标==
二、实现效果
B站视频:https://www.bilibili.com/video/BV1tL4y1q7A6/

三、流转工程搭建
打开DevEco Studio 3.0.0.600开发工具,手把手教视界 点击菜单File -> New -> New Project,味闯 弹出以下框,按截图选择创建:

下一步后的关赛界面以下,请按照标注操作。

空模板项目到此,就创建完成了,下面我们来按照上面说的步骤一步一步来完成。
四、工程讲解
1. 实现List显示, 点击某项跳转到RightAbility界面
首先把List列表显示出来,下面是List列表的XML布局文件,由于List列表项是数据驱动的,这里只提供List容器,高防服务器并给予id就可以。
<?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:alignment="center" ohos:orientation="vertical"> <ListContainer ohos:id="$+id:list" ohos:height="match_parent" ohos:width="match_parent" ohos:orientation="vertical"/> </DirectionalLayout>从上面效果图可以看出,列表项左边显示图片,右边显示文字,下面有一条分割线,XML布局文件以下:
<?xml version="1.0" encoding="utf-8"?> <DirectionalLayout xmlns:ohos="http://schemas.huawei.com/res/ohos" ohos:height="match_content" ohos:width="match_parent" ohos:orientation="vertical"> <DirectionalLayout ohos:height="100vp" ohos:width="match_parent" ohos:padding="10vp" ohos:orientation="horizontal"> <Image ohos:id="$+id:img" ohos:height="match_parent" ohos:scale_mode="zoom_center" ohos:width="120vp"></Image> <Text ohos:id="$+id:title" ohos:height="match_parent" ohos:width="match_content" ohos:padding="5vp" ohos:text_size="16fp" ohos:left_margin="10vp"/> </DirectionalLayout> <Component ohos:height="1vp" ohos:width="match_parent" ohos:background_element="#CCCCCC"/> </DirectionalLayout>List列表的页面布局就说完了,下面来说一下Java代码:先介绍一下列表项的实体类,也就两个属性,一个是图片,一个是标题
import ohos.media.image.PixelMap; public class Item { // 显示图片 private PixelMap img; // 显示每项标题 private String title; public PixelMap getImg() { return img; } public void setImg(PixelMap img) { this.img = img; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } }下面介绍一下List列表的核心部分就是适配器类的实现:大部分代码都有注释,适配器类首先要继承BaseItemProvider 基础类,然后实现里面相应的方法。
/** * 列表适配器类 */ public class ListAdapter extends BaseItemProvider { // 上下文 private static Context context; // 数据项列表 private List<Item> items; // 自定义事件处理 private MyEventHandle myEventHandle; /** * 构造方法 * @param items * @param context */ public ListAdapter(List<Item> items, Context context) { this.items = items == null ? new ArrayList() : items; this.context = context; } /** * 获取列表有多少项 * @return */ @Override public int getCount() { return items == null ? 0 : items.size(); } /** * 获取当前项数据 * @param i * @return */ @Override public Item getItem(int i) { return this.items.get(i); } /** * 获取当前Id * @param i * @return */ @Override public long getItemId(int i) { return i; } /** * 获取组件内容 * @param i * @param component * @param componentContainer * @return */ @Override public Component getComponent(int i, Component component, ComponentContainer componentContainer) { ViewHolder viewHolder = null; Component cmp = component; if (cmp == null) { cmp = LayoutScatter.getInstance(context).parse(ResourceTable.Layout_item, null, false); // 初始化项布局 viewHolder = new ViewHolder(); // 获取图片组件 viewHolder.img = (Image)cmp.findComponentById(ResourceTable.Id_img); // 获取标题组件 viewHolder.title = (Text)cmp.findComponentById(ResourceTable.Id_title); // 缓存起来 cmp.setTag(viewHolder); } else { viewHolder = (ViewHolder) cmp.getTag(); } viewHolder.img.setPixelMap(items.get(i).getImg()); viewHolder.title.setText(items.get(i).getTitle()); viewHolder.img.setClickedListener(listener -> itemClick(i)); viewHolder.title.setClickedListener(listener -> itemClick(i)); return cmp; } public void replace(Collection<Item> listConstructor) { if (listConstructor == null) { return; } this.items = null; // 重新初始化listContainer中的数据 this.items = new ArrayList<>(0); // 将重新得到的项数据放到listContainer中 this.items.addAll(listConstructor); // 刷新listContainer,调用getComponent方法重新设置页面元素布局 notifyDataChanged(); } /** * 内部类,封装列表项组件 */ private static class ViewHolder { // 显示图片 Image img; // 显示标题 Text title; } /** * 点击图片或标题事件 * @param index */ private void itemClick(int index) { // 初始化处理程序 initHandler(); // 获取内部事件 InnerEvent event = InnerEvent.get(1, 0, index); myEventHandle.sendEvent(event); } /** * 初始化处理程序 */ private void initHandler() { EventRunner runner = EventRunner.getMainEventRunner(); if (runner == null) { return; } myEventHandle = new MyEventHandle(runner); } /** * 自定义处理事件 */ public static class MyEventHandle extends EventHandler { MyEventHandle(EventRunner runner) throws IllegalArgumentException { super(runner); } @Override protected void processEvent(InnerEvent event) { super.processEvent(event); int eventId = event.eventId; int index = (Integer) event.object; if (eventId == 1) { IntentParams intentParams = new IntentParams(); intentParams.setParam("index", index); // 选择列表项下标 // 跳转到 RightAbility 分屏 Intent intent = new Intent(); intent.setParams(intentParams); ElementName element = new ElementName("", context.getBundleName(), RightAbility.class.getName()); intent.setElement(element); context.startAbility(intent, 0); } } } }项目用的数据是静态数据,在一个Utils封装好的,以下:
public class Utils { private static final HiLogLabel TAG = new HiLogLabel(HiLog.LOG_APP, 0xD001400, "Utils"); private static final List<Integer> PICTURE_IDS = Arrays.asList(ResourceTable.Media_1, ResourceTable.Media_2, ResourceTable.Media_3, ResourceTable.Media_4, ResourceTable.Media_5, ResourceTable.Media_6, ResourceTable.Media_7, ResourceTable.Media_8, ResourceTable.Media_9, ResourceTable.Media_10); private static List<PixelMap> resourcePixelMaps; public static void transResourceIdsToListOnce(Context context) { resourcePixelMaps = new ArrayList<>(0); // Set the pixel map for (int index: PICTURE_IDS) { InputStream source = null; ImageSource imageSource; try { source = context.getResourceManager().getResource(index); imageSource = ImageSource.create(source, null); resourcePixelMaps.add(imageSource.createPixelmap(null)); } catch (IOException | NotExistException e) { HiLog.error(TAG, "Get Resource PixelMap error"); } finally { try { assert source != null; source.close(); } catch (IOException e) { HiLog.error(TAG, "getPixelMap source close error"); } } } } /** * 封装列表项数据 * @return */ public static List<Item> getItems() { List<Item> items = new ArrayList<>(); int index = 1; for (PixelMap pixelMap : resourcePixelMaps) { Item item = new Item(); item.setImg(pixelMap); item.setTitle("测试标题 " + index); items.add(item); index++; } return items; } /** * 根据下标获取列表项数据 * @param index * @return */ public static Item getItem(int index) { List<Item> items = getItems(); return items.get(index); } }在MainAbilitySlice加载显示出列表容器里的内容:
public class MainAbilitySlice extends AbilitySlice { // 列表容器 private static ListContainer listContainer; // 列表适配器类 private static ListAdapter listAdapter; // 上下文 private static Context context; @Override public void onStart(Intent intent) { super.onStart(intent); super.setUIContent(ResourceTable.Layout_ability_main); context = getContext(); // 初始化界面 initView(); // 将 Media 中的图像转换为 List<PixelMap>。 使用默认大小 Utils.transResourceIdsToListOnce(context); // UI线程更新列表数据 getUITaskDispatcher().delayDispatch(() -> initData(Utils.getItems()), 10); } /** * 初始化界面 */ private void initView() { // 获取List容器 listContainer = (ListContainer) findComponentById(ResourceTable.Id_list); // 初始化列表适配器 listAdapter = new ListAdapter(null, context); // 设置列表容器项数据提供者 listContainer.setItemProvider(listAdapter); } /** * 更新界面数据 * @param items */ public static void initData(List<Item> items) { context.getUITaskDispatcher().asyncDispatch(() -> { listContainer.setItemProvider(listAdapter); listAdapter.replace(items); }); } }这样主界面的云南idc服务商List列表就完成了,项目用的素材图片是放在resources -> media下的,下来介绍一下点击列表项跳转到详情页面,这里我们要创建一个Page Ability, 取名为RightAbility,同时在使用平行视界时,这个Ability显示在平板的右边。

界面布局分为上下显示,上面显示标题和后面用到的服务流转图标,下面显示点击的图片。
<?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:alignment="center" ohos:orientation="vertical"> <DependentLayout ohos:id="$+id:container_title" ohos:height="50vp" ohos:padding="10vp" ohos:width="match_parent"> <Text ohos:id="$+id:right_title" ohos:height="match_parent" ohos:width="match_content" ohos:padding="5vp" ohos:text_size="16fp" ohos:left_margin="10vp"/> <Image ohos:id="$+id:imgConnect" ohos:left_margin="10vp" ohos:right_margin="10vp" ohos:height="30vp" ohos:width="30vp" ohos:background_element="$media:connect" ohos:left_of="$id:imgCirculation"/> <Image ohos:id="$+id:imgCirculation" ohos:height="30vp" ohos:width="30vp" ohos:right_margin="10vp" ohos:background_element="$media:circulation" ohos:align_parent_right="true"/> </DependentLayout> <Image ohos:id="$+id:right_img" ohos:height="match_parent" ohos:scale_mode="zoom_center" ohos:width="match_parent"></Image> </DirectionalLayout>在新Page Ability接收到跳转过来的参数,显示相应的标题和图片。
public class RightAbilitySlice extends AbilitySlice { private static Context context; // 上下文 private static int paramIndex; // 页面跳转参数 @Override public void onStart(Intent intent) { super.onStart(intent); super.setUIContent(ResourceTable.Layout_ability_right); context = getContext(); // 如果页面跳转参数index为空, 默认为0 paramIndex = intent.getIntParam("index", 0); Item item = Utils.getItem(paramIndex); // 初始化界面 initView(item); } private void initView(Item item) { Image img = (Image)findComponentById(ResourceTable.Id_right_img); img.setPixelMap(item.getImg()); Text title = (Text)findComponentById(ResourceTable.Id_right_title); title.setText(item.getTitle()); Image btnSelect = (Image)findComponentById(ResourceTable.Id_imgConnect); Image btnShare = (Image)findComponentById(ResourceTable.Id_imgCirculation); btnSelect.setClickedListener(va -> { // 打开设备选择框(连接) //deviceDialog.showDeviceList(); }); btnShare.setClickedListener(va -> { // 流转 //circulation(); }); } }添加JS服务卡片, 点击跳转到List列表。
先创建一个JS服务卡片,步骤以下:



默认服务卡片里面内容,不是我要的效果,我简单修改一下后:
<div class="card_root_layout"> <div class="button_containers"> <div class="item_first_container"> <div class="button_left"> <image src="/common/images/1.jpg" onclick="routerEvent"></image> </div> <div class="button_right"> <image src="/common/images/2.jpg" onclick="routerEvent"></image> </div> </div> <div class="item_second_container"> <div class="button_left"> <image src="/common/images/3.jpg" onclick="routerEvent"></image> </div> <div class="button_right"> <image src="/common/images/4.jpg" onclick="routerEvent"></image> </div> </div> </div> </div> .card_root_layout { flex-direction: column; align-items: center; justify-content: center; width: 100%; height: 100%; padding: 10px; background-color: #FFFFFF; } .button_containers { flex-direction: column; align-items: center; justify-content: center; } .item_first_container { flex-weight: 0.4; flex-direction: row; align-items: center; justify-content: center; } .item_second_container { flex-weight: 0.4; flex-direction: row; align-items: center; justify-content: center; } .button_left { flex-weight: 0.5; flex-direction: column; align-items: center; justify-content: center; padding: 4px; } .button_right { flex-weight: 0.5; flex-direction: column; align-items: center; justify-content: center; padding: 4px; } { "actions": { "routerEvent": { "action": "router", "abilityName": "com.army.study.MainAbility" } } }简单的服务卡片就修改好了,点击服务卡片,跳转到List列表界面。
平行视界, 点击List列表, 左边显示列表,右边显示详情图片。
实现平行视界,注意三点就可以了,第一点,服务器托管平行视界目前只能在两个Java Page Ability实现,第二点在resources -> rawfile目录下添加一个文件easygo.json, 第三点修改config.json配置,就可以了,下面我们就一点一点来完成平行视界效果。
第一点:上面已经创建好两个Java Page Ability了,一个是MainAbility, 一个是RightAbility。
第二点:添加easygo.json内容为:
{ "easyGoVersion": "1.0", "client": "com.army.study", // 保持和config.json bundleName一致 "logicEntities": [ { "head": { "function": "magicwindow", "required": "true" }, "body": { "mode": "1", "abilityPairs": [ { "from": "com.army.study.MainAbility", // 显示在平行视界左边的列表页面 "to": "com.army.study.RightAbility" // 显示在平行视界右边的详情页面 } ], "UX": { "isDraggable": "true", "supportRotationUxCompat": "true", "supportDraggingToFullScreen": "ALL" } } } ] }第三点:修改config.json在moudle节点下添加以下:
"metaData": { "customizeData": [ { "name": "EasyGoClient", "value": "true" } ] }平行视界效果就完成了。
服务流转, 在详情,点击连接图标, 选择流转设备; 再点击流转图标。
服务流转也是有几点注意的,第一点当然就是授权了,除了在config.json配置了权限申请,还要在应用入口加上动态授权,第二点就是实现流转的Abitily和AbilitySlic都要实现IAbilityContinuation接口,第三点就是显示当然环境下,在线设备可以流转到的设备,这里使用到了Codelabs平行视界Sample里封装好的DeviceDialog类,里面使用的流转任务管理服务管理类IContinuationRegisterManager, 简化了很多代码。下面就开始一 一介绍吧。第一点授权,在config.json里module节点下添加以下配置:
"reqPermissions": [ { "name": "ohos.permission.DISTRIBUTED_DATASYNC", "usedScene": { "ability": [ "com.army.study.MainAbility", "com.army.study.RightAbility" ], "when": "always" } }, { "name": "ohos.permission.GET_DISTRIBUTED_DEVICE_INFO", "usedScene": { "ability": [ "com.army.study.MainAbility", "com.army.study.RightAbility" ], "when": "always" } } ]在应用入口MainAbility里onStart()方法添加动态授权:
// 声明跨端迁移访问的权限 requestPermissionsFromUser(new String[]{ SystemPermission.DISTRIBUTED_DATASYNC}, 0);第二点在RightAbility和RightAbilitySlice多实现IAbilityContinuation接口,其实RightAbility实现接口后,在实现方法里面返回true就可以,具体逻辑是在RightAbilitySlice中实现。
public class RightAbility extends Ability implements IAbilityContinuation { @Override public void onStart(Intent intent) { super.onStart(intent); super.setMainRoute(RightAbilitySlice.class.getName()); } @Override public boolean onStartContinuation() { return true; } @Override public boolean onSaveData(IntentParams intentParams) { return true; } @Override public boolean onRestoreData(IntentParams intentParams) { return true; } @Override public void onCompleteContinuation(int i) { } } public class RightAbilitySlice extends AbilitySlice implements IAbilityContinuation { private boolean isCirculation = false; // 是否流转中 private static String selectDeviceId; // 选择的设备id private DeviceDialog deviceDialog; // 设备选择 private static Context context; // 上下文 private static int paramIndex; // 页面跳转参数 private static int removeIndex; // 流转参数 @Override public void onStart(Intent intent) { super.onStart(intent); super.setUIContent(ResourceTable.Layout_ability_right); context = getContext(); // 如果页面跳转参数index为空,说明是流转过来参数, 使用流转removeIndex paramIndex = intent.getIntParam("index", removeIndex); Item item = Utils.getItem(paramIndex); // 初始化界面 initView(item); // 初始化设备选择 deviceDialog = new DeviceDialog(getContinuationRegisterManager(),RightAbilitySlice.this); } private void initView(Item item) { Image img = (Image)findComponentById(ResourceTable.Id_right_img); img.setPixelMap(item.getImg()); Text title = (Text)findComponentById(ResourceTable.Id_right_title); title.setText(item.getTitle()); Image btnSelect = (Image)findComponentById(ResourceTable.Id_imgConnect); Image btnShare = (Image)findComponentById(ResourceTable.Id_imgCirculation); btnSelect.setClickedListener(va -> { // 打开设备选择框(连接) deviceDialog.showDeviceList(); }); btnShare.setClickedListener(va -> { // 流转 circulation(); }); } /** * 流转 */ private void circulation() { if (isCirculation) { Utils.createToastDialog(context, "正在流转,请稍后再试!"); return; } if (selectDeviceId == null || "".equals(selectDeviceId)) { // 选择设备以后才能流转(迁移) Utils.createToastDialog(context, "请选择连接设备后进行流转操作!"); return; } isCirculation = true; continueAbility(selectDeviceId); } /** * 设置选择设备Id * @param deviceId */ public static void setDeviceId(String deviceId) { selectDeviceId = deviceId; } @Override public void onActive() { super.onActive(); } @Override public void onForeground(Intent intent) { super.onForeground(intent); } @Override public boolean onStartContinuation() { return true; } @Override public boolean onSaveData(IntentParams intentParams) { // 流转前保存index intentParams.setParam("removeIndex", paramIndex); return true; } @Override public boolean onRestoreData(IntentParams intentParams) { // 获取流转过来的index removeIndex = Integer.parseInt(intentParams.getParam("removeIndex").toString()); return true; } @Override public void onCompleteContinuation(int i) { } }这里说一下详情页参数index,有两个地方传递了,一个是在单设备时,从列表点击跳转到详情页,传递了参数index,另一个是服务流转到另一个设备时,流转前通过onSaveData保存index,到另一个设备通过onRestoreData获取到流转过来的index。
第三点就是使用封装好的DeviceDialog类,代码不多,我都添加注释了,把这个类理解了,分布式工作就省了不少代码了。
public class DeviceDialog { private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD001100, "设备对话框"); // 上下文 private final Context context; // 获取流转任务管理服务管理类 private final IContinuationRegisterManager continuationRegisterManager; // 注册传输任务管理服务后返回的能力令牌 private int abilityToken; // 用户在设备列表中选择设备后返回的设备ID private String selectDeviceId; /** * 初始化设备对话框,设置传输任务管理服务管理类,并注册传输任务管理服务管理类。 * @param continuationRegisterManager * @param slice */ public DeviceDialog(IContinuationRegisterManager continuationRegisterManager, Context slice) { this.continuationRegisterManager = continuationRegisterManager; this.context = slice; // 注册流转任务管理服务管理类 registerManager(); } /** * 注册流转任务管理服务管理类 */ private void registerManager() { // 增加过滤条件 ExtraParams params = new ExtraParams(); String[] devTypes = new String[]{ ExtraParams.DEVICETYPE_SMART_PAD, ExtraParams.DEVICETYPE_SMART_PHONE}; params.setDevType(devTypes); continuationRegisterManager.register(context.getBundleName(), params, callback, requestCallback); } // 设置流转任务管理服务设备状态变更的回调 private final IContinuationDeviceCallback callback = new IContinuationDeviceCallback() { @Override public void onDeviceConnectDone(String str, String str1) { // 在用户选择设备后设置设备ID selectDeviceId = str; continuationRegisterManager.updateConnectStatus(abilityToken, selectDeviceId, DeviceConnectState.CONNECTED.getState(), null); // 返回设备ID returnDeviceId(); } @Override public void onDeviceDisconnectDone(String str) { } }; // 设置注册流转任务管理服务回调 private final RequestCallback requestCallback = new RequestCallback() { @Override public void onResult(int result) { abilityToken = result; } }; /** * 返回设备ID */ private void returnDeviceId() { HiLog.info(LABEL_LOG, "deviceid::" + selectDeviceId); // 将选择设备Id, 告诉RightAbilitySlice RightAbilitySlice.setDeviceId(selectDeviceId); } /** * 打开设备选择框 * * @since 2021-09-10 */ public void showDeviceList() { // 设置过滤设备类型 ExtraParams params = new ExtraParams(); String[] devTypes = new String[]{ ExtraParams.DEVICETYPE_SMART_PAD, ExtraParams.DEVICETYPE_SMART_PHONE}; params.setDevType(devTypes); // 注册 continuationRegisterManager.register(context.getBundleName(), params, callback, requestCallback); // 显示选择设备列表 continuationRegisterManager.showDeviceList(abilityToken, params, null); } /** * 断开流转任务管理服务 * * @since 2021-09-10 */ public void clearRegisterManager() { // 解注册流转任务管理服务 continuationRegisterManager.unregister(abilityToken, null); // 断开流转任务管理服务连接 continuationRegisterManager.disconnect(); } }到这里就介绍完我说的四个步骤了,帖子有些长,要有点耐心看才行,如果不能集中看完,也可以直接同步源码,先运行起来,看看效果,然后再一步一步的去理解就,最后合在一起就成了。
想了解更多内容,请访问:
和华为官方合作共建的鸿蒙技术社区
https://harmonyos.51cto.com
很赞哦!(8215)
上一篇: 戴尔科技集团XOps三重奏 助力企业实现数字化转型
下一篇: 光缆连接在智能建筑中的应用优势