您现在的位置是:亿华云 > 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)