鸿蒙应用开发-目标管理(总结)(增加了网格布局,待办事项,组内转场)

目标管理

1. 主页面(MainPage)

1.1 布局

分为行容器,目标卡片组件,子目标组件

在这里插入图片描述

1.2 代码说明

Build()中,将页面分成了3部分

  • row()行组件:为首页第一行文字"目标卡片" "更多"在“更多>”中设置了点击跳转页面至page0

  • TargetInformation()目标卡片:将其封装成 了一个组件,并通过父组件传递相应参数

  • TargetList():封装子目标组件,包括下方列表和按钮,并传递相应参数,使用$targetDate进行双向数据传递,以及onAddClick:方法

onAddClick: (): void => this.dialogController.open()

dialogController是一个用于控制对话框的控制器,而open()方法则是用来显示对话框的函数。当用户执行特定的操作(如点击一个按钮)时,通过onAddClick函数触发dialogController.open(),从而显示一个自定义的对话框。这种方式常用于在用户界面中提供反馈、收集信息或进行进一步的交互‌

1.3 完整代码
import TargetInformation from '../view/TargetInformation';
import AddTargetDialog from '../view/AddTargetDialog';
import TargetList from '../view/TargetList';
import DataModel, { TaskItemBean } from '../viewmodel/DataModel';
import { CommonConstants } from '../common/constant/CommonConstant';
import getCurrentTime from '../common/utils/DateUtil';
import promptAction from '@ohos.promptAction';
import { router } from '@kit.ArkUI';

@Entry
@Component
struct MainPage {
  @State targetData: Array<TaskItemBean> = DataModel.getData()//获取多个子目标
  @State totalTasksNumber: number = 0;//所有事项
  @State completedTasksNumber: number = 0;//完成事项
  @State latestUpdateDate: string = CommonConstants.DEFAULT_PROGRESS_VALUE;//最新日期
  @Provide @Watch('onProgressChanged') overAllProgressChanged: boolean = false;//监听到数据改变
  dialogController: CustomDialogController = new CustomDialogController({
    builder: AddTargetDialog({ //弹窗函数名
      onClickOk: (value: string): void => this.saveTask(value) //调用saveTask方法接收参数
    }),
    alignment: DialogAlignment.Bottom, //弹框位于底部
    offset: {//偏移量
      dx: CommonConstants.DIALOG_OFFSET_X, // 0
      dy: $r('app.float.dialog_offset_y')
    },
    // customStyle: true 这一属性或配置通常用于指示某个组件或界面元素使用自定义样式。
    // 这意味着开发者可以提供自己的样式定义,而不是使用系统默认的样式。
    customStyle: true,
    autoCancel: false//自动关闭
  });


  /**
   * 监听子目标数据
   */
  onProgressChanged() {
    this.totalTasksNumber = this.targetData.length; // 总任务数
    this.completedTasksNumber = this.targetData.filter((item: TaskItemBean) => {//filter过滤  当一个任务达到100%时,完成数+1
      return item.progressValue === CommonConstants.SLIDER_MAX_VALUE; // filter方法筛选出了所有progressValue为100的对象,
    }).length;  // .length属性用于获取这些对象的数量
    this.latestUpdateDate = getCurrentTime();//获取系统时间
  }

  build() {
    Column() {
      Row(){
        this.titleBar()//文字:目标卡片
        Text('更多>')
          .onClick(() => {
            router.pushUrl({url:'pages/page0'})
          })
          .fontSize(18)
          .fontWeight(700)
      }
      .margin({left: 10, right: 20})

      //目标卡片
      TargetInformation({
        latestUpdateDate: this.latestUpdateDate,
        totalTasksNumber: this.totalTasksNumber,
        completedTasksNumber: this.completedTasksNumber
      })
      //子目标
      TargetList({
        targetData: $targetData, //$targetDara是双向数据传递 使用@link
        onAddClick: (): void => this.dialogController.open() //在用户点击某个按钮或触发某个事件时,调用dialogController.open()方法打开一个对话框。
      })
        .height(CommonConstants.LIST_BOARD_HEIGHT)
    }
    .width(CommonConstants.FULL_WIDTH)
    .height(CommonConstants.FULL_HEIGHT)
    .backgroundColor($r('app.color.index_background'))
  }

  // 标题装饰
  @Builder
  titleBar() {
    Text($r('app.string.title'))
      .width(CommonConstants.TITLE_WIDTH)
      .height($r('app.float.title_height'))
      .fontSize($r('app.float.title_font'))
      .fontWeight(CommonConstants.FONT_WEIGHT_LARGE)
      .textAlign(TextAlign.Start)
      .margin({
        top: $r('app.float.title_margin'),
        bottom: $r('app.float.title_margin')
      })
  }

  /**
   * Save the progress value and update time after you click OK in the dialog box.
   *
   * @param taskName Latest Progress Value. 最新进度值
   * 增加子目标弹窗中,点击确定后,保存进度并保存数据以及更新时间
   * 保存添加子目标时输入框接收的数据
   */
  saveTask(taskName: string) {
    if (taskName === '') {
      promptAction.showToast({//判断任务名是否为空
        message: $r('app.string.cannot_input_empty'),
        duration: CommonConstants.TOAST_TIME,
        bottom: CommonConstants.TOAST_MARGIN_BOTTOM
      });
      return;
    }
    DataModel.addData(new TaskItemBean(taskName, 0, getCurrentTime()));//调用增加函数
    this.targetData = DataModel.getData();//重新获取刷新数据
    this.overAllProgressChanged = !this.overAllProgressChanged;
    this.dialogController.close();
  }
}

2. 目标卡片组件(TargeInformation)

2.1 布局

将卡片分为上下两行,并使用了堆叠布局

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.2 代码说明

将卡片分为两部分:

  • this.TargetItem():为图片 + 标题
  • this.OverallProgress():为日期 + 进度条

this.TargetItem()中包含一个行容器,里面嵌套着列容器,显示默认的文本内容

this.OverallProgress()中先包含一个行容器,里面分为一个列容器和堆叠组件,并使用Blank()空白组件撑开,列容器中展示最新更新的时间,堆叠部分使用了Progress和行容器展示了目标进度和完成度。其中更新时间,总任务数以及完成任务数是由父组件传递过来的,在此页面中使用prop装饰器展示不变更的数据

2.3 完整代码

// 目标卡片组件

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

@Component
export default struct TargetInformation {
  // 展示不变更的数据 使用prop装饰器
  @Prop latestUpdateDate: string = ''; //时间
  @Prop totalTasksNumber: number = 0; //总任务数
  @Prop completedTasksNumber: number = 0; //完成任务数

  build() {
    Column() {
      // image + title
      this.TargetItem()
      //data + progress
      this.OverallProgress()
    }
    .padding($r('app.float.target_padding'))
    .width(CommonConstants.MAIN_BOARD_WIDTH)
    .height($r('app.float.target_info_height'))
    .backgroundColor(Color.White)
    .borderRadius(CommonConstants.TARGET_BORDER_RADIUS)
  }

  @Builder
  TargetItem() {
    Row() {
      Image($r("app.media.ic_main"))
        .width($r('app.float.target_image_length'))
        .height($r('app.float.target_image_length'))
        // 通过objectFit()方法来设置图片的显示效果
        // ImageFit.Fill : 不保持宽高比进行放大缩小,使得图片充满显示区域。
        .objectFit(ImageFit.Fill)
        .borderRadius(CommonConstants.IMAGE_BORDER_RADIUS)
      Column() {
        Text($r('app.string.target_name'))
          .fontSize($r('app.float.target_name_font'))
          .fontWeight(CommonConstants.FONT_WEIGHT_LARGE)
          .width(CommonConstants.TITLE_WIDTH)

        Text($r('app.string.target_info'))
          .opacityTextStyle()
          .fontSize($r('app.float.target_desc_font'))
          .margin({ top: $r('app.float.title_margin') })
      }
      .width('60%')
      .margin({ left: CommonConstants.TARGET_MARGIN_LEFT })
      .alignItems(HorizontalAlign.Start)
    }
    .width(CommonConstants.FULL_WIDTH)
  }

  @Builder
  OverallProgress() {
    Row() {
      Column() {
        Text($r('app.string.overall_progress'))
          .fontSize($r('app.float.button_font'))
          .fontColor($r('app.color.title_black_color'))
          .fontWeight(CommonConstants.FONT_WEIGHT)
        Row() {
          Text($r('app.string.latest_updateTime'))
            .opacityTextStyle()
          Text(this.latestUpdateDate)
            .opacityTextStyle()
        }
        .margin({ top: $r('app.float.text_margin') })
      }
      .alignItems(HorizontalAlign.Start)//首部对齐

      Blank()
      // 堆叠
      Stack() {
        Row() {
          Text(this.completedTasksNumber.toString())
            .fontSize($r('app.float.progress_font'))
            .fontWeight(CommonConstants.FONT_WEIGHT)
            .fontColor($r('app.color.main_blue'))
          Text(`/${this.totalTasksNumber}`)
            .fontSize($r('app.float.progress_font'))
            .fontWeight(CommonConstants.FONT_WEIGHT)
        }

        // 进度条
        Progress({
          value: this.completedTasksNumber,
          total: this.totalTasksNumber,
          type: ProgressType.Ring//圆形
        })
          .color($r('app.color.main_blue'))
          .style({//将圆形变粗
            strokeWidth: CommonConstants.STROKE_WIDTH
          })
          .width($r('app.float.progress_length'))
          .height($r('app.float.progress_length'))
      }
    }
    .width(CommonConstants.FULL_WIDTH)
    .height($r('app.float.progress_length'))
    .margin({ top: $r('app.float.progress_margin_top') })
  }
}

/**
 * 自定义透明文本样式
 */
@Extend(Text) function opacityTextStyle() {
  .fontSize($r('app.float.text_font'))
  .fontColor($r('app.color.title_black_color'))
  // opacity 属性指定了一个元素的不透明度。
  // 换言之,opacity 属性指定了一个元素后面的背景的被覆盖程度
  .opacity(CommonConstants.OPACITY)
  .fontWeight(CommonConstants.FONT_WEIGHT)
}

3. 子目标组件(TargetList)

3.1 布局

使用列容器,里面包含一个行容器,list容器和按钮

在这里插入图片描述

在这里插入图片描述

3.2 代码说明
3.2.1 行文本

行容器中显示文本:子目标,当列表数据大于0时,显示编辑操作,否则不显示

this.targetData列表数据使用@link装饰器实现双向的数据绑定,为数组类型,其中的元素类型为TaskItemBean类,该类是在DataModel中定义的任务项实体类别,包括任务项的名称,更新时间,进度值,并定义了构造函数,以确保每个任务对象在创建时都具有这些基本信息‌

当存在数据时,通过isEditMode进行控制渲染,其默认值为false,显示编辑字样,当被点击时为真,显示取消和全选,以及Checkbox复选框进行多项选择,点击复选框时,selectAll变为true,勾选,并调用selectAllOrCancel函数,将数据以boolean的形式存储为一个新数组

3.2.2 list列表

列表使用循环渲染,并利用键值生成器,用于给数组中的每一个数据项生成唯一且固定的键值,需选择一个自定义方法,方法默认接收item和index参数,列表项的具体内容封装在TargetListItem里,并进行传参

删除或添加按钮

(1)删除

通过isSelectRows()检测是否有选择行函数,来控制删除按钮的启动与关闭,和透明度的显示

点击时会调用deleteSelected()删除并退出编辑函数,该函数定义在DataModel里,删除传入的数组中为真的数据,然后重新获取数组

(2)添加

点击时新建一个弹框,调用弹框组件AddTargeDialog,其中定义了提示内容、输入框和按钮,点击确定时,传递输入框内容,父组件调用saveTask方法接收参数,调用增加函数addData,将数据加入到数组中,重新获取数据,并监听数据的改变

3.3 完整代码
/*
 * 目标列表
 */

import TargetListItem from './TargetListItem';
import { CommonConstants } from '../common/constant/CommonConstant';
import DataModel, { TaskItemBean } from '../viewmodel/DataModel';

@Component
export default struct TargetList {
  @Consume overAllProgressChanged: boolean; //弹窗
  //@Consume装饰器用于在鸿蒙系统中实现数据的双向传递。
  // 不需要传递参数,可以直接消费父组件@Provide修饰的数据变量
  @State isEditMode: boolean = false; //是否处于编辑状态  控制渲染
  @State selectArray: Array<boolean> = [];
  @State clickIndex: number = CommonConstants.DEFAULT_CLICK_INDEX; // -1
  @State selectAll: boolean = false; //勾选 数据状态
  // link装饰器实现双向的数据绑定
  @Link targetData: Array<TaskItemBean>;
  onAddClick?: () => void; //可选的点击事件处理函数

  build() {
    Column() {
      Row() {
        Text($r('app.string.sub_goals'))
          .fontSize($r('app.float.secondary_title'))
          .fontWeight(CommonConstants.FONT_WEIGHT_LARGE)
          .fontColor($r('app.color.title_black_color'))
        // 空白分开
        Blank()
        if (this.targetData.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;
                this.selectAllOrCancel(false);
              })
            Text($r('app.string.select_all_button'))
              .operateTextStyle($r('app.color.main_blue'))
              .margin({
                left: $r('app.float.operate_button_margin')
              })
            Checkbox()
              .select(this.isSelectAll()) //复选框是否被选中
              .selectedColor($r('app.color.main_blue'))
              .width(CommonConstants.CHECKBOX_WIDTH)
              .onClick(() => {
                this.selectAll = !this.selectAll; //变为true 勾选
                this.selectAllOrCancel(this.selectAll);
              })
          } else {
            Text($r('app.string.edit_button'))
              .operateTextStyle($r('app.color.main_blue'))
              .onClick(() => {
                this.isEditMode = true;
                this.selectAllOrCancel(false);//全选为假
              })
          }
        }
      }
      .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.targetData, (item: TaskItemBean, index: number | undefined) => { //键值生成器
          ListItem() {
            TargetListItem({
              taskItem: item,
              index: index,
              selectArr: $selectArray,
              isEditMode: this.isEditMode,
              clickIndex: $clickIndex
            })
          }
        }, (item: TaskItemBean) => JSON.stringify(item)) //把对象改成字符串,把他序列化,保证每一个对象是新的
      }
      .edgeEffect(EdgeEffect.None) //消除列表上下拉动的弹性效果,使得列表的滚动更加平滑和自然‌
      .margin({ top: $r('app.float.list_margin_top') })
      .width(CommonConstants.FULL_WIDTH)
      .height(CommonConstants.LIST_HEIGHT)

      Blank()

      // 删除或添加按钮
      if (this.isEditMode) {
        Button($r('app.string.delete_button'))
          .opacity(this.isSelectRows() ? CommonConstants.NO_OPACITY : CommonConstants.OPACITY) //1:0.4
          //opacity属性用于设置元素的不透明度
          .enabled(this.isSelectRows() ? true : false) //组件的启动与关闭,关闭时不可点击
          .operateButtonStyle($r('app.color.main_red'))
          .onClick(() => {
            this.deleteSelected();
            this.selectAllOrCancel(false);
            this.selectAll = false;
          })
      } else {
        Button($r('app.string.add_task'))
          .operateButtonStyle($r('app.color.main_blue'))
          .onClick(() => {
            if (this.onAddClick !== undefined) {
              //为了避免在调用函数时出现错误
              // 如果onAddClick函数已经定义,那么表达式的结果为true,否则为false
              this.onAddClick()
            }
          })
      }
    }
    .width(CommonConstants.MAIN_BOARD_WIDTH)
    .height(CommonConstants.FULL_HEIGHT)
    .padding({ top: $r('app.float.operate_row_margin') })
  }

  /**
   * 删除所选项目 并 退出编辑模式
   */
  deleteSelected() {
    DataModel.deleteData(this.selectArray);
    this.targetData = DataModel.getData();
    this.overAllProgressChanged = !this.overAllProgressChanged;
    this.isEditMode = false;
  }

  /**
   * 选择或取消选择全部。
   * @param selectStatus true:选择全部。否则,取消选择全部
   */
  selectAllOrCancel(selectStatus: boolean) {
    let newSelectArray: Array<boolean> = []; //存储为真或假的数组,真则是要删除的
    this.targetData.forEach(() => {
      newSelectArray.push(selectStatus);
    });
    this.selectArray = newSelectArray;
  }

  /**
   * 是否全部选择
   */
  isSelectAll(): boolean {
    if (this.selectArray.length === 0) {
      return false;
    }
    let deSelectCount: Length = this.selectArray.filter((selected: boolean) => selected === false).length;//过滤为假的数组,即未选中
    if (deSelectCount === 0) {
      this.selectAll = true;
      return true;
    }
    this.selectAll = false;
    return false;
  }

  /**
   * 检查是否有选择的行
   */
  isSelectRows(): boolean {
    return this.selectArray.filter((selected: boolean) => selected === true).length !== 0;
    //length !== 0 将返回 true
  }
}

/**
 * 自定义文本按钮
 */
@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)
}

/**
 * 自定义按钮样式
 */
@Extend(Button) function operateButtonStyle(color: Resource) {
  .width($r('app.float.button_width'))
  .height($r('app.float.button_height'))
  .fontSize($r('app.float.button_font'))
  .fontWeight(CommonConstants.FONT_WEIGHT)
  .fontColor(color)
  .backgroundColor($r('app.color.button_background'))
}

4. 具体列表项(TargetListItem)

4.1 布局

分为基本信息 + 进度条

在这里插入图片描述

在这里插入图片描述

4.2 代码说明

使用@Watch监控变量overAllProgressChanged表示页面有更新,再使用@Provide和@Consume装饰器装饰变量overAllProgressChanged

祖先组件使用@Provide装饰器装饰变量overAllProgressChanged

包含标题、进度、更新时间,设置了一个控制展开进度条的变量,在非编辑状态下,点击目标项,将子目标展开,并传递索引值,并监听索引值,控制子目标的展开和隐藏,进度条设置封装在ProgressEditPanel,

当点击子目标项时,展开进度条,调用ProgressEditPanel进度条组件

4.3 完整代码
/*
 * 目标列表项
 */

import { CommonConstants } from '../common/constant/CommonConstant';
import getCurrentTime from '../common/utils/DateUtil';
import DataModel, { TaskItemBean } from '../viewmodel/DataModel';
import ProgressEditPanel from './ProgressEditPanel';

@Component
export default struct TargetListItem {
  private taskItem?: TaskItemBean; //标题
  @State latestProgress?: number = 0; //任务进度
  @State updateDate?: string = ''; //添加时间
  @Link selectArr: Array<boolean>; //选中数据
  @Prop isEditMode: boolean = false; //从父组件单向同步状态 是否是可编辑状态
  @Link @Watch('onClickIndexChanged') clickIndex: number; //索引号
  @State isExpanded: boolean = false; //控制子目标展开和隐藏
  @Consume overAllProgressChanged: boolean;
  @State sliderMode: number = CommonConstants.DEFAULT_SLIDER_MODE; //滑动模块?
  public index: number = 0;

  // 生命周期函数
  // 组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行
  // 在执行其build函数之前。这个方法允许开发者在组件显示之前进行一些准备工作
  aboutToAppear() {
    this.latestProgress = this.taskItem?.progressValue; // 任务进度 ?表示可选的可变的
    this.updateDate = this.taskItem?.updateDate; //时间
  }

  /**
   * Listening click index.
   * 监听索引
   */
  onClickIndexChanged() {
    if (this.clickIndex !== this.index) {
      this.isExpanded = false;
    }
  }

  build() {
    //层叠布局
    Stack({ alignContent: Alignment.Start }) { //子组件在主轴方向首端对齐
      Column() {
        // 基本信息 标题,进度,更新时间
        this.TargetItem()
        if (this.isExpanded) {
          // 如果展开了
          Blank()
          // 进度条
          ProgressEditPanel({
            slidingProgress: this.latestProgress, //当前进度
            onCancel: () => this.isExpanded = false, //点击取消
            onClickOK: (progress: number): void => { //点击确定
              this.latestProgress = progress;
              this.updateDate = getCurrentTime(); //重新获取时间
              let result = DataModel.updateProgress(this.index, this.latestProgress, this.updateDate);
              //实时更新当前索引的数据变化 变化了则为真
              if (result) {
                this.overAllProgressChanged = !this.overAllProgressChanged;
              }
              this.isExpanded = false;
            },
            sliderMode: $sliderMode
          })
            // 缩放动画
            .transition({
              scale: {
                x: CommonConstants.TRANSITION_ANIMATION_X,
                y: CommonConstants.TRANSITION_ANIMATION_Y
              }
            })
        }
      }
      .padding({
        left: $r('app.float.list_padding'),
        top: $r('app.float.list_padding_top'),
        bottom: $r('app.float.list_padding_bottom'),
        right: this.isEditMode ? $r('app.float.list_edit_padding') : $r('app.float.list_padding') //如果点击了编辑 调整右边距(留出复选框的位置)
      })
      .height(this.isExpanded ? $r('app.float.expanded_item_height') : $r('app.float.list_item_height')) //如果点击了展开 扩展高度
      .width(CommonConstants.FULL_WIDTH)
      .opacity( //不透明度
        this.latestProgress === CommonConstants.SLIDER_MAX_VALUE ?
        CommonConstants.OPACITY : CommonConstants.NO_OPACITY
        //任务进度为100时透明度变淡
      )
      .borderRadius(CommonConstants.LIST_RADIUS)
      .animation({ duration: CommonConstants.DURATION }) //实现动画效果 duration参数用于指定动画的持续时间,单位为毫秒
      .backgroundColor(this.selectArr[this.index] ? $r('app.color.edit_blue') : Color.White)
      .onClick(() => {
        if (this.sliderMode === CommonConstants.CLICK_SLIDER_MODE) {
          this.sliderMode = CommonConstants.DEFAULT_SLIDER_MODE;
          return;
        }
        if (!this.isEditMode) {
          // 是否展开下方进度条
          // 进度条动画默认是关闭
          animateTo({ duration: CommonConstants.DURATION }, () => { //显示动画 duration:动画持续时间,单位为毫秒
            this.isExpanded = !this.isExpanded; //子目标展开状态
          })
          this.clickIndex = this.index;
        }
      })

      if (this.isEditMode) {
        Row() {
          // 点击时触发事件,
          Checkbox()
            .select(this.selectArr[this.index])
            .selectedColor($r('app.color.main_blue'))
            .width(CommonConstants.CHECKBOX_WIDTH)
            .margin({ right: $r('app.float.list_padding') })
            .onChange((isCheck: boolean) => {
              this.selectArr[this.index] = isCheck;
            })
        }
        .width(CommonConstants.FULL_WIDTH)
        .justifyContent(FlexAlign.End)
      }
    }
    .width(CommonConstants.FULL_WIDTH)
  }

  // 子目标基本信息
  @Builder TargetItem() {
    Row() {
      // 标题
      Text(this.taskItem?.taskName)
        .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)
    }
    .width(CommonConstants.FULL_WIDTH)
  }
}

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

5. 进度条(ProgressEditPanel)

5.1 布局

滑动进度条 + ”取消“”确定“按钮

在这里插入图片描述

5.2 代码说明

通过slider定义滑动条的相关属性,点击确定,就会进行回调,将数据返回给父组件,触发事件监听

5.3 完整代码
// 调整进度
import { CommonConstants } from '../common/constant/CommonConstant';

@Component
export default struct ProgressEditPanel {
  @Link sliderMode: number;
  @Prop slidingProgress: number = 0; //当前进度
  onCancel?: () => void;
  onClickOK?: (progress: number) => void;

  build() {
    Column() {
      Row() {
        // 进度滑动条
        Slider({
          value: this.slidingProgress, //当前进度
          min: CommonConstants.SLIDER_MIN_VALUE, //0
          max: CommonConstants.SLIDER_MAX_VALUE, //100
          style: SliderStyle.InSet, //内部滑动条
          step: CommonConstants.SLIDER_STEP //步长为1
        })
          .width(CommonConstants.SLIDER_INNER_WIDTH)
          .onChange((value: number, mode: SliderChangeMode) => {
            this.slidingProgress = Math.floor(value); //使用math.floor不会丢失精度
            // this.slidingProgress = value; //使用math.floor不会丢失精度
            this.sliderMode = mode;
          })
        // 文字进度
        Text(`${this.slidingProgress}%`)
          .fontSize($r('app.float.progress_font'))
          .fontWeight(CommonConstants.FONT_WEIGHT)
          .fontColor($r('app.color.dialog_progress'))
          .textAlign(TextAlign.Center)
          .margin({ left: $r('app.float.slider_margin_left') })
      }
      .width(CommonConstants.SLIDER_WIDTH)
      .height(CommonConstants.SLIDER_HEIGHT)

      Row() {
        CustomButton({
          buttonText: $r('app.string.cancel_button')
        })
          .onClick(() => {
            if (this.onCancel !== undefined) {
              this.onCancel();
            }
          })
        CustomButton({
          buttonText: $r('app.string.confirm_button')
        })
          .onClick(() => {
            if (this.onClickOK !== undefined) {
              this.onClickOK(this.slidingProgress);
            }
          })
      }
      .margin({ top: CommonConstants.SLIDER_BUTTON_MARGIN })
      .width(CommonConstants.DIALOG_OPERATION_WIDTH)
      .justifyContent(FlexAlign.SpaceBetween)
    }
    .height($r('app.float.edit_panel_height'))
    .width(CommonConstants.FULL_WIDTH)
    .justifyContent(FlexAlign.End)
  }
}

@Component
struct CustomButton {
  @State buttonColor: Resource = $r('app.color.start_window_background');
  buttonText?: Resource;

  build() {
    Text(this.buttonText)
      .dialogButtonStyle()
      .backgroundColor(this.buttonColor)
      .borderRadius(CommonConstants.LIST_RADIUS)
      .textAlign(TextAlign.Center)
      .onTouch((event?: TouchEvent) => {
        if (event !== undefined && event.type === TouchType.Down) {
          this.buttonColor = $r('app.color.custom_button_color');
        }
        if (event !== undefined && event.type === TouchType.Up) {
          this.buttonColor = $r('app.color.start_window_background');
        }
      })
  }
}

/**
 * 自定义按钮样式
 */
@Extend(Text) function dialogButtonStyle() {
  .fontSize($r('app.float.button_font'))
  .height($r('app.float.dialog_btn_height'))
  .width($r('app.float.dialog_btn_width'))
  .fontColor($r('app.color.main_blue'))
}

6. 更多页面(page0)

6.1 布局

行文本 + 网格布局 + 走马灯 + 点赞,差评

在这里插入图片描述

6.2 代码说明

更换主题功能:点击标题时触发更换主题函数,通过AlertDialog.show弹出提示框,点击确定变量值改变,更换背景色

网格布局官网:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/arkts-layout-development-create-grid-0000001504486057-V2

走马灯使用的堆叠布局,循环播放文字

点赞,差评按钮点击时变量加1,并通过if判断使得单数时展示表情包

6.3 完整代码
import router from '@ohos.router';
import prompt from '@ohos.promptAction';

/**
 * 更多页面
 */
@Entry
@Component
struct NewsSPage {
  @State message: string = '< 返回'
  @State likeBtn: string = '点赞'
  @State badBtn: string = '差评'
  @State visitCount: number = 0
  @State likeCount: number = 0
  @State badCount: number = 0
  @State isClickLike: boolean = false
  @State isClickBad: boolean = false

  @State selectTab: number = 0
  @State start: boolean = true //控制跑马灯是否播放
  @State step: number = 3 //step滚动动画步长
  @State loop: number = -1 //loop设置重复滚动的次数,小于等于0时无限循环
  @State fromStart: boolean = true //设置文本从头开始
  @State src: string = '             如果觉得这个项目对你有帮助的话,就给我点个赞吧!!!希望你的心情能像星星一样, 常年闪闪发亮,偶尔躲躲乌云。'
  @State allowScale: boolean = false // 是否允许文本缩放
  @State widthValue: number = 300

  @State willChange: boolean = true  //改变主题标识
  @State colorCode: number = 0 //颜色代号 0为第一个 1为第二个
  @State themeF: string = '#d9ebff' //主题0
  @State themeS: string = '#ffecdd' //主题1
  @State themeCurrent: string = '' //当前主题 默认为空

  build() {
    Column() {
      Row({ space: 200 }) {

        Button(this.message)
          .fontSize(20)
          .width('30%')
          .height('100%')
          .fontColor('#000')
          .fontSize('18')
          .margin({ left: -120,top: 0 })
          .onClick(() => {
            router.back({ url: 'pages/MainPage' })
          })
          .backgroundColor(this.colorCode ? this.themeS : this.themeF)

        // Text('访问次数:' + this.visitCount.toString())
        Text('有趣的小组件')
          .fontSize(20)
          .width('40%')
          .height('60%')
          .margin({ left: -180 })
          // .backgroundColor('#d8e6ff')
          .fontColor(Color.Black)
          .textAlign(TextAlign.Center)
          .onClick(() => {
            this.ChangeColor()
          })
      }
      .width('100%')
      .height('6%')
      .justifyContent(FlexAlign.Center)

      // Button('测试')
      //   .onClick(() => {
      //     router.pushUrl({url:'pages/page2'})
      //   })

      // .backgroundColor('#000')
      // 网格布局https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/arkts-layout-development-create-grid-0000001504486057-V2
      Column() {
          Grid() {
            GridItem() {
              Text('组内转场')
                .backgroundColor('#fff')
                .width("80")
                .height("80")
                .textAlign(TextAlign.Center)
                .borderRadius(10)
            }
            .onClick(() => {
              router.pushUrl({url:'pages/page1'})
            })



          GridItem() {
            Text('待办列表')
              .backgroundColor('#fff')
              .width("80")
              .height("80")
              .textAlign(TextAlign.Center)
              .borderRadius(10)
          }
          .onClick(() => {
            router.pushUrl({url:'pages/page2'})
          })

          GridItem() {
            Text('3')
              .backgroundColor('#fff')
              .width("80")
              .height("80")
              .textAlign(TextAlign.Center)
              .borderRadius(10)
          }


          GridItem() {
            Text('4')
              .backgroundColor('#fff')
              .width("80")
              .height("80")
              .textAlign(TextAlign.Center)
              .borderRadius(10)
          }

          GridItem() {
            Text('5')
              .backgroundColor('#fff')
              .width("80")
              .height("80")
              .textAlign(TextAlign.Center)
              .borderRadius(10)
          }

          GridItem() {
            Text('6')
              .backgroundColor('#fff')
              .width("80")
              .height("80")
              .textAlign(TextAlign.Center)
              .borderRadius(10)
          }

          GridItem() {
            Text('7')
              .backgroundColor('#fff')
              .width("80")
              .height("80")
              .textAlign(TextAlign.Center)
              .borderRadius(10)
          }

          GridItem() {
            Text('8')
              .backgroundColor('#fff')
              .width("80")
              .height("80")
              .textAlign(TextAlign.Center)
              .borderRadius(10)
          }

          GridItem() {
            Text('9')
              .backgroundColor('#fff')
              .width("80")
              .height("80")
              .textAlign(TextAlign.Center)
              .borderRadius(10)
          }

          GridItem() {
            Text('10')
              .backgroundColor('#fff')
              .width("80")
              .height("80")
              .textAlign(TextAlign.Center)
              .borderRadius(10)
          }

          GridItem() {
            Text('11')
              .backgroundColor('#fff')
              .width("80")
              .height("80")
              .textAlign(TextAlign.Center)
              .borderRadius(10)
          }

          GridItem() {
            Text('12')
              .backgroundColor('#fff')
              .width("80")
              .height("80")
              .textAlign(TextAlign.Center)
              .borderRadius(10)
          }
        }
        .margin({bottom: 20,top: 20 ,left: 16})
        .rowsTemplate('1fr 1fr') // 只设置rowsTemplate属性,当内容超出Grid区域时,可水平滚动。
        // .maxCount(3)
        // .layoutDirection(GridDirection.Row)
        // 当前layoutDirection设置为Row时,先从左到右排列,排满一行再
        // 排一下一行。当前layoutDirection设置为Column时,先从上到下
        // 排列,排满一列再排一下一列,如上图所示。此时,将maxCount属性
        // 设为3,表示主轴方向上最大显示的网格单元数量为3。
        .columnsGap(16)
        .rowsGap(16)
      //   通过Grid的rowsGap和columnsGap可以设置网格布局的行列间距

      }
      .height('30%')


  //     跑马灯
      Row(){
        // 层叠布局
        Stack(){
          Image('images/1.jpg')
            .height(200)

          Marquee({
            start: this.start,
            step: this.step,
            loop: this.loop,
            fromStart: this.fromStart,
            src: this.src
          })
            // .fontColor('#fff')
            .fontSize(21)
            .width(this.widthValue)
            .allowScale(this.allowScale)
        }
      }


  //间距为30
      Row({ space: 30 }) {
        //好评
        Button(this.likeBtn)
          .fontSize(20)
          .width('40%')
          .height('90%')
          .onClick(() => {
            this.likeCount++
            this.isClickLike = !this.isClickLike
          })
          .backgroundColor('#08c2ea')
        //差评
        Button(this.badBtn)
          .fontSize(20)
          .width('40%')
          .height('90%')
          .onClick(() => {
            this.badCount++
            this.isClickBad = !this.isClickBad
          })
          .backgroundColor('#08c2ea')
      }
      .width('100%')
      .height('5%')
      .margin({top: 30})
      .justifyContent(FlexAlign.Center)


        //如果为偶数有动态表情包
      if(this.isClickLike) {
        Image($r('app.media.addLike'))
          .width(100)
          .height(100)
          .margin({ left: -200})
          .visibility(Visibility.Visible)
      }
      else {
        Image($r('app.media.addLike'))
          .width(100)
          .height(100)
          .margin({ left: -200})
          .visibility(Visibility.Hidden)
        //隐藏
      }

      if(this.isClickBad) {
        Image($r('app.media.badLike'))
          .width(100)
          .height(100)
          .margin({ right: -200, top:-90})
          .visibility(Visibility.Visible)
      } else {
        Image($r('app.media.badLike'))
          .width(100)
          .height(100)
          .margin({ right: -200, top:-90})
          .visibility(Visibility.Hidden)
      }

    Row() {
      Text('点赞数:' + this.likeCount)
        .fontSize(20)
        .width('50%')
        .height('5%')
        // .backgroundColor('#d8e6ff')
        .fontColor(Color.Black)
        .textAlign(TextAlign.Center)
      Text('差评数:' + this.badCount)
        .fontSize(20)
        .width('50%')
        .height('5%')
        // .backgroundColor('#d8e6ff')
        .fontColor(Color.Black)
        .textAlign(TextAlign.Center)
    }
      .width('100%')
      .height('20%')
      .justifyContent(FlexAlign.Center)
      .margin({ top: -70})
      }
    .backgroundColor(this.colorCode ? this.themeS : this.themeF)
    .height('100%')
  }


  //更换主题
  ChangeColor() {
    //提示框
    AlertDialog.show({
      title: '提示消息',
      message: '确定要更新主题吗?',
      //垂直底部对齐
      alignment:DialogAlignment.Bottom,
      offset:{ dx: 0, dy: -20},
      //自动关闭
      autoCancel: true,
      //弹窗宽度占栅格列数的个数
      gridCount: 4,
      //取消操作
      primaryButton: {
        value: '取消',
        action:() => {
          //取消操作
        }
      },
      secondaryButton: {
        value: '确定',
        action: () => {
          // this.willChange = !this.willChange
          if(this.willChange) {
            if(this.colorCode == 0){
              this.colorCode = 1
              this.themeCurrent = this.themeS
            }
            else {
              this.colorCode = 0
              this.themeCurrent = this.themeF
            }
            //提示
            prompt.showToast({
              message: '恭喜您,更换主题成功!!!当前主题为' + this.colorCode
            })
          }
        }
      }
    })
  }
}

7. 组内转场(page1)

7.1 布局

按钮 + 图片

在这里插入图片描述

7.2 代码说明

点击按钮时更改按钮文字,显示图片

在插入时,组件从相对于组件正常布局位置x方向平移200vpy方向平移-200vp的位置,变化到x、y方向平移量为0、透明度为0的状态(scale缩放)

if/else语句可以控制组件的插入和删除

animateTo闭包中改变flag的值,指定动画时长为1000ms,曲线使用animateTo函数默认的曲线,改变flag的值。则由flag变化所引起的一切变化,都会按照该动画参数,产生动画。由此flag会影响Image的出现和消失

transition函数的入参为组件内转场的效果,可以定义平移、透明度、旋转、缩放这几种转场样式的单个或者组合的转场效果,必须和animateTo一起使用才能产生组件转场效果。

参数名称参数类型必填参数描述
typeTransitionType默认包括组件新增和删除。默认值:TransitionType.All**说明:**不指定Type时说明插入删除使用同一种效果。
opacitynumber设置组件转场时的透明度效果,为插入时起点和删除时终点的值。默认值:1取值范围: [0, 1]**说明:**设置小于0或大于1的非法值时,按1处理。
translate{x? : number | string,y? : number | string,z? : number | string}设置组件转场时的平移效果,为插入时起点和删除时终点的值。-x:横向的平移距离。-y:纵向的平移距离。-z:竖向的平移距离。
scale{x? : number,y? : number,z? : number,centerX? : number | string,centerY? : number | string}设置组件转场时的缩放效果,为插入时起点和删除时终点的值。-x:横向放大倍数(或缩小比例)。-y:纵向放大倍数(或缩小比例)。-z:当前为二维显示,该参数无效。- centerX、centerY指缩放中心点,centerX和centerY默认值是"50%"。- 中心点为0时,默认的是组件的左上角。
rotate{x?: number,y?: number,z?: number,angle: number | string,centerX?: number | string,centerY?: number | string}设置组件转场时的旋转效果,为插入时起点和删除时终点的值。-x:横向的旋转向量。-y:纵向的旋转向量。-z:竖向的旋转向量。- centerX,centerY指旋转中心点,centerX和centerY默认值是"50%"。- 中心点为(0,0)时,默认的是组件的左上角。
7.3 完整代码
// 组内转场

@Entry
@Component
struct IfElseTransition {
  @State flag: boolean = false;
  @State flag2: boolean = false;
  @State flag3: boolean = false;
  @State flag4: boolean = false;
  @State flag5: boolean = false;
  @State show: string = 'one';
  @State show2: string = 'two';
  @State show3: string = 'three';
  @State show4: string = 'four';
  @State show5: string = 'five';

  scroller: Scroller = new Scroller(); //创建滚动条
  build() {
    Scroll(this.scroller) { //使用滚动条包裹Column,实现竖向滚动展示的效果
      Column() {
        Button(this.show).width(80).height(30).margin(30)
          .onClick(() => {
            if (this.flag) {
              this.show = 'one';
            } else {
              this.show = 'hide';
            }
            // 动画时间1000Ms
            animateTo({ duration: 1000 }, () => {
              // 动画闭包内控制Image组件的出现和消失
              this.flag = !this.flag;
            })
          })
        if (this.flag) {
          // Image的出现和消失配置为不同的过渡效果
          Image('images/1.jpg').width(200).height(200)
            // 图片转场效果
            .transition({ type: TransitionType.Insert, translate: { x: 200, y: -200 } })
            .transition({ type: TransitionType.Delete, opacity: 0, scale: { x: 0, y: 0 } })
        }

        Button(this.show2).width(80).height(30).margin(30)
          .onClick(() => {
            if (this.flag2) {
              this.show2 = 'two';
            } else {
              this.show2 = 'hide';
            }

            animateTo({ duration: 1000 }, () => {
              // 动画闭包内控制Image组件的出现和消失
              this.flag2 = !this.flag2;
            })
          })
        if (this.flag2) {
          // Image的出现和消失配置为不同的过渡效果
          Image('images/2.jpg').width(200).height(200)
            .transition({ type: TransitionType.Insert, translate: { x: 200, y: -200 } })
            .transition({ type: TransitionType.Delete, opacity: 0, scale: { x: 0, y: 0 } })
        }

        Button(this.show3).width(80).height(30).margin(30)
          .onClick(() => {
            if (this.flag3) {
              this.show3 = 'three';
            } else {
              this.show3 = 'hide';
            }

            animateTo({ duration: 1000 }, () => {
              // 动画闭包内控制Image组件的出现和消失
              this.flag3 = !this.flag3;
            })
          })
        if (this.flag3) {
          // Image的出现和消失配置为不同的过渡效果
          Image('images/3.jpg').width(200).height(200)
            .transition({ type: TransitionType.Insert, translate: { x: 200, y: -200 } }) // 平移
            .transition({ type: TransitionType.Delete, opacity: 0, scale: { x: 0, y: 0 } }) //缩放
        }

        Button(this.show4).width(80).height(30).margin(30)
          .onClick(() => {
            if (this.flag4) {
              this.show4 = 'four';
            } else {
              this.show4 = 'hide';
            }

            animateTo({ duration: 1000 }, () => {
              // 动画闭包内控制Image组件的出现和消失
              this.flag4 = !this.flag4;
            })
          })
        if (this.flag4) {
          // Image的出现和消失配置为不同的过渡效果
          Image('images/4.jpg').width(200).height(200)
            .transition({ type: TransitionType.Insert, translate: { x: 200, y: -200 } })
            .transition({ type: TransitionType.Delete, opacity: 0, scale: { x: 0, y: 0 } })
        }

        Button(this.show5).width(80).height(30).margin(30)
          .onClick(() => {
            if (this.flag5) {
              this.show5 = 'five';
            } else {
              this.show5 = 'hide';
            }

            animateTo({ duration: 1000 }, () => {
              // 动画闭包内控制Image组件的出现和消失
              this.flag5 = !this.flag5;
            })
          })
        if (this.flag5) {
          // Image的出现和消失配置为不同的过渡效果
          Image('images/5.jpg').width(200).height(200)
            .transition({ type: TransitionType.Insert, translate: { x: 200, y: -200 } })
            .transition({ type: TransitionType.Delete, opacity: 0, scale: { x: 0, y: 0 } })
        }
      }
    // .height('100%')
      .width('100%')
      .margin({top: 50, bottom: 70})
    }
    .scrollable(ScrollDirection.Vertical) // 滚动方向纵向
    .scrollBar(BarState.On) // 滚动条常驻显示
    .scrollBarColor(Color.Gray) // 滚动条颜色
    .scrollBarWidth(6) // 滚动条宽度
    .edgeEffect(EdgeEffect.Spring) // 滚动到边沿后回弹
  }
}

8. 待办事项(page2)

8.1 布局

文本 + 循环遍历数据

在这里插入图片描述

8.2 代码说明
8.2.1 DataModel1-表项数据模块
//列表中展示的数据

export class DataModel{
  private  tasks: Array<string> = [
    "早起晨练",
    "准备早餐",
    "读书背书",
    "打游戏",
    "刷抖音",
    "去远足",

  ]

  getDate(): Array<string> {
    return this.tasks;
  }
}

// 拓展一个实例出去,将来要用直接去调
export default new DataModel();
8.2.2 TodoItem-表项组件

提高复用性

图标官网:iconfont-阿里巴巴矢量图标库

在这里插入图片描述

在这里插入图片描述

// 待办事项组件
@Component
export default struct  ToDoItem {
  private content?: string;//列表文字内容
  @State isComplete: boolean = false;//判断事项是否完成

  // 在构建组件之前,一些复用的组件要自己来构建,通过@builder装饰器来定义
  //待办事项勾选图标
  @Builder labelIcon(icon: Resource) {
    Image(icon)
      .objectFit(ImageFit.Contain)
      .width('28vp')
  }

  build() {
    Row({space:10}){
      if (this.isComplete) {
        this.labelIcon($r('app.media.ic_ok'));
      }
      else
      {
        this.labelIcon($r('app.media.ic_default'))
      }
      Text(this.content)
        .opacity(this.isComplete ? 0.4 : 1)//如果完成,淡化透明度
        .decoration({type: this.isComplete ? TextDecorationType.LineThrough : TextDecorationType.None})//文字删除线
    }
    .backgroundColor("#fff")
    .padding(20)
    .width("100%")
    .borderRadius(10)
    // 点击事件触发,任务完成
    .onClick(() => {
      this.isComplete = !this.isComplete;
    })
  }

}
8.2.3 page2-待办页面
import ToDoItem from '../view/TodoItem';
import DataModel from '../viewmodel/DataModel1'

// 待办事项
@Entry
@Component
struct page2 {
  private data: Array<string> = DataModel.getDate();

  build() {
    Column({space: 16}) {
      Text('待办')
        .fontSize(25)
      // 遍历数据
      ForEach(this.data, (item: string) => {
        ToDoItem({ content: item})
      })
    }
    .padding(20)
    .height('100%')
    .width('100%')
    .alignItems(HorizontalAlign.Start)
    .backgroundColor("#d9ebff")

  }
}

// 待办事项组件
@Component
export default struct ToDoItem {
private content?: string;//列表文字内容
@State isComplete: boolean = false;//判断事项是否完成

// 在构建组件之前,一些复用的组件要自己来构建,通过@builder装饰器来定义
//待办事项勾选图标
@Builder labelIcon(icon: Resource) {
Image(icon)
.objectFit(ImageFit.Contain)
.width(‘28vp’)
}

build() {
Row({space:10}){
if (this.isComplete) {
this.labelIcon(KaTeX parse error: Expected 'EOF', got '}' at position 30: …c_ok')); }̲ else …r(‘app.media.ic_default’))
}
Text(this.content)
.opacity(this.isComplete ? 0.4 : 1)//如果完成,淡化透明度
.decoration({type: this.isComplete ? TextDecorationType.LineThrough : TextDecorationType.None})//文字删除线
}
.backgroundColor(“#fff”)
.padding(20)
.width(“100%”)
.borderRadius(10)
// 点击事件触发,任务完成
.onClick(() => {
this.isComplete = !this.isComplete;
})
}

}




##### 8.2.3 page2-待办页面

```java
import ToDoItem from '../view/TodoItem';
import DataModel from '../viewmodel/DataModel1'

// 待办事项
@Entry
@Component
struct page2 {
  private data: Array<string> = DataModel.getDate();

  build() {
    Column({space: 16}) {
      Text('待办')
        .fontSize(25)
      // 遍历数据
      ForEach(this.data, (item: string) => {
        ToDoItem({ content: item})
      })
    }
    .padding(20)
    .height('100%')
    .width('100%')
    .alignItems(HorizontalAlign.Start)
    .backgroundColor("#d9ebff")

  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值