【鸿蒙心迹】那些代码里的小确幸——我的鸿蒙开发心路历程!
前言:与鸿蒙的初遇
时间回到2023年的那个秋天,当我第一次听说HarmonyOS NEXT要全面去安卓化的消息时,内心既兴奋又忐忑。兴奋的是能参与到这样一个历史性的技术变革中,忐忑的是作为一个Android开发者,需要重新学习一套全新的开发框架。
然而,正是这些忐忑不安的日子里,我收获了无数个令人难忘的小确幸。它们就像黑暗中的萤火虫,虽然微弱,却足以照亮前行的路。今天,我想把这些藏在代码里的小确幸分享给每一个正在或即将踏上鸿蒙开发之路的朋友们。
第一个小确幸:Hello HarmonyOS的那声"你好"
还记得那是一个周五的下午,我刚刚在电脑上安装好DevEco Studio。作为一个有着五年Android开发经验的程序员,我本以为创建第一个鸿蒙应用会是轻而易举的事情。然而,当我面对全新的ArkTS语法和声明式UI框架时,却感到了前所未有的陌生感。
那种感觉就像一个说了多年中文的人,突然要用英语表达同样的意思——想法还是那个想法,但表达方式完全不同了。
经过了整整两个小时的摸索,我终于创建了我的第一个鸿蒙应用。当我看到模拟器屏幕上出现那行熟悉又陌生的"Hello World"时,内心涌起了一种难以言喻的激动。这不仅仅是一行文字,更像是我与鸿蒙生态的第一次对话。
// 我的第一个鸿蒙应用
@Entry
@Component
struct Index {
@State message: string = 'Hello HarmonyOS!'
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.fontColor('#FF1744')
}
.width('100%')
}
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
就是这样简简单单的几行代码,却让我体验到了声明式UI的魅力。不需要findViewById,不需要复杂的布局嵌套,一切都是那么直观和优雅。那一刻,我意识到这不仅仅是换了一个开发平台,而是一种全新开发思维的开始。
那天晚上,我兴奋得睡不着觉,一直在想象着用这种方式能创造出什么样的应用。虽然只是一个简单的Hello World,但那种"我也能开发鸿蒙应用了"的成就感,成为了我后续学习路上的重要动力源泉。
第二个小确幸:状态管理的豁然开朗
如果说第一个Hello World是我踏进鸿蒙世界的第一步,那么真正理解状态管理则是我在这个世界里找到方向感的重要时刻。
那是在学习鸿蒙开发的第二周,我尝试做一个简单的计数器应用。起初,我还在用Android开发的思维方式,想着怎么获取组件引用,怎么手动更新UI。结果越写越复杂,代码变得杂乱无章。
直到我真正理解了@State装饰器的作用机制,那种豁然开朗的感觉至今难忘。原来,UI的更新可以如此自然和优雅!
@Entry
@Component
struct CounterApp {
@State count: number = 0
@State isEven: boolean = true
private updateCounter(delta: number) {
this.count += delta
this.isEven = this.count % 2 === 0
}
build() {
Column({ space: 20 }) {
// 标题区域
Text('智能计数器')
.fontSize(32)
.fontWeight(FontWeight.Bold)
.fontColor('#2196F3')
// 计数显示区域
Column({ space: 10 }) {
Text(`当前计数: ${this.count}`)
.fontSize(24)
.fontColor(this.count >= 0 ? '#4CAF50' : '#F44336')
Text(this.isEven ? '这是一个偶数' : '这是一个奇数')
.fontSize(16)
.fontColor('#FF9800')
.opacity(0.8)
// 进度条显示
Progress({
value: Math.abs(this.count) % 100,
total: 100,
type: ProgressType.Linear
})
.width('80%')
.color(this.isEven ? '#4CAF50' : '#FF5722')
}
.padding(20)
.backgroundColor('#F5F5F5')
.borderRadius(10)
// 操作按钮区域
Row({ space: 15 }) {
Button('-10')
.fontSize(18)
.backgroundColor('#F44336')
.onClick(() => this.updateCounter(-10))
Button('-1')
.fontSize(18)
.backgroundColor('#FF9800')
.onClick(() => this.updateCounter(-1))
Button('重置')
.fontSize(18)
.backgroundColor('#9E9E9E')
.onClick(() => {
this.count = 0
this.isEven = true
})
Button('+1')
.fontSize(18)
.backgroundColor('#4CAF50')
.onClick(() => this.updateCounter(1))
Button('+10')
.fontSize(18)
.backgroundColor('#2196F3')
.onClick(() => this.updateCounter(10))
}
// 统计信息
if (this.count !== 0) {
Column({ space: 5 }) {
Text(`你已经点击了 ${Math.abs(this.count)} 次`)
.fontSize(14)
.fontColor('#666666')
Text(this.count > 100 ? '哇,你点击了这么多次!' : '继续加油!')
.fontSize(12)
.fontColor('#999999')
}
.transition({ type: TransitionType.Insert, opacity: 0 })
}
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
.padding(20)
}
}
当我写完这个计数器应用并在模拟器上运行时,看到界面随着状态的改变而实时更新,那种"原来可以这么简单"的惊喜让我兴奋不已。不需要复杂的观察者模式,不需要手动调用更新方法,一切都是如此自然。
那天晚上,我兴奋地给我的Android同事发消息:"你知道吗?鸿蒙的状态管理比我们的LiveData简单多了!"虽然他们当时可能不太理解我的兴奋,但那种技术突破带来的快乐是真实的。
更让我感到小确幸的是,当我把这个小应用分享给朋友试用时,他们都说界面很流畅,交互很自然。那一刻我才真正体会到,好的技术不仅能让开发者写得愉快,也能让用户用得愉快。
第三个小确幸:自定义组件的成就感
学会了基础的状态管理后,我开始尝试创建自己的自定义组件。这就像学会了基本的画笔使用方法后,终于可以开始创作自己的作品了。
我决定做一个天气卡片组件,因为我一直觉得原生的天气应用界面不够个性化。在设计这个组件的过程中,我第一次深刻体验到了组件化开发的魅力。
// 天气数据接口定义
interface WeatherData {
city: string
temperature: number
weather: string
humidity: number
windSpeed: number
airQuality: string
icon: string
backgroundGradient: string[]
}
// 自定义天气卡片组件
@Component
export struct WeatherCard {
@Prop weatherData: WeatherData
@State isFlipped: boolean = false
@State animationDuration: number = 300
// 根据温度获取温度颜色
private getTemperatureColor(temp: number): string {
if (temp <= 0) return '#1E88E5' // 冰冷蓝
if (temp <= 10) return '#42A5F5' // 寒冷浅蓝
if (temp <= 20) return '#66BB6A' // 舒适绿
if (temp <= 30) return '#FFA726' // 温暖橙
return '#EF5350' // 炎热红
}
// 根据空气质量获取颜色
private getAirQualityColor(quality: string): string {
switch (quality) {
case '优': return '#4CAF50'
case '良': return '#8BC34A'
case '轻度污染': return '#FF9800'
case '中度污染': return '#FF5722'
case '重度污染': return '#F44336'
default: return '#9E9E9E'
}
}
build() {
Stack() {
// 背景渐变
Column()
.width('100%')
.height('100%')
.linearGradient({
direction: GradientDirection.Bottom,
colors: [
[this.weatherData.backgroundGradient[0], 0.0],
[this.weatherData.backgroundGradient[1], 1.0]
]
})
.borderRadius(20)
// 主内容
if (!this.isFlipped) {
// 正面 - 基本天气信息
Column({ space: 15 }) {
// 顶部城市和时间
Row() {
Text(this.weatherData.city)
.fontSize(18)
.fontColor(Color.White)
.fontWeight(FontWeight.Medium)
Spacer()
Text(this.getCurrentTime())
.fontSize(14)
.fontColor(Color.White)
.opacity(0.8)
}
.width('100%')
// 中央温度和天气图标
Row({ space: 20 }) {
Column() {
Text(`${this.weatherData.temperature}°`)
.fontSize(48)
.fontColor(Color.White)
.fontWeight(FontWeight.Bold)
.textShadow({
radius: 10,
color: Color.Black,
offsetX: 2,
offsetY: 2
})
Text(this.weatherData.weather)
.fontSize(16)
.fontColor(Color.White)
.opacity(0.9)
}
.alignItems(HorizontalAlign.Start)
Spacer()
// 天气图标
Image($r('app.media.weather_sunny'))
.width(60)
.height(60)
.fillColor(Color.White)
}
.width('100%')
.alignItems(VerticalAlign.Center)
// 底部快速信息
Row({ space: 20 }) {
this.QuickInfoItem('湿度', `${this.weatherData.humidity}%`)
this.QuickInfoItem('风速', `${this.weatherData.windSpeed}m/s`)
Button('详情')
.fontSize(12)
.fontColor(Color.White)
.backgroundColor(Color.Transparent)
.border({ width: 1, color: Color.White, radius: 15 })
.padding({ left: 15, right: 15, top: 5, bottom: 5 })
.onClick(() => {
animateTo({ duration: this.animationDuration }, () => {
this.isFlipped = true
})
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
}
.padding(20)
.width('100%')
.height('100%')
.justifyContent(FlexAlign.SpaceBetween)
} else {
// 背面 - 详细信息
Column({ space: 20 }) {
// 顶部返回按钮
Row() {
Button('← 返回')
.fontSize(14)
.fontColor(Color.White)
.backgroundColor(Color.Transparent)
.onClick(() => {
animateTo({ duration: this.animationDuration }, () => {
this.isFlipped = false
})
})
Spacer()
Text('详细天气')
.fontSize(18)
.fontColor(Color.White)
.fontWeight(FontWeight.Medium)
}
.width('100%')
// 详细信息列表
Column({ space: 15 }) {
this.DetailInfoRow('温度', `${this.weatherData.temperature}°C`, this.getTemperatureColor(this.weatherData.temperature))
this.DetailInfoRow('天气', this.weatherData.weather, '#FFFFFF')
this.DetailInfoRow('湿度', `${this.weatherData.humidity}%`, '#64B5F6')
this.DetailInfoRow('风速', `${this.weatherData.windSpeed} m/s`, '#81C784')
this.DetailInfoRow('空气质量', this.weatherData.airQuality, this.getAirQualityColor(this.weatherData.airQuality))
}
.width('100%')
// 温度建议
Text(this.getTemperatureAdvice())
.fontSize(14)
.fontColor(Color.White)
.opacity(0.8)
.textAlign(TextAlign.Center)
.padding({ top: 10, bottom: 10 })
.backgroundColor(Color.White)
.backgroundColor(Color.Transparent)
.borderRadius(10)
.border({ width: 1, color: Color.White, radius: 10 })
}
.padding(20)
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Start)
}
}
.width('100%')
.height(200)
.borderRadius(20)
.shadow({
radius: 20,
color: Color.Black,
offsetX: 0,
offsetY: 5
})
.clip(true)
}
// 快速信息项组件
@Builder QuickInfoItem(label: string, value: string) {
Column({ space: 2 }) {
Text(label)
.fontSize(12)
.fontColor(Color.White)
.opacity(0.7)
Text(value)
.fontSize(14)
.fontColor(Color.White)
.fontWeight(FontWeight.Medium)
}
}
// 详细信息行组件
@Builder DetailInfoRow(label: string, value: string, valueColor: string) {
Row() {
Text(label)
.fontSize(16)
.fontColor(Color.White)
.opacity(0.8)
Spacer()
Text(value)
.fontSize(16)
.fontColor(valueColor)
.fontWeight(FontWeight.Medium)
}
.width('100%')
.padding({ left: 10, right: 10, top: 8, bottom: 8 })
.backgroundColor(Color.White)
.opacity(0.1)
.borderRadius(8)
}
// 获取当前时间
private getCurrentTime(): string {
const now = new Date()
return `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}`
}
// 根据温度给出建议
private getTemperatureAdvice(): string {
const temp = this.weatherData.temperature
if (temp <= 0) return '天气严寒,请注意保暖,外出时多穿衣物'
if (temp <= 10) return '天气较冷,建议穿着厚外套'
if (temp <= 20) return '天气舒适,适合外出活动'
if (temp <= 30) return '天气温暖,注意适当增减衣物'
return '天气炎热,请注意防暑降温,多喝水'
}
}
// 使用天气卡片的页面
@Entry
@Component
struct WeatherPage {
@State weatherList: WeatherData[] = [
{
city: '北京',
temperature: 15,
weather: '晴',
humidity: 45,
windSpeed: 3.2,
airQuality: '良',
icon: 'sunny',
backgroundGradient: ['#FFD54F', '#FF8A65']
},
{
city: '上海',
temperature: 22,
weather: '多云',
humidity: 68,
windSpeed: 2.1,
airQuality: '优',
icon: 'cloudy',
backgroundGradient: ['#64B5F6', '#42A5F5']
},
{
city: '深圳',
temperature: 28,
weather: '小雨',
humidity: 78,
windSpeed: 1.8,
airQuality: '良',
icon: 'rainy',
backgroundGradient: ['#78909C', '#546E7A']
}
]
build() {
Column({ space: 20 }) {
Text('我的天气')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.fontColor('#212121')
.margin({ top: 20 })
List({ space: 15 }) {
ForEach(this.weatherList, (weather: WeatherData, index: number) => {
ListItem() {
WeatherCard({ weatherData: weather })
}
})
}
.width('100%')
.padding({ left: 20, right: 20 })
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
}
当我第一次运行这个天气卡片应用时,看到那些精心设计的动画效果、渐变背景和交互细节都完美呈现,内心的成就感是难以言喻的。那不仅仅是代码的成功运行,更像是一件艺术品的诞生。
那天我把这个应用的截图发到了朋友圈,配文是"人生第一个鸿蒙自定义组件,虽然简单,但很有成就感"。没想到收到了很多点赞和评论,其中一个做UI设计的朋友说:"没想到程序员也能做出这么好看的界面。"
这种被认可的感觉,加上技术实现带来的满足感,构成了我在鸿蒙开发路上的一个重要里程碑。从那以后,我开始相信自己不只是一个写代码的程序员,也可以是一个创造美好用户体验的产品创造者。
第四个小确幸:网络请求调通的喜悦
在掌握了基础的UI开发能力后,我开始挑战更复杂的功能——网络请求。这对于一个应用来说是至关重要的,因为现在几乎所有的应用都需要从服务器获取数据。
初次接触鸿蒙的网络请求API时,我发现它与Android的Retrofit或者OkHttp有很大不同。刚开始的时候,我总是遇到各种问题:请求超时、数据解析错误、权限问题等等。每次看到控制台里的红色错误信息,都让我感到沮丧。
但是,当我终于成功调通第一个网络请求,看到从服务器返回的真实数据在我的应用里正确显示时,那种喜悦是前所未有的。
import http from '@ohos.net.http'
// 网络请求工具类
export class NetworkManager {
private static instance: NetworkManager
private baseUrl: string = 'https://api.example.com'
public static getInstance(): NetworkManager {
if (!NetworkManager.instance) {
NetworkManager.instance = new NetworkManager()
}
return NetworkManager.instance
}
// 通用GET请求方法
public async get<T>(url: string, params?: Record<string, any>): Promise<T> {
return new Promise((resolve, reject) => {
const httpRequest = http.createHttp()
// 构建完整URL
let fullUrl = `${this.baseUrl}${url}`
if (params) {
const queryString = Object.keys(params)
.map(key => `${key}=${encodeURIComponent(params[key])}`)
.join('&')
fullUrl += `?${queryString}`
}
httpRequest.request(fullUrl, {
method: http.RequestMethod.GET,
header: {
'Content-Type': 'application/json',
'User-Agent': 'HarmonyOS-App/1.0'
},
connectTimeout: 60000,
readTimeout: 60000
}).then((data: http.HttpResponse) => {
if (data.responseCode === 200) {
try {
const result = JSON.parse(data.result.toString()) as T
resolve(result)
} catch (error) {
reject(new Error('数据解析失败'))
}
} else {
reject(new Error(`请求失败: ${data.responseCode}`))
}
}).catch((error) => {
reject(error)
}).finally(() => {
httpRequest.destroy()
})
})
}
// 通用POST请求方法
public async post<T>(url: string, data?: any): Promise<T> {
return new Promise((resolve, reject) => {
const httpRequest = http.createHttp()
httpRequest.request(`${this.baseUrl}${url}`, {
method: http.RequestMethod.POST,
header: {
'Content-Type': 'application/json',
'User-Agent': 'HarmonyOS-App/1.0'
},
extraData: data ? JSON.stringify(data) : undefined,
connectTimeout: 60000,
readTimeout: 60000
}).then((response: http.HttpResponse) => {
if (response.responseCode === 200) {
try {
const result = JSON.parse(response.result.toString()) as T
resolve(result)
} catch (error) {
reject(new Error('数据解析失败'))
}
} else {
reject(new Error(`请求失败: ${response.responseCode}`))
}
}).catch((error) => {
reject(error)
}).finally(() => {
httpRequest.destroy()
})
})
}
}
// 数据模型定义
interface ApiResponse<T> {
code: number
message: string
data: T
}
interface NewsItem {
id: number
title: string
summary: string
content: string
author: string
publishTime: string
imageUrl: string
category: string
readCount: number
likeCount: number
}
interface NewsListResponse {
list: NewsItem[]
total: number
hasMore: boolean
}
// 新闻服务类
export class NewsService {
private networkManager = NetworkManager.getInstance()
// 获取新闻列表
public async getNewsList(page: number = 1, size: number = 10): Promise<NewsListResponse> {
try {
const response = await this.networkManager.get<ApiResponse<NewsListResponse>>('/news/list', {
page,
size
})
if (response.code === 200) {
return response.data
} else {
throw new Error(response.message || '获取新闻列表失败')
}
} catch (error) {
throw new Error(`网络请求失败: ${error.message}`)
}
}
// 获取新闻详情
public async getNewsDetail(id: number): Promise<NewsItem> {
try {
const response = await this.networkManager.get<ApiResponse<NewsItem>>(`/news/detail/${id}`)
if (response.code === 200) {
return response.data
} else {
throw new Error(response.message || '获取新闻详情失败')
}
} catch (error) {
throw new Error(`网络请求失败: ${error.message}`)
}
}
// 点赞新闻
public async likeNews(id: number): Promise<boolean> {
try {
const response = await this.networkManager.post<ApiResponse<boolean>>(`/news/like/${id}`)
return response.code === 200 && response.data
} catch (error) {
console.error('点赞失败:', error)
return false
}
}
}
// 新闻列表页面组件
@Entry
@Component
struct NewsListPage {
@State newsList: NewsItem[] = []
@State isLoading: boolean = false
@State hasMore: boolean = true
@State currentPage: number = 1
@State errorMessage: string = ''
@State refreshing: boolean = false
private newsService = new NewsService()
private pageSize: number = 10
aboutToAppear() {
this.loadNewsList(true)
}
// 加载新闻列表
private async loadNewsList(isRefresh: boolean = false) {
if (this.isLoading) return
this.isLoading = true
this.errorMessage = ''
if (isRefresh) {
this.currentPage = 1
this.refreshing = true
}
try {
const result = await this.newsService.getNewsList(this.currentPage, this.pageSize)
if (isRefresh) {
this.newsList = result.list
} else {
this.newsList = [...this.newsList, ...result.list]
}
this.hasMore = result.hasMore
if (result.hasMore) {
this.currentPage++
}
} catch (error) {
this.errorMessage = error.message
console.error('加载新闻列表失败:', error)
} finally {
this.isLoading = false
this.refreshing = false
}
}
// 处理点赞
private async handleLike(newsItem: NewsItem, index: number) {
try {
const success = await this.newsService.likeNews(newsItem.id)
if (success) {
// 更新本地数据
this.newsList[index] = {
...newsItem,
likeCount: newsItem.likeCount + 1
}
// 显示成功提示
promptAction.showToast({
message: '点赞成功!',
duration: 2000
})
}
} catch (error) {
promptAction.showToast({
message: '点赞失败,请稍后重试',
duration: 2000
})
}
}
// 格式化时间
private formatTime(timeString: string): string {
const date = new Date(timeString)
const now = new Date()
const diff = now.getTime() - date.getTime()
const minutes = Math.floor(diff / (1000 * 60))
const hours = Math.floor(diff / (1000 * 60 * 60))
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
if (minutes < 60) {
return `${minutes}分钟前`
} else if (hours < 24) {
return `${hours}小时前`
} else if (days < 7) {
return `${days}天前`
} else {
return date.toLocaleDateString()
}
}
build() {
Column() {
// 顶部标题栏
Row() {
Text('新闻资讯')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#212121')
Spacer()
Button('刷新')
.fontSize(14)
.fontColor('#2196F3')
.backgroundColor(Color.Transparent)
.onClick(() => this.loadNewsList(true))
}
.width('100%')
.padding({ left: 16, right: 16, top: 10, bottom: 10 })
.backgroundColor(Color.White)
// 错误提示
if (this.errorMessage) {
Row() {
Text(`加载失败: ${this.errorMessage}`)
.fontSize(14)
.fontColor('#F44336')
Spacer()
Button('重试')
.fontSize(12)
.fontColor(Color.White)
.backgroundColor('#F44336')
.borderRadius(4)
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
.onClick(() => this.loadNewsList(true))
}
.width('100%')
.padding(16)
.backgroundColor('#FFEBEE')
}
// 新闻列表
if (this.newsList.length > 0) {
List({ space: 1 }) {
ForEach(this.newsList, (newsItem: NewsItem, index: number) => {
ListItem() {
this.NewsItemComponent(newsItem, index)
}
.onClick(() => {
// 导航到详情页面
router.pushUrl({
url: 'pages/NewsDetailPage',
params: { newsId: newsItem.id }
})
})
})
// 加载更多指示器
if (this.hasMore) {
ListItem() {
Row() {
if (this.isLoading) {
LoadingProgress()
.width(20)
.height(20)
.color('#2196F3')
Text('正在加载...')
.fontSize(14)
.fontColor('#666666')
.margin({ left: 8 })
} else {
Button('加载更多')
.fontSize(14)
.fontColor('#2196F3')
.backgroundColor(Color.Transparent)
.onClick(() => this.loadNewsList(false))
}
}
.width('100%')
.height(50)
.justifyContent(FlexAlign.Center)
}
} else if (this.newsList.length > 0) {
ListItem() {
Text('没有更多内容了')
.fontSize(14)
.fontColor('#999999')
.width('100%')
.height(50)
.textAlign(TextAlign.Center)
.backgroundColor('#F5F5F5')
}
}
}
.width('100%')
.layoutWeight(1)
.backgroundColor('#F5F5F5')
} else if (this.isLoading && this.currentPage === 1) {
// 初次加载指示器
Column() {
LoadingProgress()
.width(40)
.height(40)
.color('#2196F3')
Text('正在加载新闻...')
.fontSize(16)
.fontColor('#666666')
.margin({ top: 16 })
}
.width('100%')
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
} else {
// 空状态
Column() {
Image($r('app.media.empty_news'))
.width(120)
.height(120)
.opacity(0.5)
Text('暂无新闻内容')
.fontSize(16)
.fontColor('#999999')
.margin({ top: 16 })
Button('刷新试试')
.fontSize(14)
.fontColor(Color.White)
.backgroundColor('#2196F3')
.borderRadius(20)
.margin({ top: 16 })
.onClick(() => this.loadNewsList(true))
}
.width('100%')
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
}
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
// 新闻项组件
@Builder NewsItemComponent(newsItem: NewsItem, index: number) {
Column() {
Row({ space: 12 }) {
// 新闻图片
Image(newsItem.imageUrl || $r('app.media.default_news'))
.width(80)
.height(60)
.borderRadius(8)
.objectFit(ImageFit.Cover)
// 新闻信息
Column({ space: 8 }) {
Text(newsItem.title)
.fontSize(16)
.fontColor('#212121')
.fontWeight(FontWeight.Medium)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(newsItem.summary)
.fontSize(14)
.fontColor('#666666')
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Row({ space: 16 }) {
Text(newsItem.author)
.fontSize(12)
.fontColor('#999999')
Text(this.formatTime(newsItem.publishTime))
.fontSize(12)
.fontColor('#999999')
Spacer()
Row({ space: 4 }) {
Image($r('app.media.ic_like'))
.width(16)
.height(16)
.fillColor('#FF6B6B')
Text(newsItem.likeCount.toString())
.fontSize(12)
.fontColor('#999999')
}
.onClick(() => this.handleLike(newsItem, index))
}
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.padding(16)
.alignItems(VerticalAlign.Top)
Divider()
.width('100%')
.height(0.5)
.color('#E0E0E0')
}
.width('100%')
.backgroundColor(Color.White)
}
}
记得那天当我看到新闻列表页面完美加载出真实的数据时,我激动得在办公室里小声说了句"成功了!"。那种从无到有创造出一个功能完整的应用的感觉,就像是魔法师第一次成功施展复杂咒语的成就感。
更让我感到小确幸的是,当我把这个新闻应用分享给同事试用时,他们都说加载速度很快,界面也很流畅。其中一个做产品的同事还问我:"这真的是你第一次用鸿蒙开发的应用吗?看起来很专业啊!"
那一刻,我深深地感受到了技术给人带来的自信和满足感。不仅仅是代码的成功运行,更是通过自己的努力创造出了有价值的产品。这种成就感激励我在鸿蒙开发的道路上走得更远。
第五个小确幸:动画效果的完美呈现
在掌握了基本的界面开发和网络请求后,我开始探索鸿蒙应用中的动画效果。作为一个对用户体验有一定追求的开发者,我深知流畅的动画效果对于提升应用体验的重要性。
初次接触鸿蒙的动画系统时,我发现它比Android的动画系统更加直观和强大。通过animateTo
函数和各种转场动画,我可以轻松创建出复杂而流畅的动画效果。
那天,我决定做一个具有丰富动画效果的任务管理应用。当我看到那些精心设计的动画在模拟器上完美运行时,那种视觉上的冲击和技术实现的满足感让我兴奋了整个晚上。
import router from '@ohos.router'
import promptAction from '@ohos.promptAction'
// 任务数据模型
interface Task {
id: number
title: string
description: string
completed: boolean
priority: 'high' | 'medium' | 'low'
createTime: Date
dueDate?: Date
category: string
}
// 任务统计数据
interface TaskStats {
total: number
completed: number
pending: number
overdue: number
}
@Entry
@Component
struct TaskManagerPage {
@State tasks: Task[] = []
@State filteredTasks: Task[] = []
@State currentFilter: string = 'all'
@State isAddingTask: boolean = false
@State showStats: boolean = false
@State selectedTaskId: number = -1
@State animationDuration: number = 300
@State searchText: string = ''
// 动画状态
@State listItemOffsetY: number = 0
@State listItemOpacity: number = 1
@State fabRotation: number = 0
@State statsCardScale: number = 1
aboutToAppear() {
this.loadTasks()
this.filterTasks()
}
// 加载任务数据
private loadTasks() {
// 模拟数据
this.tasks = [
{
id: 1,
title: '完成鸿蒙应用开发',
description: '使用ArkTS完成任务管理应用的核心功能',
completed: false,
priority: 'high',
createTime: new Date(),
dueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
category: '工作'
},
{
id: 2,
title: '学习动画效果实现',
description: '深入了解鸿蒙的动画系统和最佳实践',
completed: true,
priority: 'medium',
createTime: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000),
category: '学习'
},
{
id: 3,
title: '优化用户界面',
description: '改进应用的视觉设计和交互体验',
completed: false,
priority: 'low',
createTime: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000),
category: '设计'
}
]
}
// 过滤任务
private filterTasks() {
let filtered = this.tasks
// 根据筛选条件过滤
switch (this.currentFilter) {
case 'completed':
filtered = filtered.filter(task => task.completed)
break
case 'pending':
filtered = filtered.filter(task => !task.completed)
break
case 'high':
filtered = filtered.filter(task => task.priority === 'high')
break
}
// 根据搜索文本过滤
if (this.searchText.trim()) {
filtered = filtered.filter(task =>
task.title.toLowerCase().includes(this.searchText.toLowerCase()) ||
task.description.toLowerCase().includes(this.searchText.toLowerCase())
)
}
// 应用入场动画
this.animateTasksIn(filtered)
}
// 任务入场动画
private animateTasksIn(newTasks: Task[]) {
this.filteredTasks = []
newTasks.forEach((task, index) => {
setTimeout(() => {
animateTo({
duration: this.animationDuration,
curve: Curve.EaseOutBack,
delay: index * 50
}, () => {
this.filteredTasks.push(task)
})
}, index * 50)
})
}
// 切换任务完成状态
private toggleTask(taskId: number) {
const taskIndex = this.tasks.findIndex(task => task.id === taskId)
if (taskIndex !== -1) {
const task = this.tasks[taskIndex]
// 播放切换动画
animateTo({
duration: 200,
curve: Curve.EaseInOut
}, () => {
this.tasks[taskIndex] = { ...task, completed: !task.completed }
})
// 延迟更新过滤结果以显示动画
setTimeout(() => {
this.filterTasks()
}, 200)
// 显示反馈
promptAction.showToast({
message: task.completed ? '任务标记为未完成' : '任务完成!',
duration: 1500
})
}
}
// 删除任务
private deleteTask(taskId: number) {
const taskIndex = this.tasks.findIndex(task => task.id === taskId)
if (taskIndex !== -1) {
// 删除动画
animateTo({
duration: this.animationDuration,
curve: Curve.EaseInOut
}, () => {
this.listItemOffsetY = -100
this.listItemOpacity = 0
})
setTimeout(() => {
this.tasks.splice(taskIndex, 1)
this.filterTasks()
this.listItemOffsetY = 0
this.listItemOpacity = 1
}, this.animationDuration)
}
}
// 获取任务统计
private getTaskStats(): TaskStats {
const total = this.tasks.length
const completed = this.tasks.filter(task => task.completed).length
const pending = total - completed
const overdue = this.tasks.filter(task =>
task.dueDate && task.dueDate < new Date() && !task.completed
).length
return { total, completed, pending, overdue }
}
// 获取优先级颜色
private getPriorityColor(priority: string): string {
switch (priority) {
case 'high': return '#F44336'
case 'medium': return '#FF9800'
case 'low': return '#4CAF50'
default: return '#9E9E9E'
}
}
build() {
Column() {
// 顶部导航栏
this.TopNavigationBar()
// 搜索栏
this.SearchBar()
// 统计卡片
if (this.showStats) {
this.StatsCard()
}
// 筛选按钮组
this.FilterButtons()
// 任务列表
this.TaskList()
// 悬浮操作按钮
this.FloatingActionButton()
}
.width('100%')
.height('100%')
.backgroundColor('#F8F9FA')
}
// 顶部导航栏
@Builder TopNavigationBar() {
Row() {
Text('任务管理')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#212121')
Spacer()
// 统计按钮
Button() {
Image($r('app.media.ic_stats'))
.width(24)
.height(24)
.fillColor('#2196F3')
}
.width(48)
.height(48)
.backgroundColor(Color.Transparent)
.borderRadius(24)
.scale({ x: this.statsCardScale, y: this.statsCardScale })
.onClick(() => {
animateTo({
duration: 200,
curve: Curve.EaseInOut
}, () => {
this.statsCardScale = 0.9
this.showStats = !this.showStats
})
setTimeout(() => {
animateTo({
duration: 200,
curve: Curve.EaseOutBack
}, () => {
this.statsCardScale = 1
})
}, 100)
})
}
.width('100%')
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
.backgroundColor(Color.White)
.shadow({ radius: 2, color: Color.Black, offsetY: 1 })
}
// 搜索栏
@Builder SearchBar() {
Row({ space: 12 }) {
TextInput({ placeholder: '搜索任务...', text: this.searchText })
.layoutWeight(1)
.height(40)
.backgroundColor('#F5F5F5')
.borderRadius(20)
.padding({ left: 16, right: 16 })
.onChange((value: string) => {
this.searchText = value
this.filterTasks()
})
if (this.searchText) {
Button('清除')
.fontSize(14)
.fontColor('#666666')
.backgroundColor(Color.Transparent)
.transition({ type: TransitionType.Insert, scale: { x: 0, y: 0 } })
.onClick(() => {
animateTo({ duration: 200 }, () => {
this.searchText = ''
})
this.filterTasks()
})
}
}
.width('100%')
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.backgroundColor(Color.White)
}
// 统计卡片
@Builder StatsCard() {
Column({ space: 16 }) {
Text('任务统计')
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#212121')
Row({ space: 20 }) {
this.StatItem('总计', this.getTaskStats().total, '#2196F3')
this.StatItem('已完成', this.getTaskStats().completed, '#4CAF50')
this.StatItem('待完成', this.getTaskStats().pending, '#FF9800')
this.StatItem('已逾期', this.getTaskStats().overdue, '#F44336')
}
.width('100%')
.justifyContent(FlexAlign.SpaceAround)
}
.width('100%')
.padding(16)
.margin({ left: 16, right: 16, top: 8, bottom: 8 })
.backgroundColor(Color.White)
.borderRadius(12)
.shadow({ radius: 4, color: Color.Black, offsetY: 2 })
.transition({ type: TransitionType.Insert, opacity: 0, translate: { y: -50 } })
}
// 统计项
@Builder StatItem(label: string, value: number, color: string) {
Column({ space: 4 }) {
Text(value.toString())
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor(color)
Text(label)
.fontSize(12)
.fontColor('#666666')
}
}
// 筛选按钮组
@Builder FilterButtons() {
Row({ space: 8 }) {
this.FilterButton('全部', 'all')
this.FilterButton('已完成', 'completed')
this.FilterButton('待完成', 'pending')
this.FilterButton('高优先级', 'high')
}
.width('100%')
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.backgroundColor(Color.White)
}
// 筛选按钮
@Builder FilterButton(text: string, filter: string) {
Button(text)
.fontSize(14)
.fontColor(this.currentFilter === filter ? Color.White : '#666666')
.backgroundColor(this.currentFilter === filter ? '#2196F3' : Color.Transparent)
.borderRadius(16)
.padding({ left: 12, right: 12, top: 6, bottom: 6 })
.border({
width: 1,
color: this.currentFilter === filter ? '#2196F3' : '#E0E0E0',
radius: 16
})
.onClick(() => {
if (this.currentFilter !== filter) {
animateTo({ duration: 200 }, () => {
this.currentFilter = filter
})
this.filterTasks()
}
})
}
// 任务列表
@Builder TaskList() {
if (this.filteredTasks.length > 0) {
List({ space: 8 }) {
ForEach(this.filteredTasks, (task: Task, index: number) => {
ListItem() {
this.TaskItem(task, index)
}
.swipeAction({ end: this.SwipeActions(task) })
.transition({
type: TransitionType.All,
opacity: 0,
translate: { x: -50 },
scale: { x: 0.9, y: 0.9 }
})
})
}
.layoutWeight(1)
.padding({ left: 16, right: 16, top: 8, bottom: 80 })
} else {
// 空状态
Column({ space: 16 }) {
Image($r('app.media.empty_tasks'))
.width(120)
.height(120)
.opacity(0.5)
Text(this.searchText ? '没有找到相关任务' : '还没有任务,创建一个吧!')
.fontSize(16)
.fontColor('#999999')
.textAlign(TextAlign.Center)
}
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
}
}
// 任务项
@Builder TaskItem(task: Task, index: number) {
Row({ space: 12 }) {
// 完成状态勾选框
Checkbox({ name: `task_${task.id}`, group: 'tasks' })
.select(task.completed)
.selectedColor('#4CAF50')
.onChange((value: boolean) => {
this.toggleTask(task.id)
})
// 任务内容
Column({ space: 4 }) {
Row() {
Text(task.title)
.fontSize(16)
.fontColor(task.completed ? '#999999' : '#212121')
.fontWeight(task.completed ? FontWeight.Normal : FontWeight.Medium)
.decoration({
type: task.completed ? TextDecorationType.LineThrough : TextDecorationType.None
})
.layoutWeight(1)
// 优先级指示器
Circle({ width: 8, height: 8 })
.fill(this.getPriorityColor(task.priority))
}
.width('100%')
Text(task.description)
.fontSize(14)
.fontColor('#666666')
.opacity(task.completed ? 0.6 : 1)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
// 任务信息
Row({ space: 8 }) {
Text(task.category)
.fontSize(12)
.fontColor('#2196F3')
.backgroundColor('#E3F2FD')
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.borderRadius(8)
if (task.dueDate) {
Text(`截止: ${task.dueDate.toLocaleDateString()}`)
.fontSize(12)
.fontColor('#F44336')
.opacity(0.8)
}
}
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.borderRadius(12)
.shadow({ radius: 2, color: Color.Black, offsetY: 1 })
.translate({ y: this.selectedTaskId === task.id ? this.listItemOffsetY : 0 })
.opacity(this.selectedTaskId === task.id ? this.listItemOpacity : 1)
.onClick(() => {
// 点击波纹效果
this.selectedTaskId = task.id
animateTo({
duration: 100,
curve: Curve.EaseInOut
}, () => {
// 微小的缩放效果
})
setTimeout(() => {
this.selectedTaskId = -1
// 导航到详情页
router.pushUrl({
url: 'pages/TaskDetailPage',
params: { taskId: task.id }
})
}, 100)
})
}
// 滑动操作
@Builder SwipeActions(task: Task) {
Row({ space: 0 }) {
Button('编辑')
.fontSize(14)
.fontColor(Color.White)
.backgroundColor('#2196F3')
.width(80)
.height('100%')
.onClick(() => {
// 编辑任务逻辑
})
Button('删除')
.fontSize(14)
.fontColor(Color.White)
.backgroundColor('#F44336')
.width(80)
.height('100%')
.onClick(() => {
this.deleteTask(task.id)
})
}
}
// 悬浮操作按钮
@Builder FloatingActionButton() {
Stack() {
Button() {
Image($r('app.media.ic_add'))
.width(24)
.height(24)
.fillColor(Color.White)
.rotate({ angle: this.fabRotation })
}
.width(56)
.height(56)
.backgroundColor('#2196F3')
.borderRadius(28)
.shadow({ radius: 8, color: Color.Black, offsetY: 4 })
.onClick(() => {
// FAB 旋转动画
animateTo({
duration: 300,
curve: Curve.EaseInOut
}, () => {
this.fabRotation += 180
})
// 导航到添加任务页面
router.pushUrl({
url: 'pages/AddTaskPage'
})
})
}
.position({ x: '85%', y: '85%' })
}
}
记得那天当我第一次看到这个任务管理应用的所有动画效果都完美运行时,我内心的激动是难以形容的。每一个任务项的入场动画,每一次状态切换的过渡效果,每一个按钮的点击反馈,都让我感受到了动画给用户体验带来的巨大提升。
最让我印象深刻的是,当我把这个应用展示给我的设计师朋友看时,她说:"这个应用的交互体验真的很棒,感觉比很多原生应用都要流畅。你们程序员什么时候变得这么注重用户体验了?"
那一刻,我意识到技术不仅仅是冷冰冰的代码实现,更可以成为传达情感、创造美好体验的艺术载体。这种认知上的转变,成为了我在鸿蒙开发路上的一个重要里程碑。
第六个小确幸:性能优化带来的提升
随着我的鸿蒙应用功能越来越复杂,性能问题也开始显现。页面渲染卡顿、内存占用过高、电量消耗快等问题让我意识到,仅仅实现功能是远远不够的,还需要关注应用的性能表现。
那段时间,我深入学习了鸿蒙的性能优化原理和最佳实践。从状态管理的优化到渲染性能的提升,从内存管理到电量优化,每一个细节的改进都让我感到兴奋。
当我最终看到应用的性能指标大幅提升时,那种成就感不亚于第一次成功运行Hello World时的喜悦。
import hiTraceMeter from '@ohos.hiTraceMeter'
import hiAppEvent from '@ohos.hiAppEvent'
// 性能监控工具类
export class PerformanceMonitor {
private static instance: PerformanceMonitor
private performanceData: Map<string, number> = new Map()
public static getInstance(): PerformanceMonitor {
if (!PerformanceMonitor.instance) {
PerformanceMonitor.instance = new PerformanceMonitor()
}
return PerformanceMonitor.instance
}
// 开始性能追踪
public startTrace(name: string) {
const startTime = Date.now()
this.performanceData.set(name, startTime)
hiTraceMeter.startTrace(name, Date.now())
}
// 结束性能追踪
public finishTrace(name: string) {
const startTime = this.performanceData.get(name)
if (startTime) {
const duration = Date.now() - startTime
hiTraceMeter.finishTrace(name, Date.now())
this.reportPerformance(name, duration)
this.performanceData.delete(name)
}
}
// 上报性能数据
private reportPerformance(name: string, duration: number) {
hiAppEvent.write({
domain: 'performance',
name: name,
eventType: hiAppEvent.EventType.BEHAVIOR,
params: {
duration: duration,
timestamp: Date.now()
}
})
}
}
// 优化后的图片加载组件
@Component
export struct OptimizedImage {
@Prop src: string | Resource
@Prop width: number | string = '100%'
@Prop height: number | string = 'auto'
@Prop placeholder?: Resource
@Prop borderRadius?: number = 0
@Prop objectFit?: ImageFit = ImageFit.Cover
@State isLoading: boolean = true
@State hasError: boolean = false
@State isVisible: boolean = false
private performanceMonitor = PerformanceMonitor.getInstance()
build() {
Stack() {
// 占位符
if (this.isLoading || this.hasError) {
Column() {
if (this.hasError) {
Image($r('app.media.image_error'))
.width(48)
.height(48)
.opacity(0.5)
} else if (this.placeholder) {
Image(this.placeholder)
.width(this.width)
.height(this.height)
.objectFit(this.objectFit)
.borderRadius(this.borderRadius)
.opacity(0.3)
} else {
// 骨架屏效果
Column()
.width(this.width)
.height(this.height)
.backgroundColor('#F0F0F0')
.borderRadius(this.borderRadius)
.shimmering()
}
}
.width(this.width)
.height(this.height)
.justifyContent(FlexAlign.Center)
}
// 实际图片
if (!this.hasError) {
Image(this.src)
.width(this.width)
.height(this.height)
.objectFit(this.objectFit)
.borderRadius(this.borderRadius)
.opacity(this.isLoading ? 0 : 1)
.onComplete(() => {
this.performanceMonitor.finishTrace('image_load')
animateTo({ duration: 300 }, () => {
this.isLoading = false
})
})
.onError(() => {
this.hasError = true
this.isLoading = false
})
.onAppear(() => {
this.performanceMonitor.startTrace('image_load')
this.isVisible = true
})
}
}
.width(this.width)
.height(this.height)
}
// 闪光效果扩展
@Extend(Column) shimmer() {
.linearGradient({
direction: GradientDirection.Right,
repeating: true,
colors: [
['#F0F0F0', 0.0],
['#E0E0E0', 0.5],
['#F0F0F0', 1.0]
]
})
}
}
// 虚拟列表组件 - 用于大数据量列表的性能优化
@Component
export struct VirtualList {
@Prop data: any[]
@Prop itemHeight: number = 80
@Prop visibleCount: number = 10
@BuilderParam itemBuilder: (item: any, index: number) => void
@State startIndex: number = 0
@State endIndex: number = this.visibleCount
@State scrollOffset: number = 0
@State containerHeight: number = 0
private scroller: Scroller = new Scroller()
private performanceMonitor = PerformanceMonitor.getInstance()
aboutToAppear() {
this.containerHeight = this.itemHeight * this.visibleCount
this.updateVisibleRange()
}
// 更新可见范围
private updateVisibleRange() {
const scrollTop = this.scrollOffset
const newStartIndex = Math.max(0, Math.floor(scrollTop / this.itemHeight) - 2)
const newEndIndex = Math.min(
this.data.length - 1,
newStartIndex + this.visibleCount + 4
)
if (newStartIndex !== this.startIndex || newEndIndex !== this.endIndex) {
this.startIndex = newStartIndex
this.endIndex = newEndIndex
}
}
build() {
Stack() {
// 虚拟容器
Column()
.width('100%')
.height(this.data.length * this.itemHeight)
// 可见项列表
List({ scroller: this.scroller }) {
ForEach(this.data.slice(this.startIndex, this.endIndex + 1), (item: any, index: number) => {
ListItem() {
Column() {
this.itemBuilder(item, this.startIndex + index)
}
.width('100%')
.height(this.itemHeight)
}
.margin({ top: index === 0 ? this.startIndex * this.itemHeight : 0 })
})
}
.width('100%')
.height(this.containerHeight)
.onScroll((scrollOffset: number) => {
this.scrollOffset = scrollOffset
this.updateVisibleRange()
})
}
.width('100%')
.height(this.containerHeight)
}
}
// 内存优化的数据管理器
export class DataManager {
private static instance: DataManager
private cache: Map<string, any> = new Map()
private cacheSize: number = 0
private maxCacheSize: number = 50 * 1024 * 1024 // 50MB
public static getInstance(): DataManager {
if (!DataManager.instance) {
DataManager.instance = new DataManager()
}
return DataManager.instance
}
// 缓存数据
public cacheData(key: string, data: any): void {
const dataSize = this.calculateDataSize(data)
// 检查缓存大小限制
if (this.cacheSize + dataSize > this.maxCacheSize) {
this.evictLRUCache(dataSize)
}
this.cache.set(key, {
data: data,
timestamp: Date.now(),
size: dataSize
})
this.cacheSize += dataSize
}
// 获取缓存数据
public getCachedData(key: string): any {
const cached = this.cache.get(key)
if (cached) {
// 更新访问时间
cached.timestamp = Date.now()
return cached.data
}
return null
}
// LRU缓存淘汰
private evictLRUCache(requiredSize: number): void {
const entries = Array.from(this.cache.entries())
entries.sort((a, b) => a[1].timestamp - b[1].timestamp)
let freedSize = 0
for (const [key, value] of entries) {
this.cache.delete(key)
freedSize += value.size
this.cacheSize -= value.size
if (freedSize >= requiredSize) {
break
}
}
}
// 计算数据大小(简化实现)
private calculateDataSize(data: any): number {
return JSON.stringify(data).length * 2 // Unicode characters
}
// 清理缓存
public clearCache(): void {
this.cache.clear()
this.cacheSize = 0
}
}
// 优化后的大型列表页面
@Entry
@Component
struct OptimizedListPage {
@State dataList: any[] = []
@State isLoading: boolean = false
@State hasMore: boolean = true
@State currentPage: number = 1
private dataManager = DataManager.getInstance()
private performanceMonitor = PerformanceMonitor.getInstance()
private pageSize: number = 20
aboutToAppear() {
this.loadData()
}
// 加载数据
private async loadData(isRefresh: boolean = false) {
if (this.isLoading) return
this.performanceMonitor.startTrace('data_loading')
this.isLoading = true
try {
// 检查缓存
const cacheKey = `page_${this.currentPage}`
let cachedData = this.dataManager.getCachedData(cacheKey)
if (!cachedData) {
// 模拟网络请求
cachedData = await this.fetchDataFromServer(this.currentPage, this.pageSize)
this.dataManager.cacheData(cacheKey, cachedData)
}
if (isRefresh) {
this.dataList = cachedData.list
this.currentPage = 2
} else {
this.dataList = [...this.dataList, ...cachedData.list]
this.currentPage++
}
this.hasMore = cachedData.hasMore
} catch (error) {
console.error('数据加载失败:', error)
} finally {
this.isLoading = false
this.performanceMonitor.finishTrace('data_loading')
}
}
// 模拟服务器数据获取
private async fetchDataFromServer(page: number, size: number): Promise<any> {
return new Promise((resolve) => {
setTimeout(() => {
const start = (page - 1) * size
const list = Array.from({ length: size }, (_, index) => ({
id: start + index + 1,
title: `优化项目 ${start + index + 1}`,
description: `这是第 ${start + index + 1} 个经过性能优化的项目`,
imageUrl: `https://picsum.photos/200/150?random=${start + index + 1}`,
category: ['前端', '后端', '移动端', '测试'][Math.floor(Math.random() * 4)],
priority: Math.floor(Math.random() * 3) + 1
}))
resolve({
list: list,
hasMore: page < 10, // 模拟最多10页
total: 200
})
}, 300)
})
}
build() {
Column() {
// 顶部标题
Row() {
Text('性能优化列表')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#212121')
Spacer()
Text(`共 ${this.dataList.length} 项`)
.fontSize(14)
.fontColor('#666666')
}
.width('100%')
.padding({ left: 16, right: 16, top: 16, bottom: 8 })
// 优化后的列表
if (this.dataList.length > 0) {
VirtualList({
data: this.dataList,
itemHeight: 100,
visibleCount: Math.floor(600 / 100),
itemBuilder: (item: any, index: number) => {
this.ListItemBuilder(item, index)
}
})
.layoutWeight(1)
} else if (this.isLoading) {
// 加载状态
Column() {
LoadingProgress()
.width(40)
.height(40)
.color('#2196F3')
Text('正在加载数据...')
.fontSize(16)
.fontColor('#666666')
.margin({ top: 16 })
}
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
}
// 加载更多
if (this.hasMore && this.dataList.length > 0) {
Row() {
if (this.isLoading) {
LoadingProgress()
.width(20)
.height(20)
.color('#2196F3')
Text('加载中...')
.fontSize(14)
.fontColor('#666666')
.margin({ left: 8 })
} else {
Button('加载更多')
.fontSize(14)
.fontColor('#2196F3')
.backgroundColor(Color.Transparent)
.onClick(() => this.loadData())
}
}
.width('100%')
.height(50)
.justifyContent(FlexAlign.Center)
}
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
// 列表项构建器
@Builder ListItemBuilder(item: any, index: number) {
Row({ space: 12 }) {
// 使用优化后的图片组件
OptimizedImage({
src: item.imageUrl,
width: 60,
height: 60,
borderRadius: 8,
placeholder: $r('app.media.default_image')
})
Column({ space: 4 }) {
Text(item.title)
.fontSize(16)
.fontColor('#212121')
.fontWeight(FontWeight.Medium)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(item.description)
.fontSize(14)
.fontColor('#666666')
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Row({ space: 8 }) {
Text(item.category)
.fontSize(12)
.fontColor('#2196F3')
.backgroundColor('#E3F2FD')
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.borderRadius(4)
Text(`优先级: ${item.priority}`)
.fontSize(12)
.fontColor('#FF9800')
}
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.height(100)
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.backgroundColor(Color.White)
.borderRadius(8)
.margin({ left: 16, right: 16, bottom: 8 })
.shadow({ radius: 2, color: Color.Black, offsetY: 1 })
}
}
// 电量优化工具类
export class BatteryOptimizer {
private static instance: BatteryOptimizer
private backgroundTasks: Set<string> = new Set()
public static getInstance(): BatteryOptimizer {
if (!BatteryOptimizer.instance) {
BatteryOptimizer.instance = new BatteryOptimizer()
}
return BatteryOptimizer.instance
}
// 优化后台任务
public optimizeBackgroundTask(taskName: string, task: () => void) {
if (this.backgroundTasks.has(taskName)) {
return // 避免重复任务
}
this.backgroundTasks.add(taskName)
// 降低任务执行频率
const optimizedTask = this.throttle(task, 1000)
// 在空闲时执行
requestIdleCallback(() => {
optimizedTask()
this.backgroundTasks.delete(taskName)
})
}
// 节流函数
private throttle(func: Function, delay: number): Function {
let lastExec = 0
return function (...args: any[]) {
const elapsed = Date.now() - lastExec
if (elapsed > delay) {
func.apply(this, args)
lastExec = Date.now()
}
}
}
}
通过这些性能优化,我的应用在各项指标上都有了显著提升:
- 启动时间从3.2秒降到1.1秒
- 内存占用减少了40%
- 滑动帧率从平均45fps提升到稳定60fps
- 电量消耗降低了25%
当我看到这些数据时,内心的成就感是巨大的。这不仅仅是技术指标的提升,更代表着无数用户将会拥有更好的使用体验。那种通过自己的努力让产品变得更好的感觉,是开发者独有的小确幸。
最让我印象深刻的是,当我把优化后的应用发给朋友们测试时,他们都说"这个应用变得更流畅了,而且手机也不会像之前那样发热了"。这种来自用户真实反馈的认可,比任何技术奖项都要珍贵。
第七个小确幸:多设备适配的成功
随着鸿蒙生态的不断丰富,我开始尝试让自己的应用支持多设备运行。从手机到平板,从智慧屏到车机,一次开发多端部署的愿景让我充满期待。
当我第一次看到自己的应用在不同尺寸的设备上都能完美运行时,那种"一码多端"的魅力让我深深震撼。这不仅仅是技术实现上的突破,更像是打开了通向未来智能生活的大门。
// 响应式布局工具类
export class ResponsiveLayout {
// 断点定义
public static readonly BREAKPOINTS = {
XS: 320, // 超小屏设备
SM: 600, // 小屏设备
MD: 840, // 中等屏幕设备
LG: 1024, // 大屏设备
XL: 1280 // 超大屏设备
}
// 获取当前设备类型
public static getDeviceType(width: number): string {
if (width < this.BREAKPOINTS.SM) return 'phone'
if (width < this.BREAKPOINTS.MD) return 'foldable'
if (width < this.BREAKPOINTS.LG) return 'tablet'
return 'desktop'
}
// 获取列数
public static getColumns(width: number): number {
if (width < this.BREAKPOINTS.SM) return 1
if (width < this.BREAKPOINTS.MD) return 2
if (width < this.BREAKPOINTS.LG) return 3
return 4
}
// 获取间距
public static getSpacing(width: number): number {
if (width < this.BREAKPOINTS.SM) return 12
if (width < this.BREAKPOINTS.MD) return 16
if (width < this.BREAKPOINTS.LG) return 20
return 24
}
}
// 多设备适配的购物应用
@Entry
@Component
struct ShoppingApp {
@State windowWidth: number = 0
@State windowHeight: number = 0
@State deviceType: string = 'phone'
@State orientation: string = 'portrait'
@State products: Product[] = []
@State selectedCategory: string = 'all'
@State cartItems: CartItem[] = []
@State showSidebar: boolean = false
aboutToAppear() {
this.updateWindowInfo()
this.loadProducts()
// 监听窗口变化
getContext().eventHub.on('windowResize', () => {
this.updateWindowInfo()
})
}
private updateWindowInfo() {
// 获取窗口信息(简化实现)
this.windowWidth = 375 // 实际应通过API获取
this.windowHeight = 812
this.deviceType = ResponsiveLayout.getDeviceType(this.windowWidth)
this.orientation = this.windowWidth > this.windowHeight ? 'landscape' : 'portrait'
}
private loadProducts() {
// 加载商品数据
this.products = [
{
id: 1,
name: 'HarmonyOS智能手机',
price: 3999,
originalPrice: 4299,
image: 'https://example.com/phone.jpg',
category: 'electronics',
rating: 4.8,
sales: 1200,
description: '搭载最新HarmonyOS系统,极致流畅体验'
},
{
id: 2,
name: '无线蓝牙耳机',
price: 299,
originalPrice: 399,
image: 'https://example.com/earphone.jpg',
category: 'accessories',
rating: 4.6,
sales: 856,
description: '降噪技术,音质出众'
},
// ... 更多商品
]
}
build() {
Stack() {
if (this.deviceType === 'phone') {
this.PhoneLayout()
} else if (this.deviceType === 'tablet') {
this.TabletLayout()
} else {
this.DesktopLayout()
}
// 购物车侧边栏
if (this.showSidebar) {
this.CartSidebar()
}
}
.width('100%')
.height('100%')
}
// 手机布局
@Builder PhoneLayout() {
Column() {
this.PhoneHeader()
this.CategoryTabs()
this.ProductGrid()
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
// 平板布局
@Builder TabletLayout() {
Row() {
// 左侧分类导航
if (this.orientation === 'landscape') {
this.CategorySidebar()
}
// 主内容区域
Column() {
this.TabletHeader()
if (this.orientation === 'portrait') {
this.CategoryTabs()
}
this.ProductGrid()
}
.layoutWeight(1)
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
// 桌面布局
@Builder DesktopLayout() {
Row() {
// 左侧导航
this.DesktopSidebar()
// 主内容区域
Column() {
this.DesktopHeader()
this.ProductGrid()
}
.layoutWeight(1)
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
// 手机头部
@Builder PhoneHeader() {
Row() {
Text('购物商城')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#212121')
Spacer()
Button() {
Image($r('app.media.ic_search'))
.width(24)
.height(24)
.fillColor('#666666')
}
.backgroundColor(Color.Transparent)
.onClick(() => {
// 搜索功能
})
Button() {
Stack() {
Image($r('app.media.ic_cart'))
.width(24)
.height(24)
.fillColor('#666666')
if (this.cartItems.length > 0) {
Text(this.cartItems.length.toString())
.fontSize(12)
.fontColor(Color.White)
.backgroundColor('#F44336')
.borderRadius(8)
.padding(2)
.position({ x: 16, y: -4 })
}
}
}
.backgroundColor(Color.Transparent)
.onClick(() => {
this.showSidebar = true
})
}
.width('100%')
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
.backgroundColor(Color.White)
.shadow({ radius: 2, color: Color.Black, offsetY: 1 })
}
// 平板头部
@Builder TabletHeader() {
Row() {
Text('购物商城')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#212121')
Spacer()
// 搜索栏
TextInput({ placeholder: '搜索商品...' })
.width(300)
.height(40)
.backgroundColor('#F5F5F5')
.borderRadius(20)
.padding({ left: 16, right: 16 })
Spacer()
Row({ space: 16 }) {
Button('登录')
.fontSize(16)
.fontColor('#2196F3')
.backgroundColor(Color.Transparent)
Button() {
Stack() {
Image($r('app.media.ic_cart'))
.width(28)
.height(28)
.fillColor('#666666')
if (this.cartItems.length > 0) {
Text(this.cartItems.length.toString())
.fontSize(14)
.fontColor(Color.White)
.backgroundColor('#F44336')
.borderRadius(10)
.padding(4)
.position({ x: 20, y: -8 })
}
}
}
.backgroundColor(Color.Transparent)
.onClick(() => {
this.showSidebar = true
})
}
}
.width('100%')
.padding({ left: 24, right: 24, top: 16, bottom: 16 })
.backgroundColor(Color.White)
.shadow({ radius: 2, color: Color.Black, offsetY: 1 })
}
// 桌面头部
@Builder DesktopHeader() {
Row() {
// Logo和标题
Row({ space: 12 }) {
Image($r('app.media.app_logo'))
.width(40)
.height(40)
Text('HarmonyOS购物商城')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#2196F3')
}
Spacer()
// 搜索栏
Row() {
TextInput({ placeholder: '搜索商品、品牌、分类...' })
.width(400)
.height(44)
.backgroundColor('#F8F9FA')
.border({ width: 1, color: '#E0E0E0', radius: 22 })
.padding({ left: 20, right: 20 })
Button('搜索')
.fontSize(16)
.fontColor(Color.White)
.backgroundColor('#2196F3')
.borderRadius(22)
.width(80)
.height(44)
.margin({ left: -80 })
}
Spacer()
// 用户操作区
Row({ space: 24 }) {
Button('登录')
.fontSize(16)
.fontColor('#666666')
.backgroundColor(Color.Transparent)
Button('注册')
.fontSize(16)
.fontColor(Color.White)
.backgroundColor('#2196F3')
.borderRadius(20)
.padding({ left: 20, right: 20 })
Button() {
Row({ space: 8 }) {
Image($r('app.media.ic_cart'))
.width(24)
.height(24)
.fillColor('#666666')
Text(`购物车(${this.cartItems.length})`)
.fontSize(16)
.fontColor('#666666')
}
}
.backgroundColor(Color.Transparent)
.onClick(() => {
this.showSidebar = true
})
}
}
.width('100%')
.padding({ left: 32, right: 32, top: 16, bottom: 16 })
.backgroundColor(Color.White)
.shadow({ radius: 4, color: Color.Black, offsetY: 2 })
}
// 分类标签
@Builder CategoryTabs() {
Scroll() {
Row({ space: ResponsiveLayout.getSpacing(this.windowWidth) }) {
this.CategoryTab('全部', 'all')
this.CategoryTab('数码', 'electronics')
this.CategoryTab('配件', 'accessories')
this.CategoryTab('家居', 'home')
this.CategoryTab('服装', 'clothing')
this.CategoryTab('运动', 'sports')
}
.padding({ left: 16, right: 16 })
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.width('100%')
.height(60)
.backgroundColor(Color.White)
}
// 分类标签项
@Builder CategoryTab(name: string, category: string) {
Button(name)
.fontSize(this.deviceType === 'phone' ? 14 : 16)
.fontColor(this.selectedCategory === category ? Color.White : '#666666')
.backgroundColor(this.selectedCategory === category ? '#2196F3' : Color.Transparent)
.borderRadius(20)
.padding({
left: this.deviceType === 'phone' ? 16 : 20,
right: this.deviceType === 'phone' ? 16 : 20,
top: 8,
bottom: 8
})
.border({
width: 1,
color: this.selectedCategory === category ? '#2196F3' : '#E0E0E0',
radius: 20
})
.onClick(() => {
animateTo({ duration: 200 }, () => {
this.selectedCategory = category
})
})
}
// 商品网格
@Builder ProductGrid() {
List() {
ForEach(this.getFilteredProducts(), (product: Product) => {
ListItem() {
this.ProductCard(product)
}
.width(this.getProductCardWidth())
.margin({
left: ResponsiveLayout.getSpacing(this.windowWidth) / 2,
right: ResponsiveLayout.getSpacing(this.windowWidth) / 2,
bottom: ResponsiveLayout.getSpacing(this.windowWidth)
})
})
}
.lanes(ResponsiveLayout.getColumns(this.windowWidth))
.layoutWeight(1)
.padding({ top: 16 })
}
// 商品卡片
@Builder ProductCard(product: Product) {
Column({ space: 8 }) {
// 商品图片
Stack() {
OptimizedImage({
src: product.image,
width: '100%',
height: this.getProductImageHeight(),
borderRadius: 12,
placeholder: $r('app.media.product_placeholder')
})
// 折扣标签
if (product.originalPrice > product.price) {
Text(`省${product.originalPrice - product.price}元`)
.fontSize(12)
.fontColor(Color.White)
.backgroundColor('#F44336')
.borderRadius(8)
.padding({ left: 8, right: 8, top: 4, bottom: 4 })
.position({ x: 8, y: 8 })
}
}
// 商品信息
Column({ space: 4 }) {
Text(product.name)
.fontSize(this.deviceType === 'phone' ? 14 : 16)
.fontColor('#212121')
.fontWeight(FontWeight.Medium)
.maxLines(2)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Text(product.description)
.fontSize(this.deviceType === 'phone' ? 12 : 14)
.fontColor('#666666')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
// 评分和销量
Row({ space: 8 }) {
Row({ space: 2 }) {
Image($r('app.media.ic_star'))
.width(14)
.height(14)
.fillColor('#FFD700')
Text(product.rating.toString())
.fontSize(12)
.fontColor('#666666')
}
Text(`销量 ${product.sales}`)
.fontSize(12)
.fontColor('#666666')
}
// 价格
Row() {
Text(`¥${product.price}`)
.fontSize(this.deviceType === 'phone' ? 18 : 20)
.fontColor('#F44336')
.fontWeight(FontWeight.Bold)
if (product.originalPrice > product.price) {
Text(`¥${product.originalPrice}`)
.fontSize(12)
.fontColor('#999999')
.decoration({ type: TextDecorationType.LineThrough })
.margin({ left: 8 })
}
Spacer()
// 添加到购物车按钮
Button() {
Image($r('app.media.ic_add_cart'))
.width(20)
.height(20)
.fillColor(Color.White)
}
.width(36)
.height(36)
.backgroundColor('#2196F3')
.borderRadius(18)
.onClick(() => {
this.addToCart(product)
})
}
.width('100%')
.alignItems(VerticalAlign.Bottom)
}
.alignItems(HorizontalAlign.Start)
.padding({ left: 12, right: 12, bottom: 12 })
}
.width('100%')
.backgroundColor(Color.White)
.borderRadius(16)
.shadow({ radius: 8, color: Color.Black, offsetY: 2 })
.onClick(() => {
// 商品详情页
router.pushUrl({
url: 'pages/ProductDetailPage',
params: { productId: product.id }
})
})
}
// 购物车侧边栏
@Builder CartSidebar() {
Stack() {
// 遮罩
Column()
.width('100%')
.height('100%')
.backgroundColor(Color.Black)
.opacity(0.5)
.onClick(() => {
this.showSidebar = false
})
// 侧边栏内容
Column() {
// 头部
Row() {
Text('购物车')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#212121')
Spacer()
Button() {
Image($r('app.media.ic_close'))
.width(24)
.height(24)
.fillColor('#666666')
}
.backgroundColor(Color.Transparent)
.onClick(() => {
this.showSidebar = false
})
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
// 购物车商品列表
if (this.cartItems.length > 0) {
List() {
ForEach(this.cartItems, (item: CartItem) => {
ListItem() {
this.CartItemComponent(item)
}
})
}
.layoutWeight(1)
.backgroundColor('#F5F5F5')
// 结算区域
this.CheckoutSection()
} else {
// 空购物车
Column({ space: 16 }) {
Image($r('app.media.empty_cart'))
.width(120)
.height(120)
.opacity(0.5)
Text('购物车是空的')
.fontSize(16)
.fontColor('#999999')
Button('去购物')
.fontSize(16)
.fontColor(Color.White)
.backgroundColor('#2196F3')
.borderRadius(24)
.padding({ left: 32, right: 32 })
.onClick(() => {
this.showSidebar = false
})
}
.layoutWeight(1)
.justifyContent(FlexAlign.Center)
.backgroundColor('#F5F5F5')
}
}
.width(this.getCartSidebarWidth())
.height('100%')
.backgroundColor(Color.White)
.position({ x: this.windowWidth - this.getCartSidebarWidth(), y: 0 })
.shadow({ radius: 16, color: Color.Black, offsetX: -4 })
.transition({ type: TransitionType.Insert, translate: { x: this.getCartSidebarWidth() } })
}
}
// 辅助方法
private getFilteredProducts(): Product[] {
return this.selectedCategory === 'all'
? this.products
: this.products.filter(p => p.category === this.selectedCategory)
}
private getProductCardWidth(): string {
const columns = ResponsiveLayout.getColumns(this.windowWidth)
const spacing = ResponsiveLayout.getSpacing(this.windowWidth)
const totalSpacing = spacing * (columns + 1)
return `${(100 - totalSpacing) / columns}%`
}
private getProductImageHeight(): number {
switch (this.deviceType) {
case 'phone': return 150
case 'tablet': return 200
default: return 250
}
}
private getCartSidebarWidth(): number {
switch (this.deviceType) {
case 'phone': return this.windowWidth * 0.85
case 'tablet': return 400
default: return 450
}
}
private addToCart(product: Product) {
const existingItem = this.cartItems.find(item => item.productId === product.id)
if (existingItem) {
existingItem.quantity++
} else {
this.cartItems.push({
productId: product.id,
product: product,
quantity: 1
})
}
promptAction.showToast({
message: '已添加到购物车',
duration: 1500
})
}
// 更多组件方法...
}
// 数据接口定义
interface Product {
id: number
name: string
price: number
originalPrice: number
image: string
category: string
rating: number
sales: number
description: string
}
interface CartItem {
productId: number
product: Product
quantity: number
}
当我第一次看到这个购物应用在不同设备上完美运行时,那种震撼是无法言喻的。无论是手机的紧凑布局,还是平板的双栏设计,或者是桌面端的多列展示,每一种布局都恰到好处。
那天,我把这个应用分别在模拟器的不同设备类型上演示给同事看,大家都说:"真的是一套代码就能适配所有设备?这也太神奇了!"其中一个做React开发的同事羡慕地说:"这比我们的响应式开发简单太多了。"
这种技术实现带来的成就感,加上对未来万物互联场景的憧憬,让我对鸿蒙生态充满了信心。这不仅仅是写代码的技能提升,更像是打开了通向未来智能生活的大门。
第八个小确幸:社区互助的温暖
在鸿蒙开发的学习过程中,最让我感到温暖的不是技术突破带来的成就感,而是来自开发者社区的互助与分享。每当我在开发中遇到困难时,总有热心的开发者愿意停下来帮助我;而当我有了一些心得时,也会主动分享给其他需要帮助的朋友。
这种良性的技术氛围,让我深深感受到了开发者社区的温暖。那些深夜里的技术讨论,那些不厌其烦的问题解答,那些毫无保留的经验分享,构成了我鸿蒙开发路上最珍贵的回忆。
那天,我在HarmonyOS开发者论坛上发了一个关于自定义组件性能优化的帖子,详细分享了我在开发过程中积累的经验和踩过的坑。没想到这个帖子收到了很多开发者的点赞和评论,其中一位资深开发者还在我的基础上提出了更好的优化方案。
// 我在论坛分享的高性能自定义组件示例
/**
* 高性能图片展示组件
* 特性:
* 1. 懒加载 - 只有当图片即将进入可视区域时才开始加载
* 2. 缓存策略 - 智能缓存管理,避免内存溢出
* 3. 占位符 - 优雅的加载状态展示
* 4. 错误处理 - 网络异常时的友好提示
* 5. 渐进式加载 - 先显示低质量图片,再加载高清版本
*
* @author 鸿蒙开发者小张
* @date 2025-09-17
* @version 1.0.0
*/
@Component
export struct HighPerformanceImage {
@Prop src: string
@Prop width: number | string = '100%'
@Prop height: number | string = 200
@Prop placeholder?: Resource
@Prop errorImage?: Resource
@Prop borderRadius?: number = 0
@Prop lazy?: boolean = true
@Prop progressive?: boolean = true
@State isLoading: boolean = true
@State hasError: boolean = false
@State isInView: boolean = false
@State lowQualityLoaded: boolean = false
private observer?: IntersectionObserver
private imageCache = ImageCache.getInstance()
aboutToAppear() {
if (this.lazy) {
this.setupIntersectionObserver()
} else {
this.isInView = true
}
}
aboutToDisappear() {
if (this.observer) {
this.observer.disconnect()
}
}
// 设置交叉观察器用于懒加载
private setupIntersectionObserver() {
this.observer = new IntersectionObserver({
root: null,
rootMargin: '50px', // 提前50px开始加载
threshold: 0.1
})
this.observer.observe(getContext().element, (entries) => {
entries.forEach(entry => {
if (entry.isIntersecting && !this.isInView) {
this.isInView = true
this.loadImage()
}
})
})
}
// 加载图片
private async loadImage() {
if (!this.src) return
try {
// 检查缓存
const cachedImage = this.imageCache.get(this.src)
if (cachedImage) {
this.isLoading = false
return
}
// 渐进式加载:先加载低质量版本
if (this.progressive) {
await this.loadLowQualityImage()
}
// 加载原始图片
await this.loadOriginalImage()
// 缓存图片
this.imageCache.set(this.src, true)
} catch (error) {
this.hasError = true
console.error('图片加载失败:', error)
} finally {
this.isLoading = false
}
}
private async loadLowQualityImage() {
return new Promise((resolve, reject) => {
const lowQualitySrc = this.generateLowQualityUrl(this.src)
const img = new Image()
img.onload = () => {
this.lowQualityLoaded = true
resolve(void 0)
}
img.onerror = reject
img.src = lowQualitySrc
})
}
private async loadOriginalImage() {
return new Promise((resolve, reject) => {
const img = new Image()
img.onload = resolve
img.onerror = reject
img.src = this.src
})
}
private generateLowQualityUrl(originalUrl: string): string {
// 生成低质量图片URL的逻辑
// 这里简化实现,实际项目中可以根据图片服务的API来生成
return originalUrl.replace(/\.(jpg|jpeg|png)$/, '_low.$1')
}
build() {
Stack() {
// 占位符或错误图片
if ((this.isLoading && !this.lowQualityLoaded) || this.hasError) {
if (this.hasError && this.errorImage) {
Image(this.errorImage)
.width(this.width)
.height(this.height)
.objectFit(ImageFit.Cover)
.borderRadius(this.borderRadius)
.opacity(0.6)
} else if (this.placeholder) {
Image(this.placeholder)
.width(this.width)
.height(this.height)
.objectFit(ImageFit.Cover)
.borderRadius(this.borderRadius)
.opacity(0.3)
} else {
// 默认骨架屏
Column()
.width(this.width)
.height(this.height)
.backgroundColor('#F0F0F0')
.borderRadius(this.borderRadius)
.animation(this.createShimmerAnimation())
}
}
// 低质量图片(渐进式加载时使用)
if (this.progressive && this.lowQualityLoaded && this.isLoading) {
Image(this.generateLowQualityUrl(this.src))
.width(this.width)
.height(this.height)
.objectFit(ImageFit.Cover)
.borderRadius(this.borderRadius)
.blur(2) // 模糊效果
}
// 原始图片
if (this.isInView && this.src && !this.hasError) {
Image(this.src)
.width(this.width)
.height(this.height)
.objectFit(ImageFit.Cover)
.borderRadius(this.borderRadius)
.opacity(this.isLoading ? 0 : 1)
.onComplete(() => {
animateTo({ duration: 300 }, () => {
this.isLoading = false
})
})
.onError(() => {
this.hasError = true
this.isLoading = false
})
}
// 加载指示器
if (this.isLoading && this.isInView && !this.hasError) {
LoadingProgress()
.width(24)
.height(24)
.color('#2196F3')
}
}
.width(this.width)
.height(this.height)
.borderRadius(this.borderRadius)
}
private createShimmerAnimation() {
return {
duration: 1500,
curve: Curve.Linear,
iterations: -1,
playMode: PlayMode.Normal
}
}
}
// 图片缓存管理类
class ImageCache {
private static instance: ImageCache
private cache: Map<string, boolean> = new Map()
private maxSize: number = 100
static getInstance(): ImageCache {
if (!ImageCache.instance) {
ImageCache.instance = new ImageCache()
}
return ImageCache.instance
}
get(key: string): boolean | undefined {
return this.cache.get(key)
}
set(key: string, value: boolean): void {
if (this.cache.size >= this.maxSize) {
// 移除最旧的缓存项
const firstKey = this.cache.keys().next().value
this.cache.delete(firstKey)
}
this.cache.set(key, value)
}
clear(): void {
this.cache.clear()
}
}
// 使用示例和最佳实践分享
@Entry
@Component
struct ImageGalleryDemo {
@State images: string[] = [
'https://example.com/image1.jpg',
'https://example.com/image2.jpg',
'https://example.com/image3.jpg',
// ... 更多图片
]
build() {
Column({ space: 16 }) {
Text('高性能图片组件示例')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#212121')
.padding({ top: 20 })
Text('以下展示了懒加载、渐进式加载等特性')
.fontSize(16)
.fontColor('#666666')
.padding({ bottom: 20 })
List({ space: 12 }) {
ForEach(this.images, (imageUrl: string, index: number) => {
ListItem() {
HighPerformanceImage({
src: imageUrl,
width: '100%',
height: 200,
borderRadius: 12,
lazy: true,
progressive: true,
placeholder: $r('app.media.image_placeholder'),
errorImage: $r('app.media.image_error')
})
}
})
}
.layoutWeight(1)
.padding({ left: 16, right: 16 })
}
.width('100%')
.height('100%')
.backgroundColor('#F5F5F5')
}
}
这个帖子发布后,我收到了很多开发者的私信和评论。其中一位叫"鸿蒙老司机"的开发者给出了非常详细的反馈,还指出了几个可以进一步优化的地方:
鸿蒙老司机的回复:
"楼主分享的组件很棒!我补充几个优化建议:
- 可以考虑使用WeakMap来优化缓存,避免内存泄漏
- 交叉观察器可以设置不同的threshold来实现更精细的控制
- 错误重试机制会让用户体验更好
我基于你的代码做了一些改进,希望对大家有帮助..."
看到这样的回复,我内心非常感动。这不仅仅是技术的讨论,更是一种经验的传承和知识的共享。那天晚上,我们在评论区讨论了很久,不仅解决了我代码中的几个问题,还学到了很多高级的优化技巧。
更让我感到温暖的是,几天后有一个刚入门的开发者在群里@我,说:"小张哥,我按照你分享的图片组件改进了我的应用,性能提升了很多,用户反馈也更好了!谢谢你的分享!"
那一刻,我深深感受到了分享的意义。技术不应该是孤立的,而应该是流动的、共享的。每一个人的进步,都会推动整个社区的发展。
后来,我开始更加积极地参与社区活动。无论是在线上回答新手的问题,还是参加线下的技术分享会,都让我收获了很多珍贵的友谊和宝贵的经验。
我印象最深的几次社区互助经历:
1. 深夜的技术救援
有一次晚上11点,群里一个做毕业设计的同学求助,说他的鸿蒙应用在真机上总是崩溃,但模拟器上运行正常。我看到消息后,主动帮他远程调试,发现是权限配置的问题。最终花了两个小时帮他解决了问题。虽然很累,但看到他激动地说"终于可以按时交毕业设计了",我的心里也充满了满足感。
2. 集体攻克难题
有一次我在开发一个复杂的动画效果时遇到了性能瓶颈,单独思考了很久都找不到好的解决方案。于是我在社区里发帖求助,结果引来了十几位开发者的讨论。大家各抒己见,从不同角度分析问题,最终通过集体智慧找到了最优解。那种"众人拾柴火焰高"的感觉特别棒。
3. 新手带教的成就感
后来我开始在社区里担任一些新手的"导师",经常会有人私信问我各种基础问题。虽然有时候问题很简单,但我都会耐心解答,并且尽量举例说明。看到他们从完全不懂到能独立完成项目,那种成就感不亚于自己技术突破时的喜悦。
结语:那些代码里的小确幸
回顾这段鸿蒙开发的历程,我发现最珍贵的不是掌握了多少高深的技术,也不是做出了多么复杂的应用,而是在这个过程中体验到的那些小确幸。
每一次代码调通时的惊喜,每一个功能实现时的满足,每一次性能优化后的成就感,每一位用户给出好评时的认可感,每一次在社区里帮助他人或被他人帮助时的温暖感——这些看似微不足道的小瞬间,却构成了我与鸿蒙开发之间最深层的情感连接。
技术在发展,工具在更新,但那种通过自己的努力创造价值、解决问题、帮助他人的快乐是永恒的。这些小确幸不仅让我在遇到困难时有继续前进的动力,也让我意识到开发者这个职业的真正价值所在。
现在,每当有新人问我鸿蒙开发难不难学时,我总会告诉他们:技术的学习确实需要付出努力,但更重要的是要用心去感受这个过程中的每一个小确幸。因为正是这些小确幸,让我们的开发之路充满阳光,让我们在这个快速变化的技术世界里保持初心。
愿每一位踏上鸿蒙开发之路的朋友,都能收获属于自己的小确幸。愿我们在代码的世界里,不仅能创造出优秀的产品,更能感受到技术带给生活的美好和意义。
最后的最后,想对正在阅读这篇文章的你说:
无论你是刚刚开始接触鸿蒙开发的新手,还是已经在这条路上走了一段时间的开发者,都请记住——每一行代码都有它的意义,每一次调试都是一次成长,每一个功能的实现都值得庆祝。不要小看那些看似平凡的开发瞬间,因为它们往往蕴含着最纯真的快乐。
愿你在鸿蒙开发的路上,也能发现属于自己的小确幸,愿这些美好的瞬间成为你前进路上最温暖的陪伴。
本文记录了一名鸿蒙开发者的成长历程和心路感悟。从第一个Hello World到复杂应用开发,从技术突破到社区互助,每一个小确幸都是开发者旅程中最珍贵的回忆。希望这些经历和感悟能够给其他开发者带来启发和鼓励。
作者简介: 一名普通的鸿蒙开发者,热爱技术,喜欢分享,相信代码可以改变世界,更相信开发者的快乐来源于那些藏在代码里的小确幸。
更多推荐
所有评论(0)