温馨提示:本篇博客的详细代码已发布到 git : https://gitcode.com/nutpi/HarmonyosNext 可以下载运行哦!
第五篇:UVList组件最佳实践与实际应用案例
文章目录
1. 最佳实践总结
1.1 组件设计最佳实践
在开发UVList组件的过程中,我们遵循了以下设计原则,这些原则也适用于其他组件的开发:
- 职责单一原则:UVList和UVListItem各自负责不同的功能,保持了代码的清晰和可维护性
- 接口优先设计:先定义清晰的接口(ListProps和ListItemProps),再基于接口实现功能
- 组合优于继承:通过组合不同的组件实现复杂功能,而非通过继承
- 默认值设计:为属性提供合理的默认值,减少使用时的配置负担
- 条件渲染:根据条件渲染不同的内容,提高组件的灵活性和性能
1.2 代码组织最佳实践
// 接口定义(interfaces.ets)
export interface ListItemProps { /* ... */ }
export interface ListProps { /* ... */ }
// 列表容器组件(UVList.ets)
@Component
struct UVList {
private props: ListProps = {} as ListProps;
build() { /* ... */ }
}
// 列表项组件(UVListItem.ets)
@Component
struct UVListItem {
private item: ListItemProps = {} as ListItemProps;
private showIcon: boolean = true;
// 其他属性...
build() { /* ... */ }
}
代码组织建议:
- 分离接口定义:将接口定义放在单独的文件中,便于复用和维护
- 组件职责明确:每个组件只负责单一的功能,避免过于复杂的组件
- 合理的文件结构:相关的文件放在一起,便于查找和维护
2. 实际应用案例
2.1 电商应用商品列表
// 商品列表项接口
interface ProductItemProps extends ListItemProps {
price: string;
originalPrice?: string;
sales?: string;
rating?: number;
}
@Component
struct ProductList {
@State products: Array<ProductItemProps> = [];
@Builder
private renderProductItem(item: ProductItemProps) {
Row() {
// 商品图片
Image(item.icon)
.width(80)
.height(80)
.objectFit(ImageFit.Cover)
.borderRadius(4)
.margin({ right: 12 })
// 商品信息
Column() {
// 商品标题
Text(item.title)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.width('100%')
.textAlign(TextAlign.Start)
// 商品描述
if (item.note) {
Text(item.note)
.fontSize(14)
.fontColor('#999')
.width('100%')
.textAlign(TextAlign.Start)
.margin({ top: 4 })
}
// 价格和销量
Row() {
// 价格
Text(`¥${item.price}`)
.fontSize(16)
.fontColor('#ff6b00')
.fontWeight(FontWeight.Bold)
// 原价
if (item.originalPrice) {
Text(`¥${item.originalPrice}`)
.fontSize(12)
.fontColor('#999')
.decoration({ type: TextDecorationType.LineThrough })
.margin({ left: 4 })
}
// 销量
if (item.sales) {
Text(`已售${item.sales}`)
.fontSize(12)
.fontColor('#999')
.margin({ left: 8 })
}
}
.width('100%')
.margin({ top: 4 })
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
}
.width('100%')
.padding(12)
.backgroundColor(Color.White)
.borderRadius(8)
.onClick(() => {
if (item.onClick) {
item.onClick();
}
})
}
build() {
List() {
ForEach(this.products, (item: ProductItemProps) => {
ListItem() {
this.renderProductItem(item)
}
.padding({ left: 16, right: 16, top: 4, bottom: 4 })
})
}
.width('100%')
.divider({ strokeWidth: 1, color: '#f5f5f5', startMargin: 16, endMargin: 16 })
}
}
2.2 社交应用消息列表
// 消息列表项接口
interface MessageItemProps extends ListItemProps {
avatar?: string | Resource;
time?: string;
unread?: number;
}
@Component
struct MessageList {
@State messages: Array<MessageItemProps> = [];
@Builder
private renderMessageItem(item: MessageItemProps) {
Row() {
// 头像
if (item.avatar) {
Image(item.avatar)
.width(48)
.height(48)
.borderRadius(24)
.margin({ right: 12 })
}
// 消息内容
Column() {
// 标题和时间
Row() {
Text(item.title)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.layoutWeight(1)
if (item.time) {
Text(item.time)
.fontSize(12)
.fontColor('#999')
}
}
.width('100%')
// 消息内容和未读数
Row() {
Text(item.note || '')
.fontSize(14)
.fontColor('#999')
.layoutWeight(1)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
if (item.unread && item.unread > 0) {
Text(`${item.unread > 99 ? '99+' : item.unread}`)
.fontSize(12)
.fontColor(Color.White)
.backgroundColor('#ff5252')
.borderRadius(10)
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
}
}
.width('100%')
.margin({ top: 4 })
}
.layoutWeight(1)
}
.width('100%')
.padding(12)
.backgroundColor(Color.White)
.onClick(() => {
if (item.onClick) {
item.onClick();
}
})
}
build() {
List() {
ForEach(this.messages, (item: MessageItemProps) => {
ListItem() {
this.renderMessageItem(item)
}
})
}
.width('100%')
.divider({ strokeWidth: 1, color: '#f5f5f5' })
}
}
3. 性能优化实践
3.1 大数据量列表优化
当列表数据量较大时,可以采用以下优化策略:
// 数据源类定义
class ListDataSource implements IDataSource {
private dataArray: Array<ListItemProps> = [];
private listener: DataChangeListener = null;
constructor(data: Array<ListItemProps>) {
this.dataArray = data;
}
totalCount(): number {
return this.dataArray.length;
}
getData(index: number): ListItemProps {
return this.dataArray[index];
}
registerDataChangeListener(listener: DataChangeListener): void {
this.listener = listener;
}
unregisterDataChangeListener(): void {
this.listener = null;
}
// 更新数据
updateData(data: Array<ListItemProps>): void {
this.dataArray = data;
if (this.listener) {
this.listener.onDataReloaded();
}
}
}
// 使用LazyForEach实现高性能列表
@Component
struct OptimizedList {
@State private dataSource: ListDataSource = new ListDataSource([]);
aboutToAppear() {
// 初始化数据
this.loadData();
}
private loadData() {
// 模拟加载大量数据
const data: Array<ListItemProps> = [];
for (let i = 0; i < 1000; i++) {
data.push({
title: `列表项 ${i}`,
note: `这是第 ${i} 个列表项的描述`,
onClick: () => console.info(`点击了第 ${i} 项`)
});
}
this.dataSource = new ListDataSource(data);
}
build() {
List() {
LazyForEach(this.dataSource, (item: ListItemProps, index: number) => {
ListItem() {
UVListItem({
item: item,
showIcon: false
})
}
.padding({ left: 16, right: 16, top: 4, bottom: 4 })
}, item => item.title) // 使用标题作为唯一键
}
.width('100%')
.divider({ strokeWidth: 1, color: '#f5f5f5', startMargin: 16, endMargin: 16 })
}
}
3.2 列表刷新与加载更多
实现下拉刷新和上拉加载更多功能:
@Component
struct RefreshableList {
@State listItems: Array<ListItemProps> = [];
@State refreshing: boolean = false;
@State loading: boolean = false;
@State page: number = 1;
@State hasMore: boolean = true;
aboutToAppear() {
this.loadData();
}
private async loadData(refresh: boolean = false) {
if (refresh) {
this.refreshing = true;
this.page = 1;
} else {
this.loading = true;
}
try {
// 模拟网络请求
await new Promise(resolve => setTimeout(resolve, 1000));
const newData = [];
for (let i = 0; i < 10; i++) {
const index = (this.page - 1) * 10 + i;
newData.push({
title: `列表项 ${index}`,
note: `这是第 ${index} 个列表项的描述`
});
}
if (refresh) {
this.listItems = newData;
} else {
this.listItems = [...this.listItems, ...newData];
}
this.hasMore = this.page < 5; // 模拟只有5页数据
this.page++;
} finally {
this.refreshing = false;
this.loading = false;
}
}
build() {
Column() {
// 使用Refresh组件实现下拉刷新
Refresh({
refreshing: this.refreshing,
onRefresh: () => {
this.loadData(true);
}
}) {
// 列表内容
List() {
ForEach(this.listItems, (item: ListItemProps) => {
ListItem() {
UVListItem({
item: item
})
}
})
// 加载更多
if (this.hasMore) {
ListItem() {
Row() {
if (this.loading) {
LoadingProgress()
.width(24)
.height(24)
.margin({ right: 8 })
}
Text(this.loading ? '加载中...' : '点击加载更多')
.fontSize(14)
.fontColor('#999')
}
.width('100%')
.justifyContent(FlexAlign.Center)
.padding(16)
.onClick(() => {
if (!this.loading) {
this.loadData();
}
})
}
} else {
ListItem() {
Text('没有更多数据了')
.fontSize(14)
.fontColor('#999')
.width('100%')
.textAlign(TextAlign.Center)
.padding(16)
}
}
}
.width('100%')
.divider({ strokeWidth: 1, color: '#f5f5f5', startMargin: 16, endMargin: 16 })
}
}
}
}
3.3 列表动画优化
为列表项添加动画效果,提升用户体验:
@Component
struct AnimatedListItem {
private item: ListItemProps;
@State private opacity: number = 0;
@State private translateY: number = 50;
aboutToAppear() {
// 延迟执行动画,错开多个列表项的动画时间
setTimeout(() => {
animateTo({
duration: 300,
curve: Curve.EaseOut,
}, () => {
this.opacity = 1;
this.translateY = 0;
});
}, 50);
}
build() {
UVListItem({
item: this.item
})
.opacity(this.opacity)
.translate({ y: this.translateY })
}
}
// 在列表中使用动画列表项
ForEach(this.listItems, (item: ListItemProps) => {
ListItem() {
AnimatedListItem({ item: item })
}
})
4. 扩展与定制
4.1 主题定制
支持主题切换的UVList组件:
// 主题接口
interface ListTheme {
backgroundColor: ResourceColor;
textColor: ResourceColor;
secondaryTextColor: ResourceColor;
dividerColor: ResourceColor;
iconBackgroundColor: ResourceColor;
disabledBackgroundColor: ResourceColor;
disabledTextColor: ResourceColor;
}
// 预定义主题
const LightTheme: ListTheme = {
backgroundColor: Color.White,
textColor: '#333',
secondaryTextColor: '#999',
dividerColor: '#f5f5f5',
iconBackgroundColor: '#f5f5f5',
disabledBackgroundColor: '#fafafa',
disabledTextColor: '#bdbdbd'
};
const DarkTheme: ListTheme = {
backgroundColor: '#1e1e1e',
textColor: '#e0e0e0',
secondaryTextColor: '#a0a0a0',
dividerColor: '#333333',
iconBackgroundColor: '#333333',
disabledBackgroundColor: '#2d2d2d',
disabledTextColor: '#666666'
};
// 支持主题的列表项组件
@Component
struct ThemedListItem {
private item: ListItemProps;
private theme: ListTheme = LightTheme;
build() {
Row() {
// 左侧图标区域
if (this.item.icon) {
Row() {
if (typeof this.item.icon === 'string') {
Text(this.item.icon)
.fontSize(16)
.fontColor(this.theme.textColor)
} else {
Image(this.item.icon)
.width(24)
.height(24)
.objectFit(ImageFit.Contain)
}
}
.width(40)
.height(40)
.justifyContent(FlexAlign.Center)
.alignItems(VerticalAlign.Center)
.margin({ right: 12 })
.borderRadius(4)
.backgroundColor(this.theme.iconBackgroundColor)
}
// 中间内容区域
Column() {
Text(this.item.title)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.fontColor(this.item.disabled ? this.theme.disabledTextColor : this.theme.textColor)
.width('100%')
.textAlign(TextAlign.Start)
if (this.item.note) {
Text(this.item.note)
.fontSize(14)
.fontColor(this.item.disabled ? this.theme.disabledTextColor : this.theme.secondaryTextColor)
.width('100%')
.textAlign(TextAlign.Start)
.margin({ top: 4 })
}
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
// 右侧区域
Row() {
if (this.item.rightText) {
Text(this.item.rightText)
.fontSize(14)
.fontColor(this.item.disabled ? this.theme.disabledTextColor : this.theme.secondaryTextColor)
.margin({ right: 4 })
}
if (this.item.showArrow !== false) {
Image($r('app.media.ic_arrow_right'))
.width(16)
.height(16)
.objectFit(ImageFit.Contain)
.opacity(this.item.disabled ? 0.3 : 1)
}
}
.alignItems(VerticalAlign.Center)
}
.width('100%')
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
.backgroundColor(this.item.disabled ? this.theme.disabledBackgroundColor : this.theme.backgroundColor)
.borderRadius(8)
.onClick(() => {
if (!this.item.disabled && this.item.onClick) {
this.item.onClick();
}
})
}
}
4.2 自定义列表项类型
扩展列表项类型,支持更多场景:
// 开关列表项接口
interface SwitchListItemProps extends ListItemProps {
checked: boolean;
onCheckedChange: (checked: boolean) => void;
}
// 开关列表项组件
@Component
struct SwitchListItem {
private item: SwitchListItemProps;
build() {
Row() {
// 左侧图标
if (this.item.icon) {
// 图标渲染代码...
}
// 中间内容
Column() {
Text(this.item.title)
.fontSize(16)
.fontWeight(FontWeight.Medium)
.width('100%')
.textAlign(TextAlign.Start)
if (this.item.note) {
Text(this.item.note)
.fontSize(14)
.fontColor('#999')
.width('100%')
.textAlign(TextAlign.Start)
.margin({ top: 4 })
}
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
// 右侧开关
Toggle({ type: ToggleType.Switch, isOn: this.item.checked })
.onChange((isOn: boolean) => {
if (!this.item.disabled && this.item.onCheckedChange) {
this.item.onCheckedChange(isOn);
}
})
.enabled(!this.item.disabled)
}
.width('100%')
.padding({ left: 16, right: 16, top: 12, bottom: 12 })
.backgroundColor(this.item.disabled ? '#fafafa' : Color.White)
.borderRadius(8)
}
}
5. 总结
通过本系列教程,我们深入讲解了UVList组件的设计思路、接口定义、实现细节、使用方法和性能优化。UVList组件作为一个功能强大的列表组件,可以满足大多数应用场景的需求,并且具有高度的可定制性和扩展性。
在实际开发中,我们可以根据具体需求对UVList组件进行定制和扩展,以满足不同应用场景的需求。同时,我们也可以借鉴UVList组件的设计思路和实现方式,开发其他高质量的组件,提升应用的用户体验和开发效率。
希望本系列教程能够帮助你更好地理解和使用UVList组件,构建出更优秀的HarmonyOS NEXT应用!