制作目标管理工具
·
本篇案例将介绍如何使用@State、@Prop、@Link、@Watch、@Provide、@Consume管理页面级变量的状态,实现对页面数据的增加、删除、修改。要求完成以下功能:
- 实现一个自定义弹窗,完成添加子目标的功能。
- 实现一个可编辑列表,可点击指定行展开调节工作目标进度,可多选、全选删除指定行。
1. 案例效果截图
2. 案例运用到的知识点
2.1. 核心知识点
- V1状态管理:@State、@Prop、@Link、@Watch、@Provide、@Consume。
- 自定义弹窗: 通过CustomDialogController类显示自定义弹窗。
- List列表:列表包含一系列相同宽度的列表项。
- 自定义构建函数:@Builder
- 样式扩展:@Extend
- 渲染控制:ForEach、if
2.2. 其他知识点
- ArkTS 语言基础
- 自定义组件和组件生命周期
- 内置组件:Column/Text/Row/Stack/Blank/Button
- 日志管理类的编写
- 常量与资源分类的访问
- MVVM模式
3. 代码结构
├──entry/src/main/ets // ArkTS代码区
│ ├──common
│ │ ├──constants
│ │ │ └──CommonConstant.ets // 公共常量类
│ │ └──utils
│ │ ├──DateUtil.ets // 获取格式化日期工具
│ │ └──Logger.ets // 日志打印工具类
│ ├──entryability
│ │ └──EntryAbility.ts // 程序入口类
│ ├──pages
│ │ └──MainPage.ets // 主页面
│ ├──view
│ │ ├──AddTargetDialog.ets // 自定义弹窗
│ │ ├──ProgressEditPanel.ets // 进展调节自定义组件
│ │ ├──TargetInformation.ets // 整体目标详情自定义组件
│ │ ├──TargetList.ets // 工作目标列表
│ │ └──TargetListItem.ets // 工作目标列表子项
│ └──viewmodel
│ ├──DataModel.ets // 工作目标数据操作类
│ └──TaskItemModel.ets // 任务享实体类
└──entry/src/main/resources // 资源文件目录
4. 公共文件与资源
本案例涉及到的常量类和工具类代码如下:
4.1. 通用日志类
- entry/src/main/ets/common/utils/Logger.ets
4.2. 常量类
- entry/src/main/ets/common/constant/CommonConstant.ets
本案例涉及到的资源文件如下:
- string.json
- entry/src/main/resources/base/element/string.json
- float.json
- entry/src/main/resources/base/element/float.json
- color.json
- entry/src/main/resources/base/element/color.json
资源的具体内容及其他相关文件,请参考随书配套源码。
5. 静态展示页面
5.1. 首页架构与页面标题
// entry/src/main/ets/pages/Index.ets
import { CommonConstants } from '../common/constant/CommonConstant'
@Entry
@Component
struct Index {
build() {
Column() {
this.titleBar()
}
.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')
})
}
}
5.2. 目标展示
- 实现TargetInformation 组件
// entry/src/main/ets/view/TargetInformation.ets
import { CommonConstants } from '../common/constant/CommonConstant'
@Component
export default struct TargetInformation {
@Prop latestUpdateDate: string = ''
@Prop totalTasksNumber: number = 0
@Prop completedTasksNumber: number = 0
build() {
Column() {
this.TargetItem()
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.goals"))
.width($r('app.float.target_image_length'))
.height($r('app.float.target_image_length'))
.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') })
}
.layoutWeight(1)
.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(CommonConstants.OPACITY)
.fontWeight(CommonConstants.FONT_WEIGHT)
}
- 首页引入TargetInfomation
// entry/src/main/ets/pages/Index.ets
import { CommonConstants } from '../common/constant/CommonConstant'
// 1.引入TargetInformation模块
import TargetInformation from '../view/TargetInformation'
@Entry
@Component
struct Index {
// 2.定义相关变量
@State totalTasksNumber: number = 0
@State completedTasksNumber: number = 0
@State latestUpdateDate: string = CommonConstants.DEFAULT_PROGRESS_VALUE
build() {
Column() {
this.titleBar()
// 3.调用 TargetInformation 模块
TargetInformation({
latestUpdateDate: this.latestUpdateDate,
totalTasksNumber: this.totalTasksNumber,
completedTasksNumber: this.completedTasksNumber
})
}
.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')
})
}
}
5.3. 子任务列表
- 定义子任务项数据结构
// entry/src/main/ets/viewModel/TaskItemModel.ets
export default class TaskItemModel {
taskName: string
updateDate: string
progressValue: number
constructor(taskName: string, progressValue: number, updateDate: string) {
this.taskName = taskName
this.progressValue = progressValue
this.updateDate = updateDate
}
}
- 定义数据模型
// entry/src/main/ets/viewModel/DataModel.ets
import TaskItemModel from './TaskItemModel'
import getCurrentTime from '../common/utils/DateUtil'
export class DataModel {
private targetData: Array<TaskItemModel> = [
new TaskItemModel('私域流量提升10%', 0, getCurrentTime()),
new TaskItemModel('公域域流量提升10%', 10, getCurrentTime()),
new TaskItemModel('活跃度提升20%', 20, getCurrentTime())
]
getData(): Array<TaskItemModel> {
return this.targetData
}
}
export default new DataModel()
- 实现日期构建函数
// entry/src/main/ets/common/utils/DateUtil.ets
import { CommonConstants } from '../constant/CommonConstant'
export default function getCurrentTime(): string {
let date = new Date()
let year = date.getFullYear()
let month = date.getMonth() + CommonConstants.PLUS_ONE
let day = date.getDate()
let hours = date.getHours()
let minutes = date.getMinutes().toString()
if (Number.parseInt(minutes) < CommonConstants.TEN) {
minutes = `0${minutes}`
}
let second = date.getSeconds().toString();
if (Number.parseInt(second) < CommonConstants.TEN) {
second = `0${second}`
}
return `${year}/${month}/${day} ${hours}:${minutes}:${second}`
}
- 实现子任务列表项组件
// entry/src/main/ets/pages/view/TargetListItem.ets
import TaskItemModel from '../viewModel/TaskItemModel'
import { CommonConstants } from '../common/constant/CommonConstant'
@Component
export default struct TargetListItem {
private taskItem?: TaskItemModel
build() {
Stack({ alignContent: Alignment.Start }) {
Column() {
this.TargetItem()
}
.padding({
left: $r('app.float.list_padding'),
top: $r('app.float.list_padding_top'),
bottom: $r('app.float.list_padding_bottom'),
right: false
? $r('app.float.list_edit_padding')
: $r('app.float.list_padding')
})
.height(false
? $r('app.float.expanded_item_height')
: $r('app.float.list_item_height'))
.width(CommonConstants.FULL_WIDTH)
.opacity(
10 === CommonConstants.SLIDER_MAX_VALUE ?
CommonConstants.OPACITY : CommonConstants.NO_OPACITY
)
.borderRadius(CommonConstants.LIST_RADIUS)
.animation({ duration: CommonConstants.DURATION })
.backgroundColor(false ? $r('app.color.edit_blue') : Color.White)
}
.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(`10%`)
.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('2024-10-10 12:12:10')
.opacityTextStyle()
}
.margin({ top: $r('app.float.text_margin') })
}
.alignItems(HorizontalAlign.End)
}
.width(CommonConstants.FULL_WIDTH)
}
}
@Extend(Text) function opacityTextStyle() {
.fontSize($r('app.float.text_font'))
.fontColor($r('app.color.title_black_color'))
.opacity(CommonConstants.OPACITY)
.fontWeight(CommonConstants.FONT_WEIGHT)
}
- 首页中渲染 TargetList 组件
// entry/src/main/ets/pages/Index.ets
import { CommonConstants } from '../common/constant/CommonConstant'
import TargetInformation from '../view/TargetInformation'
// 1.引入TargetList 相关模块
import TaskItemModel from '../viewModel/TaskItemModel'
import DataModel from '../viewModel/DataModel'
import TargetList from '../view/TargetList'
@Entry
@Component
struct Index {
@State totalTasksNumber: number = 0
@State completedTasksNumber: number = 0
@State latestUpdateDate: string = CommonConstants.DEFAULT_PROGRESS_VALUE
// 2.定义数据源
@State targetData: Array<TaskItemModel> = DataModel.getData()
build() {
Column() {
this.titleBar()
TargetInformation({
latestUpdateDate: this.latestUpdateDate,
totalTasksNumber: this.totalTasksNumber,
completedTasksNumber: this.completedTasksNumber
})
// 3.引入 TargetList 组件,传入数据
TargetList({
targetData: $targetData
})
.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')
})
}
}
- 实现TargetList 组件
// entry/src/main/ets/pages/view/TargetList.ets
import { CommonConstants } from "../common/constant/CommonConstant"
import TaskItemModel from '../viewModel/TaskItemModel'
import TargetListItem from "./TargetListItem"
@Component
export default struct TargetList {
@Link targetData: Array<TaskItemModel>
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'))
}
.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: TaskItemModel, index: number | undefined) => {
ListItem() {
TargetListItem({
taskItem: item
})
}
}, (item: TaskItemModel) => JSON.stringify(item))
}
.edgeEffect(EdgeEffect.None)
.margin({ top: $r('app.float.list_margin_top') })
.width(CommonConstants.FULL_WIDTH)
.height(CommonConstants.LIST_HEIGHT)
}
.width(CommonConstants.MAIN_BOARD_WIDTH)
.height(CommonConstants.FULL_HEIGHT)
.padding({ top: $r('app.float.operate_row_margin') })
}
}
6. 添加任务子目标
6.1. 给 TargetList 添加 Button
// entry/src/main/ets/pages/view/TargetList.ets
import { CommonConstants } from "../common/constant/CommonConstant"
import TaskItemModel from '../viewModel/TaskItemModel'
import TargetListItem from "./TargetListItem"
@Component
export default struct TargetList {
@Link targetData: Array<TaskItemModel>
// 3. 接收父组件的添加事件
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'))
}
.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: TaskItemModel, index: number | undefined) => {
ListItem() {
TargetListItem({
taskItem: item
})
}
}, (item: TaskItemModel) => JSON.stringify(item))
}
.edgeEffect(EdgeEffect.None)
.margin({ top: $r('app.float.list_margin_top') })
.width(CommonConstants.FULL_WIDTH)
.height(CommonConstants.LIST_HEIGHT)
// 1. "添加子目标"按钮
Blank()
Button($r('app.string.add_task'))
.operateButtonStyle($r('app.color.main_blue'))
.onClick(() => {
if (this.onAddClick !== undefined) {
this.onAddClick()
}
})
}
.width(CommonConstants.MAIN_BOARD_WIDTH)
.height(CommonConstants.FULL_HEIGHT)
.padding({ top: $r('app.float.operate_row_margin') })
}
}
// 2. "添加子目标"按钮样式
@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'))
}
6.2. 首页处理
// entry/src/main/ets/pages/Index.ets
import { CommonConstants } from '../common/constant/CommonConstant'
import TargetInformation from '../view/TargetInformation'
import TaskItemModel from '../viewModel/TaskItemModel'
import DataModel from '../viewModel/DataModel'
import TargetList from '../view/TargetList'
// 3.实现 AddTargetDialog 并导入
import AddTargetDialog from '../view/AddTargetDialog'
// 5.导入 promptAction 模块
import { promptAction } from '@kit.ArkUI'
// 7. 导入 getCurrentTime
import getCurrentTime from '../common/utils/DateUtil'
@Entry
@Component
struct Index {
@State totalTasksNumber: number = 0
@State completedTasksNumber: number = 0
@State latestUpdateDate: string = CommonConstants.DEFAULT_PROGRESS_VALUE
@State targetData: Array<TaskItemModel> = DataModel.getData()
// 2.定义 dialogController
dialogController: CustomDialogController = new CustomDialogController({
builder: AddTargetDialog({
onClickOk: (value: string): void => this.saveTask(value)
}),
alignment: DialogAlignment.Bottom,
offset: {
dx: CommonConstants.DIALOG_OFFSET_X,
dy: $r('app.float.dialog_offset_y')
},
customStyle: true,
autoCancel: false
})
// 9. 定义 overAllProgressChanged
@Provide @Watch('onProgressChanged') overAllProgressChanged: boolean = false
build() {
Column() {
this.titleBar()
TargetInformation({
latestUpdateDate: this.latestUpdateDate,
totalTasksNumber: this.totalTasksNumber,
completedTasksNumber: this.completedTasksNumber
})
TargetList({
targetData: $targetData,
// 1. 传入 onAddClick 事件
onAddClick: (): void => this.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')
})
}
// 4. 实现保存功能
saveTask(taskName: string) {
if (taskName === '') {
promptAction.showToast({
message: $r('app.string.cannot_input_empty'),
duration: CommonConstants.TOAST_TIME,
bottom: CommonConstants.TOAST_MARGIN_BOTTOM
})
return
}
// 6. 在 DateModel 里实现 addData 方法(见下文),并调用
DataModel.addData(new TaskItemModel(taskName, 0, getCurrentTime()))
this.targetData = DataModel.getData()
// 11. 设置 overAllProgressChanged 的值
this.overAllProgressChanged = !this.overAllProgressChanged
this.dialogController.close()
}
// 10. 实现 onProgressChanged
onProgressChanged() {
this.totalTasksNumber = this.targetData.length
this.completedTasksNumber = this.targetData.filter((item: TaskItemModel) => {
return item.progressValue === CommonConstants.SLIDER_MAX_VALUE
}).length
this.latestUpdateDate = getCurrentTime()
}
}
6.3. 实现AddTargetDialog
// entry/src/main/ets/pages/view/AddTargetDialog.ets
import { CommonConstants } from '../common/constant/CommonConstant'
@CustomDialog
export default struct AddTargetDialog {
@State subtaskName: string = ''
private controller?: CustomDialogController
onClickOk?: (value: string) => void
build() {
Column() {
Text($r('app.string.add_task_dialog'))
.width(CommonConstants.FULL_WIDTH)
.fontSize($r('app.float.secondary_title'))
.fontWeight(CommonConstants.FONT_WEIGHT)
.fontColor($r('app.color.title_black_color'))
.textAlign(TextAlign.Start)
TextInput({ placeholder: $r('app.string.input_target_name')})
.placeholderColor(Color.Grey)
.placeholderFont({ size: $r('app.float.list_font')})
.caretColor(Color.Blue)
.backgroundColor($r('app.color.input_background'))
.width(CommonConstants.FULL_WIDTH)
.height(CommonConstants.DIALOG_INPUT_HEIGHT)
.margin({ top: CommonConstants.DIALOG_INPUT_MARGIN })
.fontSize($r('app.float.list_font'))
.fontColor($r('app.color.title_black_color'))
.onChange((value: string) => {
this.subtaskName = value
})
Blank()
Row() {
Button($r('app.string.cancel_button'))
.dialogButtonStyle()
.onClick(() => {
this.controller?.close()
})
Divider()
.vertical(true)
Button($r('app.string.confirm_button'))
.dialogButtonStyle()
.onClick(() => {
if (this.onClickOk !== undefined) {
this.onClickOk(this.subtaskName)
}
})
}
.width(CommonConstants.DIALOG_OPERATION_WIDTH)
.height(CommonConstants.DIALOG_OPERATION_HEIGHT)
.justifyContent(FlexAlign.SpaceBetween)
}
.padding($r('app.float.dialog_padding'))
.height($r('app.float.dialog_height'))
.width(CommonConstants.DIALOG_WIDTH)
.borderRadius(CommonConstants.DIALOG_BORDER_RADIUS)
.backgroundColor(Color.White)
}
}
@Extend(Button) function dialogButtonStyle() {
.fontSize($r('app.float.button_font'))
.height($r('app.float.dialog_btn_height'))
.width($r('app.float.dialog_btn_width'))
.backgroundColor(Color.White)
.fontColor($r('app.color.main_blue'))
}
6.4. 在 DateModel里实现addData方法
// entry/src/main/ets/viewModel/DataModel.ets
import TaskItemModel from './TaskItemModel'
import getCurrentTime from '../common/utils/DateUtil'
import Logger from '../common/utils/Logger'
// 2.定义 TAG
const TAG = '[DataModel]'
export class DataModel {
private targetData: Array<TaskItemModel> = [
new TaskItemModel('私域流量提升10%', 0, getCurrentTime()),
new TaskItemModel('公域域流量提升10%', 10, getCurrentTime()),
new TaskItemModel('活跃度提升20%', 20, getCurrentTime())
]
getData(): Array<TaskItemModel> {
return this.targetData
}
// 1. 添加 addData 函数
addData(data: TaskItemModel) {
if (!data) {
Logger.error(TAG, 'addData error because data is: ' + data)
return
}
this.targetData.push(data)
}
}
export default new DataModel()
7. 更新任务子目标进展
7.1. 渲染列表内容
// entry/src/main/ets/pages/view/TargetListItem.ets
import TaskItemModel from '../viewModel/TaskItemModel'
import { CommonConstants } from '../common/constant/CommonConstant'
@Component
export default struct TargetListItem {
private taskItem?: TaskItemModel
// 1.定义进度和时间两个变量
@State latestProgress?: number = 0
@State updateDate?: string = ''
// 2.通过 aboutToAppear 再次重写 进度和时间两个变量的值
aboutToAppear(): void {
this.latestProgress = this.taskItem?.progressValue
this.updateDate = this.taskItem?.updateDate
}
build() {
Stack({ alignContent: Alignment.Start }) {
Column() {
this.TargetItem()
}
.padding({
left: $r('app.float.list_padding'),
top: $r('app.float.list_padding_top'),
bottom: $r('app.float.list_padding_bottom'),
right: false
? $r('app.float.list_edit_padding') : $r('app.float.list_padding')
})
.height(false
? $r('app.float.expanded_item_height')
: $r('app.float.list_item_height'))
.width(CommonConstants.FULL_WIDTH)
.opacity(
10 === CommonConstants.SLIDER_MAX_VALUE ?
CommonConstants.OPACITY : CommonConstants.NO_OPACITY
)
.borderRadius(CommonConstants.LIST_RADIUS)
.animation({ duration: CommonConstants.DURATION })
.backgroundColor(false ? $r('app.color.edit_blue') : Color.White)
}
.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() {
// 3.渲染进度和更新时间
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)
}
}
@Extend(Text) function opacityTextStyle() {
.fontSize($r('app.float.text_font'))
.fontColor($r('app.color.title_black_color'))
.opacity(CommonConstants.OPACITY)
.fontWeight(CommonConstants.FONT_WEIGHT)
}
7.2. 实现单击子目标项伸缩效果
// entry/src/main/ets/view/TargetList.ets
import { CommonConstants } from "../common/constant/CommonConstant"
import TaskItemModel from '../viewModel/TaskItemModel'
import TargetListItem from "./TargetListItem"
@Component
export default struct TargetList {
@Link targetData: Array<TaskItemModel>
onAddClick?: () => void
// 1.定义与编辑子目标进度有关的变量
@State isEditMode: boolean = false
@State clickIndex: number = CommonConstants.DEFAULT_CLICK_INDEX
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'))
}
.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: TaskItemModel, index: number | undefined) => {
ListItem() {
TargetListItem({
taskItem: item,
// 2.传递与编辑子目标进度相关的属性
index: index,
isEditMode: this.isEditMode,
clickIndex: $clickIndex
})
}
}, (item: TaskItemModel) => JSON.stringify(item))
}
.edgeEffect(EdgeEffect.None)
.margin({ top: $r('app.float.list_margin_top') })
.width(CommonConstants.FULL_WIDTH)
.height(CommonConstants.LIST_HEIGHT)
Blank()
Button($r('app.string.add_task'))
.operateButtonStyle($r('app.color.main_blue'))
.onClick(() => {
if (this.onAddClick !== undefined) {
this.onAddClick()
}
})
}
.width(CommonConstants.MAIN_BOARD_WIDTH)
.height(CommonConstants.FULL_HEIGHT)
.padding({ top: $r('app.float.operate_row_margin') })
}
}
@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'))
}
// entry/src/main/ets/view/TargetListItem.ets
import TaskItemModel from '../viewModel/TaskItemModel'
import { CommonConstants } from '../common/constant/CommonConstant'
@Component
export default struct TargetListItem {
private taskItem?: TaskItemModel
@State latestProgress?: number = 0
@State updateDate?: string = ''
// 3.定义与编辑子目标进度有关的变量
@State isExpanded: boolean = false
public index: number = 0
@Prop isEditMode: boolean = false
@Link @Watch('onClickIndexChanged') clickIndex: number
aboutToAppear(): void {
this.latestProgress = this.taskItem?.progressValue
this.updateDate = this.taskItem?.updateDate
}
// 4.实现改变clickIndex后的回调
onClickIndexChanged() {
if (this.clickIndex !== this.index) {
this.isExpanded = false
}
}
build() {
Stack({ alignContent: Alignment.Start }) {
Column() {
this.TargetItem()
}
.padding({
left: $r('app.float.list_padding'),
top: $r('app.float.list_padding_top'),
bottom: $r('app.float.list_padding_bottom'),
right: $r('app.float.list_padding')
})
// 6.编辑高度
.height(this.isExpanded ? $r('app.float.expanded_item_height') : $r('app.float.list_item_height'))
.width(CommonConstants.FULL_WIDTH)
.opacity(
10 === CommonConstants.SLIDER_MAX_VALUE ?
CommonConstants.OPACITY : CommonConstants.NO_OPACITY
)
.borderRadius(CommonConstants.LIST_RADIUS)
.animation({ duration: CommonConstants.DURATION })
.backgroundColor(Color.White)
// 5.实现点击Item展开子目标编辑slider
.onClick(() => {
if (!this.isEditMode) {
animateTo({ duration: CommonConstants.DURATION }, () => {
this.isExpanded = !this.isExpanded
})
this.clickIndex = this.index
}
})
}
.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)
}
}
@Extend(Text) function opacityTextStyle() {
.fontSize($r('app.float.text_font'))
.fontColor($r('app.color.title_black_color'))
.opacity(CommonConstants.OPACITY)
.fontWeight(CommonConstants.FONT_WEIGHT)
}
7.3. 实现和引入ProgressEditPanel组件
// entry/src/main/ets/view/TargetListItem.ets=
import TaskItemModel from '../viewModel/TaskItemModel'
import { CommonConstants } from '../common/constant/CommonConstant'
// 2.实现和导入编辑子目标进度的相关的模块
import ProgressEditPanel from './ProgressEditPanel'
@Component
export default struct TargetListItem {
private taskItem?: TaskItemModel
@State latestProgress?: number = 0
@State updateDate?: string = ''
@State isExpanded: boolean = false
public index: number = 0
@Prop isEditMode: boolean = false
@Link @Watch('onClickIndexChanged') clickIndex: number
// 3.定义相关变量
@Consume overAllProgressChanged: boolean
@State sliderMode: number = CommonConstants.DEFAULT_SLIDER_MODE
aboutToAppear(): void {
this.latestProgress = this.taskItem?.progressValue
this.updateDate = this.taskItem?.updateDate
}
onClickIndexChanged() {
if (this.clickIndex !== this.index) {
this.isExpanded = false
}
}
build() {
Stack({ alignContent: Alignment.Start }) {
Column() {
this.TargetItem()
// 1.展开,显示slider
if (this.isExpanded) {
Blank()
ProgressEditPanel({
slidingProgress: this.latestProgress,
onCancel: () => this.isExpanded = false,
onClickOK: () => {},
sliderMode: $sliderMode
})
// 4.添加动画
.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: $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(CommonConstants.NO_OPACITY)
.borderRadius(CommonConstants.LIST_RADIUS)
.animation({ duration: CommonConstants.DURATION })
.backgroundColor(Color.White)
.onClick(() => {
if (!this.isEditMode) {
animateTo({ duration: CommonConstants.DURATION }, () => {
this.isExpanded = !this.isExpanded
})
this.clickIndex = this.index
}
})
}
.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)
}
}
@Extend(Text) function opacityTextStyle() {
.fontSize($r('app.float.text_font'))
.fontColor($r('app.color.title_black_color'))
.opacity(CommonConstants.OPACITY)
.fontWeight(CommonConstants.FONT_WEIGHT)
}
// entry/src/main/ets/view/ProgressEditPanel.ets
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,
max: CommonConstants.SLIDER_MAX_VALUE,
style: SliderStyle.InSet,
step: CommonConstants.SLIDER_STEP
})
.width(CommonConstants.SLIDER_INNER_WIDTH)
.onChange((value: number, mode: SliderChangeMode) => {
this.slidingProgress = Math.floor(value)
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'))
}
7.4. 存储子目标进度
// entry/src/main/ets/view/TargetListItem.ets
import TaskItemModel from '../viewModel/TaskItemModel'
import { CommonConstants } from '../common/constant/CommonConstant'
import ProgressEditPanel from './ProgressEditPanel'
// 1.导入相关模块
import getCurrentTime from '../common/utils/DateUtil'
import DataModel from '../viewModel/DataModel'
@Component
export default struct TargetListItem {
private taskItem?: TaskItemModel
@State latestProgress?: number = 0
@State updateDate?: string = ''
@State isExpanded: boolean = false
public index: number = 0
@Prop isEditMode: boolean = false
@Link @Watch('onClickIndexChanged') clickIndex: number
@State sliderMode: number = CommonConstants.DEFAULT_SLIDER_MODE
// 3.定义 overAllProgressChanged 变量
@Consume overAllProgressChanged: boolean
aboutToAppear(): void {
this.latestProgress = this.taskItem?.progressValue
this.updateDate = this.taskItem?.updateDate
}
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()
//2.调用 updateProgress 方法
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: $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(CommonConstants.NO_OPACITY)
.borderRadius(CommonConstants.LIST_RADIUS)
.animation({ duration: CommonConstants.DURATION })
.backgroundColor(Color.White)
.onClick(() => {
if (!this.isEditMode) {
animateTo({ duration: CommonConstants.DURATION }, () => {
this.isExpanded = !this.isExpanded
})
this.clickIndex = this.index
}
})
}
.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)
}
}
@Extend(Text) function opacityTextStyle() {
.fontSize($r('app.float.text_font'))
.fontColor($r('app.color.title_black_color'))
.opacity(CommonConstants.OPACITY)
.fontWeight(CommonConstants.FONT_WEIGHT)
}
// entry/src/main/ets/viewModel/DataModel.ets
import TaskItemModel from './TaskItemModel'
import getCurrentTime from '../common/utils/DateUtil'
import Logger from '../common/utils/Logger'
const TAG = '[DataModel]'
export class DataModel {
private targetData: Array<TaskItemModel> = [
new TaskItemModel('私域流量提升10%', 0, getCurrentTime()),
new TaskItemModel('公域域流量提升10%', 10, getCurrentTime()),
new TaskItemModel('活跃度提升20%', 20, getCurrentTime())
]
getData(): Array<TaskItemModel> {
return this.targetData
}
addData(data: TaskItemModel) {
if (!data) {
Logger.error(TAG, 'addData error because data is: ' + data)
return
}
this.targetData.push(data)
}
updateProgress(
index: number,
updateValue: number,
updateDate: string
): boolean {
if (!this.targetData[index]) {
return false
}
this.targetData[index].progressValue = updateValue
this.targetData[index].updateDate = updateDate
return true
}
}
export default new DataModel()
8. 编辑子目标列表
8.1. 编辑、全选和取消全选
// entry/src/main/ets/view/TargetList.ets
import { CommonConstants } from "../common/constant/CommonConstant"
import TaskItemModel from '../viewModel/TaskItemModel'
import TargetListItem from "./TargetListItem"
@Component
export default struct TargetList {
@Link targetData: Array<TaskItemModel>
onAddClick?: () => void
@State isEditMode: boolean = false
@State clickIndex: number = CommonConstants.DEFAULT_CLICK_INDEX
// 3.定义变量
@State selectAll: boolean = false
@State selectArray: Array<boolean> = []
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'))
// 1.添加编辑按钮
Blank()
if (this.targetData.length > 0) {
if (this.isEditMode) {
Text($r('app.string.cancel_button'))
// 2.添加 operateTextStyle
.operateTextStyle($r('app.color.main_blue'))
.margin({ left: $r('app.float.operate_button_margin') })
.onClick(() => {
// 3.设置变量
this.selectAll = false
this.isEditMode = false
// 4.设置全选或取消
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()
// 5.判断是否全选
.select(this.isSelectAll())
.selectedColor($r('app.color.main_blue'))
.width(CommonConstants.CHECKBOX_WIDTH)
.onClick(() => {
this.selectAll = !this.selectAll
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: TaskItemModel, index: number | undefined) => {
ListItem() {
TargetListItem({
taskItem: item,
index: index,
isEditMode: this.isEditMode,
clickIndex: $clickIndex
// 6.传递数组
selectArr: $selectArray
})
}
}, (item: TaskItemModel) => JSON.stringify(item))
}
.edgeEffect(EdgeEffect.None)
.margin({ top: $r('app.float.list_margin_top') })
.width(CommonConstants.FULL_WIDTH)
.height(CommonConstants.LIST_HEIGHT)
Blank()
Button($r('app.string.add_task'))
.operateButtonStyle($r('app.color.main_blue'))
.onClick(() => {
if (this.onAddClick !== undefined) {
this.onAddClick()
}
})
}
.width(CommonConstants.MAIN_BOARD_WIDTH)
.height(CommonConstants.FULL_HEIGHT)
.padding({ top: $r('app.float.operate_row_margin') })
}
// 4.设置全选或取消
selectAllOrCancel(selectStatus: boolean) {
let newSelectArray: Array<boolean> = []
this.targetData.forEach(() => {
newSelectArray.push(selectStatus)
})
this.selectArray = newSelectArray
}
// 5.判断是否全选
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
}
}
@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'))
}
@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)
}
// entry/src/main/ets/view/TargetListItem.ets
import TaskItemModel from '../viewModel/TaskItemModel'
import { CommonConstants } from '../common/constant/CommonConstant'
import ProgressEditPanel from './ProgressEditPanel'
import getCurrentTime from '../common/utils/DateUtil'
import DataModel from '../viewModel/DataModel'
@Component
export default struct TargetListItem {
private taskItem?: TaskItemModel
@State latestProgress?: number = 0
@State updateDate?: string = ''
@State isExpanded: boolean = false
public index: number = 0
@Prop isEditMode: boolean = false
@Link @Watch('onClickIndexChanged') clickIndex: number
@State sliderMode: number = CommonConstants.DEFAULT_SLIDER_MODE
@Consume overAllProgressChanged: boolean
// 1.定义selectArr
@Link selectArr: Array<boolean>
aboutToAppear(): void {
this.latestProgress = this.taskItem?.progressValue
this.updateDate = this.taskItem?.updateDate
}
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'),
// 2.修改 right
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)
// 3.修改 opacity
.opacity(
this.latestProgress === CommonConstants.SLIDER_MAX_VALUE ?
CommonConstants.OPACITY : CommonConstants.NO_OPACITY
)
.borderRadius(CommonConstants.LIST_RADIUS)
.animation({ duration: CommonConstants.DURATION })
//4.修改 backgroundColor
.backgroundColor(this.selectArr[this.index]
? $r('app.color.edit_blue')
: Color.White)
.onClick(() => {
if (!this.isEditMode) {
animateTo({ duration: CommonConstants.DURATION }, () => {
this.isExpanded = !this.isExpanded
})
this.clickIndex = this.index
}
})
// 5.添加编辑功能
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)
}
}
@Extend(Text) function opacityTextStyle() {
.fontSize($r('app.float.text_font'))
.fontColor($r('app.color.title_black_color'))
.opacity(CommonConstants.OPACITY)
.fontWeight(CommonConstants.FONT_WEIGHT)
}
8.2. 删除
// entry/src/main/ets/view/TargetList.ets
import { CommonConstants } from "../common/constant/CommonConstant"
import TaskItemModel from '../viewModel/TaskItemModel'
import TargetListItem from "./TargetListItem"
// 3. 删除操作, 导入模块
import DataModel from '../viewModel/DataModel'
@Component
export default struct TargetList {
@Link targetData: Array<TaskItemModel>
onAddClick?: () => void
@State isEditMode: boolean = false
@State clickIndex: number = CommonConstants.DEFAULT_CLICK_INDEX
@State selectAll: boolean = false
@State selectArray: Array<boolean> = []
// 5.更新总体进度,消费 overAllProgressChanged
@Consume overAllProgressChanged: boolean
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
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: TaskItemModel, index: number | undefined) => {
ListItem() {
TargetListItem({
taskItem: item,
index: index,
isEditMode: this.isEditMode,
clickIndex: $clickIndex,
selectArr: $selectArray
})
}
}, (item: TaskItemModel) => JSON.stringify(item))
}
.edgeEffect(EdgeEffect.None)
.margin({ top: $r('app.float.list_margin_top') })
.width(CommonConstants.FULL_WIDTH)
.height(CommonConstants.LIST_HEIGHT)
Blank()
// 1.添加删除按钮
if (this.isEditMode) {
Button($r('app.string.delete_button'))
// 2.删除是否可用(opacity & enabled)
.opacity(this.isSelectRows()
? CommonConstants.NO_OPACITY
: CommonConstants.OPACITY)
.enabled(this.isSelectRows() ? true : false)
.operateButtonStyle($r('app.color.main_red'))
.onClick(() => {
// 3.删除操作
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) {
this.onAddClick()
}
})
}
}
.width(CommonConstants.MAIN_BOARD_WIDTH)
.height(CommonConstants.FULL_HEIGHT)
.padding({ top: $r('app.float.operate_row_margin') })
}
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
}
// 2.删除是否可用opacity
isSelectRows(): boolean {
return this.selectArray.filter(
(selected: boolean) => selected === true
).length !== 0
}
// 3.删除操作
deleteSelected() {
// 4.实现 deleteData
DataModel.deleteData(this.selectArray)
this.targetData = DataModel.getData()
// 5.更新总体进度
this.overAllProgressChanged = !this.overAllProgressChanged
this.isEditMode = false
}
}
@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'))
}
@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)
}
// entry/src/main/ets/viewModel/DataModel.ets
import TaskItemModel from './TaskItemModel'
import getCurrentTime from '../common/utils/DateUtil'
import Logger from '../common/utils/Logger'
import { CommonConstants } from '../common/constant/CommonConstant'
const TAG = '[DataModel]'
export class DataModel {
private targetData: Array<TaskItemModel> = [
new TaskItemModel('私域流量提升10%', 0, getCurrentTime()),
new TaskItemModel('公域域流量提升10%', 10, getCurrentTime()),
new TaskItemModel('活跃度提升20%', 20, getCurrentTime())
]
getData(): Array<TaskItemModel> {
return this.targetData
}
addData(data: TaskItemModel) {
if (!data) {
Logger.error(TAG, 'addData error because data is: ' + data)
return
}
this.targetData.push(data)
}
updateProgress(
index: number,
updateValue: number,
updateDate: string
): boolean {
if (!this.targetData[index]) {
return false
}
this.targetData[index].progressValue = updateValue
this.targetData[index].updateDate = updateDate
return true
}
// 4.实现 deleteData
deleteData(selectArr: Array<boolean>) {
if (!selectArr) {
Logger.error(TAG, 'Failed to delete data because selectArr is '
+ selectArr)
}
let dataLen = this.targetData.length - CommonConstants.ONE_TASK
for (let i = dataLen; i >= 0; i--) {
if (selectArr[i]) {
this.targetData.splice(i, CommonConstants.ONE_TASK)
}
}
}
}
export default new DataModel()
9. 代码与视频教程
完整案例代码与视频教程请参见:
代码:Code-01.zip。
视频:《制作目标管理工具》。
更多推荐
所有评论(0)