#HarmonyOS NEXT 体验官#一款应用中组件状态管理的实战案例研究 原创 精华

因为活着就一定行
发布于 2024-8-9 19:07
浏览
5收藏

任务管理小案例

在新一代HarmonyOS NEXT应用框架下,开发的核心逻辑采用了高级Stage模型作为组件间状态管理的基础。该模型为开发者提供了一种创新的途径,用以实现复杂的组件状态同步,尤其在涉及主项任务功能开发时表现出色。通过这种机制,我们不仅能够灵活地添加或移除子任务,还能实时同步数据更新,同时动态呈现Progress进度条效果。这个案例将详细展示如何运用Stage模型来高效管理鸿蒙原生应用中的任务状态,确保数据的一致性和视图的即时更新,从而极大地提高了用户体验和应用的性能。

Stage 模型概述

Stage模型作为管理应用状态与组件交互的核心机制,极大地简化了跨组件数据同步和状态管理的复杂性。首先,每个应用的AbilityStage实例在加载时创建,为组件管理提供基础。开发者根据需要选择UIAbility或ExtensionAbility组件,前者适用于直接用户交互,后者针对特定场景如卡片或后台任务。WindowStage负责UIAbility内的窗口管理,而Context及其派生类提供了访问资源与系统能力的途径。此外,Stage模型支持组件间通信与状态同步,确保应用响应一致并及时更新界面。

案例实现

关于首页布局

这段代码是一个使用HarmonyOS NEXT框架的自定义组件,用于构建一个任务管理应用的首页界面。主要包括以下几个部分:

  1. 定义了一个名为Home的结构体,包含了任务列表、任务总数、已完成的任务数、最新更新时间等状态变量。
  2. 使用了@Entry@Component装饰器,表示这是一个自定义组件。
  3. 定义了一个对话框控制器dialogController,用于控制新增任务对话框的显示和隐藏。
  4. 定义了onProgressChanged()方法,用于在总进度发生变化时更新任务总数和已完成的任务数。
  5. 定义了saveTask(name: string)方法,用于保存新增的任务。
  6. 使用了@Builder装饰器,定义了一个名为titleBar()的方法,用于构建标题栏。
  7. build()方法中,创建了一个垂直布局容器Column,包含了标题栏、任务概览和任务列表组件。同时设置了背景颜色、高度和宽度。
//ArkUS——Home
@Entry
@Component
struct Home {
    @State tasks: Task[] = Tasks.allData; //任务组
    @State total: number = 0; //任务数
    @State finish: number = 0; //完成数
    @State latestUpdate: string = CommonConstants.DEFAULT_PROGRESS_VALUE; //最新更新
    @Provide @Watch('onProgressChanged') totalProgressChanged: boolean = false; //总进度

    // 新增任务对话框控制器
    dialogController = new CustomDialogController({
        builder: AddTask({
        //确认按钮的回调: 将输入框的值,保存为新任务
        onConfirm: (value): void => this.saveTask(value)
        }),
        alignment: DialogAlignment.Bottom, //显示在底部
        offset: {
        //x,y轴的相对偏移
        dx: CommonConstants.DIALOG_OFFSET_X, //0
        dy: $r('app.float.dialog_offset_y')
        },
        customStyle: true, //自定义样式
        autoCancel: false //自动取消:否
    })

    // 当总进度发生变化时
    onProgressChanged() {
        this.total = this.tasks.length //更新总任务数
        this.finish = this.tasks.filter((task) => {
        return task.progress == CommonConstants.SLIDER_MAX_VALUE
        }).length //筛选任务组中进度为最大值(100)的数目
        this.latestUpdate = currentTime() //使用当前时间
    }

    //保存新增的任务
    saveTask(name: string) {
        if (name === '') { //如果任务名是空
        promptAction.showToast({
            //弹出提示提醒用户
            message: $r('app.string.cannot_input_empty'),
            duration: CommonConstants.TOAST_TIME,
            bottom: CommonConstants.TOAST_MARGIN_BOTTOM //底边距
        })
        return //退出保存
        }

        Tasks.add(new Task(name, currentTime(), 0)) //增加一条任务
        this.tasks = Tasks.allData //再获取任务组
        //总进度变化
        this.totalProgressChanged = !this.totalProgressChanged
        //关闭对话框
        this.dialogController.close()
    }
    
    @Builder // 轻量的UI元素复用机制@Builder,@Builder所装饰的函数遵循build()函数语法规则
    titleBar() { //标题栏,写在Build方法外
        Text($r('app.string.title'))
        .width('86.7%')
        .height('32vp')
        .fontSize('24fp')
        .fontWeight(700)
        .textAlign(TextAlign.Start)
        .margin({
            top: 12,
            bottom: 12
        })
    }

    build() {
        Column() { //首页根容器,垂直容器组件
        this.titleBar() //标题栏
        TaskOverview({ //任务概览
            latestUpdate: this.latestUpdate,
            total: this.total,
            finish: this.finish
        })
        // 任务列表组件,接收两个参数tasks和回调函数 onAddClick
        TaskList({
            tasks: $tasks, //参数——任务组,双向同步
            onAddClick: (): void => this.dialogController.open() // 子组件接收回调函数,调用打开模态框的方法
        })
            //list高度限制
            .height('55%')
        }
        .backgroundColor($r('app.color.index_background'))
        .height('100%')
        .width('100%')
    }
}

#HarmonyOS NEXT 体验官#一款应用中组件状态管理的实战案例研究-鸿蒙开发者社区#HarmonyOS NEXT 体验官#一款应用中组件状态管理的实战案例研究-鸿蒙开发者社区

关于任务概览自定义组件的介绍

任务概览界面在HarmonyOS NEXT中,它组件间的状态管理展示最新更新时间、总任务数、已完成的任务数以及一个直观的任务进度条,使用户能够迅速把握任务管理的总体情况。
这一界面位于应用的首页或专门的任务管理页面,便于用户快速访问。

TaskOverview 这是一个使用ArkUI和ArkTS开发的自定义组件,用于展示一个任务概览界面。
这个界面包含两部分:头部(Header)和总进度(TotalProgress)。头部显示了应用的图标、标题和副标题,总进度部分显示了任务的完成情况,包括已完成的任务数、总任务数和最近更新时间。

// ArkUI——TaskOverview

import { CommonConstants } from '../common/constant/CommonConstant'

@Extend(Text) function subTextStyle(){
  .fontSize($r('sys.float.ohos_id_text_size_sub_title3'))
  .fontColor($r('sys.color.ohos_id_color_text_secondary'))
}

// @Entry
@Component export
struct TaskOverview {
  //接收来自上层的最近更新时间,Prop可以有默认值
  @Prop latestUpdate : string = '2024/3/2 19:00:21'

  @Prop total: number = 3 //总数
  @Prop finish: number = 1 //已完成


  build() {
      Column() {
        this.Header()
        this.TotalProgress()
      }
      .width(CommonConstants.MAIN_BOARD_WIDTH)
      .backgroundColor(Color.White)
      .borderRadius(CommonConstants.TARGET_BORDER_RADIUS)
  }

  @Builder TotalProgress(){ }

  @Builder Header(){}

}

这段代码是用于构建一个Header组件,包含一个图片和一个文本列。通过 $r() 的方式引入了一个路径为"app.media.startIcon"的任务项的图片资源,并设置了宽度、高度和填充方式。文本列中包含了两个文本元素,分别是"2024开发任务"和"高质量完成交付"。这两个文本元素分别使用了不同的字体大小、颜色和字体加粗属性。最后,整个Header组件还设置了一些内边距和对齐方式。

// ArkUI-Header
@Builder Header(){
    Row() {
      Image($r("app.media.startIcon"))
        .width(96).height(96)
        .objectFit(ImageFit.Fill)
      Column() {
        Text('2024开发任务')
          .fontSize($r('sys.float.ohos_id_text_size_dialog_tittle'))
          .fontWeight(FontWeight.Bolder)
        Text('高质量完成交付')
          .fontColor($r('sys.color.ohos_id_color_text_secondary'))
          .fontSize($r('sys.float.ohos_id_text_size_sub_title1'))
      }
      .margin({ left: 20 })
      .alignItems(HorizontalAlign.Start)
    }
    .padding(20)
    .width('100%')
  }

ArkUI-TotalProgress 这段代码是用于构建一个TotalProgress组件,包含一个文本列和一个进度条。文本列中包含了两个文本元素,分别是"总进度"和"最近更新:",它们使用了不同的字体大小和加粗属性。另外,还使用了一个名为subTextStyle的Extend拓展样式方法来设置这两个文本元素的样式。在进度条部分,使用了Row和Stack布局,其中包含了两个文本元素,分别显示已完成的任务数和总任务数,以及一个圆形的进度条。最后,整个TotalProgress组件还设置了一些内边距和对齐方式。

// ArkUI-TotalProgress
@Builder TotalProgress(){
    Row(){
      Column(){
        Text('总进度')
          .fontSize($r('sys.float.ohos_id_text_size_sub_title1'))
          .fontWeight(FontWeight.Bold)
        Row(){
          Text('最近更新:').subTextStyle()
          Text(this.latestUpdate).subTextStyle()
        }
      }.alignItems(HorizontalAlign.Start)

      Blank()

      Stack(){
        Row(){
          Text(this.finish.toString())
            .fontSize($r('sys.float.ohos_id_text_size_sub_title3'))
          Text("/" + this.total.toString())
            .fontSize($r('sys.float.ohos_id_text_size_sub_title3'))
        }

        Progress({
          value: this.finish,
          total: this.total,
          type: ProgressType.Ring
        }).width(50)

      }.padding(20)

    }.width('100%').padding(20)
  }

这段样式代码目的视为提供整个项目开发过程中的统一通用规范样式常量,以保持代码的一致性和可维护性。

// ArkUI-CommonConstants


/**
 * Style constants that can be used by all modules
 * 所有模块都可以使用的样式常量
 */
export class CommonConstants {

  /**
   * Full width.
   */
  static readonly FULL_WIDTH: string = '100%';

  /**
   * Full height.
   */
  static readonly FULL_HEIGHT: string = '100%';

  /**
   * Normal font weight.
   */
  static readonly FONT_WEIGHT: number = 500;

  /**
   * Larger font weight.
   */
  static readonly FONT_WEIGHT_LARGE: number = 700;

  /**
   * Width of the content area.
   */
  static readonly MAIN_BOARD_WIDTH: string = '93.3%';

  /**
   * Opacity of text.
   */
  static readonly OPACITY: number = 0.4;

  /**
   * Opacity of not.
   */
  static readonly NO_OPACITY: number = 1;

  /**
   * Toast Duration.
   */
  static readonly TOAST_TIME: number = 3000;

  /**
   * Bottom toast.
   */
  static readonly TOAST_MARGIN_BOTTOM: number = 64;

  /**
   * Width of title.
   */
  static readonly TITLE_WIDTH: string = '86.7%';

  /**
   * Radius of bookInfo area.
   */
  static readonly TARGET_BORDER_RADIUS: number = 24;

  /**
   * Radius of bookInfo area.
   */
  static readonly IMAGE_BORDER_RADIUS: number = 12;

  /**
   * Margin left of book item.
   */
  static readonly TARGET_MARGIN_LEFT: string = '5%';

  /**
   * Width of slider.
   */
  static readonly STROKE_WIDTH: number = 4.8;

  /**
   * Width of dialog.
   */
  static readonly DIALOG_WIDTH: string = '90.3%';

  /**
   * Radius of dialog.
   */
  static readonly DIALOG_BORDER_RADIUS: number = 32;

  /**
   * Button width of dialog.
   */
  static readonly DIALOG_OPERATION_WIDTH: string = '70%';

  /**
   * Button height of dialog.
   */
  static readonly DIALOG_OPERATION_HEIGHT: string = '10%';

  /**
   * Button height of dialog.
   */
  static readonly DIALOG_INPUT_HEIGHT: string = '40%';

  /**
   * Input margin of dialog.
   */
  static readonly DIALOG_INPUT_MARGIN: string = '6%';

  /**
   * Horizontal offset of dialog.
   */
  static readonly DIALOG_OFFSET_X: number = 0;

  /**
   * The smallest value of slider.
   */
  static readonly SLIDER_MIN_VALUE: number = 0;

  /**
   * The maximum value of slider.
   */
  static readonly SLIDER_MAX_VALUE: number = 100;

  /**
   * Step of slider.
   */
  static readonly SLIDER_STEP: number = 1;

  /**
   * Width of slider.
   */
  static readonly SLIDER_WIDTH: string = '90%';

  /**
   * Height of slider.
   */
  static readonly SLIDER_HEIGHT: string = '20%';

  /**
   * Height of slider button.
   */
  static readonly SLIDER_BUTTON_HEIGHT: string = '21%';

  /**
   * Margin top of slider button.
   */
  static readonly SLIDER_BUTTON_MARGIN: string = '4%';

  /**
   * Margin top of slider button.
   */
  static readonly SLIDER_INNER_WIDTH: string = '90%';

  /**
   * Width of checkbox.
   */
  static readonly CHECKBOX_WIDTH: number = 20;

  /**
   * Max of line.
   */
  static readonly MAX_LINES: number = 2;

  /**
   * Height of list.
   */
  static readonly LIST_HEIGHT: string = '80%';

  /**
   * Height of list board.
   */
  static readonly LIST_BOARD_HEIGHT: string = '67%';

  /**
   * list space.
   */
  static readonly LIST_SPACE: number = 12;

  /**
   * Width of task item.
   */
  static readonly TASK_NAME_WIDTH: string = '30%';

  /**
   * Radius of list.
   */
  static readonly LIST_RADIUS: number = 24;

  /**
   * Default progress value.
   */
  static readonly DEFAULT_PROGRESS_VALUE: string = '-';

  /**
   * Current month plus one.
   */
  static readonly PLUS_ONE: number = 1;

  /**
   * The fraction or number of seconds is less than 10.
   */
  static readonly TEN: number = 10;

  /**
   * One task of data.
   */
  static readonly ONE_TASK: number = 1;

  /**
   * animation duration.
   */
  static readonly DURATION: number = 300;

  /**
   * default click index.
   */
  static readonly DEFAULT_CLICK_INDEX: number = -1;

  /**
   * default SliderChangeMode.
   */
  static readonly DEFAULT_SLIDER_MODE: number = -1;

  /**
   * click SliderChangeMode.
   */
  static readonly CLICK_SLIDER_MODE: number = 3;

  /**
   * x-axis transition animation.
   */
  static readonly TRANSITION_ANIMATION_X: number = 1;

  /**
   * y-axis transition animation.
   */
  static readonly TRANSITION_ANIMATION_Y: number = 0;
}

关于任务列表自定义组件的介绍

这段代码是一个基于ArkUI和ArkTS写的自定义组件代码,用于展示一个任务列表。这个组件包含了一个标题栏、任务列表和底部的操作按钮。标题栏包括了文字和编辑按钮,任务列表展示了所有的任务,底部的操作按钮包括了删除选中任务和添加新任务两个按钮。同时,这个组件还支持编辑模式,可以在编辑模式下进行任务的删除操作。

整端代码代码实现了一个任务列表组件,包括以下功能:

  1. 显示任务列表:组件通过ListListItem展示所有的任务,每个任务由TaskItem组件表示。

  2. 编辑模式:组件支持编辑模式,可以在编辑模式下进行任务的删除操作。在编辑模式下,可以全选或取消全选任务,或者删除选中的任务。

  3. 添加新任务:在非编辑模式下,可以通过点击"添加任务"按钮来添加新的任务。

  4. 删除任务:在编辑模式下,可以通过点击"删除"按钮来删除选中的任务。同时,会重置选中状态,并将总进度状态发生变化。

  5. 样式定制:组件提供了一些可扩展的样式方法,如operateButtonStyleoperateTextStyle,用于自定义按钮和文字的样式。

import { CommonConstants } from '../common/constant/CommonConstant'
import Task from '../model/Task'
import Tasks from '../model/Tasks'
import { TaskItem } from './TaskItem'
import currentTime from '../common/utils/DateLocal'

@Component export
struct TaskList {
  //总进度是否发生变化。 预览用State, 实际:Consume,因为子组件可以变更它
  @Consume totalProgressChanged: boolean //= false
  //是否开启了编辑模式(默认关闭)
  @State isEditMode: boolean = false
  //选中的任务组
  @State selectedTasks: boolean[] = []
  //点击的任务,默认没选中任何任务
  @State clickIndex: number = CommonConstants.DEFAULT_CLICK_INDEX
  @State selectAll: boolean = false //全选
  @Link tasks: Task[]

  onAddClick?: () => void //点击新任务按钮的事件(回调)

  build() {
    Column() {
      Row(){ //标题栏, 左侧是文字,右侧是编辑按钮
        Text($r('app.string.sub_goals'))
          .fontSize($r('app.float.secondary_title'))
          .fontWeight(CommonConstants.FONT_WEIGHT_LARGE)
        Blank() //中间空白
        if (this.tasks.length > 0) { //如果有任务,才可以编辑
          //此处是编辑状态代码
          if (this.isEditMode) { //如果处于可编辑状态,有取消/全选
            Text($r('app.string.cancel_button')) //取消按钮
              .operateTextStyle($r('app.color.main_blue'))
              .margin({left:$r('app.float.operate_button_margin')})
              .onClick(() =>{
                this.selectAll = false //全选为否
                this.isEditMode = false
                //根据任务数组,映射一个全部是false的数组
                this.selectedTasks = this.tasks.map(() => false)
                console.log('取消:' + JSON.stringify(this.selectedTasks))
              })
            Text($r('app.string.select_all_button'))
              .operateTextStyle($r('app.color.main_blue'))
              .margin({left: $r('app.float.operate_button_margin')})
            Checkbox() //选择框
              .select(this.selectAll) //是否全选
              .selectedColor($r('app.color.main_blue')) //选中色
              .width(CommonConstants.CHECKBOX_WIDTH)
              .onClick(() => {
                this.selectAll = !this.selectAll //全选状态反转
                //根据任务数组,映射一个全选状态的数组,全部是false或true
                this.selectedTasks = this.tasks.map(() => this.selectAll)
                console.log('全选: ' + this.selectAll + JSON.stringify(this.selectedTasks))
              })
          } else { //不在编辑模式时,显示编辑文字按钮
            Text($r('app.string.edit_button'))
              .operateTextStyle($r('app.color.main_blue'))
              .onClick(() => {
                this.isEditMode = true //启用编辑模式
              })
          }

        }
      }.width(CommonConstants.FULL_WIDTH)
      .height($r('app.float.history_line_height'))
      .padding({
        left: $r('app.float.list_padding'),
        right: $r('app.float.list_padding_right')
      })

      List({space: CommonConstants.LIST_SPACE}){
        ForEach(this.tasks,(item: Task,index) => {
          ListItem() {
            TaskItem({
              task: item, //任务
              index: index,
              isEditMode: this.isEditMode,
              selectedTasks: this.selectedTasks, //被选中的任务(双向)
              clickIndex: this.clickIndex //被选中的任务序号(双向)
            })
          }
        })
      }
      .margin({top: $r('app.float.list_margin_top')})
      .edgeEffect(EdgeEffect.Spring) //边缘动效:回弹
      .width(CommonConstants.FULL_WIDTH)
      .height(CommonConstants.LIST_HEIGHT)

      Blank()
      if (this.isEditMode) {
        Button($r('app.string.delete_button'))
          //如果有选中的任务,则显示为非透明,否则透明
          .opacity(this.isAnyTaskSelected() ? CommonConstants.NO_OPACITY : CommonConstants.OPACITY)
          //如果有选中的任务,按钮有效,否则无效(不可交互:例如点击)
          .enabled(this.isAnyTaskSelected() ? true : false)
          //删除是敏感操作,红色样式
          .operateButtonStyle($r('app.color.main_red'))
          .onClick(() =>{
            this.delSelected()
          })
      } else {
        //新增任务按钮
        Button($r('app.string.add_task'))
          .operateButtonStyle($r('app.color.main_blue'))
          .onClick(() => {
            if (this.onAddClick) {
              this.onAddClick()
            }
          })
      }
    }
    .width(CommonConstants.MAIN_BOARD_WIDTH)
    .height(CommonConstants.FULL_HEIGHT)
    .padding({top: $r('app.float.operate_row_margin')})
  }


  delSelected(){ //删除选中任务,并重置选中状态
    Tasks.del(this.selectedTasks) //删除选中的任务
    this.tasks = Tasks.allData //重新获取全部任务
    //总进度状态发生变化
    this.totalProgressChanged = !this.totalProgressChanged
    this.isEditMode = false //删除后退出编辑模式
    //选中任务全部清空(重置)
    this.selectedTasks = this.tasks.map(() => false)
    this.selectAll = false //取消全部选中
  }

  isAnyTaskSelected() {
    return this.selectedTasks.filter((selected) => selected === true).length > 0
  }
}

//"可互动的"按钮样式
@Extend(Button) function operateButtonStyle(color: Resource){
  .fontSize($r('app.float.button_font'))
  .fontColor(color)
  .fontWeight(CommonConstants.FONT_WEIGHT)
  .width($r('app.float.button_width'))
  .height($r('app.float.button_height'))
  .backgroundColor($r('app.color.button_background'))
}


//"可互动的"文字样式
@Extend(Text) function operateTextStyle(color: Resource){
  .fontSize($r('app.float.text_button_font'))
  .fontColor(color)
  .lineHeight($r('app.float.text_line_height'))
  .fontWeight(CommonConstants.FONT_WEIGHT)
}

这段代码定义了一个名为TaskItem的自定义组件,用于展示一个任务列表项,用于提供的主要功能如下:

  1. 显示任务名称、进度信息和更新日期。
  2. 支持点击展开或折叠任务的进度信息。
  3. 在编辑模式下,可以对任务进行删除操作。
  4. 支持动画效果,如展开和折叠时的缩放动画。
  5. 根据任务的进度和是否被选中,设置不同的透明度和背景颜色。
  6. 支持滑块模式,当用户点击滑块时,滑块事件归位,不继续处理任务本身的点击事件。
  7. 在编辑模式下,显示单选框,用于选择任务。
//ArkUI-TaskItem
import { CommonConstants } from '../common/constant/CommonConstant'
import currentTime from '../common/utils/DateLocal'
import Task from '../model/Task'
import Tasks from '../model/Tasks'
import { ProgressEdit } from './ProgressEdit'

@Extend(Text)  function opacityTextStyle(){
  .fontSize($r('app.float.text_font'))
  .fontColor($r('app.color.title_black_color'))
  .opacity(CommonConstants.OPACITY)
  .fontWeight(CommonConstants.FONT_WEIGHT)
}

@Component export
struct TaskItem {
  // 当前的任务;给一个默认值,方便调试
  @State task: Task = new Task('task 1', currentTime(),10)

  //当前任务的序号
  index = 0

  //总进度是否发生变化,需要回传,来自最上层Provide。 预览用State
  @Consume totalProgressChanged: boolean //= false

  //选中的任务组,来自上级,如果更新需回传(@link);为了调试方便,暂用@state
  @Link selectedTasks : boolean[]  //= []

  //是否开启了编辑模式,由上级组件决定
  @Prop isEditMode: boolean = true

  //展开进度面板
  @State isExpanded : boolean = true
  //进度面板滑动模式
  @State mode: number = CommonConstants.DEFAULT_SLIDER_MODE


  //点击的任务,保存其序号并回传(@link);为了调试方便,暂用@state
  //并且监听点击,@Watch,处理函数changeIndex
  @Link @Watch('onClickIndex') clickIndex: number //= 0

  //索引点击监听器:如果点击的不是当前任务,则折叠进度编辑面板。
  onClickIndex(){
    if (this.clickIndex !== this.index) {
      this.isExpanded == false
    }
  }

  //最新进度
  @State latestProgress: number = 0
  //更新日期
  @State updateDate?: string = ''



  //页面即将显示
  aboutToAppear(): void {
    this.latestProgress = this.task.progress
    this.updateDate = this.task.update
  }

  @Builder TaskInfo() { //任务信息文字栏
    Row(){
      Text(this.task.name) //任务名
        .fontSize($r('app.float.list_font'))
        .fontWeight(CommonConstants.FONT_WEIGHT)
        .fontColor($r('app.color.title_black_color'))
        .width(CommonConstants.TASK_NAME_WIDTH)
        .textAlign(TextAlign.Start)
        .maxLines(CommonConstants.MAX_LINES)
      Blank()
      Column(){ //进度信息
        Text(`${this.latestProgress}%`) //任务进度
          .fontSize($r('app.float.list_font'))
          .fontWeight(CommonConstants.FONT_WEIGHT)
          .fontColor($r('app.color.title_black_color'))

        Row(){
          Text($r('app.string.latest_updateTime')).opacityTextStyle()
          Text(this.updateDate).opacityTextStyle()
        }.margin({top : $r('app.float.text_margin')})
      }.alignItems(HorizontalAlign.End) //列内容居右排列
    }
  }



  build() {
    Stack({alignContent: Alignment.TopEnd}){
      Column() {
        this.TaskInfo()
        if (this.isExpanded) { //如果是展开的话,
          Blank() //列的自动间隔
          ProgressEdit({
            progress: this.latestProgress, //最新进度
            mode: this.mode, //滑动模式
            onCancel: () => this.isExpanded = false, //取消事件: 折叠面板
            onConfirm: (value) => { //确认更新进度后
              this.latestProgress = value //把最新进度设置为滑动后的值
              this.updateDate = currentTime() //使用最新的系统日期更新

              //如果更新指定索引的任务成功
              if (Tasks.updateTo(this.index, this.latestProgress, this.updateDate)) {
                this.totalProgressChanged = !this.totalProgressChanged //整体进度发生了变化


              }
              this.isExpanded = false //更新进度折叠面板
            }
          })//进度面板
            .transition({ //折叠动画
              scale: { //尺寸缩放:收缩成一条线消失
                x: CommonConstants.TRANSITION_ANIMATION_X, //x轴方向为原来1倍
                y: CommonConstants.TRANSITION_ANIMATION_Y //y轴方向为原来0倍
              }
            })
        }
      }
      .height(this.isExpanded ? $r('app.float.expanded_item_height')
        : $r('app.float.list_item_height'))
      .width(CommonConstants.FULL_WIDTH)
      .opacity( //如果进度拉满(100),则整个容器变成半透明状态,否则是不透明
        this.latestProgress === CommonConstants.SLIDER_MAX_VALUE ?
        CommonConstants.OPACITY : CommonConstants.NO_OPACITY
      )
      .borderRadius(CommonConstants.LIST_RADIUS) //单个任务列表的圆角
      .animation({duration: CommonConstants.DURATION}) //属性动画:时长
      .backgroundColor(this.selectedTasks[this.index] ? $r('app.color.edit_blue')
        : Color.White) //背景色:当前任务蓝色
      .onClick(() => { //点击事件处理
        if (this.mode == CommonConstants.CLICK_SLIDER_MODE) { //如果点击的是滑块
          this.mode = CommonConstants.DEFAULT_SLIDER_MODE //滑块事件归位
          return //返回。不继续处理 任务本身容器的点击事件
        }

        if (!this.isEditMode) { //非编辑状态,
          // 显式动画: 折叠/展开效果
          animateTo({duration: CommonConstants.DURATION}, () => {
            this.isExpanded = !this.isExpanded
          })
          this.clickIndex = this.index //点击索引赋值
        }
      })
      .padding({
        top: $r('app.float.list_padding_top'),
        bottom: $r('app.float.list_padding_bottom'),
        left: $r('app.float.list_padding'),
        right: this.isEditMode ? $r('app.float.list_edit_padding') :
        $r('app.float.list_padding') //编辑模式,右边距加大
      })

      if (this.isEditMode) { //如果处于编辑模式
        Column() {
          Checkbox() //单选框
            .margin({right: $r('app.float.list_padding')})
            .width(CommonConstants.CHECKBOX_WIDTH)
            .selectedColor($r('app.color.main_blue'))
            .onChange((isChecked) => { //点击后,给任务选择状态赋值
              this.selectedTasks[this.index] = isChecked
            })
              //选中状态,与点击绑定
            .select(this.selectedTasks[this.index])
        }
        .height(CommonConstants.FULL_HEIGHT)
        .justifyContent(FlexAlign.Center) //列元素居中
      }
    }.width(CommonConstants.FULL_WIDTH)
    .height(this.isExpanded ? $r('app.float.expanded_item_height')
      : $r('app.float.list_item_height'))
  }
}

这段代码定义了两个组件 ProgressEdit和TextButton。它们可以组合在一起使用,用于创建任务进度编辑界面

  1. ProgressEdit组件用于展示一个任务的进度编辑界面,包括一个滑块和一个显示进度百分比的文本框。用户可以通过滑动滑块来调整进度,点击取消按钮可以关闭编辑界面,点击确定按钮后会返回当前进度值给上级组件。

  2. TextButton组件是一个文本按钮,具有正常状态下的背景色、圆角、字体大小、字体颜色等属性。点击按钮时,会有按下和松开两种状态的颜色变化效果。

// ArkUI-ProgressEdit
@Component export
struct ProgressEdit {
  //进度,来自上一级的任务数据。 单位为百分比,进度可以反馈给上级,为了独立预览,采用Prop
  @Prop progress: number = 10 //
  //滑动模式
  @State mode: number = 1
  //点击取消
  onCancel? : () => void
  //点击确定后,返回进度
  onConfirm? : (progress: number) => void

  build() {
    Column() {
      Row({ space: 10 }) {
        Slider({
          value: this.progress, //进度
          step: 1, //步长,每次加1
          style: SliderStyle.InSet //滑块在轨道内
        })
          .width('80%')
          .onChange((value, mode) => {
            this.progress = Math.trunc(value) //进度取整数
            this.mode = mode
          })

        Text(this.progress + '%')//进度百分比
          .fontSize($r('sys.float.ohos_id_text_size_button1'))//系统1号按钮大小
          .fontColor($r('sys.color.ohos_id_text_color_active')) //活跃文字的颜色

      }.justifyContent(FlexAlign.Center) //Row的主轴方向(水平),元素居中排列
      .width('100%')

      Row(){
        TextButton({title: '取消'})
          .onClick(() => {
            if (this.onCancel) {
              this.onCancel()
            }
          })
        Blank()
        TextButton({title: '确定'})
          .onClick(() =>{
            if (this.onConfirm) {
              this.onConfirm(this.progress) //返回进度值
            }
          })
      }.width('70%').padding(10)
    }
  }
}

@Component
struct TextButton {
  //背景色:正常按钮颜色
  @State bgColor: Resource = $r('sys.color.ohos_id_color_button_normal')
  title: ResourceStr = '标题' //标题确定后不变,所以只使用普通变量

  build() {
    Text(this.title)
      .backgroundColor(this.bgColor)//背景色
      .borderRadius(10)//按钮圆角
      .fontSize($r('sys.float.ohos_id_text_size_button1'))//系统1号按钮大小
      .fontColor($r('sys.color.ohos_id_text_color_active'))//活跃文字的颜色
      .width(96)
      .textAlign(TextAlign.Center)
      .onTouch((e) => { //点击按钮后
        if (e.type == TouchType.Down) { //如果是按下,
          //Picker选择器的按下色
          this.bgColor = $r('sys.color.ohos_id_picker_press_color')
        }
        if (e.type == TouchType.Up) { //如果松开,背景色还原
          this.bgColor = $r('sys.color.ohos_id_color_button_normal')
        }
      })
      .padding(5) //内边距5
  }
}

定义并暴露了一个名为currentTime的函数。该函数不接受任何参数,当调用时,会返回当前的本地时间字符串,格式为’zh-CN’,并指定时区为’Asia/Shanghai’(亚洲/上海)。

export  default  function currentTime()  {
  return new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai'})
}

定义数据模型 Task

// ArkTS data model Task

export default
class Task {
  constructor(name: string, update: string, progress: number) {
    this.name = name
    this.update = update
    this.progress = progress
  }
  name: string
  update: string
  progress: number

}

定义数据模型 Tasks

// ArkTS data model Tasks
import Task from './Task'


class Tasks {
  private _allData: Task[] = []

  public get allData(): Task[] {
    return this._allData
  }

  //添加
  add(task: Task) {
    this._allData.push(task)
  }

  //删除选中的1个或多个任务,selected: 任务选中状态数组,长度与任务数一致
  del(selected: boolean[]) {
    //对任务组进行筛选, 只取index序号
    this._allData = this._allData.filter((_, index) => {
        // 通过判断任务组勾选状态,控制过滤的allData,最终返回未被删除的任务组实现软删除
        if (!selected[index]) {
            selected[index] = false;
            return true;
        } else {
            return false;
        }
    })
    return selected;
  }

  //获取最新一条任务的进度: 因为是新任务是添加到数组最后的 (add方法),所以取数组的最后一个元素
  getLatestProgress() {
    if (this._allData.length == 0) {
      return 0
    }
    return this._allData.splice(-1)[0].progress
  }

  //更新指定任务的进度、日期
  updateTo(index: number, progress: number, update: string): boolean {
    if (this._allData[index]) { //如果是有效的任务 (索引可访问)
      this._allData[index].progress = progress
      this._allData[index].update = update
      return true
    }
    return false
  }

}

export  default new Tasks()


最终效果

#HarmonyOS NEXT 体验官#一款应用中组件状态管理的实战案例研究-鸿蒙开发者社区

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2024-8-14 15:36:10修改
3
收藏 5
回复
举报
3条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

感觉使用后能事半功倍

回复
2024-8-12 11:17:58
FlashinMiami
FlashinMiami

UI挺不错的

回复
2024-8-14 15:03:08
用户7936110435
用户7936110435

盛世公司网上网投游戏注册网址www.TL9259.com

回复
2024-8-14 22:57:17
回复
    相关推荐