一、照片相册的基础网格布局
① 数据模型定义
-
相册数据模型:
interface Album {
id: number, // 相册唯一标识
name: string, // 相册名称
count: number, // 相册中的照片数量
cover: Resource, // 相册封面图片
date: string. // 相册创建或更新日期
} -
照片数据模型:
interface Recentphoto {
id: number, // 照片唯一标识
image: Resource, // 照片资源
date: string, // 照片拍摄日期时间
location?: string. // 照片拍摄地点
}
② 页面布局
-
标签切换,使用两个 Text 组件实现,通过 currentTab 状态变量控制当前选中的标签样式:
// 标签切换
Row() {
Text('相册')
.fontSize(16)
.fontWeight(this.currentTab === 0 ? FontWeight.Bold : FontWeight.Normal)
.fontColor(this.currentTab === 0 ? '#007AFF' : '#8E8E93')
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.borderRadius(16)
.backgroundColor(this.currentTab === 0 ? 'rgba(0, 122, 255, 0.1)' : 'transparent')
.onClick(() => {
this.currentTab = 0
})Text('最近项目') .fontSize(16) .fontWeight(this.currentTab === 1 ? FontWeight.Bold : FontWeight.Normal) .fontColor(this.currentTab === 1 ? '#007AFF' : '#8E8E93') .padding({ left: 16, right: 16, top: 8, bottom: 8 }) .borderRadius(16) .backgroundColor(this.currentTab === 1 ? 'rgba(0, 122, 255, 0.1)' : 'transparent') .margin({ left: 12 }) .onClick(() => { this.currentTab = 1 })}
.width('100%')
.padding({ left: 20, right: 20, top: 8, bottom: 8 })
.backgroundColor('#FFFFFF') -
相册视图(2 列布局),使用 Grid 组件实现 2 列布局,每个 GridItem 包含相册封面和相册信息:
Column() {
Text('我的相册')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#000000')
.alignSelf(ItemAlign.Start)
.margin({ bottom: 16 })Grid() { ForEach(this.albums, (album:Album) => { GridItem() { Column() { // 相册封面 Image(album.cover) .width('100%') .height(140) .objectFit(ImageFit.Cover) .borderRadius(12) // 相册信息 Column() { Text(album.name) .fontSize(16) .fontWeight(FontWeight.Medium) .fontColor('#000000') .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) Row() { Text(`${album.count}张`) .fontSize(14) .fontColor('#8E8E93') Blank() Text(album.date) .fontSize(12) .fontColor('#8E8E93') } .width('100%') .margin({ top: 4 }) } .alignItems(HorizontalAlign.Start) .width('100%') .margin({ top: 12 }) } .width('100%') .padding(16) .backgroundColor('#FFFFFF') .borderRadius(16) .shadow({ radius: 8, color: 'rgba(0, 0, 0, 0.08)', offsetX: 0, offsetY: 2 }) } .onClick(() => { console.log(`打开相册: ${album.name}`) }) }) } .columnsTemplate('1fr 1fr') // 2列布局 .columnsGap(16) .rowsGap(16) .width('100%') .layoutWeight(1)}
.width('100%')
.layoutWeight(1)
.padding({ left: 20, right: 20, top: 16, bottom: 20 })
.backgroundColor('#F2F2F7') -
最近项目视图(3 列布局),使用 Grid 组件实现 3 列布局,每个 GridItem 包含照片和可选的位置信息覆盖层:
Column() {
Row() {
Text('最近添加')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#000000')Blank() Text('选择') .fontSize(16) .fontColor('#007AFF') } .width('100%') .margin({ bottom: 16 }) Grid() { ForEach(this.recentPhotos, (photo:Recentphoto) => { GridItem() { Stack({ alignContent: Alignment.BottomStart }) { Image(photo.image) .width('100%') .height(120) .objectFit(ImageFit.Cover) .borderRadius(8) // 位置信息覆盖层 if (photo.location) { Row() { Image($r('app.media.location_icon')) .width(12) .height(12) .fillColor('#FFFFFF') Text(photo.location) .fontSize(10) .fontColor('#FFFFFF') .margin({ left: 4 }) } .padding({ left: 6, right: 6, top: 4, bottom: 4 }) .backgroundColor('rgba(0, 0, 0, 0.6)') .borderRadius(8) .margin({ left: 8, bottom: 8 }) } } .width('100%') .height(120) } .onClick(() => { console.log(`查看照片: ${photo.id}`) }) }) } .columnsTemplate('1fr 1fr 1fr') // 3列布局 .columnsGap(4) .rowsGap(4) .width('100%') .layoutWeight(1)}
.width('100%')
.layoutWeight(1)
.padding({ left: 20, right: 20, top: 16, bottom: 20 })
.backgroundColor('#F2F2F7')
二、状态管理与交互
-
使用 @State 装饰器定义了几个关键的状态变量:
@State currentTab: number = 0; // 当前选中的标签页(0: 相册, 1: 最近项目)
@State albums: Album[] = []; // 相册数据
@State recentPhotos: Recentphoto[] = []; // 最近照片数据 -
标签页切换是照片相册应用中的核心交互之一,可以通过以下方式实现:
Text('相册')
.fontSize(16)
.fontWeight(this.currentTab === 0 ? FontWeight.Bold : FontWeight.Normal)
.fontColor(this.currentTab === 0 ? '#007AFF' : '#8E8E93')
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.borderRadius(16)
.backgroundColor(this.currentTab === 0 ? 'rgba(0, 122, 255, 0.1)' : 'transparent')
.onClick(() => {
this.currentTab = 0
}) -
根据当前选中的标签页,使用条件渲染显示不同的内容:
if (this.currentTab === 0) {
// 相册视图
Column() {
// 相册内容...
}
} else {
// 最近项目视图
Column() {
// 最近项目内容...
}
}
三、Grid 组件进阶布局
① 不同列数的网格布局
-
为不同的内容区域设置了不同的列数:
// 相册视图 - 2列布局
Grid() {
// GridItem 内容...
}
.columnsTemplate('1fr 1fr') // 2列等宽布局
.columnsGap(16)
.rowsGap(16)// 最近项目视图 - 3列布局
Grid() {
// GridItem 内容...
}
.columnsTemplate('1fr 1fr 1fr') // 3列等宽布局
.columnsGap(4)
.rowsGap(4) -
不同列数的设计考虑以下因素:
-
- 相册视图:每个相册包含的信息较多(封面、名称、照片数量、日期),需要更大的显示空间,因此采用 2 列布局;
-
- 最近项目视图:照片本身是主要内容,信息较少,可以采用 3 列布局,在同样的空间内展示更多照片。
② 自适应高度的 GridItem
-
不为 GridItem 设置固定高度,而是让其根据内容自适应:
GridItem() {
Column() {
// 相册封面 - 固定高度
Image(album.cover)
.width('100%')
.height(140)
.objectFit(ImageFit.Cover)
.borderRadius(12)// 相册信息 - 自适应高度 Column() { Text(album.name) .fontSize(16) .fontWeight(FontWeight.Medium) .fontColor('#000000') .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) Row() { Text(`${album.count}张`) .fontSize(14) .fontColor('#8E8E93') Blank() Text(album.date) .fontSize(12) .fontColor('#8E8E93') } .width('100%') .margin({ top: 4 }) } .alignItems(HorizontalAlign.Start) .width('100%') .margin({ top: 12 }) } .width('100%') .padding(16)}
-
这样设计的优势在于:
-
- 适应不同内容长度:相册名称可能有长有短,自适应高度可以确保所有内容都能完整显示;
-
- 布局灵活性:不同 GridItem 可以有不同的高度,更符合实际内容的需求;
-
- 维护简便:后续如果需要在 GridItem 中添加新的内容,不需要重新计算和调整高度。
③ 固定高度的 GridItem
-
设置固定高度:
GridItem() {
Stack({ alignContent: Alignment.BottomStart }) {
Image(photo.image)
.width('100%')
.height(120)
.objectFit(ImageFit.Cover)
.borderRadius(8)// 位置信息覆盖层 if (photo.location) { // 位置信息内容... } } .width('100%') .height(120)}
-
固定高度的设计适用于以下场景:
-
- 内容统一:所有照片都使用相同的显示尺寸,视觉上更加整齐;
-
- 性能优化:固定高度可以减少布局计算,提高渲染性能;
-
- 网格美观:确保所有照片在网格中排列整齐,不会因为内容不同而导致高度不一。
四、组件复用与封装
① 可复用的 UI 组件
-
提取相册卡片组件:
@Builder
function AlbumCard(album: Album) {
Column() {
// 相册封面
Image(album.cover)
.width('100%')
.height(140)
.objectFit(ImageFit.Cover)
.borderRadius(12)// 相册信息 Column() { Text(album.name) .fontSize(16) .fontWeight(FontWeight.Medium) .fontColor('#000000') .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) Row() { Text(`${album.count}张`) .fontSize(14) .fontColor('#8E8E93') Blank() Text(album.date) .fontSize(12) .fontColor('#8E8E93') } .width('100%') .margin({ top: 4 }) } .alignItems(HorizontalAlign.Start) .width('100%') .margin({ top: 12 }) } .width('100%') .padding(16) .backgroundColor('#FFFFFF') .borderRadius(16) .shadow({ radius: 8, color: 'rgba(0, 0, 0, 0.08)', offsetX: 0, offsetY: 2 })}
-
使用提取的组件:
Grid() {
ForEach(this.albums, (album:Album) => {
GridItem() {
AlbumCard(album)
}
.onClick(() => {
console.log(打开相册: ${album.name})
})
})
}
② 组件封装
-
可以封装优化一下交互逻辑,使代码更加清晰:
// 封装标签切换逻辑
@Builder
function TabItem(text: string, index: number, currentIndex: number, onTabClick: () => void) {
Text(text)
.fontSize(16)
.fontWeight(currentIndex === index ? FontWeight.Bold : FontWeight.Normal)
.fontColor(currentIndex === index ? '#007AFF' : '#8E8E93')
.padding({ left: 16, right: 16, top: 8, bottom: 8 })
.borderRadius(16)
.backgroundColor(currentIndex === index ? 'rgba(0, 122, 255, 0.1)' : 'transparent')
.onClick(onTabClick)
}// 使用封装的标签组件
Row() {
TabItem('相册', 0, this.currentTab, () => { this.currentTab = 0 })
TabItem('最近项目', 1, this.currentTab, () => { this.currentTab = 1 })
.margin({ left: 12 })
}
五、拓展功能实现
-
照片位置信息显示:
if (photo.location) {
Row() {
Image($r('app.media.location_icon'))
.width(12)
.height(12)
.fillColor('#FFFFFF')Text(photo.location) .fontSize(10) .fontColor('#FFFFFF') .margin({ left: 4 }) } .padding({ left: 6, right: 6, top: 4, bottom: 4 }) .backgroundColor('rgba(0, 0, 0, 0.6)') .borderRadius(8) .margin({ left: 8, bottom: 8 })}
-
为相册和照片添加了点击事件处理:
// 相册点击事件
GridItem() {
AlbumCard(album)
}
.onClick(() => {
console.log(打开相册: ${album.name})
})// 照片点击事件
GridItem() {
// 照片内容...
}
.onClick(() => {
console.log(查看照片: ${photo.id})
}) -
点击事件可以用于以下功能:
-
- 打开相册详情:点击相册卡片,导航到相册详情页面,显示该相册中的所有照片;
-
- 查看照片大图:点击照片,打开照片查看器,支持放大、缩小、滑动等操作;
-
- 编辑照片信息:长按照片,弹出编辑菜单,支持修改照片信息、删除照片等操作。
六、Grid 组件高级应用
① Grid 组件高级定位应用
-
网格项定位与跨行跨列:使用 Grid 组件的高级定位特性,实现更复杂的布局效果:
// 使用 rowStart、rowEnd、columnStart、columnEnd 实现跨行跨列
Grid() {
// 标题行 - 跨越所有列
GridItem() {
Text('今日精选')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#000000')
}
.rowStart(0)
.rowEnd(1)
.columnStart(0)
.columnEnd(3) // 跨越所有3列// 主图 - 跨越2行2列 GridItem() { Image(this.featuredPhotos[0].image) .width('100%') .height('100%') .objectFit(ImageFit.Cover) .borderRadius(12) } .rowStart(1) .rowEnd(3) // 跨越2行 .columnStart(0) .columnEnd(2) // 跨越2列 // 右侧小图1 GridItem() { Image(this.featuredPhotos[1].image) .width('100%') .height('100%') .objectFit(ImageFit.Cover) .borderRadius(12) } .rowStart(1) .rowEnd(2) .columnStart(2) .columnEnd(3) // 右侧小图2 GridItem() { Image(this.featuredPhotos[2].image) .width('100%') .height('100%') .objectFit(ImageFit.Cover) .borderRadius(12) } .rowStart(2) .rowEnd(3) .columnStart(2) .columnEnd(3)}
.columnsTemplate('1fr 1fr 1fr')
.rowsTemplate('auto 1fr 1fr')
.columnsGap(12)
.rowsGap(12)
.width('100%')
.height(360) -
网格自动布局与手动布局结合,实现更灵活的照片展示,可以在同一个 Grid 中同时使用手动定位和自动布局,非常适合需要特殊处理某些网格项的场景:
// 结合自动布局和手动布局
Grid() {
// 手动布局部分 - 精选照片
GridItem() {
// 精选照片内容...
}
.rowStart(0)
.rowEnd(1)
.columnStart(0)
.columnEnd(3)// 自动布局部分 - 普通照片列表 ForEach(this.normalPhotos, (photo: Recentphoto) => { GridItem() { // 普通照片内容... } })}
.columnsTemplate('1fr 1fr 1fr')
.rowsTemplate('auto 1fr 1fr 1fr') // 第一行为精选照片,后续行为普通照片 -
嵌套 Grid 实现复杂布局,如分区展示、混合布局等:
Grid() {
// 相册分区
GridItem() {
Column() {
Text('我的相册')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#000000')
.alignSelf(ItemAlign.Start)
.margin({ bottom: 16 })// 内层 Grid - 相册网格 Grid() { ForEach(this.albums, (album: Album) => { GridItem() { // 相册卡片内容... } }) } .columnsTemplate('1fr 1fr') .columnsGap(16) .rowsGap(16) .width('100%') } .width('100%') } .rowStart(0) .rowEnd(1) .columnStart(0) .columnEnd(1) // 最近照片分区 GridItem() { Column() { Text('最近照片') .fontSize(20) .fontWeight(FontWeight.Bold) .fontColor('#000000') .alignSelf(ItemAlign.Start) .margin({ bottom: 16 }) // 内层 Grid - 照片网格 Grid() { ForEach(this.recentPhotos.slice(0, 9), (photo: Recentphoto) => { GridItem() { // 照片内容... } }) } .columnsTemplate('1fr 1fr 1fr') .columnsGap(4) .rowsGap(4) .width('100%') } .width('100%') } .rowStart(0) .rowEnd(1) .columnStart(1) .columnEnd(2)}
.columnsTemplate('1fr 1fr')
.columnsGap(24)
.width('100%')
② 高级交互与动画效果
-
网格项动画效果:
// 网格项动画效果
@State pressedItem: number = -1; // 记录当前按下的项GridItem() {
Stack({ alignContent: Alignment.BottomStart }) {
// 照片内容...
}
.width('100%')
.height(120)
.scale({ x: this.pressedItem === photo.id ? 0.95 : 1, y: this.pressedItem === photo.id ? 0.95 : 1 })
.opacity(this.pressedItem === photo.id ? 0.8 : 1)
.animation({
duration: 100,
curve: Curve.FastOutSlowIn
})
}
.onTouch((event: TouchEvent) => {
if (event.type === TouchType.Down) {
this.pressedItem = photo.id;
} else if (event.type === TouchType.Up || event.type === TouchType.Cancel) {
this.pressedItem = -1;
}
})
.onClick(() => {
console.log(查看照片: ${photo.id});
}) -
网格视图切换动画:
@State currentTab: number = 0;
@State previousTab: number = 0;
@State animationValue: number = 0; // 0 表示相册视图,1 表示最近项目视图// 切换标签页的函数
changeTab(index: number) {
this.previousTab = this.currentTab;
this.currentTab = index;// 创建动画效果 animateTo({ duration: 300, curve: Curve.EaseInOut, onFinish: () => { // 动画完成后更新状态 this.animationValue = index; } }, () => { this.animationValue = index; });}
// 在构建函数中使用动画值
build() {
Column() {
// 标签切换部分...// 内容区域 Stack() { // 相册视图 Column() { // 相册内容... } .width('100%') .layoutWeight(1) .opacity(1 - this.animationValue) .translate({ x: this.animationValue * -100 }) // 最近项目视图 Column() { // 最近项目内容... } .width('100%') .layoutWeight(1) .opacity(this.animationValue) .translate({ x: (1 - this.animationValue) * 100 }) } .width('100%') .layoutWeight(1) }}
-
滚动加载与刷新:
@State isRefreshing: boolean = false;
@State isLoading: boolean = false;
@State hasMorePhotos: boolean = true;Column() {
// 最近项目视图
Refresh({ refreshing: $$this.isRefreshing, offset: 120, friction: 100 }) {
List({ space: 0 }) {
// 照片网格
ListItem() {
Grid() {
ForEach(this.recentPhotos, (photo: Recentphoto) => {
GridItem() {
// 照片内容...
}
})
}
.columnsTemplate('1fr 1fr 1fr')
.columnsGap(4)
.rowsGap(4)
.width('100%')
}// 加载更多 if (this.hasMorePhotos) { ListItem() { Row() { if (this.isLoading) { LoadingProgress() .width(24) .height(24) .color('#007AFF') Text('加载中...') .fontSize(14) .fontColor('#8E8E93') .margin({ left: 8 }) } else { Text('加载更多') .fontSize(14) .fontColor('#007AFF') } } .width('100%') .justifyContent(FlexAlign.Center) .height(60) .onClick(() => { if (!this.isLoading) { this.loadMorePhotos(); } }) } } } .width('100%') .layoutWeight(1) .onReachEnd(() => { if (this.hasMorePhotos && !this.isLoading) { this.loadMorePhotos(); } }) } .onRefreshing(() => { this.refreshPhotos(); })}
// 刷新照片数据
async refreshPhotos() {
this.isRefreshing = true;// 模拟网络请求 await new Promise((resolve) => setTimeout(resolve, 1500)); // 更新数据 // ... this.isRefreshing = false;}
// 加载更多照片
async loadMorePhotos() {
this.isLoading = true;// 模拟网络请求 await new Promise((resolve) => setTimeout(resolve, 1500)); // 添加更多照片 // ... this.isLoading = false; // 判断是否还有更多照片 // ...}
③ 复杂布局与交互场景
-
照片分组与分类展示:
interface PhotoGroup {
title: string,
date: string,
photos: Recentphoto[]
}@State photoGroups: PhotoGroup[] = [
{
title: '今天',
date: '2023年5月15日',
photos: [/* 照片数据 /]
},
{
title: '昨天',
date: '2023年5月14日',
photos: [/ 照片数据 */]
},
// 更多分组...
];// 分组展示照片
Column() {
List({ space: 20 }) {
ForEach(this.photoGroups, (group: PhotoGroup) => {
ListItem() {
Column() {
// 分组标题
Row() {
Text(group.title)
.fontSize(18)
.fontWeight(FontWeight.Bold)
.fontColor('#000000')Text(group.date) .fontSize(14) .fontColor('#8E8E93') .margin({ left: 8 }) } .width('100%') .margin({ bottom: 12 }) // 照片网格 Grid() { ForEach(group.photos, (photo: Recentphoto) => { GridItem() { // 照片内容... } }) } .columnsTemplate('1fr 1fr 1fr') .columnsGap(4) .rowsGap(4) .width('100%') } .width('100%') } }) } .width('100%') .layoutWeight(1)}
-
实现照片的多选模式,可以支持批量删除、分享、移动等操作,提升用户效率:
// 多选模式
@State isSelectMode: boolean = false;
@State selectedPhotos: number[] = []; // 存储已选中照片的 ID// 切换选择模式
toggleSelectMode() {
this.isSelectMode = !this.isSelectMode;
if (!this.isSelectMode) {
this.selectedPhotos = [];
}
}// 选择或取消选择照片
toggleSelectPhoto(photoId: number) {
const index = this.selectedPhotos.indexOf(photoId);
if (index === -1) {
this.selectedPhotos.push(photoId);
} else {
this.selectedPhotos.splice(index, 1);
}
}// 在 GridItem 中实现选择状态
GridItem() {
Stack({ alignContent: Alignment.TopEnd }) {
// 照片内容...// 选择状态指示器 if (this.isSelectMode) { Image(this.selectedPhotos.includes(photo.id) ? $r('app.media.selected_icon') : $r('app.media.unselected_icon')) .width(24) .height(24) .margin({ top: 8, right: 8 }) } } .width('100%') .height(120)}
.onClick(() => {
if (this.isSelectMode) {
this.toggleSelectPhoto(photo.id);
} else {
console.log(查看照片: ${photo.id});
}
}) -
拖拽排序功能,可以让用户自定义照片的排列顺序:
@State isDragging: boolean = false;
@State draggedPhotoId: number = -1;
@State draggedPosition: { x: number, y: number } = { x: 0, y: 0 };
@State originalPosition: { x: number, y: number } = { x: 0, y: 0 };
@State photoPositions: Map<number, { row: number, col: number }> = new Map();// 在 GridItem 中实现拖拽功能
GridItem() {
Stack() {
// 照片内容...
}
.width('100%')
.height(120)
.position({ x: this.draggedPhotoId === photo.id ? this.draggedPosition.x : 0, y: this.draggedPhotoId === photo.id ? this.draggedPosition.y : 0 })
.zIndex(this.draggedPhotoId === photo.id ? 999 : 1)
.opacity(this.draggedPhotoId === photo.id ? 0.8 : 1)
.animation({
duration: this.isDragging ? 0 : 300,
curve: Curve.EaseOut
})
}
.gesture(
PanGesture({ fingers: 1, direction: PanDirection.All })
.onActionStart((event: GestureEvent) => {
if (this.isEditMode) {
this.isDragging = true;
this.draggedPhotoId = photo.id;
this.originalPosition = { x: 0, y: 0 };
this.draggedPosition = { x: 0, y: 0 };
}
})
.onActionUpdate((event: GestureEvent) => {
if (this.isDragging && this.draggedPhotoId === photo.id) {
this.draggedPosition = {
x: this.originalPosition.x + event.offsetX,
y: this.originalPosition.y + event.offsetY
};// 计算当前位置对应的网格位置 // 实现照片位置交换逻辑 // ... } }) .onActionEnd(() => { if (this.isDragging && this.draggedPhotoId === photo.id) { this.isDragging = false; this.draggedPosition = { x: 0, y: 0 }; this.draggedPhotoId = -1; // 更新照片顺序 // ... } }))
④ 瀑布流布局实现
// 瀑布流布局
@State photoHeights: Map<number, number> = new Map(); // 存储每张照片的高度
// 计算每列的高度
getColumnHeight(columnIndex: number): number {
let height = 0;
for (const [photoId, photoInfo] of this.photoPositions.entries()) {
if (photoInfo.col === columnIndex) {
height += this.photoHeights.get(photoId) || 0;
}
}
return height;
}
// 为新照片选择最短的列
getShortestColumn(): number {
let shortestColumn = 0;
let minHeight = this.getColumnHeight(0);
for (let i = 1; i < 3; i++) { // 假设有3列
const height = this.getColumnHeight(i);
if (height < minHeight) {
minHeight = height;
shortestColumn = i;
}
}
return shortestColumn;
}
// 在加载照片时计算位置
loadPhotos() {
// 清空现有位置信息
this.photoPositions.clear();
// 为每张照片分配位置
this.recentPhotos.forEach((photo, index) => {
// 根据照片宽高比计算高度
const aspectRatio = photo.width / photo.height;
const width = px2vp(window.getWindowWidth() - 48) / 3; // 3列布局,减去边距和间距
const height = width / aspectRatio;
this.photoHeights.set(photo.id, height);
// 选择最短的列
const column = this.getShortestColumn();
// 记录照片位置
this.photoPositions.set(photo.id, {
row: 0, // 行号在瀑布流中不重要
col: column
});
});
}
// 在 Grid 中展示照片
Grid() {
ForEach(this.recentPhotos, (photo: Recentphoto) => {
GridItem() {
Image(photo.image)
.width('100%')
.height(this.photoHeights.get(photo.id) || 120)
.objectFit(ImageFit.Cover)
.borderRadius(8)
}
.columnStart(this.photoPositions.get(photo.id)?.col || 0)
.columnEnd((this.photoPositions.get(photo.id)?.col || 0) + 1)
})
}
.columnsTemplate('1fr 1fr 1fr')
.columnsGap(4)
.rowsGap(4)
.width('100%')
⑤ 混合布局
/ 混合布局策略
Column() {
// 顶部轮播图
Swiper() {
ForEach(this.featuredPhotos, (photo: Recentphoto) => {
Image(photo.image)
.width('100%')
.height(200)
.objectFit(ImageFit.Cover)
.borderRadius(16)
})
}
.width('100%')
.height(200)
.margin({ bottom: 20 })
.indicatorStyle({ selectedColor: '#007AFF' })
// 相册快速访问 - 水平滚动
Text('我的相册')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#000000')
.alignSelf(ItemAlign.Start)
.margin({ bottom: 12 })
ScrollBar({ direction: ScrollBarDirection.Horizontal }) {
Row() {
ForEach(this.albums, (album: Album) => {
Column() {
// 相册封面
Image(album.cover)
.width(120)
.height(120)
.objectFit(ImageFit.Cover)
.borderRadius(12)
// 相册名称
Text(album.name)
.fontSize(14)
.fontColor('#000000')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.width(120)
.margin({ top: 8 })
}
.margin({ right: 16 })
})
}
.width('100%')
.padding({ left: 20, right: 20 })
}
.width('100%')
.height(160)
.margin({ bottom: 20 })
// 最近照片 - 网格布局
Text('最近照片')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#000000')
.alignSelf(ItemAlign.Start)
.margin({ bottom: 12 })
Grid() {
// 照片网格内容...
}
.columnsTemplate('1fr 1fr 1fr')
.columnsGap(4)
.rowsGap(4)
.width('100%')
.layoutWeight(1)
}
⑥ 动态布局
// 动态布局适配
@State screenWidth: number = 0;
@State screenHeight: number = 0;
@State orientation: string = 'portrait'; // 'portrait' 或 'landscape'
aboutToAppear() {
// 获取屏幕尺寸
this.updateScreenSize();
// 监听屏幕旋转
window.on('resize', () => {
this.updateScreenSize();
});
}
updateScreenSize() {
this.screenWidth = px2vp(window.getWindowWidth());
this.screenHeight = px2vp(window.getWindowHeight());
this.orientation = this.screenWidth > this.screenHeight ? 'landscape' : 'portrait';
}
build() {
if (this.orientation === 'portrait') {
// 竖屏布局
Column() {
// 竖屏内容...
}
} else {
// 横屏布局
Row() {
// 左侧导航
Column() {
// 导航内容...
}
.width('25%')
.height('100%')
// 右侧内容
Column() {
// 相册/照片内容...
}
.width('75%')
.height('100%')
}
}
}