grid-hybrid项目 gitee.com/harmonyos_s...
此项目交互场景相对复杂,可作为实际应用交互场景的草稿,进一步完善。譬如百度文库的UI交互。
一、GridNestListIndex页面交互
-- 多种控件的Scroller滚动联动
项目中GridNestListIndex 页面通过以下方式实现了滚动联动效果:
1. 核心组件与状态
Scroll组件:用于页面的整体滚动。List组件:用于展示网格项列表。Scroller对象 :scrollerForScroll:控制页面整体滚动。scrollerForList:控制列表滚动。scrollerForTitle:控制标题栏的横向滚动。
- 状态变量 :
listPosition:记录列表的滚动位置(顶部、中间、底部)。scrollPosition:记录页面的滚动位置(顶部、中间、底部)。currentIndex:记录当前显示的列表项的索引。
2. 滚动联动实现逻辑
(1) 列表滚动触发标题栏联动
onScrollIndex事件 :- 当列表滚动时,通过
onScrollIndex获取当前显示的第一个子组件的索引start。 - 更新
currentIndex状态,并调用scrollerForTitle.scrollToIndex同步标题栏的滚动位置。
- 当列表滚动时,通过
(2) 标题栏点击触发列表联动
- 标题栏点击事件 :
- 点击标题栏时,调用
scrollerForList.scrollToIndex滚动列表到对应索引。 - 同时调用
scrollerForScroll.scrollEdge(Edge.Bottom)确保页面滚动到底部(避免标题栏遮挡列表)。 - 更新
currentIndex状态。
- 点击标题栏时,调用
(3) 页面滚动与列表滚动联动
onScrollFrameBegin事件 :- 当列表滚动时,通过
onScrollFrameBegin计算偏移量offset。 - 如果页面已滚动到底部(
scrollPosition === ScrollPosition.end),且列表不在顶部或向上滚动,则允许列表继续滚动。 - 否则,通过
scrollerForScroll.scrollBy同步页面的滚动。
- 当列表滚动时,通过
(4) 页面滚动状态同步
onWillScroll和onScrollEdge事件 :- 监听页面滚动方向,更新
scrollPosition状态(顶部、中间、底部)。 - 当页面滚动到顶部或底部时,禁止进一步滚动(通过
onScrollFrameBegin返回offsetRemain: 0)。
- 监听页面滚动方向,更新
3. 关键代码片段
-
列表滚动索引同步:
typescript.onScrollIndex((start: number) => { this.currentIndex = start; this.scrollerForTitle.scrollToIndex(this.currentIndex); }) -
标题栏点击联动:
typescript.onClick(() => { this.scrollerForList.scrollToIndex(index); this.scrollerForScroll.scrollEdge(Edge.Bottom); this.scrollerForTitle.scrollToIndex(index); this.currentIndex = index; }) -
页面与列表滚动同步:
typescript.onScrollFrameBegin((offset: number) => { if (this.scrollPosition === ScrollPosition.end && (this.listPosition != ScrollPosition.start || offset > 0)) { return { offsetRemain: offset }; } else { this.scrollerForScroll.scrollBy(0, offset); return { offsetRemain: offset }; } })
4. 总结
通过 Scroller 对象和状态变量的协同工作,GridNestListIndex 实现了以下联动效果:
- 列表滚动时,标题栏自动同步到对应位置。
- 点击标题栏时,列表滚动到对应项,并确保页面布局正确。
- 页面滚动时,动态调整列表的滚动行为,避免冲突。
这种设计确保了用户体验的流畅性和一致性。
-- Grid的样式设置
在 GridComponent.ets 文件中,Grid 组件的行列数、宽高和布局是通过以下方式控制的:
1. 行列数控制
(1) 列数控制
columnsTemplate属性 :- 通过
CommonConstants.GRID_COLUMNS_TEMPLATE定义列模板。 - 例如:
'1fr 1fr 1fr 1fr'表示 4 列,每列宽度均分剩余空间。
- 通过
(2) 行数动态计算
- 动态计算 :
- 行数由
gridData.gridItemList.length和列数决定。 - 例如:
Math.ceil(this.gridData.gridItemList.length / 4)表示每行 4 个项,计算总行数。
- 行数由
2. 宽高控制
(1) 宽度
width属性 :-
默认继承父容器宽度(
CommonConstants.FULL_PERCENT)。 -
可通过
margin调整左右边距:typescript.margin({ left: $r('app.float.grid_left_margin'), right: $r('app.float.grid_right_margin') })
-
(2) 高度
height属性 :-
通过
getGridHeight()方法动态计算:typescript.height(this.getGridHeight()) -
计算逻辑 :
-
每行高度固定为
68(网格项高度)。 -
行间距为
8。 -
上下边距为
12 * 2。 -
公式:
typescriptgridHeight += Math.ceil(this.gridData.gridItemList.length / 4) * 68; // 行高 gridHeight += (Math.ceil(this.gridData.gridItemList.length / 4) - 1) * 8; // 行间距 gridHeight += 12 * 2; // 上下边距
-
-
3. 间距与对齐
(1) 行间距
rowsGap属性 :- 通过
$r('app.float.grid_rows_gap_size')设置行间距。
- 通过
(2) 内边距
padding属性 :- 通过
$r('app.float.grid_padding_size')设置内边距。
- 通过
(3) 圆角
borderRadius属性 :- 通过
$r('app.float.grid_border_radius')设置圆角。
- 通过
4. 关键代码片段
typescript
Grid() {
ForEach(this.gridData.gridItemList, (item: string) => {
GridItemComponent({ itemName: item })
})
}
.columnsTemplate(CommonConstants.GRID_COLUMNS_TEMPLATE) // 列模板
.margin({ left: $r('app.float.grid_left_margin'), right: $r('app.float.grid_right_margin') }) // 左右边距
.height(this.getGridHeight()) // 动态高度
.rowsGap($r('app.float.grid_rows_gap_size')) // 行间距
.padding($r('app.float.grid_padding_size')) // 内边距
.borderRadius($r('app.float.grid_border_radius')) // 圆角
5. 总结
- 列数 :由
columnsTemplate定义(如 4 列)。 - 行数:动态计算(根据数据项数和列数)。
- 宽度 :继承父容器,通过
margin调整边距。 - 高度:动态计算(行高 + 行间距 + 边距)。
- 间距与样式 :通过
rowsGap、padding等属性控制。
这种设计确保了网格布局的灵活性和响应性。
二、GridNestSwiperIndex页面交互
在 GridNestSwiperIndex.ets 文件中,页面通过 Swiper 和 WaterFlow 组件的协同工作,实现了一个动态的混合布局效果。以下是页面的完整解析:
1. 页面结构与功能
(1) 整体布局
- 顶层容器 :
Column纵向排列,包含Swiper(顶部滑动菜单)和WaterFlow(下方瀑布流内容)。 - 交互逻辑 :
Swiper切换分类时,动态调整自身高度,间接影响WaterFlow的展示空间。WaterFlow根据数据源和分区配置,动态渲染内容项。
(2) 核心组件
| 组件 | 作用 | 数据依赖 | 动态控制点 |
|---|---|---|---|
Swiper |
横向滑动菜单 | GRID_COL_LIST |
swiperDistance(高度) |
WaterFlow |
瀑布流内容展示 | WATER_FLOW_DATA |
sections(分区配置) |
2. 数据驱动设计
(1) Swiper 分页与高度
-
分页数据 :
GRID_COL_LIST定义分页内容和数量,例如:typescriptGRID_COL_LIST = [ ["推荐", "热门"], // 第一页 ["最新", "分类"] // 第二页 ] -
高度控制 :
通过滑动事件 (onGestureSwipe) 和动画回调 (onAnimationStart) 动态设置swiperDistance:typescript.onGestureSwipe((index: number) => { this.swiperDistance = (index === 0) ? SWIPER_HEIGHT_SIZE2 : SWIPER_HEIGHT_SIZE1; })
(2) WaterFlow 分区与内容
-
数据源 :
WATER_FLOW_DATA提供内容项数据(如图片资源),通过WaterFlowDataSource封装。 -
分区配置 :
sections定义布局规则,例如:typescriptsections = [ { itemsCount: 1, crossCount: 1 }, // 单栏Banner { itemsCount: 6, crossCount: 2 } // 双栏商品列表 ] -
动态渲染 :
使用LazyForEach按需生成FlowItem:typescriptLazyForEach(this.waterFlowDataSource, (item: Resource) => { FlowItem() { Image(item) // 绑定数据 } })
3. 关键交互流程
-
初始化:
- 加载
GRID_COL_LIST和WATER_FLOW_DATA。 - 配置
sections分区。
- 加载
-
用户滑动
Swiper:- 触发
onGestureSwipe→ 更新swiperDistance。 - 动画过渡高度变化。
- 触发
-
WaterFlow响应:- 高度变化后,
layoutWeight(1)自动调整内容区域空间。 - 根据当前分区重新布局项。
- 高度变化后,
4. 设计优势与扩展性
(1) 优势
- 性能优化 :
LazyForEach懒加载 + 动画平滑过渡。 - 灵活配置 :通过修改
sections或数据源即可调整布局。
(2) 扩展建议
- 动态数据 :通过接口异步更新
GRID_COL_LIST和WATER_FLOW_DATA。 - 高度自适应 :根据
WaterFlow内容高度动态计算swiperDistance。
如果需要进一步调整或扩展功能,请具体说明需求!