
回复
天下产品一大”借鉴“,可以发现某宝、东、鱼等APP的首页菜单,横向滚动第一页是一行展示5个完整的菜单和一小部分第二页的一个菜单,第二页是三行展示菜单,左右滑动可动态调整菜单栏的高度。
看一下模仿实现效果:
实现思路:
1.首先想到使用Swiper+Grid实现,通过设置Swiper的nextMargin可以显示出第二页的一部分,但是由于设置了该属性,滑动过程中第一页和第二页的Grid间隔是一样的,和目标不一致,如果滑动时修改,连贯性不好,因此该方案被否定
2.使用横向的List+Grid实现,一个ListItem+一个Grid实现,可以设置第一个ListItem的宽度不充满屏幕,即可露出第二Item
3.观察发现菜单高度最小和最大即两个Grid的高度,因此只要根据滑动距离,动态设置菜单的高度
4.计算横向滚动偏移量总和:当前水平滑动偏移 + 新增偏移量
5.动态变化的高度 = (横向滚动偏移/ 第一个ListItem的宽度) * 菜单高度最大和最小的高度差
6.根据计算结果更新容器高度(当前高度 = 最小高度 + 动态变化高度)
实现源码:
let tabs:string[]=['首页','推荐','其他'];
let numberList: number[][] = [
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
];
//实现根据内容高度随滑动距离变动的效果
@Entry
@ComponentV2
struct Index {
private listScroller: ListScroller = new ListScroller()
@Local currentIndex: number = 0
@Local containerWidth:number = 0 ; // 横向List容器宽度
@Local containerHeight: number = 0 // 横向List容器的高度 根据滑动动态改变
@Local containerMaxHeight: number = 0 //第二页 三行内容的高度
@Local containerMinHeight: number = 0 //第一页 一行内容的高度
build() {
Column() {
//顶部Tab标签
Row(){
Tabs(){
ForEach(tabs,(item: string, index:number)=>{
TabContent() {
}.tabBar(item)
})
}
.width('40%')
.height('auto')
}.width('100%')
//滚动菜单
Scroll(){
List({ scroller: this.listScroller }){
ListItem(){
Child({numberList:numberList[0],childGridHeight:(contentHeight:number)=>{
// 单行高度
this.containerMinHeight = contentHeight
this.containerHeight = contentHeight
console.log('单行高度:'+contentHeight)
}})
}.width('90%')
ListItem(){
Child({numberList:numberList[1],childGridHeight:(contentHeight:number)=>{
// 三行高度
this.containerMaxHeight =contentHeight
console.log('最高高度:'+contentHeight)
}})
}.width('100%')
}
.scrollBar(BarState.Off)
.listDirection(Axis.Horizontal)
.scrollSnapAlign(ScrollSnapAlign.START)
.edgeEffect(EdgeEffect.None)
.onAreaChange( (oldValue: Area, newValue: Area) =>{
if (!this.containerWidth) {
this.containerWidth = Number(newValue.width)
// this.containerHeight = Number(newValue.height)
}
})
.onWillScroll((scrollOffset: number, scrollState: ScrollState, scrollSource: ScrollSource) => {
console.log('当前滚动位置:'+this.listScroller.currentOffset().xOffset)
// 计算横向滚动偏移量总和:当前水平滑动偏移 + 新增偏移量
let offsetX = this.listScroller.currentOffset().xOffset + scrollOffset
console.log('list滚动偏移量:'+offsetX)
if (offsetX > 0) {
//动态变化的高度 = (横向滚动偏移/ 第一个ListItem的宽度) * 菜单高度最大和最小的高度差
let h = offsetX / (this.containerWidth*0.9) * (this.containerMaxHeight - this.containerMinHeight)
// 根据计算结果更新容器高度(当前高度 = 最小高度 + 动态变化高度)
this.containerHeight = this.containerMinHeight + h
} else {
this.containerHeight = this.containerMinHeight
}
console.log('list动态计算的高度:'+ this.containerHeight )
})
.onScrollIndex((start, end) => {
this.currentIndex = start
})
}.height(this.containerHeight || 'auto')
//菜单页指示器
Row({ space: 5 }) {
ForEach(new Array(2).fill(0), (item: number, index: number) => {
Circle()
.width(5)
.height(5)
.fill(this.currentIndex === index ? '#0A59F7' : '#660a59f7')
.visibility(index === 0 ? Visibility.Visible : Visibility.None)
.onClick(() => {
this.listScroller.scrollToIndex(index, true)
this.currentIndex = index
})
Image($r('app.media.slide_fill'))
.borderRadius(5)
.width(15)
.height(5)
.fillColor(this.currentIndex === index ? '#0A59F7' : '#660a59f7')
.visibility(index === 1 ? Visibility.Visible : Visibility.None)
.onClick(() => {
this.listScroller.scrollToIndex(index, true)
this.currentIndex = index
})
})
}
.margin({ top: 12, bottom: 16 })
.height(8)
//其他内容
Scroll(){
Column(){
Image($r('app.media.img_swiper_heght_test')).width('100%').objectFit(ImageFit.Fill)
}
}
}
}
}
@ComponentV2
struct Child {
@Require @Param numberList: number[];
@Event childGridHeight:(contentHeight:number)=>void
build() {
Grid() {
ForEach(this.numberList, (item: number) => {
GridItem() {
Column() {
Image($r('app.media.startIcon'))
.width(40)
.height(40)
Text('Menu' + item)
.fontSize(15)
}
}
})
}
.rowsGap(20)
.scrollBar(BarState.Off)
.columnsTemplate('1fr 1fr 1fr 1fr 1fr')
.onAreaChange( (oldValue: Area, newValue: Area) =>{
//保证只赋值一次
if (oldValue.height==0) {
this.childGridHeight(Number(newValue.height))
}
})
}
}