当前位置: 首页 > ops >正文

开发经典的瀑布流

本案例主要介绍WaterFlow容器的使用。如图所示对高度不相等的子组件,实现如瀑布般的紧密布局。

1. 案例效果截图

2. 案例运用到的知识点

2.1. 核心知识点

  • WaterFlow:瀑布流容器,由“行”和“列”分割的单元格所组成,通过容器自身的排列规则,将不同大小的“项目”自上而下,如瀑布般紧密布局。
  • FlowItem:瀑布流容器的子组件。
  • LazyForEach:LazyForEach从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当LazyForEach在滚动容器中使用了,框架会根据滚动容器可视区域按需创建组件,当组件划出可视区域外时,框架会进行组件销毁回收以降低内存占用。

2.2. 其他知识点

  • ArkTS 语言基础
  • V2版状态管理:@ComponentV2/@Provider/@Consumer
  • 自定义组件和组件生命周期
  • 内置组件:Column/Button
  • 日志管理类的编写
  • 常量与资源分类的访问
  • MVVM模式

3. 代码结构

├──entry/src/main/ets                     // 代码区
│  ├──common
│  │  ├──constants
│  │  │  └──CommonConstants.ets           // 公共常量类
│  │  └──utils
│  │     └──Logger.ets                    // 日志打印类
│  ├──entryability
│  │  └──EntryAbility.ets                 // 程序入口类
│  ├──pages
│  │  └──HomePage.ets                     // 主界面
│  ├──view
│  │  ├──ClassifyComponent.ets            // 分类信息类
│  │  ├──FlowItemComponent.ets            // 瀑布流Item组件类
│  │  ├──SearchComponent.ets              // 查询组件类
│  │  ├──SwiperComponent.ets              // banner组件类
│  │  └──WaterFlowComponent.ets           // 自定义组件类
│  └──viewmodel
│     ├──HomeViewModel.ets                // 瀑布流数据类
│     ├──ProductItem.ets                  // 产品信息类
│     └──WaterFlowDataSource.ets          // 瀑布流数据类
└──entry/src/main/resources               // 资源文件目录

4. 公共文件与资源

本案例涉及到的常量类和工具类代码如下:

  1. 通用常量类
// entry/src/main/ets/common/constants/CommonConstants.ets
export class CommonConstants {static readonly FULL_OPACITY: number = 1static readonly SIXTY_OPACITY: number = 0.6static readonly EIGHTY_OPACITY: number = 0.8static readonly FONT_WEIGHT_FIVE: number = 500static readonly WATER_FLOW_LAYOUT_WEIGHT: number = 1static readonly WATER_FLOW_COLUMNS_TEMPLATE: string = '1fr 1fr'static readonly FULL_WIDTH: string = '100%'static readonly FULL_HEIGHT: string = '100%'static readonly INVALID_INDEX: number = -1
}
  1. 日志类
// entry/src/main/ets/common/utils/Logger.ets
import { hilog } from '@kit.PerformanceAnalysisKit'export default class Logger {private static domain: number = 0xFF00private static prefix: string = 'WaterFlow'private static format: string = '%{public}s, %{public}s'static debug(...args: string[]): void {hilog.debug(Logger.domain, Logger.prefix, Logger.format, args)}static info(...args: string[]): void {hilog.info(Logger.domain, Logger.prefix, Logger.format, args)}static warn(...args: string[]): void {hilog.warn(Logger.domain, Logger.prefix, Logger.format, args)}static error(...args: string[]): void {hilog.error(Logger.domain, Logger.prefix, Logger.format, args)}
}

本案例涉及到的资源文件如下:

  1. string.json
// entry/main/resources/base/element/string.json
{"string": [{"name": "module_desc","value": "模块描述"},{"name": "EntryAbility_desc","value": "description"},{"name": "EntryAbility_label","value": "WaterFlow组件的使用"},{"name": "title_bar_homepage","value": "首页"},{"name": "title_bar_phone","value": "手机"},{"name": "title_bar_computer","value": "电脑"},{"name": "title_bar_foods","value": "食品"},{"name": "title_bar_men_wear","value": "男装"},{"name": "title_bar_fresh","value": "生鲜"},{"name": "title_bar_furniture_kitchenware","value": "家具厨具"},{"name": "title_bar_classification","value": "分类"},{"name": "search_text","value": "儿童餐椅"},{"name": "footer_text","value": "已经到底了"}]
}
  1. float.json
// entry/main/resources/base/element/float.json
{"float": [{"name": "smaller_font_size","value": "12fp"},{"name": "small_font_size","value": "14fp"},{"name": "middle_font_size","value": "16fp"},{"name": "split_line_width","value": "1vp"},{"name": "split_line_height","value": "18vp"},{"name": "more_width","value": "16vp"},{"name": "more_height","value": "16vp"},{"name": "more_margin_left","value": "2vp"},{"name": "more_margin_right","value": "2vp"},{"name": "classify_title_margin","value": "12vp"},{"name": "search_width","value": "20vp"},{"name": "search_height","value": "20vp"},{"name": "search_radius","value": "20vp"},{"name": "search_margin_left","value": "12vp"},{"name": "search_margin_right","value": "8vp"},{"name": "search_swiper_height","value": "40vp"},{"name": "search_margin_top","value": "16vp"},{"name": "swiper_image_height","value": "150vp"},{"name": "swiper_radius","value": "16vp"},{"name": "swiper_margin_top","value": "12vp"},{"name": "swiper_margin_bottom","value": "12vp"},{"name": "image_background_height","value": "222vp"},{"name": "home_margin_left","value": "12vp"},{"name": "home_margin_right","value": "12vp"},{"name": "product_layout_radius","value": "8vp"},{"name": "product_layout_margin_left","value": "12vp"},{"name": "product_layout_margin_right","value": "12vp"},{"name": "product_layout_margin_bottom","value": "12vp"},{"name": "product_image_size","value": "132vp"},{"name": "water_flow_image_top","value": "12vp"},{"name": "water_flow_image_bottom","value": "8vp"},{"name": "discount_text_bottom","value": "4vp"},{"name": "piece_line_height","value": "19vp"},{"name": "promotion_font_size","value": "10fp"},{"name": "promotion_radius","value": "4vp"},{"name": "promotion_text_height","value": "16vp"},{"name": "promotion_padding_left","value": "4vp"},{"name": "promotion_padding_right","value": "4vp"},{"name": "promotion_margin_top","value": "6vp"},{"name": "promotion_margin_right","value": "8vp"},{"name": "bonus_points_radius_width","value": "0.5vp"},{"name": "bonus_points_margin_top","value": "6vp"},{"name": "bonus_points_padding_left","value": "8vp"},{"name": "bonus_points_padding_right","value": "8vp"},{"name": "water_flow_columns_gap","value": "8vp"},{"name": "water_flow_row_gap","value": "8vp"},{"name": "footer_text_size","value": "10vp"},{"name": "footer_text_height","value": "20vp"}]
}
  1. color.json
// entry/main/resources/base/element/color.json
{"color": [{"name": "start_window_background","value": "#FFFFFF"},{"name": "indicator_select","value": "#F74E42"},{"name": "focus_color","value": "#E92F4F"},{"name": "home_background_color","value": "#F1F3F5"}]
}

其他资源请到源码中获取。

5. 首页

// entry/src/main/est/pages/HomePage.ets
import { CommonConstants as Const } from '../common/constants/CommonConstants'
import ClassifyComponent from '../view/ClassifyComponent'
import SearchComponent from '../view/SearchComponent'
import SwiperComponent from '../view/SwiperComponent'
import WaterFlowComponent from '../view/WaterFlowComponent'@Entry
@Component
struct HomePage {build() {Stack({ alignContent: Alignment.Top }) {Image($r('app.media.ic_app_background')).width(Const.FULL_WIDTH).height($r('app.float.image_background_height')).objectFit(ImageFit.Cover)Column() {SearchComponent()ClassifyComponent()SwiperComponent()WaterFlowComponent()}.padding({left: $r('app.float.home_margin_left'),right: $r('app.float.home_margin_right')})}.backgroundColor($r('app.color.home_background_color'))}
}

6. 搜索组件

// entry/src/main/est/view/SearchComponent.ets
import { CommonConstants as Const } from '../common/constants/CommonConstants'@Component
export default struct SearchComponent {build() {Row() {Image($r('app.media.ic_search')).width($r('app.float.search_width')).height($r('app.float.search_height')).margin({left: $r('app.float.search_margin_left'),right: $r('app.float.search_margin_right')})Text($r('app.string.search_text')).fontSize($r('app.float.small_font_size')).fontColor(Color.Black).opacity(Const.SIXTY_OPACITY).fontWeight(FontWeight.Normal)}.width(Const.FULL_WIDTH).height($r('app.float.search_swiper_height')).borderRadius($r('app.float.search_radius')).backgroundColor(Color.White).margin({ top: $r('app.float.search_margin_top') })}
}

7. 分类导航组件

// entry/src/main/view/ClassifyComponent.ets
import { classifyTitle } from '../viewmodel/HomeViewModel'
import { CommonConstants as Const } from '../common/constants/CommonConstants'@Component
export default struct ClassifyComponent {@State titleIndex: number = 0build() {Flex({ justifyContent: FlexAlign.SpaceBetween }) {ForEach(classifyTitle, (item: Resource, index: number | undefined) => {if (index !== undefined) {Text(item).fontSize($r('app.float.middle_font_size')).opacity(this.titleIndex === index ? Const.FULL_OPACITY : Const.EIGHTY_OPACITY).fontWeight(this.titleIndex === index ? Const.FONT_WEIGHT_FIVE : FontWeight.Normal).fontColor(Color.White).onClick(() => {this.titleIndex = index})}}, (item: Resource) => JSON.stringify(item))Row() {Image($r('app.media.ic_split_line')).width($r('app.float.split_line_width')).height($r('app.float.split_line_height'))Image($r('app.media.ic_more')).width($r('app.float.more_width')).height($r('app.float.more_height')).margin({left: $r('app.float.more_margin_left'),right: $r('app.float.more_margin_right')})Text($r('app.string.title_bar_classification')).fontSize($r('app.float.middle_font_size')).fontColor(Color.White).opacity(this.titleIndex === Const.INVALID_INDEX ? Const.FULL_OPACITY : Const.EIGHTY_OPACITY).fontWeight(this.titleIndex === Const.INVALID_INDEX ? Const.FONT_WEIGHT_FIVE : FontWeight.Normal)}.onClick(() => {this.titleIndex = Const.INVALID_INDEX})}.width(Const.FULL_WIDTH).margin({ top: $r('app.float.classify_title_margin') })}
}
// entry/src/main/ets/viewmodel/HomeViewModel.ets
const classifyTitle: Resource[] = [$r('app.string.title_bar_homepage'),$r('app.string.title_bar_phone'),$r('app.string.title_bar_computer'),$r('app.string.title_bar_foods'),$r('app.string.title_bar_men_wear'),$r('app.string.title_bar_fresh'),$r('app.string.title_bar_furniture_kitchenware')
]export { classifyTitle }

8. 轮播图组件

// entry/src/main/ets/view/SwiperComponent.ets
import { CommonConstants as Const } from '../common/constants/CommonConstants'
import { swiperImage } from '../viewmodel/HomeViewModel'@Component
export default struct SwiperComponent {private dotIndicator: DotIndicator = new DotIndicator()aboutToAppear(){this.dotIndicator.selectedColor($r('app.color.indicator_select'))}build() {Swiper() {ForEach(swiperImage, (item: Resource) => {Image(item).width(Const.FULL_WIDTH).height($r('app.float.swiper_image_height')).borderRadius($r('app.float.swiper_radius')).backgroundColor(Color.White)}, (item: Resource) => JSON.stringify(item))}.indicator(this.dotIndicator).autoPlay(true).itemSpace(0).width(Const.FULL_WIDTH).displayCount(1).margin({top: $r('app.float.swiper_margin_top'),bottom: $r('app.float.swiper_margin_bottom')})}
}
// entry/src/main/ets/viewmodel/HomeViewModel.ets
const classifyTitle: Resource[] = [$r('app.string.title_bar_homepage'),$r('app.string.title_bar_phone'),$r('app.string.title_bar_computer'),$r('app.string.title_bar_foods'),$r('app.string.title_bar_men_wear'),$r('app.string.title_bar_fresh'),$r('app.string.title_bar_furniture_kitchenware')
]const swiperImage: Resource[] = [$r('app.media.ic_home_appliances_special'),$r('app.media.ic_coupons'),$r('app.media.ic_internal_purchase_price')
]export { classifyTitle, swiperImage }

9. 瀑布流组件

// entry/src/main/ets/view/WaterFlowComponent.ets
import ProductItem from '../viewmodel/ProductItem'
import { WaterFlowDataSource } from '../viewmodel/WaterFlowDataSource'
import { CommonConstants as Const } from '../common/constants/CommonConstants'
import { waterFlowData } from '../viewmodel/HomeViewModel'
import FlowItemComponent from '../view/FlowItemComponent'@Component
export default struct WaterFlowComponent {private datasource: WaterFlowDataSource = new WaterFlowDataSource()aboutToAppear() {this.datasource.setDataArray(waterFlowData)}build() {WaterFlow({ footer: (): void => this.itemFoot() }) {LazyForEach(this.datasource, (item: ProductItem) => {FlowItem() {FlowItemComponent({ item: item })}}, (item: ProductItem) => JSON.stringify(item))}.layoutWeight(Const.WATER_FLOW_LAYOUT_WEIGHT).layoutDirection(FlexDirection.Column).columnsTemplate(Const.WATER_FLOW_COLUMNS_TEMPLATE).columnsGap($r('app.float.water_flow_columns_gap')).rowsGap($r('app.float.water_flow_row_gap'))}@BuilderitemFoot() {Column() {Text($r('app.string.footer_text')).fontColor(Color.Gray).fontSize($r('app.float.footer_text_size')).width(Const.FULL_WIDTH).height($r('app.float.footer_text_height')).textAlign(TextAlign.Center)}}
}
// entry/src/main/ets/viewmodel/ProductItem.ets
export interface IProductItem {image_url: Resourcename: stringdiscount: stringprice: stringpromotion: stringbonus_points: string
}export default class ProductItem implements IProductItem {image_url: Resourcename: stringdiscount: stringprice: stringpromotion: stringbonus_points: stringconstructor(props: ProductItem) {this.image_url = props.image_urlthis.name = props.namethis.discount = props.discountthis.price = props.pricethis.promotion = props.promotionthis.bonus_points = props.bonus_points}
}
// entry/src/main/ets/viewmodel/WaterFlowDataSource.ets
import ProductItem from './ProductItem'export class WaterFlowDataSource implements IDataSource {private dataArray: ProductItem[] = []private listeners: DataChangeListener[] = []public setDataArray(productDataArray: ProductItem[]): void {this.dataArray = productDataArray}public totalCount(): number {return this.dataArray.length}public getData(index: number): ProductItem {return this.dataArray[index]}registerDataChangeListener(listener: DataChangeListener): void {if (this.listeners.indexOf(listener) < 0) {this.listeners.push(listener)}}unregisterDataChangeListener(listener: DataChangeListener): void {let pos = this.listeners.indexOf(listener)if (pos >= 0) {this.listeners.splice(pos, 1)}}
}
// entry/src/main/ets/viewmodel/HomeViewModel.ets
import { IProductItem } from './ProductItem'const classifyTitle: Resource[] = [$r('app.string.title_bar_homepage'),$r('app.string.title_bar_phone'),$r('app.string.title_bar_computer'),$r('app.string.title_bar_foods'),$r('app.string.title_bar_men_wear'),$r('app.string.title_bar_fresh'),$r('app.string.title_bar_furniture_kitchenware')
]const swiperImage: Resource[] = [$r('app.media.ic_home_appliances_special'),$r('app.media.ic_coupons'),$r('app.media.ic_internal_purchase_price')
]const waterFlowData: IProductItem[] = [{image_url: $r('app.media.ic_holder_50e'),name: 'XXX50E',discount: '',price: '¥4088',promotion: '',bonus_points: ''},{image_url: $r('app.media.ic_holder_xs2'),name: 'XXXPadMate Xs 2 \n8GB+256GB (雅黑)',discount: '',price: '¥9999',promotion: '限时',bonus_points: ''},{image_url: $r('app.media.ic_holder_computer'),name: 'XX设备  新品优惠!新品优惠!机不可失!失不再来!快来购买!快来购买!',discount: '限时省200',price: '¥10099',promotion: '商品',bonus_points: ''},{image_url: $r('app.media.ic_holder_mouse'),name: '送给亲人  快速购买!',discount: '限时省200',price: '¥199',promotion: '',bonus_points: ''},{image_url: $r('app.media.ic_holder_pad'),name: 'XXXPad Pro',discount: '',price: '¥3499',promotion: '',bonus_points: ''},{image_url: $r('app.media.ic_holder_mate50'),name: 'XXXMate 50 8GB+256GB',discount: '',price: '¥5499',promotion: '',bonus_points: ''},{image_url: $r('app.media.ic_holder_60pro'),name: 'XXX60 Pro 新品上市!\n你值得拥有!\n限时折扣!\n速速购买!',discount: '限时省200',price: '¥1299',promotion: '限时',bonus_points: ''},{image_url: $r('app.media.ic_holder_50e'),name: 'XXX50E',discount: '',price: '¥4088',promotion: '',bonus_points: ''},{image_url: $r('app.media.ic_holder_xs2'),name: 'XXXPadMate Xs 2 \n8GB+256GB (雅黑)',discount: '限时省200',price: '¥9999',promotion: '限时',bonus_points: ''},{image_url: $r('app.media.ic_holder_computer'),name: 'XX设备  新品优惠!新品优惠!',discount: '限时省200',price: '¥10099',promotion: '商品',bonus_points: ''},{image_url: $r('app.media.ic_holder_mouse'),name: '送给亲人  快速购买!',discount: '限时省200',price: '¥199',promotion: '限时',bonus_points: '赠送积分'},{image_url: $r('app.media.ic_holder_pad'),name: 'XXXPad Pro\n限时折扣!\n速速购买!\n机不可失!失不再来!',discount: '限时省200',price: '¥3499',promotion: '',bonus_points: '赠送积分'},{image_url: $r('app.media.ic_holder_mate50'),name: 'XXXMate 50 \n8GB+256GB',discount: '',price: '¥5499',promotion: '',bonus_points: ''},{image_url: $r('app.media.ic_holder_60pro'),name: 'XXX60 Pro',discount: '限时省200',price: '¥1299',promotion: '限时',bonus_points: '',}
];export { classifyTitle, swiperImage, waterFlowData }
// entry/src/main/ets/view/FlowItemComponent.ets
import { CommonConstants as Const } from '../common/constants/CommonConstants'
import ProductItem from '../viewmodel/ProductItem'
import { waterFlowData } from '../viewmodel/HomeViewModel'@Component
export default struct FlowItemComponent {item: ProductItem = waterFlowData[0]build() {Column() {Image(this.item?.image_url).width($r('app.float.product_image_size')).height($r('app.float.product_image_size')).objectFit(ImageFit.Contain).margin({top: $r('app.float.water_flow_image_top'),bottom: $r('app.float.water_flow_image_bottom')})Text(this.item?.name).fontSize($r('app.float.small_font_size')).fontColor(Color.Black).fontWeight(FontWeight.Normal).alignSelf(ItemAlign.Start)Text(this.item?.discount).fontSize($r('app.float.smaller_font_size')).fontColor(Color.Black).fontWeight(FontWeight.Normal).opacity(Const.SIXTY_OPACITY).alignSelf(ItemAlign.Start).margin({bottom: $r('app.float.discount_text_bottom')})Text(this.item?.price).fontSize($r('app.float.middle_font_size')).fontColor($r('app.color.focus_color')).fontWeight(FontWeight.Normal).alignSelf(ItemAlign.Start).lineHeight($r('app.float.piece_line_height'))Row() {if (this.item?.promotion) {Text(`${this.item?.promotion}`).height($r('app.float.promotion_text_height')).fontSize($r('app.float.promotion_font_size')).fontColor(Color.White).borderRadius($r('app.float.promotion_radius')).backgroundColor($r('app.color.focus_color')).padding({left: $r('app.float.promotion_padding_left'),right: $r('app.float.promotion_padding_right')}).margin({top: $r('app.float.promotion_margin_top'),right: $r('app.float.promotion_margin_right')})}if (this.item?.bonus_points) {Text(`${this.item?.bonus_points}`).height($r('app.float.promotion_text_height')).fontSize($r('app.float.promotion_font_size')).fontColor($r('app.color.focus_color')).borderRadius($r('app.float.promotion_radius')).borderWidth($r('app.float.bonus_points_radius_width')).borderColor($r('app.color.focus_color')).padding({left: $r('app.float.bonus_points_padding_left'),right: $r('app.float.bonus_points_padding_right')}).margin({ top: $r('app.float.bonus_points_margin_top') })}}.width(Const.FULL_WIDTH).justifyContent(FlexAlign.Start)}.borderRadius($r('app.float.product_layout_radius')).backgroundColor(Color.White).padding({left: $r('app.float.product_layout_margin_left'),right: $r('app.float.product_layout_margin_right'),bottom: $r('app.float.product_layout_margin_bottom')})}
}

10. 代码与视频教程

完整案例代码与视频教程请参见:

代码:Code-06-02.zip。

视频:《开发经典的瀑布流》。

http://www.xdnf.cn/news/7878.html

相关文章:

  • c++11特性——可变参数模板及emplace系列接口
  • 【ffmpeg】SPS与PPS的概念
  • BurpSuite Montoya API 详解
  • 基于stm32的空气质量监测系统
  • 2025年二级等保实施全攻略:传统架构与云等保方案深度解析
  • 乘法逆元:费马小定理(利用快速乘法幂)(JAVA)
  • GitHub 趋势日报 (2025年05月20日)
  • 洛谷B3840 [GESP202306 二级] 找素数
  • MySQL--day5--多表查询
  • 第22天-Python ttkbootstrap 界面美化指南
  • 漏洞扫描企业如何助力企业预防安全风险应对网络攻击?
  • GUI实验
  • vue3 threejs 物体发光描边
  • Python人工智能算法 模拟退火算法:原理、实现与应用
  • 项目执行中缺乏问题记录和总结,如何改进?
  • [java]数组
  • 7.数据的预测分析及可视化
  • 嵌入式STM32学习——串口USART 2.0(printf重定义及串口发送)
  • Word2Vec模型学习和Word2Vec提取相似文本体验
  • 豪越智能仓储:为消防应急物资管理“上锁”
  • Nginx 强制 HTTPS:提升网站安全性的关键一步
  • Arthas:Java诊断利器实战指南
  • 游戏服务器开发:如何实现客户端与服务端通信
  • 【Unity 如何使用 Mixamo下载免费模型/动画资源】Mixamo 结合在 Unity 中的实现(Animtor动画系统,完整配置以及效果展示)
  • 如何通过小贝加速实现精准网络故障排查
  • 使用 Shadcn UI 构建 Java 桌面应用
  • 六:操作系统虚拟内存之缺页中断
  • PHP:经典编程语言在当代Web开发中的新活力
  • YOLOv4深度解析:从架构创新到工业落地的目标检测里程碑
  • git工具使用