任务统计案例详细实操
如下图所示,我们需要做这样的一个进度案例,当我门点击时会新增加任务,向左滑动时会有删除
一、创建项目
在这里我所创建的项目命名为 TaskExample,只要命名规范,都可以
二、导入所需要的图片
根据样式图片导入我们所需要的的图片,这里需要添加删除的图片,
需要图片我们可以直接在浏览器搜索 iconfont-阿里巴巴矢量图标库,进去直接搜索我们所需要图片保存即可。
保存图片的路径:D:\develop\MyApplication1\entry\src\main\resources\base\media
三、页面初始化准备
1.设置自定义状态变量
用来显示任务总量,完成的任务数量以及任务数组
struct TaskExample {
//任务总量
@State totalTask: number = 0
//以完成的任务数量
@State finishTask: number = 0
//任务数组
@State tasks: Task[] = []
2.定义一个Task类,用来传入id值,以及任务名称name,完成状态等变量
代码示例:
// 定义一个Task类,用来传入id的值,以及name,完成状态等变量
class Task {
//静态 id 属性 所有对象共享的
static id: number = 1
//任务名称 每触发一次就会依次增加
name: string = `任务${Task.id++}`
//任务状态是否完成
finished: boolean = false
}
3.设置定义一个card的样式,统一的卡片样式
//设置card()的样式 统一的卡片样式
@Styles function card() {
.width("95%")
.padding(20)
.backgroundColor(Color.White)
.borderRadius(15)
.shadow({ radius: 6, color: "#1F000000", offsetX: 2, offsetY: 4 }) //阴影
}
4. 利用@Extend修饰器定义任务完成样式
在里面添加相应的属性样式
//任务完成样式
@Extend(Text) function finishedTask() {
.decoration({ type: TextDecorationType.LineThrough })
.fontColor("#B1B2B1")
}
四、绘制主页面
1.在build()里面制作任务进度卡片
1.1 利用Column 进行布局,并为其设置宽度、高度以及备件颜色的样式
代码示例:
build() {
Column({space:10}){
//1任务进度卡片
}
.width('100%')
.height('100%')
.backgroundColor('#F1F2F3')
}
}
1.2 在Column下继续运用Row排列,以及添加文本内容
分别设置字体的大小,字体加粗
代码示例:
Row(){
Text('任务进度:')
.fontSize(30)
.fontWeight(FontWeight.Bold)
}
1.3 给Row容器设置外边距,以及在主轴上的排列方式
由于前面我们已经给页面设置了卡片的样式,这里我们直接调用card()即可。
代码示例:
//1任务进度卡片
Row(){
Text('任务进度:')
.fontSize(30)
.fontWeight(FontWeight.Bold)
}
.card()
.margin({top:20,bottom:10})
.justifyContent(FlexAlign.SpaceEvenly)
到这里我们运行结果显示:
1.4 添加已经完成任务和总的任务量的文本样式
同样地,利用Row在同一水平方向上排列,添加Text文本组件
代码示例:
Row(){
//已经完成的任务
Text(this.finishTask.toString())
.fontSize(24)
.fontColor('#36D')
// 总的任务量
Text(' / '+this.totalTask.toString())
.fontSize(24)
}
Text文本输入的是字符串内容,这里一定要通过toString()转化为字符串才能好使,否则就会报错。
这里的运行代码显示:
1.5 利用进度条组件Progress设置,并显示效果
Progress进度条组件,用于显示内容加载或操作处理等进度
代码示例:
嵌套 Progress进度条组件,用于显示内容加载或操作处理等进度
Progress({
//调用 finishTask 指定当前进度值
value: this.finishTask,
total: this.totalTask, //指定进度总长
type: ProgressType.Ring //指定进度条类型
//Ring:环形无刻度样式,环形圆环逐渐显示至完全填充效果
})
.width(100)
代码运行效果:
1.6 利用层叠组件Stack()使进度条和文本内容层叠
代码示例:
//利用层叠组件 嵌套 Progress进度条组件,用于显示内容加载或操作处理等进度
Stack() {
Progress({
//调用 finishTask 指定当前进度值
value: this.finishTask,
total: this.totalTask, //指定进度总长
type: ProgressType.Ring //指定进度条类型
//Ring:环形无刻度样式,环形圆环逐渐显示至完全填充效果
})
.width(100)
Row() {
Text(this.finishTask.toString())
.fontSize(24)
.fontColor("#36D")
Text(" / " + this.totalTask.toString())
.fontSize(24)
}
}
运行效果:
2. 设置新增任务按钮
2.1 添加Button组件,并添加内容,设置其宽度
//设置新增任务按钮
Button("新增任务")
.width(200)
2.2 定义一个组件TaskList
同样地,把新增任务按钮和列表任务给剪切到这里,相应的变量也要剪切过来,否则就会报错。
代码示例:
//任务列表
@Component
struct TaskList {
@State totalTask:number =0
@State finishTask :number=0
@State tasks: Task[] = []
//设置任务修改时触发的函数
handleTaskChange(){
//更新任务总数量
this.totalTask = this.tasks.length
//更新已完成任务数量
this.finishTask = this.tasks.filter(item=>item.finished).length
}
build() {
//2 新增任务按钮
Button('新增任务')
.width(200)
.onClick(()=>{
this.tasks.push(new Task())
// this.totalTask = this.tasks.length
this.handleTaskChange()
})
//3任务列表
List({space:10}){
ForEach(this.tasks,(item:Task,index)=>{
ListItem(){
Row(){
Text(item.name)
.fontSize(20)
//复选框
Checkbox()
.select(item.finished)
.onChange(value =>{
item.finished =value
//filter 是过滤的意思
// this.finishTask = this.tasks.filter(item=>item.finished).length
this.handleTaskChange()
})
}
.card()
//其文本和复选框在两端排列
.justifyContent(FlexAlign.SpaceBetween)
}
.swipeAction({end:this.DeleteButton(index)})
})
}.width('100%')
//设置权重,让其剩余空间占满
.layoutWeight(1)
.alignListItem(ListItemAlign.Center)
}
}
2.3 给按钮添加点击事件
代码示例:
//设置新增任务按钮
Button("新增任务")
.width(200)
.onClick(() => {
//1.新增任务数据
this.tasks.push(new Task())
//2.更新任务总数量
this.totalTask = this.tasks.length
})
运行效果:
3.设置任务列表,利用ForEach并对其进行渲染
3.1 设置一个Row渲染任务名称以及设置字体大小
//3任务列表
ForEach(this.tasks,(item:Task,index)=>{
Row(){
Text(item.name)
.fontSize(20)
3.2 添加复选框,设置固定为任务完成,在需要使得其勾选
Select 设置多选框是否完成,我们这里是否选中取决于我们的任务是否完成
Checkbox()
.select(item.finished)
3.3 利用onChange为复选框添加事件
当前的复选框,我们去手动勾选时,或则取消勾选时所呈现的状态
代码示例:
.onChange(value =>{
//更新当前任务状态
item.finished =value
})
运行效果:
3.4 设置已经完成的任务数量状态变化的更新
遍历tasks数组,找出任务为true的任务,利用filter过滤
在filter()里面传递一个函数 ,并判断其为已完成任务的状态
代码示例:
this.finishTask = this.tasks.filter(item=>item.finished).length
3.5设置任务修改时触发的函数,使用时即可直接调用
/设置任务修改时触发的函数
handleTaskChange(){
//更新任务总数量
this.totalTask = this.tasks.length
//更新已完成任务数量
this.finishTask = this.tasks.filter(item=>item.finished).length
}
调用后的代码:
//2 新增任务按钮
Button('新增任务')
.width(200)
.onClick(()=>{
this.tasks.push(new Task())
// this.totalTask = this.tasks.length
this.handleTaskChange()
})
//3任务列表
ForEach(this.tasks,(item:Task,index)=>{
Row(){
Text(item.name)
.fontSize(20)
//复选框
Checkbox()
.select(item.finished)
.onChange(value =>{
item.finished =value
//filter 是过滤的意思
// this.finishTask = this.tasks.filter(item=>item.finished).length
this.handleTaskChange()
})
}
.card()
})
此时的运行效果:
3.6添加List组件,让可页面滑动给list设置属性,并给Row容器布局,让其文本和复选框在两端排列
代码示例:
//3任务列表
List({space:10}){
ForEach(this.tasks,(item:Task,index)=>{
ListItem(){
Row(){
Text(item.name)
.fontSize(20)
//复选框
Checkbox()
.select(item.finished)
.onChange(value =>{
item.finished =value
//filter 是过滤的意思
// this.finishTask = this.tasks.filter(item=>item.finished).length
this.handleTaskChange()
})
}
.card()
//其文本和复选框在两端排列
.justifyContent(FlexAlign.SpaceBetween)
}
})
}.width('100%')
//设置权重,让其剩余空间占满
.layoutWeight(1)
.alignListItem(ListItemAlign.Center)
运行效果:
4.设置列表左滑删除事件
4.1 创建自定义构建函数DeleteButton
为列表左滑删除定义效果样式,参数为当前列表索引值
向左滑动添加按钮,早按钮里面添加Image组件,为其设置属性,以及设置按钮的样式。
代码示例:
//创建自定义构造函数DeleteButton,为列表左滑删除定义效果样式
@Builder DeleteButton (index:number){
Button(){
Image($r('app.media.shanchu1'))
//设置图标的颜色
.fillColor(Color.White)
.width(20)
}
.width(40)
.width(40)
.type(ButtonType.Circle)
.backgroundColor(Color.Red)
.margin(5)
}
4.2 引用删除按钮,在ListItem组件上添加swipeAction属性,设置左滑效果,添加代码如下:
.swipeAction({end:this.DeleteButton(index)})
4.3 为删除按钮添加单击事件
删除当前索引值列表数据,并更新统计页面数据,给button按钮添加如下代码:
//点击事件
.onClick(() =>{
//删除
this.tasks.splice(index,1)
this.handleTaskChange()
})
5. 添加相应的修饰器优化代码
5.1@Prop修饰,达到父子组件数据单向同步效果
5.1.1创建一个用于任务统计的子组件
给子组件命名为TaskStatistics 在基本样式下将我们之前写的代码关于任务进度卡片的代码直接复制过来,直接复制过来会报错,是因为我们也需要在子组件里面定义变量。
代码示例:
//任务统计
@Component
struct TaskStatistics { //子组件
@State finishTask:number=0
@State totalTask:number=0
build() {
Row(){
Text('任务进度:')
.fontSize(30)
.fontWeight(FontWeight.Bold)
Stack(){
Progress({
//调用 finishTask 指定当前进度值
value: this.finishTask,
total: this.totalTask, //指定进度总长
type: ProgressType.Ring //指定进度条类型
//Ring:环形无刻度样式,环形圆环逐渐显示至完全填充效果
})
.width(100)
Row(){
//已经完成的任务
Text(this.finishTask.toString())
.fontSize(24)
.fontColor('#36D')
// 总的任务量
Text(' / '+this.totalTask.toString())
.fontSize(24)
}
}
}
.card()
.margin({top:20,bottom:10})
.justifyContent(FlexAlign.SpaceEvenly)
}
}
将父组件TaskExample对应的代码删除,直接调用即可。下一个步骤会有详细的调用方法
5.1.2对任务统计TaskStatistics的直接调用
我们在这里需要回到父组件TaskExample下的Column里面直接调用
代码示例:
TaskStatistics({finishTask:this.finishTask,totalTask:this.totalTask})
以上的代码传递变量参数会显示报错,面对这样的情况时,我们需要再次回到子组件里面,因为父组件定义了@State ,所以在子组件里面组需要删除@state所定义的变量
修改代码如下:
//任务统计
@Component
struct TaskStatistics { //子组件
//需要定义变量 因为父组件定义了,所以在子组件里面删掉@State
finishTask:number=0
totalTask:number=0
设以后的运行结果:
点击新增任务按钮,任务进度卡片任何显示,说明父子组件之间的同步失败了,我们需要做以下操作。
5.1.3对于@Prop的使用
单向同步 父组件的数据更改会会单向的在子组件里面更新
子组件的的数据更新, 不会同步到父组件里面
给子组件加入@Prop的装饰,代码如下:
@Prop finishTask:number
@Prop totalTask:number
刚加入的时候会报错,是因为@Prop装饰器不能在本地初始化,我们不能给它
赋值,不再初始化了即可
现在点击新增任务就能增加了,如下图所示:
5.2 @Link任务列表优化新增任务的封装
5.2.2 将左滑动删除定义构造函数剪切到里面
由于build ()组件里面只能有一个根,所以这里的Button和List是不被允许的,则这里我们可以给其嵌套Column
代码如下:
//任务列表
@Component
struct TaskList {
@State totalTask:number =0
@State finishTask :number=0
@State tasks: Task[] = []
//设置任务修改时触发的函数
handleTaskChange(){
//更新任务总数量
this.totalTask = this.tasks.length
//更新已完成任务数量
this.finishTask = this.tasks.filter(item=>item.finished).length
}
build() {
Column(){
//2 新增任务按钮
Button('新增任务')
.width(200)
.onClick(()=>{
this.tasks.push(new Task())
// this.totalTask = this.tasks.length
this.handleTaskChange()
})
//3任务列表
List({space:10}){
ForEach(this.tasks,(item:Task,index)=>{
ListItem(){
Row(){
Text(item.name)
.fontSize(20)
//复选框
Checkbox()
.select(item.finished)
.onChange(value =>{
item.finished =value
//filter 是过滤的意思
// this.finishTask = this.tasks.filter(item=>item.finished).length
this.handleTaskChange()
})
}
.card()
//其文本和复选框在两端排列
.justifyContent(FlexAlign.SpaceBetween)
}
.swipeAction({end:this.DeleteButton(index)})
})
}.width('100%')
//设置权重,让其剩余空间占满
.layoutWeight(1)
.alignListItem(ListItemAlign.Center)
}
}
//创建自定义构造函数DeleteButton,为列表左滑删除定义效果样式
@Builder DeleteButton(index:number){
Button(){
Image($r("app.media.shanchu1"))
//设置图标的颜色
.fillColor(Color.White)
.width(25)
}
.width(40)
.height(40)
.type(ButtonType.Circle)
.backgroundColor(Color.Red)
.margin(5)
//点击事件
.onClick(() =>{
//删除
this.tasks.splice(index,1)
this.handleTaskChange()
})
}
}
5.2.3 对于@Link的使用(基本数据类型)
双向同步 父组件的数据更改会会单向的在子组件里面更新
子组件的的数据更新,也会同步到父组件里面
同样地,@Link修饰器也不能本地初始化
代码示例:
//任务列表
@Component
struct TaskList {
@Link totalTask:number
@Link finishTask :number
初始化时必须带上$,调用的时添加的代码:
//任务列表
TaskList({finishTask:$finishTask,totalTask:$totalTask})
这样就实现父子组件双向同步了
5.3 对于@Link的使用(对象类型)
5.3.1定义一个StaInfo类,用来定义任务统计信息
代码示例:
//任务统计信息
class StaInfo{
//总任务量
totalTask: number = 0
//已完成任务数量
finishTask: number = 0
}
5.3.2主页面中修改变量类型
定义StaInfo对象来替代原来totalTask和finishTask两个变量,代码如下:
struct PropPage { //父组件
//任务统计信息
@State stat:StaInfo =new StaInfo()
5.3.3由于数据类型改变,引用该值的地方也会报错,已引用TaskStatistics自定义组件为例,进行代码修改代码如下:
//任务进度卡片
TaskStatistics({finishTask:this.stat.finishTask,totalTask:this.stat.totalTask})
5.3.4 修改子组件里面的变量
代码如下:
@Component
struct TaskList {
@Link stat :StaInfo
@State tasks: Task[] = []
//设置任务修改时触发的函数
handleTaskChange(){
//更新任务总数量
this.stat.totalTask = this.tasks.length
//更新已完成任务数量
this.stat.finishTask = this.tasks.filter(item=>item.finished).length
}
5.3.5对调用的内容进行修改,代码如下:
//任务列表
TaskList({stat:$stat})
5.4 利用(@Provide,@Consume)优化代码
5.4.1按照任务 将TaskStatistics自定义组件的参数修改成StaInfo对象类型,并修改相关引用报错。
将主页面中stat对象从@ State装饰器修改成@Provide装饰器,代码如下:
@State stat: StaInfo = new StaInfo()
@Provide stat: StaInfo = new StaInfo()
5.4.2分别将TaskStatistics和TaskList子组件中的stat对象,使用@Consume装饰
修改代码如下:
@Consume stat:StaInfo
@Link stat:StaInfo
5.4.3 修改子组件引用方式
修改代码如下:
//1.任务进度卡片
TaskStatistics()
//2.任务列表
TaskList()
5.4.4 此时使用@Provide和@Consume装饰
达到了数据双向同步效果,此时不需要传递效果,依然能实现数据同步效果。
5.5 点击完成效果实现(@Observed@ObjectLink)
5.5.1使用@extend装饰器来做样式的扩展
代码如下:
@Extend(Text) function finishedTask(){
.decoration({type:TextDecorationType.LineThrough})
.fontColor('#B1B2B1')
}
5.5.2 在Task类上添加@Observed装饰器
代码如下:
//任务类
@Observed
class Task{
static id:number=1
//任务名称
name:string=`任务${Task.id++}`
//任务状态:是否完成
finished:boolean=false
}
5.5.3 自定义TaskItem类,使用@ObjectLink装饰变量item
定义onTaskChange事件,该事件实现方式由引用处传递
添加代码如下:
@Component
struct TaskItem {
@ObjectLink item:Task
onTaskChange:()=>void
build() {
Row(){
if(this.item.finished){
Text(this.item.name)
.finishedTask()
}else {
Text(this.item.name)
}
Checkbox()
.select(this.item.finished)
.onChange(val=>{
//1.更新当前任务状态
this.item.finished=val
//2.更新已完成任务数量
this.onTaskChange()
})
}
.card()
.justifyContent(FlexAlign.SpaceBetween)
}
}
5.5.4 修改TaskList组件中的列表渲染代码
引用TaskItem自定义组件,传入参数及onTaskChange实现函数
代码如下:
ListItem(){
TaskItem({ item:item ,onTaskChange:this.handleTaskChange.bind(this)})
} .swipeAction({end:this.DeleteButton(index)})
5.5.5相应的完成效果展示
@Observed@ObjectLink可实现嵌套类数据修改的双向同步,案例较为简单,大家可以修改Task类,进行类的嵌套,进一步查看效果
到这里我们的任务案例就完成效果了,嘻嘻嘻,祝我 ,也祝各位友友学有所成!