查看: 322|回复: 0

鸿蒙uni-app x瀑布流waterflow组件API与适配实战

[复制链接]
发表于 3 小时前 | 显示全部楼层 |阅读模式
在鸿蒙平台使用uni-app x开发时,瀑布流布局是电商、社区类应用的常见需求。waterflow是uni-app x提供的瀑布流组件,底层实现与list-view基本一致,都基于回收复用机制,子组件滑出屏幕会被回收,区别在于waterflow多列排列,list-view单列。鸿蒙4.81开始支持waterflow,且不支持Web和微信小程序。组件选择上,瀑布流用waterflow,单列列表用list-view,其他用scroll-view。

兼容性方面,鸿蒙平台API 20及以上版本支持滚动相关事件,API 18及以上版本支持load-more插槽。waterflow暂时只支持flow-item作为子组件,其他组件不可见。flow-item是瀑布流的子组件,每个flow-item代表一列内容,其type属性用于区分类型。

waterflow核心属性包括:cross-axis-count(列数)、cross-axis-gap(列间距)、main-axis-gap(行间距)、padding(内边距数组)、refresher-enabled(是否开启下拉刷新)、refresher-triggered(刷新状态)、refresher-default-style(下拉刷新样式,可设为none自定义)、associative-container(嵌套滚动关联容器)。事件有:@refresherrefresh(下拉刷新触发)、@scrolltolower(滚动到底部)、@scroll(滚动)、@refresherpulling(下拉中)、@refresherrestore(复位)、@refresherabort(中止)。load-more插槽用于上拉加载更多。

下面通过几个实战场景说明用法。

场景一:基础瀑布流。使用v-for循环生成flow-item,设置type='1',每个卡片随机高度。
  1. <template>
  2. <waterflow style='flex: 1;'>
  3. <flow-item v-for='(item, index) in dataList' :key='index' type='1'>
  4. <view style='padding: 10px;'>
  5. <view :style='{ height: item.height + "px", backgroundColor: item.color, borderRadius: "8px", alignItems: "center", justifyContent: "center" }'>
  6. <text style='font-size: 16px; color: white; font-weight: bold;'>{{item.text}}</text>
  7. </view>
  8. </view>
  9. </flow-item>
  10. </waterflow>
  11. </template>
  12. <script setup lang='uts'>
  13. type FlowItem = {
  14. height: number
  15. color: string
  16. text: string
  17. }
  18. const colors = ['#E91E63', '#9C27B0', '#3F51B5', '#009688', '#FF9800', '#795548', '#607D8B']
  19. const dataList = ref([] as Array<FlowItem>)
  20. for (let i = 0; i < 30; i++) {
  21. dataList.value.push({
  22. height: 100 + Math.floor(Math.random() * 200),
  23. color: colors[i % colors.length],
  24. text: '卡片 ' + (i + 1)
  25. })
  26. }
  27. </script>
复制代码

场景二:商品列表瀑布流。两列布局,支持下拉刷新和上拉加载。关键属性:cross-axis-count='2',cross-axis-gap='8',main-axis-gap='8',refresher-enabled='true'。load-more插槽的flow-item设置type='2'。
  1. <template>
  2. <waterflow :cross-axis-count='2' :cross-axis-gap='8' :main-axis-gap='8'
  3. :padding='[0, 8, 8, 8]'
  4. :refresher-enabled='true' :refresher-triggered='isRefreshing'
  5. @refresherrefresh='onRefresh'
  6. @scrolltolower='loadMore'
  7. style='flex: 1;'>
  8. <flow-item v-for='(item, index) in productList' :key='index' type='1'>
  9. <view style='background-color: white; border-radius: 8px; overflow: hidden; margin-bottom: 8px;'>
  10. <image :src='item.image' style='width: 100%; height: 200px;' mode='aspectFill'></image>
  11. <view style='padding: 8px;'>
  12. <text style='font-size: 14px; color: #333; lines: 2;'>{{item.title}}</text>
  13. <view style='flex-direction: row; align-items: center; margin-top: 6px;'>
  14. <text style='font-size: 16px; color: #F44336; font-weight: bold;'>¥{{item.price}}</text>
  15. <text style='font-size: 12px; color: #999; margin-left: 5px; text-decoration: line-through;'>¥{{item.originalPrice}}</text>
  16. </view>
  17. <text style='font-size: 12px; color: #999; margin-top: 4px;'>{{item.sales}}人付款</text>
  18. </view>
  19. </view>
  20. </flow-item>
  21. <flow-item slot='load-more' type='2'>
  22. <view style='padding: 15px; align-items: center;'>
  23. <text style='font-size: 14px; color: #999;'>{{loadingText}}</text>
  24. </view>
  25. </flow-item>
  26. </waterflow>
  27. </template>
  28. <script setup lang='uts'>
  29. type Product = {
  30. title: string
  31. image: string
  32. price: string
  33. originalPrice: string
  34. sales: string
  35. }
  36. const productList = ref([] as Array<Product>)
  37. const isRefreshing = ref(false)
  38. const loadingText = ref('加载更多...')
  39. const page = ref(1)
  40. const loadProducts = () => {
  41. productList.value = []
  42. for (let i = 1; i <= 20; i++) {
  43. productList.value.push({
  44. title: '这是商品标题,可能会比较长,需要两行显示',
  45. image: 'https://picsum.photos/200/' + (200 + i * 10),
  46. price: (99 + i * 10).toString(),
  47. originalPrice: (199 + i * 10).toString(),
  48. sales: (100 + i * 50).toString()
  49. })
  50. }
  51. }
  52. const onRefresh = () => {
  53. isRefreshing.value = true
  54. page.value = 1
  55. loadingText.value = '加载更多...'
  56. setTimeout(() => {
  57. loadProducts()
  58. isRefreshing.value = false
  59. uni.showToast({ title: '刷新成功', icon: 'success' })
  60. }, 1500)
  61. }
  62. const loadMore = () => {
  63. if (loadingText.value == '没有更多了') return
  64. loadingText.value = '加载中...'
  65. setTimeout(() => {
  66. const start = productList.value.length
  67. for (let i = 1; i <= 15; i++) {
  68. productList.value.push({
  69. title: '新加载的商品标题 ' + (start + i),
  70. image: 'https://picsum.photos/200/' + (200 + (start + i) * 10),
  71. price: (99 + (start + i) * 10).toString(),
  72. originalPrice: (199 + (start + i) * 10).toString(),
  73. sales: (100 + (start + i) * 50).toString()
  74. })
  75. }
  76. page.value++
  77. if (page.value >= 5) {
  78. loadingText.value = '没有更多了'
  79. } else {
  80. loadingText.value = '加载更多...'
  81. }
  82. }, 1000)
  83. }
  84. onMounted(() => {
  85. loadProducts()
  86. })
  87. </script>
复制代码

场景三:图片瀑布流。类似小红书,图片高度随机,展示作者和点赞数。
  1. <template>
  2. <waterflow :cross-axis-count='2' :cross-axis-gap='6' :main-axis-gap='6'
  3. :padding='[6, 6, 6, 6]'
  4. style='flex: 1;'>
  5. <flow-item v-for='(item, index) in imageList' :key='index' type='1'>
  6. <view style='border-radius: 8px; overflow: hidden; background-color: white;'>
  7. <image :src='item.url' :style='{ width: "100%", height: item.height + "px" }' mode='aspectFill'></image>
  8. <view style='padding: 8px;'>
  9. <text style='font-size: 14px; color: #333; lines: 2;'>{{item.desc}}</text>
  10. <view style='flex-direction: row; align-items: center; margin-top: 6px; justify-content: space-between;'>
  11. <view style='flex-direction: row; align-items: center;'>
  12. <image :src='item.avatar' style='width: 20px; height: 20px; border-radius: 10px; margin-right: 5px;'></image>
  13. <text style='font-size: 12px; color: #666;'>{{item.author}}</text>
  14. </view>
  15. <text style='font-size: 12px; color: #999;'>{{item.likes}}</text>
  16. </view>
  17. </view>
  18. </view>
  19. </flow-item>
  20. </waterflow>
  21. </template>
  22. <script setup lang='uts'>
  23. type ImageItem = {
  24. url: string
  25. height: number
  26. desc: string
  27. avatar: string
  28. author: string
  29. likes: string
  30. }
  31. const imageList = ref([] as Array<ImageItem>)
  32. for (let i = 0; i < 20; i++) {
  33. const h = 150 + Math.floor(Math.random() * 150)
  34. imageList.value.push({
  35. url: 'https://picsum.photos/200/' + h,
  36. height: h,
  37. desc: '这是第 ' + (i + 1) + ' 张图片的描述,可能会比较长',
  38. avatar: 'https://picsum.photos/50/50',
  39. author: '用户' + (i + 1),
  40. likes: (Math.floor(Math.random() * 1000)).toString()
  41. })
  42. }
  43. </script>
复制代码

场景四:三列瀑布流。设置cross-axis-count='3'即可。
  1. <template>
  2. <waterflow :cross-axis-count='3' :cross-axis-gap='5' :main-axis-gap='5'
  3. :padding='[5, 5, 5, 5]'
  4. style='flex: 1;'>
  5. <flow-item v-for='(item, index) in dataList' :key='index' type='1'>
  6. <view :style='{ height: item.height + "px", backgroundColor: item.color, borderRadius: "5px", alignItems: "center", justifyContent: "center" }'>
  7. <text style='font-size: 13px; color: white;'>{{item.text}}</text>
  8. </view>
  9. </flow-item>
  10. </waterflow>
  11. </template>
  12. <script setup lang='uts'>
  13. type FlowItem = {
  14. height: number
  15. color: string
  16. text: string
  17. }
  18. const colors = ['#E91E63', '#9C27B0', '#3F51B5', '#009688', '#FF9800', '#795548']
  19. const dataList = ref([] as Array<FlowItem>)
  20. for (let i = 0; i < 30; i++) {
  21. dataList.value.push({
  22. height: 80 + Math.floor(Math.random() * 150),
  23. color: colors[i % colors.length],
  24. text: '' + (i + 1)
  25. })
  26. }
  27. </script>
复制代码

场景五:自定义下拉刷新。设置refresher-default-style='none',使用slot='refresher'的flow-item,并监听refresherpulling事件改变提示文字。注意自定义刷新元素不要放在第一个子元素位置。
  1. <template>
  2. <waterflow
  3. refresher-default-style='none'
  4. :refresher-enabled='true'
  5. :refresher-triggered='isRefreshing'
  6. @refresherpulling='onPulling'
  7. @refresherrefresh='onRefresh'
  8. @refresherrestore='onRestore'
  9. @refresherabort='onAbort'
  10. :cross-axis-count='2' :cross-axis-gap='8' :main-axis-gap='8'
  11. style='flex: 1;'>
  12. <flow-item v-for='(item, index) in dataList' :key='index' type='1'>
  13. <view style='padding: 8px;'>
  14. <view :style='{ height: item.height + "px", backgroundColor: "#E3F2FD", borderRadius: "8px", alignItems: "center", justifyContent: "center" }'>
  15. <text style='font-size: 15px; color: #1976D2;'>{{item.text}}</text>
  16. </view>
  17. </view>
  18. </flow-item>
  19. <flow-item slot='refresher' type='2'>
  20. <view style='height: 50px; align-items: center; justify-content: center;'>
  21. <text style='font-size: 14px; color: #2196F3;'>{{refreshText}}</text>
  22. </view>
  23. </flow-item>
  24. </waterflow>
  25. </template>
  26. <script setup lang='uts'>
  27. type FlowItem = {
  28. height: number
  29. text: string
  30. }
  31. const dataList = ref([] as Array<FlowItem>)
  32. const isRefreshing = ref(false)
  33. const refreshText = ref('下拉刷新')
  34. const onPulling = (event: UniRefresherEvent) => {
  35. if (event.detail.dy > 45) {
  36. refreshText.value = '释放立即刷新'
  37. } else {
  38. refreshText.value = '下拉刷新'
  39. }
  40. }
  41. const onRefresh = () => {
  42. isRefreshing.value = true
  43. refreshText.value = '正在刷新...'
  44. setTimeout(() => {
  45. loadData()
  46. isRefreshing.value = false
  47. }, 1500)
  48. }
  49. const onRestore = () => {
  50. refreshText.value = '下拉刷新'
  51. }
  52. const onAbort = () => {
  53. refreshText.value = '下拉刷新'
  54. }
  55. const loadData = () => {
  56. dataList.value = []
  57. for (let i = 0; i < 20; i++) {
  58. dataList.value.push({
  59. height: 100 + Math.floor(Math.random() * 150),
  60. text: '卡片 ' + (i + 1)
  61. })
  62. }
  63. }
  64. onMounted(() => {
  65. loadData()
  66. })
  67. </script>
复制代码

场景六:嵌套滚动。将waterflow嵌套在scroll-view中,使用type='nested',并设置waterflow的associative-container='nested-scroll-view',配合nested-scroll-header和nested-scroll-body。
  1. <template>
  2. <scroll-view type='nested' style='flex: 1;'>
  3. <nested-scroll-header>
  4. <view style='height: 200px; background-color: #E3F2FD; align-items: center; justify-content: center;'>
  5. <text style='font-size: 18px; color: #1976D2;'>头部区域</text>
  6. </view>
  7. </nested-scroll-header>
  8. <nested-scroll-body>
  9. <waterflow
  10. associative-container='nested-scroll-view'
  11. :cross-axis-count='2' :cross-axis-gap='8' :main-axis-gap='8'
  12. style='flex: 1;'>
  13. <flow-item v-for='i in 30' :key='i' type='1'>
  14. <view style='padding: 8px;'>
  15. <view :style='{ height: (100 + i * 10) + "px", backgroundColor: "#E8F5E9", borderRadius: "8px", alignItems: "center", justifyContent: "center" }'>
  16. <text>卡片 {{i}}</text>
  17. </view>
  18. </view>
  19. </flow-item>
  20. </waterflow>
  21. </nested-scroll-body>
  22. </scroll-view>
  23. </template>
复制代码

完整实战示例:一个商品瀑布流综合页面,包含标题栏、两列商品、下拉刷新、上拉加载和滚动监听。
  1. <template>
  2. <view style='flex: 1;'>
  3. <view style='height: 45px; background-color: white; border-bottom: 1px solid #eee; align-items: center; justify-content: center;'>
  4. <text style='font-size: 16px; font-weight: bold;'>商品瀑布流</text>
  5. </view>
  6. <waterflow
  7. :cross-axis-count='2' :cross-axis-gap='8' :main-axis-gap='8'
  8. :padding='[0, 8, 8, 8]'
  9. :refresher-enabled='true' :refresher-triggered='isRefreshing'
  10. @refresherrefresh='onRefresh'
  11. @scrolltolower='loadMore'
  12. @scroll='onScroll'
  13. style='flex: 1;'>
  14. <flow-item v-for='(item, index) in productList' :key='index' type='1'>
  15. <view style='background-color: white; border-radius: 8px; overflow: hidden; margin-bottom: 8px;'>
  16. <image :src='item.image' :style='{ width: "100%", height: item.imgHeight + "px" }' mode='aspectFill'></image>
  17. <view style='padding: 8px;'>
  18. <text style='font-size: 14px; color: #333; lines: 2;'>{{item.title}}</text>
  19. <view style='flex-direction: row; align-items: center; margin-top: 6px;'>
  20. <text style='font-size: 16px; color: #F44336; font-weight: bold;'>¥{{item.price}}</text>
  21. <text style='font-size: 12px; color: #999; margin-left: 5px; text-decoration: line-through;'>¥{{item.originalPrice}}</text>
  22. </view>
  23. <view style='flex-direction: row; align-items: center; margin-top: 4px; justify-content: space-between;'>
  24. <text style='font-size: 12px; color: #999;'>{{item.sales}}人付款</text>
  25. <text style='font-size: 12px; color: #FF9800;'>{{item.shop}}</text>
  26. </view>
  27. </view>
  28. </view>
  29. </flow-item>
  30. <flow-item slot='load-more' type='2'>
  31. <view style='padding: 15px; align-items: center;'>
  32. <text style='font-size: 14px; color: #999;'>{{loadingText}}</text>
  33. </view>
  34. </flow-item>
  35. </waterflow>
  36. </view>
  37. </template>
  38. <script setup lang='uts'>
  39. type Product = {
  40. title: string
  41. image: string
  42. imgHeight: number
  43. price: string
  44. originalPrice: string
  45. sales: string
  46. shop: string
  47. }
  48. const productList = ref([] as Array<Product>)
  49. const isRefreshing = ref(false)
  50. const loadingText = ref('加载更多...')
  51. const page = ref(1)
  52. const scrollPos = ref(0)
  53. const shops = ['旗舰店', '自营店', '专营店', '专卖店']
  54. const loadProducts = () => {
  55. productList.value = []
  56. for (let i = 1; i <= 20; i++) {
  57. const h = 180 + Math.floor(Math.random() * 100)
  58. productList.value.push({
  59. title: '这是商品标题,可能会比较长需要两行显示 ' + i,
  60. image: 'https://picsum.photos/200/' + h,
  61. imgHeight: h,
  62. price: (99 + i * 10).toString(),
  63. originalPrice: (199 + i * 10).toString(),
  64. sales: (100 + i * 50).toString(),
  65. shop: shops[i % shops.length]
  66. })
  67. }
  68. }
  69. const onRefresh = () => {
  70. isRefreshing.value = true
  71. page.value = 1
  72. loadingText.value = '加载更多...'
  73. setTimeout(() => {
  74. loadProducts()
  75. isRefreshing.value = false
  76. uni.showToast({ title: '刷新成功', icon: 'success' })
  77. }, 1500)
  78. }
  79. const loadMore = () => {
  80. if (loadingText.value == '没有更多了') return
  81. loadingText.value = '加载中...'
  82. setTimeout(() => {
  83. const start = productList.value.length
  84. for (let i = 1; i <= 15; i++) {
  85. const h = 180 + Math.floor(Math.random() * 100)
  86. productList.value.push({
  87. title: '新加载的商品 ' + (start + i),
  88. image: 'https://picsum.photos/200/' + h,
  89. imgHeight: h,
  90. price: (99 + (start + i) * 10).toString(),
  91. originalPrice: (199 + (start + i) * 10).toString(),
  92. sales: (100 + (start + i) * 50).toString(),
  93. shop: shops[(start + i) % shops.length]
  94. })
  95. }
  96. page.value++
  97. if (page.value >= 5) {
  98. loadingText.value = '没有更多了'
  99. } else {
  100. loadingText.value = '加载更多...'
  101. }
  102. }, 1000)
  103. }
  104. const onScroll = (event: UniScrollEvent) => {
  105. scrollPos.value = event.detail.scrollTop
  106. }
  107. onMounted(() => {
  108. loadProducts()
  109. })
  110. </script>
复制代码

注意事项:自定义下拉刷新元素不能放在第一个子元素位置;load-more插槽的flow-item需设置type='2';鸿蒙平台滚动相关事件需API 20及以上版本,load-more插槽需API 18及以上版本;waterflow子组件仅支持flow-item。

总结:waterflow为鸿蒙平台uni-app x提供了高效的瀑布流实现,底层基于回收复用,支持多列布局、下拉刷新、上拉加载及嵌套滚动。开发时需注意鸿蒙版本兼容性和子组件限制,合理使用属性与事件即可快速构建商品列表、图片社区等场景。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-9-20 12:06 , Processed in 0.047634 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部