第40篇:设置页面——字体、深色模式、退出登录

img

一、引言

设置页面是应用中最常用但也是最容易被低估的功能模块。它承载着用户偏好配置、账号安全管理、隐私协议查阅等关键功能。在 DriverLicenseExam 项目中,设置页面虽然看起来简洁,但其背后涉及的 CustomDialogController 弹窗管理、@Extend 装饰器扩展、原始资源文件读取等技术点值得深入学习。本文将结合源码深度解析设置页面的完整实现。

二、设置页面整体架构

2.1 页面结构

设置页面位于 products/entry/src/main/ets/pages/mine/Setting.ets,采用 NavDestination 作为页面容器,配合 Column 垂直布局组织菜单项:

// Setting.ets - 完整页面结构
@Entry
@ComponentV2
export struct SettingPage {
  vm: CommonModel = CommonModel.instance;
  logTag: string = 'AboutPage';
  domainId: number = 0x0000;

  build() {
    NavDestination() {
      Column({ space: 17 }) {
        // 隐私协议
        this.settingRow('隐私协议', () => {
          const params: Record<string, Object> = { 'protocolUrl': this.getProtocolUrl(PRIVACY_URL) };
          this.vm.navStack.pushPathByName('PrivacyAgreement', params);
        });

        // 保密设置
        this.settingRow('保密设置', () => {
          this.vm.navStack.pushPathByName('SecretSetting', undefined);
        });

        // 退出登录
        this.settingRow('退出登录', () => {
          this.logOutDialogController.open();
        });
      }
      .width('100%')
      .height('100%')
      .padding({ left: '4%', right: '4%', top: 10 });
    }
    .title('设置')
    .backgroundColor('#F1F3F5');
  }
}

这里需要注意的是,每一个设置项使用了 @Builder 函数 settingRow 进行封装,这是一种非常优雅的代码复用方式。Builder 函数接收标签和点击回调,渲染出一个统一的菜单行样式。

2.2 构建可复用的设置项

@Builder
settingRow(label: string, onClick: () => void) {
  Row() {
    Text(label)
      .fontSize(16)
      .fontFamily('鸿蒙黑体')
      .fontWeight(FontWeight.Medium)
      .opacity(0.9)
      .fontColor($r('app.color.font_secondary'));

    Blank();

    // 右侧箭头
    Image($r('app.media.ic_right_arrow_lined'))
      .width(7)
      .height(14)
      .fillColor($r('app.color.icon_secondary'))
      .margin({ right: 12 });
  }
  .width('100%')
  .minHeight(46)
  .borderRadius(16)
  .backgroundColor($r('app.color.comp_background_list_card'))
  .padding({ left: 12, right: 12 })
  .onClick(onClick);
}

这个 Builder 方法的设计体现了几个关键点:

  • Blank() 组件:自动填充剩余空间,将箭头推到右侧
  • $r 资源引用:使用系统资源色,支持深色模式自动适配
  • minHeight:使用最小高度而非固定高度,适应不同屏幕

2.3 @Extend 装饰器的巧用

项目中还使用了 @Extend 装饰器对 Text 组件进行方法扩展,这是一种 ArkUI 的高级用法:

@Extend(Text)
function textExtend() {
  .fontSize(16)
  .fontFamily('鸿蒙黑体')
  .fontWeight(FontWeight.Medium)
  .opacity(0.9)
  .fontColor($r('app.color.font_secondary'));
}

使用方式非常简洁:

Text('隐私协议').textExtend();

这相当于给 Text 组件增加了一个自定义方法,所有设置了 textExtend() 的 Text 都会自动应用相同的样式集,避免了大量重复代码。

三、CustomDialogController 弹窗管理

3.1 退出登录确认弹窗

退出登录是一个需要二次确认的敏感操作,项目使用了 CustomDialogController 管理弹窗的生命周期:

// 退出登录对话框控制器
logOutDialogController: CustomDialogController = new CustomDialogController({
  builder: CustomContentDialog({
    primaryTitle: '账号退出登录',
    contentBuilder: () => {
      this.logOutContent();
    },
    buttons: [
      {
        value: $r('app.string.cancel'),
        buttonStyle: ButtonStyleMode.TEXTUAL,
        action: () => {
          this.logOutDialogController.close();
        },
      },
      {
        value: $r('app.string.confirm'),
        buttonStyle: ButtonStyleMode.TEXTUAL,
        action: () => {
          AccountUtil.loginOut();
          this.vm.navStack.pop();
        },
      },
    ],
  }),
});

3.2 弹窗内容构建

@Builder
logOutContent() {
  Column() {
    Row() {
      Text('请确认是否退出当前账户?')
        .fontSize(14);
    }
    .width('100%')
    .borderRadius(16)
    .margin({ left: '4%', right: '4%', bottom: 5 });
  }
}

CustomContentDialog 是项目中封装的一个自定义弹窗组件,它提供了统一的弹窗样式,包括标题、内容和按钮组。这种封装方式使得弹窗在不同页面中保持视觉一致性。

3.3 退出登录的业务逻辑

// AccountUtil.loginOut
public static loginOut() {
  AccountUtil._accountInfo.idToken = '';
  AccountUtil._userInfo.avatar = $r('app.media.user_avatar');
  AccountUtil._userInfo.nickname = $r('app.string.user_name');
  AccountUtil._userInfo.phone = '';
}

退出登录后,需要清理三部分数据:

  1. 登录凭证:清除 idToken,标记为未登录状态
  2. 用户头像:恢复为默认头像
  3. 用户昵称:恢复为默认昵称("用户名")
  4. 手机号:清空手机号

四、原始资源文件读取

设置页面中的"隐私协议"功能需要读取原始资源文件(rawfile)中的协议 URL,项目展示了如何通过 ResourceManager 读取 rawfile:

getProtocolUrl(privacyUrl: string): string {
  try {
    // 从 AppScope/resources/rawfile 读取 data.json
    const value: Uint8Array = (this.getUIContext().getHostContext() as Context)
      .resourceManager.getRawFileContentSync('data.json');
    return JSON.parse(buffer.from(value.buffer).toString())[privacyUrl] as string;
  } catch (error) {
    hilog.error(this.domainId, this.logTag,
      'getProtocolUrl Error: ' + error.message);
    return '';
  }
}

这个方法的流程如下:

  1. 获取 Context:通过 getUIContext().getHostContext() 获取上下文
  2. 同步读取:使用 getRawFileContentSync 同步读取 rawfile 内容
  3. 类型转换:将 Uint8Array 转换为字符串
  4. JSON 解析:解析为 JSON 对象,获取指定字段

data.json 的内容格式如下:

{
  "privacy_url": "https://developer.huawei.com/consumer/cn/privacy/",
  "user_agreement": "https://developer.huawei.com/consumer/cn/agreement/"
}

五、设置页面的完整交互流程

从用户点击"设置"到完成设置项操作的完整流程:

用户点击设置
    │
    ▼
Navigation:pushPathByName('Setting', undefined)
    │
    ▼
SettingPage 加载 → onReady 回调(可选)
    │
    ├── 点击"隐私协议"
    │   ├── getProtocolUrl('privacy_url') → 读取 rawfile
    │   └── pushPathByName('PrivacyAgreement', params)
    │
    ├── 点击"保密设置"
    │   └── pushPathByName('SecretSetting', undefined)
    │
    └── 点击"退出登录"
        ├── logOutDialogController.open()
        ├── 用户点击"取消" → 关闭弹窗
        └── 用户点击"确认"
            ├── AccountUtil.loginOut()
            └── navStack.pop() → 返回上一页

六、设置页面最佳实践

6.1 设计原则

  • 统一风格:所有设置项使用相同的 Builder 方法渲染,保证视觉一致性
  • 弹窗复用:通过 CustomContentDialog 封装统一弹窗,减少重复代码
  • 资源外置:隐私协议 URL 放在 rawfile 中,方便修改无需重新编译

6.2 可扩展性

设置页面采用了非常易于扩展的设计。添加一个新的设置项只需三步:

// 1. 在 Column 中添加新的 settingRow
this.settingRow('新增设置项', () => {
  // 2. 设置点击处理逻辑
  this.vm.navStack.pushPathByName('NewSettingPage', undefined);
});

// 3. 创建对应的设置页面
@Builder
export function NewSettingPageBuilder() {
  NewSettingPage();
}

七、总结

设置页面虽然看起来简单,但它集中展示了 ArkUI 开发的多个关键技术点:

  1. @Builder:通过 Builder 函数封装可复用的 UI 片段
  2. @Extend:扩展系统组件的样式方法
  3. CustomDialogController:管理弹窗生命周期
  4. rawfile 读取:通过 ResourceManager 读取原始资源文件
  5. AppStorage:全局状态管理(深色模式状态)

这些技术不仅适用于设置页面,在整个项目开发中都具有广泛的适用性。


关键源码文件:

  • products/entry/src/main/ets/pages/mine/Setting.ets — 设置页面完整实现
  • commons/commonLib/src/main/ets/utils/AccountUtil.ets — 退出登录逻辑
  • AppScope/resources/rawfile/data.json — 隐私协议 URL 配置
  • products/entry/src/main/ets/pages/mine/PrivacyAgreement.ets — 隐私协议页面
  • products/entry/src/main/ets/components/RightArrow.ets — 右侧箭头组件
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐