HarmonyOS 6.0 轻量化服务卡片交互设计方案

一、服务卡片概述

1.1 什么是服务卡片

服务卡片(Service Widget)是HarmonyOS系统中一种独特的原子化服务形态,它将应用的核心功能以轻量化、可视化的方式呈现于用户桌面,让用户无需打开完整应用即可快速获取信息或执行操作。作为HarmonyOS"一次开发,多端部署"理念的重要载体,服务卡片在6.0版本中实现了交互能力的全面升级,支持更深层次的卡片内交互操作,真正做到了"触手可及,一触即达"。

服务卡片的本质是应用的精简视图,它以卡片的形式悬浮或嵌入在桌面、服务中心、锁屏等位置,根据用户需求动态展示信息或响应操作。这种设计哲学源于"服务找人"的核心理念------用户无需主动寻找应用,应用的功能主动以卡片形式出现在用户的使用场景中。

1.2 卡片形态与尺寸规范

HarmonyOS服务卡片采用标准的网格系统设计,支持多种尺寸规格以适应不同的展示场景和内容复杂度。开发者需要根据卡片功能选择合适的尺寸,避免内容堆砌或大面积空白。

尺寸类型 网格单位 适用场景 典型示例
1×2 1列2行 单信息展示、快捷操作 快捷开关、单条待办
2×2 2列2行 基础信息展示 天气、时间、步数
2×4 2列4行 列表型信息展示 日程列表、快递追踪
4×4 4列4行 复杂混合场景 全功能控制面板

不同尺寸的卡片在内容布局上需要遵循响应式设计原则,同一套代码通过条件渲染和布局适配来适配多种尺寸。例如,待办事项卡片在1×2尺寸下仅显示一条待办和勾选按钮,在2×4尺寸下则可展示多条待办事项和添加按钮。

1.3 卡片宿主位置

服务卡片根据宿主位置的不同,承担着差异化的功能定位:

  • 桌面卡片:作为桌面的一部分,提供高频信息的快速浏览和基础交互,是用户日常接触最频繁的卡片形态
  • 服务中心卡片:通过下拉桌面或左滑进入,展示智能推荐的服务和最近使用的原子化服务
  • 锁屏卡片:在锁屏界面展示关键信息如日程提醒、快递进度,保护隐私的同时确保重要信息不遗漏
  • 智慧搜索建议:在搜索框输入时,根据上下文智能推荐相关服务卡片

1.4 轻量化设计的核心理念

轻量化设计是服务卡片区别于普通应用页面的根本特征,其核心在于**"少而精"**。具体体现在以下四个维度:

  1. 体积轻量化:单卡片资源包建议控制在500KB以内,包含图片资源的卡片不超过2MB,确保秒开体验
  2. 交互轻量化:单次操作路径不超过2步,核心功能一步直达,减少用户决策成本
  3. 信息轻量化:遵循"一卡一功能"原则,每张卡片只展示和处理一个核心任务
  4. 资源轻量化:采用按需加载策略,只在卡片可见时加载必要资源,可见性消失后及时释放

二、轻量化交互设计原则

2.1 信息精简原则

信息精简是服务卡片设计的首要原则。在有限的卡片空间内传递最有价值的信息,需要开发者具备信息优先级判断能力和视觉层次设计能力。

核心信息优先展示:每张卡片应明确其核心价值,将最重要的信息放在视觉焦点位置。例如,天气卡片的核心是当前温度和天气状况,次要信息如空气质量、紫外线指数等应降级展示或折叠处理。

信息层级清晰划分:通过字号、字重、颜色、间距等视觉变量建立清晰的信息层级。通常采用"标题-正文-辅助"三级结构,标题传达核心信息,正文提供详细信息,辅助内容以次要视觉权重呈现。

克制信息数量:1×2卡片建议展示1-2个信息点,2×2卡片3-5个信息点,2×4卡片5-8个信息点。避免信息过载导致用户阅读疲劳和卡片内容杂乱。

2.2 操作便捷原则

操作便捷强调零学习成本操作确定性。用户看到卡片应立即理解其功能,点击按钮应获得明确反馈。

操作目标明确:每个可交互元素应有明确的操作意图表达。通过图标+文字的组合形式,确保用户无需猜测即可理解操作含义。

反馈即时可见:操作后应立即在卡片上反映结果状态,如勾选完成、切换开关、显示加载等反馈都要在500ms内呈现,避免用户产生疑惑。

撤销机制设计:对于不可逆操作,建议设计确认机制或提供撤销入口,降低用户操作焦虑。

2.3 视觉轻盈原则

视觉轻盈并不意味着简单,而是通过精心设计的视觉语言传达清爽、高效的感受。

合理运用留白:留白不是空间的浪费,而是信息呼吸的保障。元素之间保持适当间距,避免拥挤感,让用户视线自然聚焦。

色彩克制使用:主色调不超过2种,辅以中性色系。色彩的主要功能是传达信息状态,而非装饰。使用品牌色的同时要注意与系统深色/浅色模式的协调。

图标风格统一:卡片内所有图标应保持统一的视觉风格(线性/填充/扁平),统一的线条粗细,统一的圆角度数。

2.4 性能优先原则

性能是服务卡片的生命线,直接影响用户体验和系统资源消耗。

首屏渲染时间<100ms:这是卡片呈现的黄金标准。采用骨架屏、资源预加载、异步渲染等技术手段确保卡片快速呈现。

内存占用<10MB:单卡片进程的内存预算应严格控制。避免在卡片内执行复杂运算,减少图片资源尺寸,谨慎使用动画效果。

功耗友好设计:避免使用CPU密集型动画,减少网络请求频率,利用系统提供的低功耗更新机制。

2.5 实时更新原则

卡片信息的时效性直接影响其使用价值,需要在更新频率和资源消耗之间取得平衡。

智能刷新策略:根据数据变化频率设置差异化的刷新周期。高频数据(如股价、赛事)可设置分钟级刷新,低频数据(如日程、提醒)可设置小时级或事件驱动刷新。

增量更新优先:优先使用增量更新而非全量刷新,减少数据传输量和渲染开销。

更新时机选择:利用系统低功耗窗口进行数据预取,在用户即将查看卡片时触发更新,提升感知时效性。

三、交互设计模式详解

3.1 信息展示型卡片

信息展示型卡片是最基础的卡片形态,其核心功能是信息聚合与呈现,用户通过卡片获取关键数据,点击后跳转到应用详情页查看完整内容。

典型应用场景

  • 天气卡片:展示当前位置的天气、温度、空气质量
  • 日程卡片:展示今日重要日程安排
  • 股价卡片:展示自选股实时行情
  • 运动卡片:展示当日步数、运动时长、消耗卡路里

设计要点

复制代码
┌─────────────────────────────────────┐
│  核心数值大字号展示(72pt+)         │
│  单位标注小字号(28pt)              │
├─────────────────────────────────────┤
│  辅助信息中字号(32pt)              │
│  状态描述或趋势指示                  │
├─────────────────────────────────────┤
│  更新时间戳或数据来源               │
└─────────────────────────────────────┘

交互设计:点击卡片主体区域触发router事件跳转到应用详情页,部分卡片支持长按进入编辑模式或调整设置。

3.2 快捷操作型卡片

快捷操作型卡片在信息展示的基础上增加了直接操作能力,用户无需进入应用即可完成特定任务。

典型应用场景

  • 音乐控制卡片:播放/暂停、切歌、音量调节
  • 智能家居卡片:开关灯、调节温度、场景切换
  • 支付卡片:展示付款码、收款码
  • 备忘录快捷添加卡片

设计要点

复制代码
┌─────────────────────────────────────┐
│  当前状态可视化展示                  │
│  (播放中曲目、设备状态等)          │
├─────────────────────────────────────┤
│  [ ◀◀ ]  [ ▶/❚❚ ]  [ ▶▶ ]          │
│       操作按钮组(56pt触控区域)      │
├─────────────────────────────────────┤
│  进度条或状态指示                    │
└─────────────────────────────────────┘

交互设计:通过call事件调用卡片提供方能力,在卡片UI内实时响应操作结果,无需跳转应用。

3.3 混合型卡片

混合型卡片是信息展示与快捷操作的有机结合,适用于需要同时呈现状态和提供操作入口的场景。

典型应用场景

  • 快递追踪卡片:展示快递进度+一键拨打电话
  • 订单卡片:展示订单状态+查看物流/确认收货
  • 待办卡片:展示待办事项+快速勾选/添加
  • 出行卡片:展示行程信息+值机/改签操作

设计要点

复制代码
┌─────────────────────────────────────┐
│  主要信息区(占60%)                 │
│  关键数据可视化展示                   │
├─────────────────────────────────────┤
│  次要信息区(占25%)                 │
│  补充说明或进度展示                   │
├─────────────────────────────────────┤
│  操作区(占15%)                     │
│  快捷操作按钮组                       │
└─────────────────────────────────────┘

交互设计:信息区点击跳转详情,操作区触发卡片内交互,操作结果实时反馈在信息区。

3.4 深度交互型卡片

深度交互型卡片是HarmonyOS 6.0重点增强的能力,支持复杂的卡片内交互,包括列表滑动、展开收起、自定义手势等。

典型应用场景

  • 股票自选卡片:支持滑动查看多只股票、展开查看分时图
  • 音乐播放卡片:展开显示歌词、歌词进度跟随
  • 日历卡片:左右滑动切换日期、点击日期展开日程
  • 健康卡片:展开显示详细健康数据、趋势图表

技术实现:HarmonyOS 6.0的卡片框架新增了丰富的交互事件支持,包括触摸事件、滑动事件、长按事件等,配合响应式布局实现复杂交互。

设计要点

复制代码
┌─────────────────────────────────────┐
│  折叠态:核心信息概览                 │
│  ━━━━━━━━━━━●━━━━━━━━━━━━           │
│  展开指示器                           │
├─────────────────────────────────────┤
│  展开态:详细信息/列表                │
│  [ 股票1 ▼  股票2 ▼  股票3 ]          │
│  [    分时图/详细数据    ]           │
└─────────────────────────────────────┘

四、技术实现方案

4.1 卡片开发基础

4.1.1 FormExtensionAbility生命周期

服务卡片基于FormExtensionAbility扩展能力实现,其生命周期由系统统一管理:

typescript 复制代码
// FormExtensionAbility生命周期管理
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import formBindingData from '@ohos.app.form.formBindingData';
import FormInfo from '@ohos.app.form.FormInfo';

export default class MyFormAbility extends FormExtensionAbility {
  // 卡片创建时调用,可进行数据初始化
  onAddForm(want) {
    // want.parameters中包含卡片尺寸等配置信息
    const formId = want.parameters['ohos.extra.param.key.form_identity'];
    const formName = want.parameters['ohos.extra.param.key.form_name'];
    const dimension = want.parameters['ohos.extra.param.key.form_dimension'];
    
    // 初始化卡片数据
    const initialData = {
      title: '待办事项',
      items: [],
      completedCount: 0,
      totalCount: 0
    };
    
    return formBindingData.createFormBindingData(initialData);
  }

  // 卡片更新时调用,可刷新数据
  onUpdateForm(formId, formBindingData) {
    // 当卡片需要更新时返回新的绑定数据
    return formBindingData.createFormBindingData({
      updateTime: new Date().toLocaleString()
    });
  }

  // 卡片销毁时调用,清理资源
  onRemoveForm(formId) {
    // 清理与该卡片关联的资源和订阅
    console.info(`Form ${formId} removed`);
  }

  // 获取卡片状态
  onAcquireFormState(want) {
    // 返回卡片应处的状态
    return FormInfo.FormState.READY;
  }
}
4.1.2 卡片数据模型设计

合理的数据模型设计是卡片高效运行的基础。建议采用扁平化、轻量化的数据结构:

typescript 复制代码
// 待办事项卡片数据模型
interface TodoItem {
  id: string;           // 唯一标识
  content: string;      // 待办内容
  completed: boolean;   // 完成状态
  priority: 'high' | 'medium' | 'low';  // 优先级
  dueDate?: string;     // 截止日期
}

interface TodoCardData {
  cardTitle: string;           // 卡片标题
  items: TodoItem[];           // 待办列表
  completedCount: number;     // 已完成数量
  totalCount: number;          // 总数量
  lastUpdateTime: string;      // 最后更新时间
}

// 创建默认卡片数据
function createDefaultCardData(): TodoCardData {
  return {
    cardTitle: '今日待办',
    items: [
      {
        id: '1',
        content: '完成项目文档编写',
        completed: false,
        priority: 'high'
      },
      {
        id: '2',
        content: '团队周会汇报',
        completed: false,
        priority: 'medium',
        dueDate: '2024-12-20'
      },
      {
        id: '3',
        content: '代码Review',
        completed: true,
        priority: 'low'
      }
    ],
    completedCount: 1,
    totalCount: 3,
    lastUpdateTime: new Date().toLocaleString()
  };
}
4.1.3 卡片提供方与宿主通信机制

HarmonyOS卡片采用双向通信机制,支持从应用侧推送更新到卡片,以及从卡片接收用户交互事件:

typescript 复制代码
// 卡片提供方:向卡片发送数据更新
import formManager from '@ohos.app.form.formHost';
import formBindingData from '@ohos.app.form.formBindingData';

// 更新指定卡片
async function updateCard(formId: string, newData: TodoCardData) {
  try {
    const bindingData = formBindingData.createFormBindingData(newData);
    await formManager.updateForm(formId, bindingData);
    console.info('Card updated successfully');
  } catch (err) {
    console.error('Failed to update card: ${err.message}');
  }
}

// 卡片提供方:接收卡片事件
importWant(want) {
  const eventType = want.parameters['ohos.extra.param.key.form_customize_id'];
  const data = want.parameters;
  
  // 根据事件类型处理
  if (data.actionType === 'toggleComplete') {
    this.handleToggleComplete(data.itemId);
  } else if (data.actionType === 'addItem') {
    this.handleAddItem(data.content);
  }
}

4.2 卡片UI开发

4.2.1 ArkTS卡片UI组件

HarmonyOS 6.0采用ArkTS作为卡片UI开发语言,提供声明式UI范式:

typescript 复制代码
// 卡片UI主文件:TodoCard.ets
import formBindingData from '@ohos.app.form.formBindingData';
import FormBindingData from '@ohos.app.form.formBindingData';

// 卡片入口组件
@Entry
@Component
struct WidgetCard {
  // 从数据绑定中解析数据
  @LocalStorageProp('cardData') cardData: TodoCardData = createDefaultCardData();
  
  build() {
    Column() {
      // 卡片头部
      this.CardHeader()
      
      // 待办列表
      this.TodoList()
      
      // 底部操作栏
      this.BottomBar()
    }
    .width('100%')
    .height('100%')
    .padding(16)
    .backgroundColor('#FFFFFF')
  }
  
  // 卡片头部组件
  @Builder
  CardHeader() {
    Row() {
      Column() {
        Text('待办事项')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .fontColor('#333333')
        
        Text('${this.cardData.completedCount}/${this.cardData.totalCount} 已完成')
          .fontSize(12)
          .fontColor('#999999')
          .margin({ top: 4 })
      }
      .alignItems(HorizontalAlign.Start)
      
      Blank()
      
      // 添加按钮
      Button('+')
        .width(32)
        .height(32)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .backgroundColor('#4A90E2')
        .borderRadius(16)
        .onClick(() => {
          postCardAction(this, {
            action: 'message',
            params: { actionType: 'addItem' }
          });
        })
    }
    .width('100%')
    .margin({ bottom: 12 })
  }
  
  // 待办列表组件
  @Builder
  TodoList() {
    Column() {
      ForEach(this.cardData.items, (item: TodoItem, index: number) => {
        TodoItemRow({
          item: item,
          onToggle: () => this.handleToggle(item.id)
        })
        
        if (index < this.cardData.items.length - 1) {
          Divider()
            .strokeWidth(0.5)
            .color('#E5E5E5')
            .margin({ left: 40 })
        }
      }, (item: TodoItem) => item.id)
    }
  }
  
  // 底部操作栏
  @Builder
  BottomBar() {
    Row() {
      Text('最后更新: ${this.cardData.lastUpdateTime}')
        .fontSize(10)
        .fontColor('#CCCCCC')
      
      Blank()
      
      Text('点击查看详情 >')
        .fontSize(12)
        .fontColor('#4A90E2')
        .onClick(() => {
          postCardAction(this, {
            action: 'router',
            bundleName: 'com.example.todo',
            abilityName: 'MainAbility',
            params: { page: 'detail' }
          });
        })
    }
    .width('100%')
    .margin({ top: 12 })
    .padding({ top: 12 })
    .border({ width: { top: 1 }, color: '#F0F0F0' })
  }
  
  // 处理勾选状态切换
  handleToggle(itemId: string) {
    postCardAction(this, {
      action: 'message',
      params: { actionType: 'toggleComplete', itemId: itemId }
    });
  }
}

// 待办项行组件
@Component
struct TodoItemRow {
  @ObjectLink item: TodoItem;
  onToggle: () => void;

  build() {
    Row() {
      // 勾选框
      Checkbox()
        .select(this.item.completed)
        .selectedColor('#4CAF50')
        .shape(CheckBoxShape.ROUNDED_SQUARE)
        .size({ width: 20, height: 20 })
        .onChange((isChecked: boolean) => {
          this.onToggle();
        })
      
      // 待办内容
      Text(this.item.content)
        .fontSize(14)
        .fontColor(this.item.completed ? '#CCCCCC' : '#333333')
        .decoration(this.item.completed ? 
          { type: TextDecorationType.LineThrough } : 
          { type: TextDecorationType.None })
        .maxLines(2)
        .textOverflow({ overflow: TextOverflow.Ellipsis })
        .margin({ left: 12 })
      
      // 优先级指示
      if (this.item.priority === 'high') {
        Text('!')
          .fontSize(12)
          .fontColor('#FFFFFF')
          .backgroundColor('#FF5252')
          .borderRadius(10)
          .padding({ left: 6, right: 6, top: 2, bottom: 2 })
          .margin({ left: 8 })
      }
    }
    .width('100%')
    .padding({ top: 8, bottom: 8 })
  }
}

// 创建默认数据
function createDefaultCardData(): TodoCardData {
  return {
    cardTitle: '今日待办',
    items: [],
    completedCount: 0,
    totalCount: 0,
    lastUpdateTime: ''
  };
}
4.2.2 响应式布局适配多尺寸

使用if-else@Responsive实现单代码多尺寸适配:

typescript 复制代码
// 多尺寸适配的TodoCard
@Entry
@Component
struct ResponsiveTodoCard {
  @LocalStorageProp('cardData') cardData: TodoCardData = createDefaultCardData();
  @StorageProp('currentWidth') cardWidth: number = 300;

  build() {
    Column() {
      this.CardHeader()
      
      // 根据卡片宽度选择布局
      if (this.cardWidth >= 400) {
        // 2×4 或 4×4 尺寸:显示完整列表
        this.FullListView()
      } else if (this.cardWidth >= 200) {
        // 2×2 尺寸:显示摘要
        this.SummaryView()
      } else {
        // 1×2 尺寸:只显示单个待办
        this.SingleItemView()
      }
      
      this.BottomBar()
    }
    .width('100%')
    .height('100%')
    .padding(16)
    .backgroundColor('#FFFFFF')
  }

  // 完整列表视图(适用于2×4)
  @Builder
  FullListView() {
    Column() {
      ForEach(this.cardData.items, (item: TodoItem, index: number) => {
        Row() {
          Checkbox()
            .select(item.completed)
            .onChange(() => this.handleToggle(item.id))
          
          Column() {
            Text(item.content)
              .fontSize(14)
              .fontColor(item.completed ? '#CCCCCC' : '#333333')
            
            if (item.dueDate) {
              Text(item.dueDate)
                .fontSize(10)
                .fontColor('#999999')
            }
          }
          .margin({ left: 12 })
          .layoutWeight(1)
          
          if (item.priority === 'high') {
            Text('高')
              .fontSize(10)
              .fontColor('#FFFFFF')
              .backgroundColor('#FF5252')
              .borderRadius(4)
              .padding({ left: 6, right: 6, top: 2, bottom: 2 })
          }
        }
        .width('100%')
        .padding({ top: 8, bottom: 8 })
        
        if (index < this.cardData.items.length - 1) {
          Divider().strokeWidth(0.5).color('#E5E5E5')
        }
      })
    }
    .layoutWeight(1)
  }

  // 摘要视图(适用于2×2)
  @Builder
  SummaryView() {
    Column() {
      Text('${this.cardData.completedCount} / ${this.cardData.totalCount}')
        .fontSize(32)
        .fontWeight(FontWeight.Bold)
        .fontColor('#4A90E2')
      
      Text('待办已完成')
        .fontSize(12)
        .fontColor('#999999')
        .margin({ top: 4 })
      
      // 显示未完成的前两项
      Column() {
        ForEach(
          this.cardData.items.filter(item => !item.completed).slice(0, 2),
          (item: TodoItem) => {
            Text(item.content)
              .fontSize(11)
              .fontColor('#666666')
              .maxLines(1)
              .textOverflow({ overflow: TextOverflow.Ellipsis })
              .margin({ top: 4 })
          }
        )
      }
      .margin({ top: 8 })
    }
    .width('100%')
    .layoutWeight(1)
    .justifyContent(FlexAlign.Center)
  }

  // 单项视图(适用于1×2)
  @Builder
  SingleItemView() {
    Row() {
      // 只显示第一个未完成项
      if (this.cardData.items.length > 0) {
        const firstIncomplete = this.cardData.items.find(item => !item.completed);
        if (firstIncomplete) {
          Checkbox()
            .select(false)
            .onChange(() => this.handleToggle(firstIncomplete.id))
          
          Text(firstIncomplete.content)
            .fontSize(12)
            .maxLines(2)
            .textOverflow({ overflow: TextOverflow.Ellipsis })
            .margin({ left: 8 })
            .layoutWeight(1)
        }
      } else {
        Text('暂无待办')
          .fontSize(12)
          .fontColor('#CCCCCC')
      }
    }
    .width('100%')
    .layoutWeight(1)
  }
  
  handleToggle(itemId: string) {
    postCardAction(this, {
      action: 'message',
      params: { actionType: 'toggleComplete', itemId: itemId }
    });
  }
}
4.2.3 主题适配(深色/浅色模式)
typescript 复制代码
// 主题配置与使用
@Entry
@Component
struct ThemedTodoCard {
  @LocalStorageProp('cardData') cardData: TodoCardData = createDefaultCardData();
  
  // 颜色资源映射
  @StorageProp('sys.color.mode') colorMode: ColorMode = ColorMode.LIGHT;
  
  // 根据主题返回颜色
  getBackgroundColor(): string {
    return this.colorMode === ColorMode.LIGHT ? '#FFFFFF' : '#1C1C1E';
  }
  
  getTextPrimaryColor(): string {
    return this.colorMode === ColorMode.LIGHT ? '#333333' : '#FFFFFF';
  }
  
  getTextSecondaryColor(): string {
    return this.colorMode === ColorMode.LIGHT ? '#999999' : '#8E8E93';
  }
  
  getDividerColor(): string {
    return this.colorMode === ColorMode.LIGHT ? '#E5E5E5' : '#38383A';
  }

  build() {
    Column() {
      // 标题
      Text('待办事项')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .fontColor(this.getTextPrimaryColor())
      
      // 待办列表使用主题颜色
      List() {
        ForEach(this.cardData.items, (item: TodoItem) => {
          ListItem() {
            Row() {
              Checkbox()
                .select(item.completed)
              
              Text(item.content)
                .fontSize(14)
                .fontColor(item.completed ? 
                  this.getTextSecondaryColor() : 
                  this.getTextPrimaryColor())
                .margin({ left: 12 })
            }
          }
        })
      }
    }
    .width('100%')
    .height('100%')
    .padding(16)
    .backgroundColor(this.getBackgroundColor())
  }
}

4.3 数据更新机制

4.3.1 定时更新

通过module.json5配置定时更新周期:

json 复制代码
{
  "module": {
    "extensionAbilities": [
      {
        "name": "MyFormAbility",
        "type": "form",
        "metadata": [
          {
            "name": "form_configuration",
            "value": {
              "updateDuration": 30,  // 30分钟更新一次
              "scheduledUpdateTime": "10:30",  // 指定时间更新
              "supportDimensions": ["1*2", "2*2", "2*4"],
              "defaultDimension": "2*2"
            }
          }
        ]
      }
    ]
  }
}
4.3.2 事件驱动更新
typescript 复制代码
// 卡片提供方:处理来自卡片的更新请求
import formHost from '@ohos.app.form.formHost';
import formBindingData from '@ohos.app.form.formBindingData';

export default class MyFormExtensionAbility extends FormExtensionAbility {
  onFormEvent(formId, message) {
    // 解析消息
    const eventData = JSON.parse(message);
    
    switch (eventData.actionType) {
      case 'toggleComplete':
        this.toggleTodoComplete(formId, eventData.itemId);
        break;
      case 'addItem':
        this.addTodoItem(formId, eventData.content);
        break;
      case 'deleteItem':
        this.deleteTodoItem(formId, eventData.itemId);
        break;
    }
  }

  async toggleTodoComplete(formId: string, itemId: string) {
    // 更新数据库
    await TodoDatabase.toggleComplete(itemId);
    
    // 获取最新数据
    const updatedData = await TodoDatabase.getCardData();
    
    // 更新卡片
    const bindingData = formBindingData.createFormBindingData(updatedData);
    await formHost.updateForm(formId, bindingData);
  }

  async addTodoItem(formId: string, content: string) {
    // 添加新待办
    const newItem = await TodoDatabase.addItem(content);
    
    // 获取更新后的数据
    const updatedData = await TodoDatabase.getCardData();
    
    // 更新卡片
    const bindingData = formBindingData.createFormBindingData(updatedData);
    await formHost.updateForm(formId, bindingData);
  }

  async deleteTodoItem(formId: string, itemId: string) {
    // 删除待办
    await TodoDatabase.deleteItem(itemId);
    
    // 获取更新后的数据并刷新卡片
    const updatedData = await TodoDatabase.getCardData();
    const bindingData = formBindingData.createFormBindingData(updatedData);
    await formHost.updateForm(formId, bindingData);
  }
}

4.4 交互事件处理

HarmonyOS卡片支持三种核心交互事件,通过postCardAction触发:

事件类型 用途 触发方式 回调位置
router 跳转到应用页面 点击卡片/按钮 Ability.onShowWindow
call 调用应用能力 快捷操作 Ability.onCommand
message 卡片内部消息 卡片内交互 FormAbility.onFormEvent
typescript 复制代码
// 完整的卡片交互事件处理示例
@Entry
@Component
struct InteractiveTodoCard {
  @LocalStorageProp('cardData') cardData: TodoCardData = createDefaultCardData();

  build() {
    Column() {
      // 卡片标题栏
      Row() {
        Text('待办')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
        
        Blank()
        
        // 跳转到应用详情页
        Image($r('app.media.ic_more'))
          .width(24)
          .height(24)
          .onClick(() => {
            postCardAction(this, {
              action: 'router',
              bundleName: 'com.example.todo',
              abilityName: 'MainAbility',
              params: { action: 'openDetail' }
            });
          })
      }
      .width('100%')
      .margin({ bottom: 12 })

      // 待办列表
      ForEach(this.cardData.items.slice(0, 3), (item: TodoItem) => {
        Row() {
          // 切换完成状态 - 使用message事件
          Checkbox()
            .select(item.completed)
            .onChange(() => {
              postCardAction(this, {
                action: 'message',
                params: {
                  actionType: 'toggleComplete',
                  itemId: item.id
                }
              });
            })
          
          Text(item.content)
            .fontSize(14)
            .margin({ left: 12 })
            .layoutWeight(1)
          
          // 删除按钮 - 使用call事件
          Image($r('app.media.ic_delete'))
            .width(20)
            .height(20)
            .onClick(() => {
              postCardAction(this, {
                action: 'call',
                bundleName: 'com.example.todo',
                abilityName: 'TodoServiceAbility',
                params: {
                  action: 'deleteItem',
                  itemId: item.id
                }
              });
            })
        }
        .width('100%')
        .padding({ top: 8, bottom: 8 })
      })
      
      // 底部统计和跳转
      Row() {
        Text('${this.cardData.completedCount}/${this.cardData.totalCount}')
          .fontSize(12)
          .fontColor('#999999')
        
        Blank()
        
        Text('查看全部')
          .fontSize(12)
          .fontColor('#4A90E2')
          .onClick(() => {
            postCardAction(this, {
              action: 'router',
              bundleName: 'com.example.todo',
              abilityName: 'MainAbility',
              params: { page: 'all' }
            });
          })
      }
      .margin({ top: 12 })
    }
    .width('100%')
    .height('100%')
    .padding(16)
  }
}

五、实战案例:待办事项服务卡片

5.1 需求分析

本案例设计一个功能完整的待办事项服务卡片,支持以下核心功能:

  1. 多尺寸适配:支持1×2、2×2、2×4三种尺寸自动布局
  2. 待办展示:显示待办事项列表,支持勾选完成状态
  3. 快捷操作:在卡片内直接勾选/取消待办,无需跳转
  4. 快速添加:一键添加新待办事项
  5. 实时同步:操作结果实时反映在卡片上
  6. 数据持久化:待办数据存储在应用侧,跨设备同步

5.2 完整代码实现

5.2.1 项目结构
复制代码
entry/
├── src/main/
│   ├── ets/
│   │   ├── entryability/
│   │   │   └── EntryAbility.ets          // 应用入口
│   │   ├── formability/
│   │   │   └── TodoFormAbility.ets       // 卡片生命周期管理
│   │   ├── pages/
│   │   │   └── Index.ets                 // 应用主页面
│   │   └── widget/
│   │       ├── TodoCardMain.ets         // 卡片主组件
│   │       ├── components/
│   │       │   ├── TodoItem.ets         // 待办项组件
│   │       │   ├── TodoHeader.ets       // 卡片头部
│   │       │   └── TodoStats.ets        // 统计区域
│   │       └── data/
│   │           └── TodoCardData.ets    // 数据模型
│   └── module.json5                      // 模块配置
5.2.2 卡片FormExtensionAbility
typescript 复制代码
// TodoFormAbility.ets - 卡片生命周期管理
import FormExtensionAbility from '@ohos.app.form.FormExtensionAbility';
import formBindingData from '@ohos.app.form.formBindingData';
import formHost from '@ohos.app.form.formHost';
import TodoDataManager from '../widget/data/TodoDataManager';

export default class TodoFormAbility extends FormExtensionAbility {
  private dataManager: TodoDataManager = new TodoDataManager();

  // 卡片创建
  onAddForm(want) {
    const formId = want.parameters['ohos.extra.param.key.form_identity'];
    const formName = want.parameters['ohos.extra.param.key.form_name'];
    const dimension = want.parameters['ohos.extra.param.key.form_dimension'];

    console.info(`[TodoForm] Card added: ${formId}, dimension: ${dimension}`);

    // 加载初始数据
    const initialData = this.dataManager.getCardData();

    // 注册卡片实例
    this.dataManager.registerCard(formId);

    return formBindingData.createFormBindingData(initialData);
  }

  // 卡片更新
  onUpdateForm(formId, formBindingData) {
    console.info(`[TodoForm] Card update requested: ${formId}`);
    
    // 获取最新数据并更新卡片
    const latestData = this.dataManager.getCardData();
    const bindingData = formBindingData.createFormBindingData(latestData);
    
    return bindingData;
  }

  // 卡片销毁
  onRemoveForm(formId) {
    console.info(`[TodoForm] Card removed: ${formId}`);
    
    // 注销卡片实例
    this.dataManager.unregisterCard(formId);
  }

  // 接收卡片事件
  onFormEvent(formId, message) {
    console.info(`[TodoForm] Event received: ${message}`);

    try {
      const event = JSON.parse(message);
      
      switch (event.actionType) {
        case 'toggleComplete':
          this.dataManager.toggleComplete(event.itemId);
          break;
          
        case 'addItem':
          this.dataManager.addItem(event.content);
          break;
          
        case 'deleteItem':
          this.dataManager.deleteItem(event.itemId);
          break;
          
        default:
          console.warn(`[TodoForm] Unknown action: ${event.actionType}`);
      }

      // 更新所有已注册的卡片
      this.broadcastUpdate();

    } catch (err) {
      console.error(`[TodoForm] Event processing error: ${err.message}`);
    }
  }

  // 广播更新到所有卡片
  private async broadcastUpdate() {
    const cardIds = this.dataManager.getAllCardIds();
    const latestData = this.dataManager.getCardData();

    for (const formId of cardIds) {
      try {
        const bindingData = formBindingData.createFormBindingData(latestData);
        await formHost.updateForm(formId, bindingData);
      } catch (err) {
        console.error(`[TodoForm] Failed to update card ${formId}: ${err.message}`);
      }
    }
  }

  // 获取卡片状态
  onAcquireFormState(want) {
    return FormInfo.FormState.READY;
  }
}
5.2.3 数据管理模块
typescript 复制代码
// TodoDataManager.ets - 数据管理
import dataPreferences from '@ohos.data.preferences';

interface TodoItem {
  id: string;
  content: string;
  completed: boolean;
  priority: 'high' | 'medium' | 'low';
  createTime: number;
  dueDate?: string;
}

interface TodoCardData {
  cardTitle: string;
  items: TodoItem[];
  completedCount: number;
  totalCount: number;
  lastUpdateTime: string;
}

class TodoDataManager {
  private context: Context;
  private preferences: dataPreferences.Preferences | null = null;
  private registeredCards: Set<string> = new Set();
  private cardData: TodoCardData = {
    cardTitle: '待办事项',
    items: [],
    completedCount: 0,
    totalCount: 0,
    lastUpdateTime: ''
  };

  constructor() {
    // 将在初始化时从Ability获取context
  }

  async init(context: Context) {
    this.context = context;
    this.preferences = await dataPreferences.Preferences.getPreferences(
      this.context,
      'todo_storage'
    );
    await this.loadData();
  }

  // 从存储加载数据
  private async loadData() {
    if (!this.preferences) return;

    const storedData = await this.preferences.get('todo_data', '');
    if (storedData) {
      try {
        const parsed = JSON.parse(storedData);
        this.cardData = {
          ...this.cardData,
          ...parsed
        };
      } catch (e) {
        console.error('[TodoDataManager] Failed to parse stored data');
      }
    }
  }

  // 保存数据到存储
  private async saveData() {
    if (!this.preferences) return;

    this.cardData.lastUpdateTime = new Date().toLocaleString();
    await this.preferences.put('todo_data', JSON.stringify(this.cardData));
    await this.preferences.flush();
  }

  // 获取卡片数据
  getCardData(): TodoCardData {
    this.updateCounts();
    return { ...this.cardData };
  }

  // 更新统计数据
  private updateCounts() {
    this.cardData.totalCount = this.cardData.items.length;
    this.cardData.completedCount = this.cardData.items.filter(
      item => item.completed
    ).length;
  }

  // 切换完成状态
  toggleComplete(itemId: string) {
    const item = this.cardData.items.find(i => i.id === itemId);
    if (item) {
      item.completed = !item.completed;
      this.saveData();
    }
  }

  // 添加待办
  addItem(content: string, priority: 'high' | 'medium' | 'low' = 'medium') {
    const newItem: TodoItem = {
      id: `todo_${Date.now()}`,
      content: content,
      completed: false,
      priority: priority,
      createTime: Date.now()
    };
    
    this.cardData.items.unshift(newItem);
    this.saveData();
  }

  // 删除待办
  deleteItem(itemId: string) {
    const index = this.cardData.items.findIndex(i => i.id === itemId);
    if (index > -1) {
      this.cardData.items.splice(index, 1);
      this.saveData();
    }
  }

  // 注册卡片
  registerCard(formId: string) {
    this.registeredCards.add(formId);
  }

  // 注销卡片
  unregisterCard(formId: string) {
    this.registeredCards.delete(formId);
  }

  // 获取所有注册的卡片ID
  getAllCardIds(): string[] {
    return Array.from(this.registeredCards);
  }
}

export default TodoDataManager;
5.2.4 卡片UI实现
typescript 复制代码
// TodoCardMain.ets - 卡片主组件
import formBindingData from '@ohos.app.form.formBindingData';

interface TodoItem {
  id: string;
  content: string;
  completed: boolean;
  priority: 'high' | 'medium' | 'low';
  dueDate?: string;
}

interface TodoCardData {
  cardTitle: string;
  items: TodoItem[];
  completedCount: number;
  totalCount: number;
  lastUpdateTime: string;
}

@Entry
@Component
struct TodoCardMain {
  // 从ArkTS卡片数据绑定获取数据
  @LocalStorageProp('title') title: string = '待办事项';
  @LocalStorageProp('items') items: TodoItem[] = [];
  @LocalStorageProp('completedCount') completedCount: number = 0;
  @LocalStorageProp('totalCount') totalCount: number = 0;
  @LocalStorageProp('lastUpdateTime') lastUpdateTime: string = '';

  // 卡片尺寸检测
  @StorageProp('width') cardWidth: number = 300;
  @StorageProp('height') cardHeight: number = 300;

  aboutToAppear() {
    // 从数据绑定中获取初始数据
    const storage = LocalStorage.GetShared();
    this.title = storage.getOrCreate('title', '待办事项') as string;
    this.items = storage.getOrCreate('items', []) as TodoItem[];
    this.completedCount = storage.getOrCreate('completedCount', 0) as number;
    this.totalCount = storage.getOrCreate('totalCount', 0) as number;
    this.lastUpdateTime = storage.getOrCreate('lastUpdateTime', '') as string;
  }

  build() {
    Column() {
      // 根据尺寸选择布局
      if (this.cardHeight > 300) {
        this.FullLayout()
      } else if (this.cardWidth > 200) {
        this.MediumLayout()
      } else {
        this.CompactLayout()
      }
    }
    .width('100%')
    .height('100%')
    .padding(16)
    .backgroundColor('#FFFFFF')
  }

  // 完整布局 - 2×4
  @Builder
  FullLayout() {
    Column() {
      // 头部
      this.Header()
      
      // 待办列表
      List() {
        ForEach(this.items.slice(0, 5), (item: TodoItem) => {
          ListItem() {
            this.TodoItemComponent(item)
          }
        }, (item: TodoItem) => item.id)
      }
      .layoutWeight(1)
      .divider({ strokeWidth: 0.5, color: '#E5E5E5', startMargin: 40, endMargin: 0 })
      
      // 底部
      this.Footer()
    }
  }

  // 中等布局 - 2×2
  @Builder
  MediumLayout() {
    Column() {
      this.Header()
      
      // 进度显示
      Column() {
        Text('${this.completedCount}')
          .fontSize(36)
          .fontWeight(FontWeight.Bold)
          .fontColor('#4A90E2')
        
        Text('/ ${this.totalCount}')
          .fontSize(20)
          .fontColor('#999999')
        
        Text('已完成')
          .fontSize(12)
          .fontColor('#999999')
          .margin({ top: 4 })
      }
      .layoutWeight(1)
      .justifyContent(FlexAlign.Center)
      
      // 快捷操作
      Row() {
        Button('添加')
          .fontSize(12)
          .height(28)
          .onClick(() => this.onAddClick())
        
        Button('查看')
          .fontSize(12)
          .height(28)
          .margin({ left: 8 })
          .onClick(() => this.onViewClick())
      }
      
      this.Footer()
    }
  }

  // 紧凑布局 - 1×2
  @Builder
  CompactLayout() {
    Row() {
      Column() {
        Text('待办')
          .fontSize(14)
          .fontWeight(FontWeight.Bold)
        
        Text('${this.completedCount}/${this.totalCount}')
          .fontSize(12)
          .fontColor('#4A90E2')
          .margin({ top: 4 })
      }
      .alignItems(HorizontalAlign.Start)
      
      Blank()
      
      // 快捷添加按钮
      Button('+')
        .width(28)
        .height(28)
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .backgroundColor('#4A90E2')
        .borderRadius(14)
        .onClick(() => this.onAddClick())
    }
    .width('100%')
    .height('100%')
    .alignItems(VerticalAlign.Center)
  }

  // 头部组件
  @Builder
  Header() {
    Row() {
      Text(this.title)
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .fontColor('#333333')
      
      Blank()
      
      Button('+')
        .width(28)
        .height(28)
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .backgroundColor('#4A90E2')
        .borderRadius(14)
        .onClick(() => this.onAddClick())
    }
    .width('100%')
    .margin({ bottom: 12 })
  }

  // 待办项组件
  @Builder
  TodoItemComponent(item: TodoItem) {
    Row() {
      Checkbox()
        .select(item.completed)
        .selectedColor('#4CAF50')
        .shape(CheckBoxShape.ROUNDED_SQUARE)
        .size({ width: 18, height: 18 })
        .onChange((isChecked: boolean) => {
          this.onToggleComplete(item.id);
        })
      
      Text(item.content)
        .fontSize(14)
        .fontColor(item.completed ? '#CCCCCC' : '#333333')
        .decoration(item.completed ? 
          { type: TextDecorationType.LineThrough } : 
          { type: TextDecorationType.None })
        .maxLines(1)
        .textOverflow({ overflow: TextOverflow.Ellipsis })
        .margin({ left: 12 })
        .layoutWeight(1)
      
      if (item.priority === 'high') {
        Text('!')
          .fontSize(10)
          .fontColor('#FFFFFF')
          .backgroundColor('#FF5252')
          .borderRadius(8)
          .padding({ left: 5, right: 5, top: 2, bottom: 2 })
      }
    }
    .width('100%')
    .padding({ top: 10, bottom: 10 })
  }

  // 底部组件
  @Builder
  Footer() {
    Row() {
      if (this.lastUpdateTime) {
        Text('更新: ${this.lastUpdateTime}')
          .fontSize(10)
          .fontColor('#CCCCCC')
      }
      
      Blank()
      
      Text('详情 >')
        .fontSize(12)
        .fontColor('#4A90E2')
        .onClick(() => this.onViewClick())
    }
    .width('100%')
    .margin({ top: 8 })
    .padding({ top: 8 })
    .border({ width: { top: 1 }, color: '#F0F0F0' })
  }

  // 切换完成状态
  onToggleComplete(itemId: string) {
    postCardAction(this, {
      action: 'message',
      params: { actionType: 'toggleComplete', itemId: itemId }
    });
  }

  // 添加待办(这里模拟添加,实际通过message事件处理)
  onAddClick() {
    postCardAction(this, {
      action: 'message',
      params: { actionType: 'addItem', content: '新的待办事项' }
    });
  }

  // 查看详情
  onViewClick() {
    postCardAction(this, {
      action: 'router',
      bundleName: 'com.example.todo',
      abilityName: 'MainAbility',
      params: { page: 'detail' }
    });
  }
}
5.2.5 module.json5配置
json 复制代码
{
  "module": {
    "name": "entry",
    "type": "entry",
    "description": "$string:module_desc",
    "mainElement": "EntryAbility",
    "deviceTypes": [
      "phone",
      "tablet"
    ],
    "extensionAbilities": [
      {
        "name": "TodoFormAbility",
        "icon": "$media:form_icon",
        "label": "$string:form_TodoCard",
        "description": "$string:form_TodoCard_desc",
        "type": "form",
        "metadata": [
          {
            "name": "ohos.extra.param.key.form_dimension",
            "value": "2*2"
          },
          {
            "name": "ohos.extra.param.key.form_name",
            "value": "TodoCard"
          },
          {
            "name": "ohos.extra.param.key.form_update_duration",
            "value": "30"
          },
          {
            "name": "ohos.extra.param.key.form_configability",
            "value": "ability.form.configability.dimension_customization"
          }
        ]
      }
    ],
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets",
        "description": "$string:EntryAbility_desc",
        "icon": "$media:icon",
        "label": "$string:EntryAbility_label",
        "startWindowIcon": "$media:icon",
        "startWindowBackground": "$color:start_window_background"
      }
    ]
  }
}

5.3 效果展示

完成上述代码后,待办事项服务卡片将具备以下效果:

1×2尺寸(紧凑模式)

  • 左侧显示待办标题和完成数量
  • 右侧提供添加按钮
  • 适合作为桌面快捷入口

2×2尺寸(中等模式)

  • 顶部标题和添加按钮
  • 中间大字号显示完成进度(如"3/5")
  • 底部添加"添加"和"查看"快捷按钮
  • 适合快速浏览和基础操作

2×4尺寸(完整模式)

  • 顶部带标题栏
  • 显示5条待办事项列表
  • 每项支持勾选完成
  • 高优先级项显示红色标识
  • 底部显示最后更新时间
  • 点击"详情"跳转到应用

六、性能优化方案

6.1 卡片内存占用控制

服务卡片运行在独立的卡片进程,其内存预算有限,需要严格控制资源使用:

typescript 复制代码
// 内存优化建议
class CardPerformanceOptimizer {
  // 1. 图片资源优化
  optimizeImage() {
    // 使用WebP等压缩格式
    // 图片尺寸精确匹配显示尺寸,避免缩放
    // 使用Lazy Image加载大图
  }

  // 2. 列表渲染优化
  optimizeList(items: TodoItem[]) {
    // 使用LazyForEach替代ForEach
    // 设置合理的可见区域高度
    // 限制最大渲染项数
  }

  // 3. 状态管理优化
  optimizeState() {
    // 避免不必要的状态更新
    // 使用@ObjectLink进行细粒度更新
    // 避免在build()中执行复杂计算
  }

  // 4. 内存预算参考
  static MEMORY_BUDGET = {
    '1*2': '5MB',
    '2*2': '8MB',
    '2*4': '10MB',
    '4*4': '15MB'
  };
}

6.2 刷新频率优化

合理的刷新策略能有效降低系统资源消耗:

typescript 复制代码
// 智能刷新策略
class SmartRefreshStrategy {
  // 根据数据类型选择刷新间隔
  static getRefreshInterval(dataType: string): number {
    const intervals = {
      'weather': 15,      // 天气:15分钟
      'stock': 1,         // 股价:1分钟
      'todo': 0,          // 待办:依赖事件
      'calendar': 30,      // 日程:30分钟
      'sports': 5          // 运动:5分钟
    };
    return intervals[dataType] || 30;
  }

  // 条件更新判断
  static shouldUpdate(oldData: any, newData: any): boolean {
    // 比较关键字段,避免无意义更新
    const keyFields = ['completedCount', 'totalCount', 'items'];
    return keyFields.some(field => 
      JSON.stringify(oldData[field]) !== JSON.stringify(newData[field])
    );
  }
}

6.3 其他优化建议

优化项 建议做法 预期收益
首屏渲染 使用骨架屏+渐进加载 感知速度提升300ms
网络请求 合并请求+本地缓存 减少60%网络调用
图片加载 压缩格式+按需加载 内存降低40%
动画效果 优先使用系统动画 GPU占用降低50%
数据结构 扁平化+最小化字段 序列化速度提升

七、最佳实践与规范

7.1 卡片设计规范总结

尺寸规范

  • 必须支持至少一种标准尺寸
  • 优先支持2×2作为默认尺寸
  • 确保各尺寸内容布局合理

色彩规范

  • 主色调使用品牌色,饱和度适中
  • 确保深色/浅色模式均可读
  • 避免使用单一颜色传达所有信息

字体规范

  • 正文字号不小于12fp
  • 标题与正文保持2-4fp字号差
  • 优先使用系统字体

间距规范

  • 内边距最小8vp
  • 元素间距最小4vp
  • 卡片与宿主边距由系统控制

7.2 常见问题与避坑指南

Q1: 卡片点击无响应

A: 检查postCardAction参数是否正确,bundleName和abilityName必须与应用配置一致

Q2: 卡片数据不更新

A: 确认FormExtensionAbility.onFormEvent是否正确处理事件,确认formHost.updateForm调用

Q3: 多尺寸适配效果不佳

A: 使用if-else而非媒体查询,通过@StorageProp获取卡片实际尺寸

Q4: 内存占用超标

A: 减少图片资源、限制列表渲染数量、避免在卡片内执行耗时操作

Q5: 深色模式显示异常

A: 使用@StorageProp('sys.color.mode')获取主题,使用颜色资源而非硬编码颜色

7.3 用户体验优化建议

  1. 渐进式披露:先展示核心信息,次要信息通过展开或滑动查看
  2. 状态可见性:操作后立即反馈结果,使用加载指示器避免等待焦虑
  3. 容错设计:网络异常时显示友好提示,提供重试机制
  4. 无障碍支持:确保颜色对比度符合WCAG标准,支持屏幕阅读器

八、总结

HarmonyOS 6.0的服务卡片作为原子化服务的核心载体,通过轻量化设计理念和强大的交互能力,为用户提供了"触手可及"的便捷体验。

  1. 轻量化是灵魂:通过精简信息、优化交互、控制资源,实现秒开体验
  2. 交互是核心:利用router、call、message三种事件机制,实现丰富的卡片交互
  3. 适配是关键:一套代码适配多种尺寸,确保卡片在各场景下均有良好体验
  4. 性能是底线:严格控制内存占用和刷新频率,保障系统流畅运行

随着HarmonyOS生态的持续发展,服务卡片将承担越来越重要的角色。希望本方案能为开发者提供实用的指导,助力构建更加出色的轻量化服务体验。

相关推荐
前端不太难2 小时前
鸿蒙游戏如何设计可扩展架构?
游戏·架构·harmonyos
互联网散修11 小时前
鸿蒙星闪实战:从零构建跨设备文件传输——拆解文件传输数据流
华为·harmonyos
南村群童欺我老无力.11 小时前
鸿蒙PC - 资源文件引用路径的隐蔽陷阱
华为·harmonyos
liu_zhiyi13 小时前
生成式 AI 交互规范:提示词工程(Prompt Engineering)技术指南
人工智能·prompt·交互
南村群童欺我老无力.13 小时前
鸿蒙PC开发的Scroll组件maxHeight属性不存在
华为·harmonyos
Swift社区16 小时前
鸿蒙游戏多设备发布流程详解
游戏·华为·harmonyos
以太浮标17 小时前
华为eNSP模拟器综合实验之- 主机没有配置缺省网关时,通过路由式Proxy ARP实现通信(arp-proxy enable)
运维·网络·网络协议·华为·智能路由器·信息与通信
Goway_Hui18 小时前
【ReactNative鸿蒙化-三方库使用与C-API集成】
c语言·react native·harmonyos
nashane19 小时前
HarmonyOS 6.0 分布式相机实战:调用远端设备摄像头与AI场景识别(API 11+)
分布式·数码相机·harmonyos·harmonyos 5