一、任务案例实操
在任务案例中,我们要实现任务的勾选、进度增加、以及滑动删除等效果
1、创建ArkTS
我们在编译器中创建一个ArkTS文件,文件命名为PropPage1,如下所示:
1.1页面初始化的准备
定义一个任务类,用来存储任务列表内容,设置静态变量id值为1,任务名字为string类型,使id不断增加,使用boolean类型判断任务状态是否完成
//任务类
class Task{
static id: number = 1 //定义静态变量
//任务名称
name: string = `任务${Task.id++}`
//任务状态:是否完成
finished: boolean = false
}
1.1.2设置统一卡片样式,避免重复写复杂化,设置任务完成样式,设置字体颜色
//统一的卡片样式
@Styles function card(){
.width('95%')
.padding(20)
.backgroundColor(Color.White)
.borderRadius(15)
.shadow({radius:6,color:'#1F000000',offsetX: 2,offsetY:4})
}
//任务完成样式
@Extend(Text) function finishedTask(){
.decoration({type:TextDecorationType.LineThrough})
.fontColor('#B1B2B1')
}
1.2我们来定义常用变量
在页面中定义常用变量,定义一个totalTask,用来记录总任务量,定义一个finishTask,用来记录已完成的任务量,定义一个tasks,用来记录任务数组。
Entry
@Component
struct PropPage {
//总任务数量
@State totalTask: number = 0
//已完成任务数量
@State finishTask: number = 0
//任务数组
@State tasks: Task[] = []
1.3进行页面初始化,删除多余代码
build() {
Column({space:10}){
}
.width('100%')
.height('100%')
.backgroundColor('#F1F2F3')
}
2、进行页面的绘制
2.1定义统一卡片样式
根据页面要求,使用@Style封装card函数,统一样式,添加全局样式代码如下:
//统一的卡片样式
@Styles function card(){
.width('95%')
.padding(20)
.backgroundColor(Color.White)
.borderRadius(15)
.shadow({radius:6,color:'#1F000000',offsetX: 2,offsetY:4})
}
2.1.1绘制任务进度卡片,添加文本,设置进度条和宽度
//1.任务进度卡片
Row(){
Text('任务进度:')
.fontSize(30)
.fontWeight(FontWeight.Bold) //字体加粗
Progress({
value: this.finishTask,
total:this.totalTask,
type:ProgressType.Ring
})
.width(100)
Row(){
Text(this.finishTask.toString())
.fontSize(24)
.fontColor('#36D')
Text(' / ' + this.totalTask.toString())
.fontSize(24)
}
}
.card()
.margin({top: 20, bottom:10}) //设置外边距,上边距为20,下边距为10
.justifyContent(FlexAlign.SpaceEvenly) //设置主轴,空间平均分配
2.1.2把进度条和文本放入层叠容器里面
Stack(){
Progress({
value: this.finishTask,
total:this.totalTask,
type:ProgressType.Ring
})
.width(100)
Row(){
Text(this.finishTask.toString())
.fontSize(24)
.fontColor('#36D')
Text(' / ' + this.totalTask.toString())
.fontSize(24)
}
2.2设置新增任务案例按钮,添加点击事件,插入数据
//2.新增任务按钮
Button('新增任务')
.width(200)
.onClick(() =>{
//1.新增任务数据
this.tasks.push(new Task())
//2.更新任务总数量
this.totalTask = this.tasks.length
})
2.2.1点击效果图片
2.3设置任务列表,利用foreach进行渲染
2.3.1写入foreach渲染,设置row布局和字体大小
ForEach(this.tasks,(item: Task, index) =>{
Row(){
Text(item.name)
.fontSize(20)
}
}
2.3.2来添加一个复选框,设置固定为任务完成,在需要使得其勾选
Select 设置多选框是否完成,我们这里是否选中取决于我们的任务是否完成
Checkbox()
.select(item.finished)
3.添加任务事件
3.1定义统计页面函数
handleTaskChange将更新任务总数量和更新已完成任务数量剪切到handleTaskChange中
handleTaskChange(){
//2.更新任务总数量
this.totalTask = this.tasks.length
//2.更新已完成任务数量
this.finishTask = this.tasks.filter(item => item.finished).length
}
3.2修改新增任务按钮中的更新任务总数量
调用handleTaskChange函数更新页面任务总数量和已完成任务数量统计,给button按钮添加如下代码:
//2.新增任务按钮
Button('新增任务')
.width(200)
.onClick(() =>{
//1.新增任务数据
this.tasks.push(new Task())
//2.更新任务总数量
this.handleTaskChange()
})
3.3任务列表完成任务事件
给任务列表的Checkbox添加onChange事件,监听事件变化,根据选择状态更新当前任务状态,并调用handleTaskChange函数更新统计页面数据,在任务列表的Checkbox组件上添加如下代码:
.onChange(val => {
//1.更新当前任务状态
item.finished = val
//2.更新已完成任务数量
this.finishTask = this.tasks.filter(item => item.finished).length
})
3.4设置卡片的样式,让它放在两端美观
ForEach(
this.tasks,
(item: Task, index) =>{
Row(){
Text(item.name)
.fontSize(20)
Checkbox()
.select(item.finished)
.onChange(val => {
//1.更新当前任务状态
item.finished = val
//2.更新已完成任务数量
this.handleTaskChange()
})
}
.card()
.justifyContent(FlexAlign.SpaceBetween)
}
)
3.5设置List
给任务列表添加list,间隙设置为10,foreach循环粘贴在list里面,因为在list里面不能直接写row,所以先用ListItem包裹
//3.任务列表
List({space:10}){
ForEach(
this.tasks,
(item: Task, index) =>{
ListItem(){
Row(){
Text(item.name)
.fontSize(20)
Checkbox()
.select(item.finished)
.onChange(val => {
//1.更新当前任务状态
item.finished = val
//2.更新已完成任务数量
this.handleTaskChange()
})
}
.card()
.justifyContent(FlexAlign.SpaceBetween)
}
}
)
}
3.6给list设置布局居中、宽度、高度
4、列表左滑删除事件
4.1创建自定义构建函数DeleteButton,为列表左滑删除定义效果样式
@Builder DeleteButton(index: number){
Button(){
Image($r('app.media.shanchu1'))
.fillColor(Color.White) //图标颜色
.width(20)
}
.width(40)
.height(40)
.type(ButtonType.Circle)
.backgroundColor(Color.Red)
用删除按钮,在ListItem组件上添加swipeAction属性,设置左滑效果,添加代码如下:
.swipeAction({end: this.DeleteButton(index) })
4.2使用margin设置删除按钮的边距
@Builder DeleteButton(index: number){
Button(){
Image($r('app.media.shanchu1'))
.fillColor(Color.White) //图标颜色
.width(20)
}
.width(40)
.height(40)
.type(ButtonType.Circle)
.backgroundColor(Color.Red)
.margin(5)
4.3设置点击事件,删除任务,更新任务总数量和已完成数量,调用handleTaskChange
.onClick(() =>{
this.tasks.splice(index,1)
this.handleTaskChange()
})
5、完整代码如下:
//任务类
class Task{
static id: number = 1 //定义静态变量
//任务名称
name: string = `任务${Task.id++}`
//任务状态:是否完成
finished: boolean = false
}
//统一的卡片样式
@Styles function card(){
.width('95%')
.padding(20)
.backgroundColor(Color.White)
.borderRadius(15)
.shadow({radius:6,color:'#1F000000',offsetX: 2,offsetY:4})
}
//任务完成样式
@Extend(Text) function finishedTask(){
.decoration({type:TextDecorationType.LineThrough})
.fontColor('#B1B2B1')
}
@Entry
@Component
struct PropPage1 {
//总任务数量
@State totalTask: number = 0
//已完成任务数量
@State finishTask: number = 0
//任务数组
@State tasks: Task[] = []
handleTaskChange(){
//2.更新任务总数量
this.totalTask = this.tasks.length
//2.更新已完成任务数量
this.finishTask = this.tasks.filter(item => item.finished).length
}
build() {
Column({space:10}){
//1.任务进度卡片
Row(){
Text('任务进度:')
.fontSize(30)
.fontWeight(FontWeight.Bold) //字体加粗
Stack(){
Progress({
value: this.finishTask,
total:this.totalTask,
type:ProgressType.Ring
})
.width(100)
Row(){
Text(this.finishTask.toString())
.fontSize(24)
.fontColor('#36D')
Text(' / ' + this.totalTask.toString())
.fontSize(24)
}
}
}
.card()
.margin({top: 20, bottom:10}) //设置外边距,上边距为20,下边距为10
.justifyContent(FlexAlign.SpaceEvenly) //设置主轴,空间平均分配
//2.新增任务按钮
Button('新增任务')
.width(200)
.onClick(() =>{
//1.新增任务数据
this.tasks.push(new Task())
//2.更新任务总数量
this.handleTaskChange()
})
//3.任务列表
List({space:10}){
ForEach(
this.tasks,
(item: Task, index) =>{
ListItem(){
Row(){
Text(item.name)
.fontSize(20)
Checkbox()
.select(item.finished)
.onChange(val => {
//1.更新当前任务状态
item.finished = val
//2.更新已完成任务数量
this.handleTaskChange()
})
}
.card()
.justifyContent(FlexAlign.SpaceBetween)
}
.swipeAction({end: this.DeleteButton(index) })
}
)
}
.width('100%')
.layoutWeight(1)
.alignListItem(ListItemAlign.Center)
}
.width('100%')
.height('100%')
.backgroundColor('#F1F2F3')
}
@Builder DeleteButton(index: number){
Button(){
Image($r('app.media.shanchu1'))
.fillColor(Color.White) //图标颜色
.width(20)
}
.width(40)
.height(40)
.type(ButtonType.Circle)
.backgroundColor(Color.Red)
.margin(5)
.onClick(() =>{
this.tasks.splice(index,1)
this.handleTaskChange()
})
}
}
6、优化代码
6.1任务卡片进度优化(@Prop)
首先创建定义TaskStatistics组件,将任务进度的卡片row里面的代码复制进来
@Component
struct TaskStatistics{
@Prop finishTask: number
@Prop totalTask: number
build() {
Row(){
Text('任务进度:')
.fontSize(30)
.fontWeight(FontWeight.Bold) //字体加粗
Stack(){
Progress({
value: this.finishTask,
total:this.totalTask,
type:ProgressType.Ring
})
.width(100)
Row(){
Text(this.finishTask.toString())
.fontSize(24)
.fontColor('#36D')
Text(' / ' + this.totalTask.toString())
.fontSize(24)
}
}
}
.card()
.margin({top: 20, bottom:10}) //设置外边距,上边距为20,下边距为10
.justifyContent(FlexAlign.SpaceEvenly) //设置主轴,空间平均分配
}
}
6.1.1加入@State变为状态变量
定义组件,加入@State,定义finishTask变量和totalTask变量,初始化
@State finishTask: number
@State totalTask: number
6.1.2调用TaskStatistics组件
删除任务进度卡片,调用TaskStatistics,传入参数
//1.任务进度卡片
TaskStatistics({finishTask: this.finishTask, totalTask: this.totalTask})
上面的代码传递变量参数会显示报错,这时,我们需要再次回到子组件里面,因为父组件定义了@State ,所以我们在子组件里面组需要删除@state定义的变量
@Component
struct TaskStatistics{
finishTask: number = 0
totalTask: number = 0
点击新增任务按钮,任务进度卡片任何显示,说明父子组件之间的同步失败了,进行以下操作
6.1.3@Prop的使用
单向同步,父组件的数据更改会会单向的在子组件里面更新,子组件的的数据更新, 不会同步到父组件里面,给子组件加上@Prop的装饰
@Prop finishTask: number = 0
@Prop totalTask: number = 0
刚加入的时候会报错,是因为@Prop装饰器不能在本地初始化,我们不能给它
赋值,不再初始化了
@Prop finishTask: number
@Prop totalTask: number
运行效果如下所示
6.2任务列表优化(@Link,基本数据类型)
6.2.1将左滑动删除定义构造函数剪切到里面
由于build ()组件里面只能有一个根,所以这里的Button和List是不被允许的,则这里我们可以给其嵌套Column
//任务列表
@Component
struct TaskList {
//总任务数量
@State totalTask: number = 0
//已完成任务数量
@State finishTask: number = 0
//任务数组
@State tasks: Task[] = []
handleTaskChange(){
//2.更新任务总数量
this.totalTask = this.tasks.length
//2.更新已完成任务数量
this.finishTask = this.tasks.filter(item => item.finished).length
}
build() {
Column(){
//2.新增任务按钮
Button('新增任务')
.width(200)
.onClick(() =>{
//1.新增任务数据
this.tasks.push(new Task())
//2.更新任务总数量
this.handleTaskChange()
})
//3.任务列表
List({space:10}){
ForEach(
this.tasks,
(item: Task, index) =>{
ListItem(){
Row(){
Text(item.name)
.fontSize(20)
Checkbox()
.select(item.finished)
.onChange(val => {
//1.更新当前任务状态
item.finished = val
//2.更新已完成任务数量
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(20)
}
.width(40)
.height(40)
.type(ButtonType.Circle)
.backgroundColor(Color.Red)
.margin(5)
.onClick(() =>{
this.tasks.splice(index,1)
this.handleTaskChange()
})
}
}
6.2.2对于@Link的使用(基本数据类型)
双向同步,父组件的数据更改会单向的在子组件里面更新,子组件的的数据更新,也会同步到父组件里面同样地,@Link修饰器也不能本地初始化
@Component
struct TaskList {
//总任务数量
@Link totalTask: number
//已完成任务数量
@Link finishTask: number
初始化时必须带上$
//2.任务列表
TaskList({finishTask: $finishTask, totalTask: $totalTask})
6.3对于@Link的使用(对象类型)
6.3.1定义一个StatInfo,用来统计任务统计信息
//任务统计信息
class StatInfo {
//总任务量
totalTask: number = 0
已完成任务量
finishTask: number = 0
}
6.3.2主页面中修改变量类型
定义StaInfo对象来替代原来totalTask和finishTask两个变量。
struct PropPage1 {
//统计信息
@State stat: StatInfo = new StatInfo()
因为数据类型的改变,引用该值的地方会进行报错,以引用TaskStatistics自定义组件为参考,进行修改。
//1.任务进度卡片
TaskStatistics({finishTask: this.stat.finishTask, totalTask: this.stat.totalTask})
6.3.3修改子组件里面的变量
@Component
struct TaskList {
//总任务数量
@Link stat: StatInfo
//任务数组
@State tasks: Task[] = []
handleTaskChange(){
//2.更新任务总数量
this.stat.totalTask = this.tasks.length
//2.更新已完成任务数量
this.stat.finishTask = this.tasks.filter(item => item.finished).length
}
6.3.4对调用的内容进行修改
//2.任务列表
TaskList({stat: $stat})
6.4利用(@Provide,@Consume)优化代码
6.4.1分别将TaskStatistics和TaskList子组件中的stat对象,使用@Consume装饰。
按照任务将TaskStatistics自定义组件的参数修改成StaInfo对象类型,并修改相关引用报错。
6.4.2将主页面中stat对象从@ State装饰器修改成@Provide装饰器,代码如下:
//@State stat: StatInfo = new StatInfo()
@Provide stat: StatInfo = new StatInfo()
6.4.3分别将TaskStatistics和TaskList子组件中的stat对象,使用@Consume装饰,修改代码如下:
@Consume stat: StatInfo
//@Link stst: StatInfo
6.4.4修改子组件引用方式,修改代码如下:
//1.任务进度卡片
TaskStatistics()
//2.任务列表
TaskList()
此时使用@Provide和@Consume装饰后,达到了数据双向同步效果,此时不需要传递效果,依然能实现数据同步效果。
6.5点击完成效果实现(@Observed@ObjectLink)
6.5.1在Task类上添加@Observed装饰器,代码如下:
//任务类
@Observed
class Task{
static id: number = 1 //定义静态变量
//任务名称
name: string = `任务${Task.id++}`
//任务状态:是否完成
finished: boolean = false
}
6.5.2自定义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)
}
}
6.5.3修改TaskList组件中的列表渲染代码
引用TaskItem自定义组件,传入参数及onTaskChange实现函数,代码如下:
ListItem(){
TaskItem({item: item, onTaskChange:this.handleTaskChange.bind(this)})
}
.swipeAction({end: this.DeleteButton(index) })
6.5.4此时点击列表完成,相应的完成效果就会展示。
@Observed@ObjectLink可实现嵌套类数据修改的双向同步,案例较为简单,大家可以修改Task类,进行类的嵌套,进一步查看效果。
7、整体完整代码
//任务类
@Observed
class Task{
static id: number = 1 //定义静态变量
//任务名称
name: string = `任务${Task.id++}`
//任务状态:是否完成
finished: boolean = false
}
//统一的卡片样式
@Styles function card(){
.width('95%')
.padding(20)
.backgroundColor(Color.White)
.borderRadius(15)
.shadow({radius:6,color:'#1F000000',offsetX: 2,offsetY:4})
}
//任务完成样式
@Extend(Text) function finishedTask(){
.decoration({type:TextDecorationType.LineThrough}) //中划线
.fontColor('#B1B2B1')
}
//任务统计信息
class StatInfo {
//总任务量
totalTask: number = 0
已完成任务量
finishTask: number = 0
}
@Entry
@Component
struct PropPage1 {
//统计信息
//@State stat: StatInfo = new StatInfo()
@Provide stat: StatInfo = new StatInfo()
build() {
Column({space:10}){
//1.任务进度卡片
TaskStatistics()
//2.任务列表
TaskList()
}
.width('100%')
.height('100%')
.backgroundColor('#F1F2F3')
}
}
@Component
struct TaskStatistics{
@Consume stat: StatInfo
build() {
Row(){
Text('任务进度:')
.fontSize(30)
.fontWeight(FontWeight.Bold) //字体加粗
Stack(){
Progress({
value: this.stat.finishTask,
total:this.stat.totalTask,
type:ProgressType.Ring
})
.width(100)
Row(){
Text(this.stat.finishTask.toString())
.fontSize(24)
.fontColor('#36D')
Text(' / ' + this.stat.totalTask.toString())
.fontSize(24)
}
}
}
.card()
.margin({top: 20, bottom:10}) //设置外边距,上边距为20,下边距为10
.justifyContent(FlexAlign.SpaceEvenly) //设置主轴,空间平均分配
}
}
@Component
struct TaskList {
//总任务数量
@Consume stat: StatInfo
//@Link stst: StatInfo
//任务数组
@State tasks: Task[] = []
handleTaskChange(){
//2.更新任务总数量
this.stat.totalTask = this.tasks.length
//2.更新已完成任务数量
this.stat.finishTask = this.tasks.filter(item => item.finished).length
}
build() {
Column(){
//2.新增任务按钮
Button('新增任务')
.width(200)
.onClick(() =>{
//1.新增任务数据
this.tasks.push(new Task())
//2.更新任务总数量
this.handleTaskChange()
})
//3.任务列表
List({space:10}){
ForEach(
this.tasks,
(item: Task, index) =>{
ListItem(){
TaskItem({item: item, onTaskChange:this.handleTaskChange.bind(this)})
}
.swipeAction({end: this.DeleteButton(index) })
}
)
}
.width('100%')
.layoutWeight(1)
.alignListItem(ListItemAlign.Center)
}
}
@Builder DeleteButton(index: number){
Button(){
Image($r('app.media.shanchu1'))
.fillColor(Color.White) //图标颜色
.width(20)
}
.width(40)
.height(40)
.type(ButtonType.Circle)
.backgroundColor(Color.Red)
.margin(5)
.onClick(() =>{
this.tasks.splice(index,1)
this.handleTaskChange()
})
}
}
@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)
}
}