
回复
在开始实现瀑布流布局前,需确保已安装好 DevEco Studio,且已配置好鸿蒙开发环境。打开 DevEco Studio,新建一个鸿蒙应用项目,选择合适的模板(如 Empty Feature Ability),设置项目名称、包名等信息,完成项目创建。
鸿蒙 Next 的瀑布流布局可以通过自定义组件结合 Column、Row 等容器组件实现。其核心思路是将数据分成若干列,每列独立滚动展示,且根据数据项高度动态调整布局,以达到类似瀑布自然流动的效果。
创建一个自定义组件 MasonryLayout,接收图片数据数组作为参数,并根据列数将数据分配到不同列中展示:
@Component
export struct MasonryLayout {
@Prop data: string[];
@State cols: number[] = Array.from<number>({ length: 2 }).fill(0);
build() {
Row({}) {
ForEach(this.cols, (_col: number, cIndex) => {
Column({ }) {
ForEach(this.data, (item: string, i) => {
if(i % this.cols.length === cIndex) {
Image(item).width(`${100 / this.cols.length}%`);
}
})
}
})
}.alignItems(VerticalAlign.Top)
}
}
build() {
MasonryLayout({
data: ["img1.png", "img2.png", "img3.png", "img4.png", "img5.png"],
});
}
通过 MediaQuery 组件根据屏幕宽度动态调整瀑布流的列数,以适配不同设备:
在 UIAbility 的 onWindowStageCreate 生命周期回调中,通过窗口对象获取启动时的应用窗口宽度并注册回调函数监听窗口尺寸变化。将窗口尺寸的长度单位由 px 换算为 vp 后,即可基于前文中介绍的规则得到当前断点值,此时可以使用状态变量记录当前的断点值方便后续使用
import { window, display } from "@kit.ArkUI";
import { UIAbility } from "@kit.AbilityKit";
export default class MainAbility extends UIAbility {
private windowObj?: window.Window;
private col: number = 2;
//...
// 根据当前窗口尺寸更新断点
private updateBreakpoint(windowWidth: number): void {
// 将长度的单位由px换算为vp
let windowWidthVp = windowWidth / display.getDefaultDisplaySync().densityPixels;
let col: number = this.col;
if (windowWidthVp < 320) {
// "xs";
col = 1;
} else if (windowWidthVp < 600) {
// "sm";
col = 2;
} else if (windowWidthVp < 840) {
// "md";
col = 3;
} else {
// "lg";
col = 4;
}
if (this.col !== col) {
this.col = col;
}
}
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.getMainWindow().then((windowObj) => {
this.windowObj = windowObj;
// 获取应用启动时的窗口尺寸
this.updateBreakpoint(windowObj.getWindowProperties().windowRect.width);
// 注册回调函数,监听窗口尺寸变化
windowObj.on("windowSizeChange", (windowSize) => {
this.updateBreakpoint(windowSize.width);
});
});
// ...
}
//...
}
interface IBpMapCol {
xs: number;
sm: number;
md: number;
lg: number;
}
const bpMapCol = new Map<string, number>();
bpMapCol.set('xs', 1)
bpMapCol.set('sm', 2)
bpMapCol.set('md', 3)
bpMapCol.set('lg', 4)
@Component
export struct MasonryLayout {
@StorageProp('currentBreakpoint') curBp: keyof IBpMapCol = 'sm';
@Prop data: string[];
@State cols: number[] = Array.from<number>({ length: bpMapCol.get(this.curBp) || 2 }).fill(0);
build() {
Row({}) {
ForEach(this.cols, (_col: number, cIndex) => {
Column({ }) {
ForEach(this.data, (item: string, i) => {
if(i % this.cols.length === cIndex) {
Image(item).width(`${100 / this.cols.length}%`);
Text(this.curBp)
}
})
}
})
}.alignItems(VerticalAlign.Top)
}
}
注:鸿蒙 next 中无法使用索引访问对象属性,如 const obj = { a: 1 } 无法使用 obj[a],这种情况可以用 Map
为了实现类似真实瀑布流不断加载新数据的效果,可以结合鸿蒙的 LazyForEach 组件,在滚动到列表底部时触发数据加载逻辑
// config.json
{
"module": {
"reqPermissions": [
{
"name": "ohos.permission.INTERNET",
"reason": "需要网络权限来加载图片"
}
]
}
}