HarmonyOS开发实战:如何实现一个运动排名榜页面

2023-12-18 09:52

本文主要是介绍HarmonyOS开发实战:如何实现一个运动排名榜页面,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

HarmonyOS开发实战:如何实现一个运动排名榜页面

代码仓库: 运动排名榜页面

项目介绍

本项目使用声明式语法和组件化基础知识,搭建一个可刷新的排行榜页面。在排行榜页面中,使用循环渲染控制语法来实现列表数据渲染,使用@Builder创建排行列表布局内容,使用装饰器@State、@Prop、@Link来管理组件状态。最后我们点击系统返回按键,来学习自定义组件生命周期函数。完成效果如图所示:

在这里插入图片描述

新建项目工程

选择Create Project新建项目,点击Application选择第一个Empty Ability应用,点击“Next”进行下一步
在这里插入图片描述
配置页中,详细信息如下:

Project name是开发者可以自行设置的项目名称,这里根据自己选择修改为自己项目名称。
Bundle name是包名称,默认情况下应用ID也会使用该名称,应用发布时对应的ID需要保持一致。
Save location为工程保存路径,建议用户自行设置相应位置。
Compile SDK是编译的API版本,这里默认选择API9。
Model选择Stage模型,其他保持默认即可。
点击“Finish”进行下一步
在这里插入图片描述

项目创建成功
在这里插入图片描述

代码实现

编写应用入口页面

RankPage是应用入口页面

@Entry
@Component
struct RankPage {@State message: string = 'My Ranking'build() {Row() {Column() {Text(this.message).fontSize(50).fontWeight(FontWeight.Bold)}.width('100%')}.height('100%')}
}

修改EntryAbility.ts应用配置,入口页面更改为pages/RankPage
在这里插入图片描述
运行成功
在这里插入图片描述

编写公共常量

公共常量的好处包括:

  • 代码可读性: 使用公共常量可以提高代码的可读性和可维护性,因为它们可以清晰地表达出程序中使用的固定数值或字符串。
  • 便于修改和维护: 如果程序中的某个数值或字符串需要修改,只需修改公共常量的定义,而不需要在整个程序中逐个修改。
  • 避免魔法数值: 使用公共常量可以避免在代码中出现“魔法数值”,即难以理解和维护的硬编码数值。
  • 提高代码重用性: 公共常量可以在整个程序中被引用和使用,从而提高代码的重用性。
  • 统一管理: 将所有的固定数值或字符串集中管理在公共常量中,有利于统一管理和维护。
/*** The font size of application.*/
export enum FontSize {SMALL = 14,MIDDLE = 16,LARGE = 20,
};/*** The font weight of application.*/
export enum FontWeight {BOLD = '400',BOLDER = '500',
};/*** The weight is global default value for component size.*/
export const WEIGHT = '100%';/*** The duration of toast.*/
export const TIME = 1000;/*** The interval time of exit.*/
export const APP_EXIT_INTERVAL: number = 4500;/*** The tag is the page name,which is used to print.*/
export const TAG: string = 'RankPage';/*** The title of TitleComponent.*/
export const TITLE: Resource = $r('app.string.title');class style {RANK_PADDING: number = 15;CONTENT_WIDTH: string = '90%';BORDER_RADIUS: number = 20;STROKE_WIDTH: number = 1;HEADER_MARGIN_TOP: number = 20;HEADER_MARGIN_BOTTOM: number = 15;LIST_HEIGHT: string = '65%';
}/*** The Style of RankPage.*/
export const Style: style = {/*** The padding of ranking.*/RANK_PADDING: 15,/*** The width of ranking content.*/CONTENT_WIDTH: '90%',/*** The border radius.*/BORDER_RADIUS: 20,/*** The stroke width of divider.*/STROKE_WIDTH: 1,/*** The top margin of ranking header.*/HEADER_MARGIN_TOP: 20,/*** The bottom margin of ranking header.*/HEADER_MARGIN_BOTTOM: 15,/*** The height of list.*/LIST_HEIGHT: '65%'
};class listHeaderStyle {FONT_WEIGHT: number = 400;LAYOUT_WEIGHT_LEFT: string = '30%';LAYOUT_WEIGHT_CENTER: string = '50%';LAYOUT_WEIGHT_RIGHT: string = '20%';
}/*** The Style of ListHeaderComponent.*/
export const ListHeaderStyle: listHeaderStyle = {/*** The weight of font.*/FONT_WEIGHT: 400,/*** The layout weight of left.*/LAYOUT_WEIGHT_LEFT: '30%',/*** The layout weight of center.*/LAYOUT_WEIGHT_CENTER: '50%',/*** The layout weight of right.*/LAYOUT_WEIGHT_RIGHT: '20%',
};class itemStyle {TEXT_LAYOUT_SIZE: number = 24;CIRCLE_TEXT_BORDER_RADIUS: number = 24;CIRCLE_TEXT_SIZE: number = 24;CIRCLE_TEXT_COLOR_STOP_1: number = 0.5;CIRCLE_TEXT_COLOR_STOP_2: number = 1.0;BAR_HEIGHT: number = 48;LAYOUT_WEIGHT_LEFT: string = '30%';LAYOUT_WEIGHT_CENTER: string = '50%';LAYOUT_WEIGHT_RIGHT: string = '20%';BORDER_WIDTH: number = 1;COLOR_GREEN: Resource = $r('app.color.item_color');COLOR_BLACK: Resource = $r('app.color.item_color_black');
}/*** The Style of ListItemComponent.*/
export const ItemStyle: itemStyle = {/*** The line height of text.*/TEXT_LAYOUT_SIZE: 24,/*** The border radius of circle text.*/CIRCLE_TEXT_BORDER_RADIUS: 24,/*** The size of circle text.*/CIRCLE_TEXT_SIZE: 24,/*** Gradient color proportion.*/CIRCLE_TEXT_COLOR_STOP_1: 0.5,/*** Gradient color proportion.*/CIRCLE_TEXT_COLOR_STOP_2: 1.0,/*** The height of item.*/BAR_HEIGHT: 48,/*** The layout weight of left.*/LAYOUT_WEIGHT_LEFT: '30%',/*** The layout weight of center.*/LAYOUT_WEIGHT_CENTER: '50%',/*** The layout weight of right.*/LAYOUT_WEIGHT_RIGHT: '20%',/*** The width of border.*/BORDER_WIDTH: 1,/*** The green color of item.*/COLOR_GREEN: $r('app.color.item_color'),/*** The black color of item.*/COLOR_BLACK: $r('app.color.item_color_black')
};class titleBarStyle {IMAGE_BACK_SIZE: number = 21;IMAGE_BACK_MARGIN_RIGHT: number = 18;IMAGE_LOADING_SIZE: number = 22;BAR_HEIGHT: number = 47;BAR_MARGIN_HORIZONTAL: number = 26;BAR_MARGIN_TOP: number = 10;WEIGHT: string = '50%';
}/*** The Style of TitleComponent.*/
export const TitleBarStyle: titleBarStyle = {/*** The image size of back button.*/IMAGE_BACK_SIZE: 21,/*** The right margin of back button.*/IMAGE_BACK_MARGIN_RIGHT: 18,/*** The size of loading image.*/IMAGE_LOADING_SIZE: 22,/*** The height of TitleComponent.*/BAR_HEIGHT: 47,/*** The horizontal margin of TitleComponent.*/BAR_MARGIN_HORIZONTAL: 26,/*** The top margin of TitleComponent.*/BAR_MARGIN_TOP: 10,/*** The weight of Row layout.*/WEIGHT: '50%',
};

静态资源

静态资源从代码仓库去获取
在这里插入图片描述

实现标题组件

TitleComponent.ets代码实现

import AppContext from '@ohos.app.ability.common';
import { FontSize, TitleBarStyle, WEIGHT } from '../common/constants/Constants';// 标题组件
@Component
export struct TitleComponent {@Link isRefreshData: boolean; // 是否刷新数据@State title: Resource = $r('app.string.title_default'); // 标题build() {Row() {Row() {// 返回箭头Image($r('app.media.ic_public_back')).height(TitleBarStyle.IMAGE_BACK_SIZE) // 高度.width(TitleBarStyle.IMAGE_BACK_SIZE) // 宽度.margin({ right: TitleBarStyle.IMAGE_BACK_MARGIN_RIGHT }) // 外边距.onClick(() => { // 返回上一个界面let handler = getContext(this) as AppContext.UIAbilityContext;handler.terminateSelf();})Text(this.title) // 标题.fontSize(FontSize.LARGE)}.width(TitleBarStyle.WEIGHT).height(WEIGHT).justifyContent(FlexAlign.Start) // 水平起始位置对齐// 刷新图标Row() {Image($r('app.media.loading')).height(TitleBarStyle.IMAGE_LOADING_SIZE).width(TitleBarStyle.IMAGE_LOADING_SIZE).onClick(() => { // 点击刷新列表数据this.isRefreshData = !this.isRefreshData;})}.width(TitleBarStyle.WEIGHT).height(WEIGHT).justifyContent(FlexAlign.End)}.width(WEIGHT).padding({ left: TitleBarStyle.BAR_MARGIN_HORIZONTAL,right: TitleBarStyle.BAR_MARGIN_HORIZONTAL }).margin({ top: TitleBarStyle.BAR_MARGIN_TOP }).height(TitleBarStyle.BAR_HEIGHT).justifyContent(FlexAlign.SpaceAround)}
}

应用入口页RankPage引入TitleComponent组件

import { APP_EXIT_INTERVAL, Style, TIME, TITLE, WEIGHT } from '../common/constants/Constants';
import { TitleComponent } from '../view/TitleComponent';@Entry
@Component
struct RankPage {@State isSwitchDataSource: boolean = true; // 是否切换数据源build() {Column() {// 标题TitleComponent({ isRefreshData: $isSwitchDataSource, title: TITLE })}}
}

运行代码:
在这里插入图片描述

实现列表头部

ListHeaderComponent .ets代码实现

import { FontSize, ListHeaderStyle } from '../common/constants/Constants';@Component
export struct ListHeaderComponent {paddingValue: Padding | Length = 0; // 内边距widthValue: Length = 0; // 宽度build() {Row() {Text('排名').fontSize(FontSize.SMALL) // 字体大小.width(ListHeaderStyle.LAYOUT_WEIGHT_LEFT) // 宽度.fontWeight(ListHeaderStyle.FONT_WEIGHT) // 字体宽度.fontColor($r('app.color.font_description')) // 字体颜色Text('姓名').fontSize(FontSize.SMALL).width(ListHeaderStyle.LAYOUT_WEIGHT_CENTER).fontWeight(ListHeaderStyle.FONT_WEIGHT).fontColor($r('app.color.font_description'))Text('步数').fontSize(FontSize.SMALL).width(ListHeaderStyle.LAYOUT_WEIGHT_RIGHT).fontWeight(ListHeaderStyle.FONT_WEIGHT).fontColor($r('app.color.font_description'))}.width(this.widthValue) // 宽度.padding(this.paddingValue) // 内边距}
}

应用入口页RankPage引入ListHeaderComponent 组件

import { APP_EXIT_INTERVAL, Style, TIME, TITLE, WEIGHT } from '../common/constants/Constants';
import { TitleComponent } from '../view/TitleComponent';
import { ListHeaderComponent } from '../view/ListHeaderComponent';@Entry
@Component
struct RankPage {@State isSwitchDataSource: boolean = true; // 是否切换数据源build() {Column() {// 标题TitleComponent({ isRefreshData: $isSwitchDataSource, title: TITLE })// 列表头部ListHeaderComponent({paddingValue: {left: Style.RANK_PADDING,right: Style.RANK_PADDING},widthValue: Style.CONTENT_WIDTH}).margin({ // 外边距top: Style.HEADER_MARGIN_TOP,bottom: Style.HEADER_MARGIN_BOTTOM})}}
}

运行代码:
在这里插入图片描述

准备排名榜数据

RankData.ets 排序类

// 排名类
export class RankData {name: string; // 姓名stepNum: string; // 步数id: string;// 构造函数constructor(id: string, name: string, stepNum: string) {this.id = id;this.name = name;this.stepNum = stepNum;}
}

DataModel.ets初始化排名数据

import { RankData } from '../viewmodel/RankData';export { rankData1, rankData2 }// 初始化排名数据1
const rankData1: RankData[] = [new RankData('1', '喜羊羊', '12080'),new RankData('2', '美羊羊', '10320'),new RankData('3', '灰太狼', '9801'),new RankData('4', '红太狼', '8431'),new RankData('5', '懒羊羊', '7546'),new RankData('6', '暖羊羊', '7431'),new RankData('7', '沸羊羊', '7187'),new RankData('8', '蕉太狼', '7003'),new RankData('9', '小灰灰', '6794'),new RankData('10', '慢羊羊', '6721')
];// 初始化排名数据2
const rankData2: RankData[] = [new RankData('11', '曹操', '8836'),new RankData('12', '马超', '8521'),new RankData('13', '关羽', '8431'),new RankData('14', '吕布', '7909'),new RankData('15', '张飞', '7547'),new RankData('16', '赵云', '7433'),new RankData('17', '刘备', '7186'),new RankData('18', '孙策', '7023'),new RankData('19', '黄忠', '6794'),new RankData('20', '许褚', '6721')
];

RankViewModel.ets获取排序数据

import { RankData } from './RankData';
import { rankData1, rankData2 } from '../model/DataModel';// 获取排序数据
export class RankViewModel {loadRankDataSource1(): RankData[] {return rankData1;}loadRankDataSource2(): RankData[] {return rankData2;}
}

RankPage.ets引入排名数据源

  • let rankModel: RankViewModel = new RankViewModel();
  • 通过aboutToAppear函数初始化数据源
import { RankViewModel } from '../viewmodel/RankViewModel';
import { RankData } from '../viewmodel/RankData';
import { APP_EXIT_INTERVAL, Style, TIME, TITLE, WEIGHT } from '../common/constants/Constants';
import { TitleComponent } from '../view/TitleComponent';
import { ListHeaderComponent } from '../view/ListHeaderComponent';let rankModel: RankViewModel = new RankViewModel();@Entry
@Component
struct RankPage {@State dataSource1: RankData[] = [];@State dataSource2: RankData[] = [];@State isSwitchDataSource: boolean = true; // 是否切换数据源// 初始化数据源aboutToAppear() {this.dataSource1 = rankModel.loadRankDataSource1();this.dataSource2 = rankModel.loadRankDataSource2();}build() {Column() {// 标题TitleComponent({ isRefreshData: $isSwitchDataSource, title: TITLE })// 列表头部ListHeaderComponent({paddingValue: {left: Style.RANK_PADDING,right: Style.RANK_PADDING},widthValue: Style.CONTENT_WIDTH}).margin({ // 外边距top: Style.HEADER_MARGIN_TOP,bottom: Style.HEADER_MARGIN_BOTTOM})}}
}

实现排名列表

ListItemComponent .ets代码实现

import { FontSize, FontWeight, ItemStyle, WEIGHT } from '../common/constants/Constants';@Component
export struct ListItemComponent {index?: number;private name?: string;stepNum: string = '';@State isChange: boolean = false;build() {Row() {// 排名Column() {if (this.isRenderCircleText()) {this.CircleText(this.index);} else {Text(this.index?.toString()).lineHeight(ItemStyle.TEXT_LAYOUT_SIZE) // 行高.textAlign(TextAlign.Center) // 文本居中.width(ItemStyle.TEXT_LAYOUT_SIZE) // 宽度.fontWeight(FontWeight.BOLD) // 字体宽度.fontSize(FontSize.SMALL) // 字体大小}}.width(ItemStyle.LAYOUT_WEIGHT_LEFT) // 宽度.alignItems(HorizontalAlign.Start) // 垂直起始位置对齐// 姓名Text(this.name).width(ItemStyle.LAYOUT_WEIGHT_CENTER).fontWeight(FontWeight.BOLDER).fontSize(FontSize.MIDDLE)// ture:绿色字体,false:黑色字体.fontColor(this.isChange ? ItemStyle.COLOR_GREEN : ItemStyle.COLOR_BLACK)// 步数Text(this.stepNum).width(ItemStyle.LAYOUT_WEIGHT_RIGHT).fontWeight(FontWeight.BOLD).fontSize(FontSize.SMALL).fontColor(this.isChange ? ItemStyle.COLOR_GREEN : ItemStyle.COLOR_BLACK)}.height(ItemStyle.BAR_HEIGHT).width(WEIGHT).onClick(() => { // 点击事件this.isChange = !this.isChange;})}// 圆圈背景@Builder CircleText(index: number) {Row() {Text(this.index?.toString()).fontWeight(FontWeight.BOLD).fontSize(FontSize.SMALL).fontColor(Color.White);}.justifyContent(FlexAlign.Center).borderRadius(ItemStyle.CIRCLE_TEXT_BORDER_RADIUS).size({ width: ItemStyle.CIRCLE_TEXT_SIZE,height: ItemStyle.CIRCLE_TEXT_SIZE }).backgroundColor($r('app.color.circle_text_background'))}// 是否显示圆圈isRenderCircleText(): boolean {return this.index === 1 || this.index === 2 || this.index === 3;}
}

应用入口页RankPage引入ListItemComponent组件

  • @Builder RankList装饰的方法用于定义组件的声明式UI描述,在一个自定义组件内快速生成多个布局内容。
import { RankViewModel } from '../viewmodel/RankViewModel';
import { RankData } from '../viewmodel/RankData';
import { APP_EXIT_INTERVAL, Style, TIME, TITLE, WEIGHT } from '../common/constants/Constants';
import { TitleComponent } from '../view/TitleComponent';
import { ListHeaderComponent } from '../view/ListHeaderComponent';
import { ListItemComponent } from '../view/ListItemComponent';let rankModel: RankViewModel = new RankViewModel();@Entry
@Component
struct RankPage {@State dataSource1: RankData[] = [];@State dataSource2: RankData[] = [];@State isSwitchDataSource: boolean = true; // 是否切换数据源// 初始化数据源aboutToAppear() {this.dataSource1 = rankModel.loadRankDataSource1();this.dataSource2 = rankModel.loadRankDataSource2();}build() {Column() {// 标题TitleComponent({ isRefreshData: $isSwitchDataSource, title: TITLE })// 列表头部ListHeaderComponent({paddingValue: {left: Style.RANK_PADDING,right: Style.RANK_PADDING},widthValue: Style.CONTENT_WIDTH}).margin({ // 外边距top: Style.HEADER_MARGIN_TOP,bottom: Style.HEADER_MARGIN_BOTTOM})// 排名列表this.RankList(Style.CONTENT_WIDTH)}.backgroundColor($r('app.color.background')) // 背景色.height(WEIGHT) // 高度.width(WEIGHT) // 宽度}@Builder RankList(widthValue: Length) {Column() {List() { // 列表ForEach(this.isSwitchDataSource ? this.dataSource1 : this.dataSource2,(item: RankData, index?: number) => {ListItem() {ListItemComponent({ index: (Number(index) + 1), name: item.name, stepNum: item.stepNum})}}, (item: RankData) => JSON.stringify(item))}.width(WEIGHT) // 宽度.height(Style.LIST_HEIGHT) // 高度.divider({ strokeWidth: Style.STROKE_WIDTH }) // 分割线}.padding({ // 内边距left: Style.RANK_PADDING,right: Style.RANK_PADDING}).borderRadius(Style.BORDER_RADIUS) // 边框半径.width(widthValue) // 宽度.alignItems(HorizontalAlign.Center) // 垂直居中对齐.backgroundColor(Color.White) // 背景色}
}

运行代码:
在这里插入图片描述
刷新列表:
在这里插入图片描述

这篇关于HarmonyOS开发实战:如何实现一个运动排名榜页面的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/507973

相关文章

Python38个游戏开发库整理汇总

《Python38个游戏开发库整理汇总》文章介绍了多种Python游戏开发库,涵盖2D/3D游戏开发、多人游戏框架及视觉小说引擎,适合不同需求的开发者入门,强调跨平台支持与易用性,并鼓励读者交流反馈以... 目录PyGameCocos2dPySoyPyOgrepygletPanda3DBlenderFife

使用Python开发一个Ditto剪贴板数据导出工具

《使用Python开发一个Ditto剪贴板数据导出工具》在日常工作中,我们经常需要处理大量的剪贴板数据,下面将介绍如何使用Python的wxPython库开发一个图形化工具,实现从Ditto数据库中读... 目录前言运行结果项目需求分析技术选型核心功能实现1. Ditto数据库结构分析2. 数据库自动定位3

python使用Akshare与Streamlit实现股票估值分析教程(图文代码)

《python使用Akshare与Streamlit实现股票估值分析教程(图文代码)》入职测试中的一道题,要求:从Akshare下载某一个股票近十年的财务报表包括,资产负债表,利润表,现金流量表,保存... 目录一、前言二、核心知识点梳理1、Akshare数据获取2、Pandas数据处理3、Matplotl

Django开发时如何避免频繁发送短信验证码(python图文代码)

《Django开发时如何避免频繁发送短信验证码(python图文代码)》Django开发时,为防止频繁发送验证码,后端需用Redis限制请求频率,结合管道技术提升效率,通过生产者消费者模式解耦业务逻辑... 目录避免频繁发送 验证码1. www.chinasem.cn避免频繁发送 验证码逻辑分析2. 避免频繁

分布式锁在Spring Boot应用中的实现过程

《分布式锁在SpringBoot应用中的实现过程》文章介绍在SpringBoot中通过自定义Lock注解、LockAspect切面和RedisLockUtils工具类实现分布式锁,确保多实例并发操作... 目录Lock注解LockASPect切面RedisLockUtils工具类总结在现代微服务架构中,分布

Java使用Thumbnailator库实现图片处理与压缩功能

《Java使用Thumbnailator库实现图片处理与压缩功能》Thumbnailator是高性能Java图像处理库,支持缩放、旋转、水印添加、裁剪及格式转换,提供易用API和性能优化,适合Web应... 目录1. 图片处理库Thumbnailator介绍2. 基本和指定大小图片缩放功能2.1 图片缩放的

精选20个好玩又实用的的Python实战项目(有图文代码)

《精选20个好玩又实用的的Python实战项目(有图文代码)》文章介绍了20个实用Python项目,涵盖游戏开发、工具应用、图像处理、机器学习等,使用Tkinter、PIL、OpenCV、Kivy等库... 目录① 猜字游戏② 闹钟③ 骰子模拟器④ 二维码⑤ 语言检测⑥ 加密和解密⑦ URL缩短⑧ 音乐播放

Spring Boot集成/输出/日志级别控制/持久化开发实践

《SpringBoot集成/输出/日志级别控制/持久化开发实践》SpringBoot默认集成Logback,支持灵活日志级别配置(INFO/DEBUG等),输出包含时间戳、级别、类名等信息,并可通过... 目录一、日志概述1.1、Spring Boot日志简介1.2、日志框架与默认配置1.3、日志的核心作用

Python使用Tenacity一行代码实现自动重试详解

《Python使用Tenacity一行代码实现自动重试详解》tenacity是一个专为Python设计的通用重试库,它的核心理念就是用简单、清晰的方式,为任何可能失败的操作添加重试能力,下面我们就来看... 目录一切始于一个简单的 API 调用Tenacity 入门:一行代码实现优雅重试精细控制:让重试按我

Redis客户端连接机制的实现方案

《Redis客户端连接机制的实现方案》本文主要介绍了Redis客户端连接机制的实现方案,包括事件驱动模型、非阻塞I/O处理、连接池应用及配置优化,具有一定的参考价值,感兴趣的可以了解一下... 目录1. Redis连接模型概述2. 连接建立过程详解2.1 连php接初始化流程2.2 关键配置参数3. 最大连