鸿蒙应用开发-目标管理(总结)(增加了网格布局,待办事项,组内转场)
目标管理
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
方向平移200vp
、y
方向平移-200vp
的位置,变化到x、y
方向平移量为0
、透明度为0的状态(scale缩放)
if/else
语句可以控制组件的插入和删除
在animateTo
闭包中改变flag
的值,指定动画时长为1000ms
,曲线使用animateTo
函数默认的曲线,改变flag
的值。则由flag
变化所引起的一切变化,都会按照该动画参数,产生动画。由此flag
会影响Image
的出现和消失
transition
函数的入参为组件内转场的效果,可以定义平移、透明度、旋转、缩放
这几种转场样式的单个或者组合的转场效果,必须和animateTo
一起使用才能产生组件转场效果。
参数名称 | 参数类型 | 必填 | 参数描述 |
---|---|---|---|
type | TransitionType | 否 | 默认包括组件新增和删除。默认值:TransitionType.All**说明:**不指定Type时说明插入删除使用同一种效果。 |
opacity | number | 否 | 设置组件转场时的透明度效果,为插入时起点和删除时终点的值。默认值: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")
}
}
原文地址:https://blog.csdn.net/qq_62652856/article/details/142956207
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!