-

以下根据实际的开发样例来展示如何在已有的 HarmonyOS 工程中添加一个智能穿戴模块。

如图所示,这是一个睡眠检测应用,应用分为主界面和详情界面,可以选择使用 PageSlider 实现界面间的切换。PageSlider 是一个布局管理器,用于实现左右滑动以及上下滑动的翻页效果。

图1 开发样例效果图 点击放大

  1. 在 DevEco Studio 上方的导航栏中,依次点击“File > New > Module”,在“Device”中选择“Wearable”,添加一个新模块。

  1. 在左侧的“Project”窗口,打开“entry > src > main > resources > base”,右键点击“base”文件夹,选择“New > Directory”,命名为“layout”。

  1. 右键点击“layout”文件夹,选择“New > File”,新建两个UI布局文件,分别命名为“layout_sleep.xml”和“layout_detail.xml”。

主界面的UI布局文件是“layout_sleep.xml”,其完整示例代码如下:

   <?xml version="1.0" encoding="utf-8"?>
   <DirectionalLayout xmlns:ohos="http://schemas.huawei.com/res/ohos"
                 ohos:width="match_parent"
                 ohos:height="match_parent"
                 ohos:background_element="#FF000000"
                 ohos:orientation="vertical">

    
       <Image
               ohos:id="$+id:sleep_moon_img"
               ohos:width="46vp"
               ohos:height="46vp"
               ohos:top_margin="11vp"
               ohos:layout_alignment="horizontal_center"/>

    
       <Text
               ohos:width="match_parent"
               ohos:height="19.5vp"
               ohos:alpha="0.66"
               ohos:layout_alignment="horizontal_center"
               ohos:text_alignment="center"
               ohos:text="$string:sleep"
               ohos:text_color="$color:sleep_text_white"
               ohos:text_size="16vp"/>

    
       <DirectionalLayout xmlns:ohos="http://schemas.huawei.com/res/ohos"
                     ohos:width="match_content"
                     ohos:height="65vp"
                     ohos:top_margin="8vp"
                     ohos:layout_alignment="horizontal_center"
                     ohos:orientation="horizontal">
           <Text
                   ohos:id="$+id:sleep_hour_text"
                   ohos:width="match_content"
                   ohos:height="match_content"
                   ohos:layout_alignment="center"
                   ohos:text_alignment="center"
                   ohos:text="$string:dash"
                   ohos:text_color="$color:sleep_text_white"
                   ohos:text_size="58vp"
           />
           <Text
                   ohos:width="match_content"
                   ohos:height="match_content"
                   ohos:left_margin="2vp"
                   ohos:alpha="0.66"
                   ohos:layout_alignment="bottom"
                   ohos:bottom_padding="9.5vp"
                   ohos:text="$string:hour"
                   ohos:text_color="$color:sleep_text_white"
                   ohos:text_size="16vp"
           />
           <Text
                   ohos:id="$+id:sleep_min_text"
                   ohos:width="match_content"
                   ohos:height="match_content"
                   ohos:left_margin="2vp"
                   ohos:layout_alignment="center"
                   ohos:text_alignment="center"
                   ohos:text="$string:double_dash"
                   ohos:text_color="$color:sleep_text_white"
                   ohos:text_size="58vp"
           />
           <Text
                   ohos:width="match_content"
                   ohos:height="match_content"
                   ohos:left_margin="2vp"
                   ohos:alpha="0.66"
                   ohos:layout_alignment="bottom"
                   ohos:bottom_padding="9.5vp"
                   ohos:text="$string:minute"
                   ohos:text_color="$color:sleep_text_white"
                   ohos:text_size="16vp"
           />
       </DirectionalLayout>
       <DirectionalLayout xmlns:ohos="http://schemas.huawei.com/res/ohos"
                     ohos:width="match_content"
                     ohos:height="25vp"
                     ohos:top_margin="20.5vp"
                     ohos:layout_alignment="horizontal_center"
                     ohos:orientation="horizontal">
           <Text
                   ohos:width="match_content"
                   ohos:height="19.5vp"
                   ohos:text="$string:goal"
                   ohos:alpha="0.66"
                   ohos:text_alignment="bottom"
                   ohos:bottom_margin="1vp"
                   ohos:text_color="$color:sleep_text_white"
                   ohos:text_size="16vp"
           />
           <Text
                   ohos:id="$+id:sleep_goal_text"
                   ohos:width="match_content"
                   ohos:height="match_parent"
                   ohos:left_margin="2vp"
                   ohos:text="$string:target_sleep_time"
                   ohos:text_weight="600"
                   ohos:text_color="$color:sleep_text_white"
                   ohos:bottom_padding="2vp"
                   ohos:text_size="21vp"
           />
           <Text
                   ohos:width="match_content"
                   ohos:height="19.5vp"
                   ohos:left_margin="2vp"
                   ohos:alpha="0.66"
                   ohos:text="$string:hour"
                   ohos:text_color="$color:sleep_text_white"
                   ohos:text_size="16vp"
           />
       </DirectionalLayout>
   </DirectionalLayout>

详情界面的 UI 布局文件是“layout_detail.xml”,其完整示例代码如下:

   <?xml version="1.0" encoding="utf-8"?>
   <DirectionalLayout xmlns:ohos="http://schemas.huawei.com/res/ohos"
                 ohos:width="match_parent"
                 ohos:height="match_parent"
                 ohos:orientation="vertical"
                 ohos:background_element="#FF000000">
       <Text
               ohos:id="$+id:detail_nodata_date"
               ohos:width="match_content"
               ohos:height="23vp"
               ohos:top_margin="20vp"
               ohos:layout_alignment="horizontal_center"
               ohos:text_alignment="bottom"
               ohos:text_color="$color:sleep_text_white"
               ohos:text_weight="600"
               ohos:text_size="19vp"/>

    
       <Image
               ohos:id="$+id:detail_nodata_img"
               ohos:width="46vp"
               ohos:height="46vp"
               ohos:top_margin="25vp"
               ohos:layout_alignment="horizontal_center"
               ohos:scale_type="scale_to_center"/>

    
       <Text
               ohos:width="match_content"
               ohos:height="match_content"
               ohos:alpha="0.66"
               ohos:top_margin="12vp"
               ohos:layout_alignment="horizontal_center"
               ohos:text_alignment="center"
               ohos:text="$string:no_data"
               ohos:text_color="$color:sleep_text_white"
               ohos:text_size="16vp"/>
       <Text
               ohos:width="match_content"
               ohos:height="match_content"
               ohos:alpha="0.66"
               ohos:layout_alignment="horizontal_center"
               ohos:text_alignment="center"
               ohos:text="$string:wearing_watch_tips"
               ohos:text_color="$color:sleep_text_white"
               ohos:text_size="16vp"/>
   </DirectionalLayout>

  1. 在左侧项目文件栏中,选择“entry > src > main > java > 应用包名 > slice”,在对应的 AbilitySlice 文件的 onStart 里,使用代码创建 PageSlider,添加这两个相应的界面。

   public class SleepPageSlice extends AbilitySlice {
       private static final String TAG = "SleepPageSlice";

    
       private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0, TAG);

    
       private List<ComponentOwner> list = new ArrayList<>();

    
       private PageSliderProvider provider = new PageSliderProvider() {
           @Override
           public int getCount() {
               return list.size();
           }

    
           @Override
           public Object createPageInContainer(ComponentContainer componentContainer, int index) {
               if (index >= list.size() || componentContainer == null) {
                   HiLog.error(LABEL, "instantiateItem index error");
                   return Optional.empty();
               }
               ComponentOwner container = list.get(index);
               componentContainer.addComponent(container.getComponent());
               container.instantiateComponent();
               return container.getComponent();
           }

    
           @Override
           public void destroyPageFromContainer(ComponentContainer componentContainer, int index, Object object) {
               HiLog.info(LABEL, "destroyItem index:" + index);
               if (index >= list.size() || componentContainer == null) {
                   return;
               }
               Component content = list.get(index).getComponent();
               componentContainer.removeComponent(content);
               return;
           }

    
           @Override
           public boolean isPageMatchToObject(Component component, Object object) {
               return component == object;
           }

    
           @Override
           public void startUpdate(ComponentContainer container) {
               super.startUpdate(container);
               HiLog.info(LABEL, "startUpdate");
           }
       };

    
       @Override
       public void onStart(Intent intent) {
           super.onStart(intent);
           HiLog.info(LABEL, "onStart");

    
           // 添加子页面
           list.add(new SleepComponentOwner(this));
           list.add(new DetailComponentOwner(this));

    
           // 设置主界面
           DirectionalLayout layout = new DirectionalLayout(this);
           ComponentContainer.LayoutConfig config = new ComponentContainer.LayoutConfig(
                   ComponentContainer.LayoutConfig.MATCH_PARENT,
                   ComponentContainer.LayoutConfig.MATCH_PARENT);
           layout.setLayoutConfig(config);

    
           // 使用PageSlider做滑动效果
           PageSlider slider = new PageSlider(this);
           ComponentContainer.LayoutConfig sliderConfig = new ComponentContainer.LayoutConfig(
                   ComponentContainer.LayoutConfig.MATCH_PARENT,
                   ComponentContainer.LayoutConfig.MATCH_PARENT);
           slider.setLayoutConfig(sliderConfig);
           slider.setOrientation(DirectionalLayout.VERTICAL);
           slider.setProvider(provider);

    
           layout.addComponent(slider);

    
           setUIContent(layout);
       }
   }

  1. 增加 ComponentOwner 容器接口和两个页面的实现方式,如下是容器的接口 ComponentOwner.java。

   public interface ComponentOwner {
       // 获取存放的component
       Component getComponent();

    
       // 当包含的component被添加到容器时回调
       void instantiateComponent();
   }

  1. 增加第一个页面 SleepComponentOwner 和第二个页面 DetailComponentOwner,这两个页面从 xml 里加载。以下是首页 SleepComponentOwner 的实现。

   public class SleepComponentOwner implements ComponentOwner {
       private static final String TAG = "SleepViewContainer";

    
       private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0, TAG);

    
       // 目标睡眠时长默认值,单位:分钟
       private static final int DEFAULT_SLEEP_TARGET_TIME = 480;

    
       // 睡眠时长默认值,单位:分钟
       private static final int DEFAULT_SLEEP_TIME = 0;

    
       private CircleProgressDrawTask drawTask;

    
       private AbilityContext myContext;

    
       private Component root;

    
       public SleepComponentOwner(AbilityContext context) {
           init(context);
       }

    
       private void init(AbilityContext context) {
           myContext = context;
           LayoutScatter scatter = LayoutScatter.getInstance(context);
           root = scatter.parse(ResourceTable.Layout_layout_sleep, null, false);
           drawTask = new CircleProgressDrawTask(root);
           drawTask.setMaxValue(DEFAULT_SLEEP_TARGET_TIME);

    
           Component imageView = root.findComponentById(ResourceTable.Id_sleep_moon_img);
           imageView.setBackground(new VectorElement(context, ResourceTable.Graphic_ic_icon_moon));
       }

    
       @Override
       public Component getComponent() {
           return root;
       }

    
       @Override
       public void instantiateComponent() {
           return;
       }
   }

以下是第二个页面 DetailComponentOwner 的实现。

   public class DetailComponentOwner implements ComponentOwner {
       private static final String TAG = "DetailViewContainer";

    
       private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0, TAG);

    
       private AbilityContext myContext;

    
       private ComponentContainer root;

    
       public DetailComponentOwner(AbilityContext context) {
           init(context);
       }

    
       private void init(AbilityContext context) {
           root = new DirectionalLayout(context);
           ComponentContainer.LayoutConfig config = new ComponentContainer.LayoutConfig(
                   ComponentContainer.LayoutConfig.MATCH_PARENT,
                   ComponentContainer.LayoutConfig.MATCH_PARENT);
           root.setLayoutConfig(config);
           myContext = context;
       }

    
       @Override
       public Component getComponent() {
           return root;
       }

    
       @Override
       public void instantiateComponent() {
           return;
       }
   }

  1. 增加一个类型为Page的Ability,并在config.json里进行注册。需要在onStart里调用setSwipeToDismiss(true),来设置右滑退出。示例代码如下:

   public class PageAbility extends Ability {
       @Override
       public void onStart(Intent intent) {
           super.onStart(intent);
           super.setMainRoute(SleepPageSlice.class.getName());
           setSwipeToDismiss(true);
       }
   }

如下是配置文件 config.json,注册 PageAbility的时候,需要指明 action.system.home,用来保证该 Ability 能在 launcher 上显示对应的图标。

   {
       "app": {
           "bundleName": "com.huawei.health.sleep",
           "vendor": "huawei",
           "version": {
               "code": 1,
               "name": "1.0.8.27"
           },
           "apiVersion: {
               "compatible": 3,
               "target": 3
           }
       },
       "deviceConfig": {
           "default": {
               }
           }
       },
       "module": {
           "package": "com.huawei.health.sleep",
           "name": ".SleepApplication",
           "distro": {
               "moduleType": "entry",
               "deliveryWithInstall": true,
               "moduleName": "entry"
           },
           "deviceType": [
               "wearable"
           ],
           "reqCapabilities": [
               "video_support"
           ],
           "abilities": [
               {
                   "name": ".PageAbility",
                   "description": "$string:ability_description",
                   "icon": "$media:icon_app",
                   "label": "$string:app_name",
                   "launchType": "standard",
                   "orientation": "portrait",
                   "visible": true,
                   "permissions": [],
                   "skills": [
                       {
                           "actions": [
                               "action.system.home"
                           ],
                           "entities": [
                               "entity.system.home"
                           ],
                       }
                   ],
                   "type": "page",
                   "formEnabled": false
               }
           ]
       }
   }

    

在睡眠界面中,我们用到了圆环效果,这里我们看一下圆环效果是如何实现的,如何实现自定义 Component 的效果。调用方代码如下:

drawTask = new CircleProgressDrawTask(root);

Componet 类提供了 UI 的基本组件,包括方法addDrawTask(Component.DrawTask task)。该方法可以给任意一个Componet 添加一段自定义绘制的代码。自定义 Component 的实现方法如下:

  1. 创建一个自定义 DrawTask,包含与该 Componet 相关的自定义属性和绘制的代码。
  2. 在构造方法里传入宿主 Component,跟自定义的 DrawTask 绑定。

实现睡眠圆环效果的示例代码如下。

public class CircleProgressDrawTask implements Component.DrawTask {
    // 用于配置圆环的粗细,具体参数可以在xml文件中配置
    private static final String STROKE_WIDTH_KEY = "stroke_width";

 
    // 用于配置圆环的最大值,具体参数可以在xml文件中配置
    private static final String MAX_PROGRESS_KEY = "max_progress";

 
    // 用于配置圆环的当前值,具体参数可以在xml文件中配置
    private static final String CURRENT_PROGRESS_KEY = "current_progress";

 
    // 用于配置起始位置的颜色,具体参数可以在xml文件中配置
    private static final String START_COLOR_KEY = "start_color";

 
    // 用于配置结束位置的颜色,具体参数可以在xml文件中配置
    private static final String END_COLOR_KEY = "end_color";

 
    // 用于配置背景色,具体参数可以在xml文件中配置
    private static final String BACKGROUND_COLOR_KEY = "background_color";

 
    // 用于配置起始位置的角度,具体参数可以在xml文件中配置
    private static final String START_ANGLE = "start_angle";

 
    private static final float MAX_ARC = 360f;

 
    private static final int DEFAULT_STROKE_WIDTH = 10;

 
    private static final int DEFAULT_MAX_VALUE = 100;

 
    private static final int DEFAULT_START_COLOR = 0xFFB566FF;

 
    private static final int DEFAULT_END_COLOR = 0xFF8A2BE2;

 
    private static final int DEFAULT_BACKGROUND_COLOR = 0xA8FFFFFF;

 
    private static final int DEFAULT_START_ANGLE = -90;

 
    private static final float DEFAULT_LINER_MAX = 100f;

 
    private static final int HALF = 2;

 
    // 圆环的宽度, 默认10个像素
    private int myStrokeWidth = DEFAULT_STROKE_WIDTH;

 
    // 最大的进度值, 默认是100
    private int myMaxValue = DEFAULT_MAX_VALUE;

 
    // 当前的进度值, 默认是0
    private int myCurrentValue = 0;

 
    // 起始位置的颜色, 默认浅紫色
    private Color myStartColor = new Color(DEFAULT_START_COLOR);

 
    // 结束位置的颜色, 默认深紫色
    private Color myEndColor = new Color(DEFAULT_END_COLOR);

 
    // 背景颜色, 默认浅灰色
    private Color myBackgroundColor = new Color(DEFAULT_BACKGROUND_COLOR);

 
    // 当前的进度值, 默认从-90度进行绘制
    private int myStartAngle = DEFAULT_START_ANGLE;

 
    private Component myComponent;

 
    // 传入要进行修改的component
    public CircleProgressDrawTask(Component component) {
        myComponent = component;
        myComponent.addDrawTask(this);
    }

 
    // 设置当前进度并且刷新component,value值为当前进度
    public void setValue(int value) {
        myCurrentValue = value;
        myComponent.invalidate();
    }

 
    public void setMaxValue(int maxValue) {
        myMaxValue = maxValue;
        myComponent.invalidate();
    }

 
    @Override
    public void onDraw(Component component, Canvas canvas) {
        // 通过canvas实现绘制圆环的功能
        ......
    }
}