HarmonyOS鸿蒙Next中无论Column高度是否超过Scroll,我们都希望Column的起始位置总是从Scroll的顶部开始显示(居上显示)。如何实现这种效果?

HarmonyOS鸿蒙Next中无论Column高度是否超过Scroll,我们都希望Column的起始位置总是从Scroll的顶部开始显示(居上显示)。如何实现这种效果? 在鸿蒙ArkUI的Scroll组件中,如果只包含一个Column子组件时,需要Column的高度超过Scroll本身才可滚动。

  • 通常情况:Column的高度由其内部内容决定。内容多则Column高(大于Scroll),会可滚动;内容少则Column矮(小于Scroll),不会滚动。这本身符合预期。

  • 当前问题:当Column高度不足(内容少)时,Column会在Scroll的可视区域内垂直居中显示。无法顶部对齐(居上显示),Scroll无法设置子组件的对齐方式。

  • 需求:无论Column高度是否超过Scroll,我们都希望Column的起始位置总是从Scroll的顶部开始显示(居上显示)。如何实现这种效果?


更多关于HarmonyOS鸿蒙Next中无论Column高度是否超过Scroll,我们都希望Column的起始位置总是从Scroll的顶部开始显示(居上显示)。如何实现这种效果?的实战教程也可以访问 https://www.itying.com/category-93-b0.html

4 回复

Column 使用以下属性设置约束尺寸,组件布局时,进行尺寸范围限制。

.constraintSize({ minHeight: '100%', maxHeight: Infinity })

Scroll 使用以下属性可设置其子组件的对齐方式为顶对齐

.align(Alignment.Top)

更多关于HarmonyOS鸿蒙Next中无论Column高度是否超过Scroll,我们都希望Column的起始位置总是从Scroll的顶部开始显示(居上显示)。如何实现这种效果?的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


【背景知识】

Scroll组件是容器组件,支持垂直和水平方向的滚动,可以实现组件内元素的前后滚动,让页面展示更多更丰富的内容。由于Scroll组件内仅支持一个子组件,一般搭配Column、Row、List、Grid等组件使用。

要实现Scroll组件的某个元素的吸顶效果,可以使用nestedScroll属性,对父子组件的向前、向后滑动设置嵌套滚动模式,实现组件与父组件的滚动联动。

【解决方案】

基于以上背景知识,吸顶效果可以通过对组件设置nestedScroll属性实现。通过改变参数的值,使父组件在向前滚动到边缘时触发边缘效果(固定在边缘)。一般而言,nestedScroll属性的参数值会设置为:

Scroll() {...}
......
.nestedScroll({
  scrollForward: NestedScrollMode.PARENT_FIRST, // 向前滚动时父组件先滚动
  scrollBackward: NestedScrollMode.SELF_FIRST // 向后滚动时组件自身先滚动
})

需要注意的是,nestedScroll是将组件与父组件进行嵌套,所以在实际开发中,要明确父组件的范围和实际效果。

以下分别以Tabs组件和List组件为例实现吸顶效果,并给出一个错误示例用于对比。

  • 实现Tabs组件的TabBar吸顶的效果
@Entry
@Component
struct ScrollCeiling1 {
  scroller: Scroller = new Scroller()
  itemData: Array<number> = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  tabTitles: Array<string> = ['Tab1', 'Tab2', 'Tab3']

  @Builder
  tabContentData(tabTitle: string) {
    TabContent() {
      List() {
        ForEach(this.itemData, (item: number) => {
          ListItem() {
            Text(`${ item }`).height(80).width('100%').textAlign(TextAlign.Center).backgroundColor(0xDDDDDD).margin({bottom: 5})
          }
        })
      }
      .nestedScroll({
        scrollForward: NestedScrollMode.PARENT_FIRST,
        scrollBackward: NestedScrollMode.SELF_FIRST
      })
    }.tabBar(tabTitle)
    .padding({top:5, bottom:5})
    .borderWidth(1)
    .borderColor(Color.Red)
  }

  /*
  设置scrollForward的滚动模式为NestedScrollMode.PARENT_FIRST:
  当控制List内元素向前滚动时,其父组件TabContent先滚动,覆盖Scroll组件嵌套的Column组件内的Image组件,随后Tabs组件触碰顶部边缘,触发边缘效果,从而固定在顶部
  设置scrollBackward的滚动模式为NestedScrollMode.SELF_FIRST:
  当控制List内元素向后滚动时,List的内容先滚动,直至滚动到List最顶部后,父组件TabContent开始滚动
  */

  build() {
    Scroll(this.scroller){
      Column() {
        Image($r('app.media.app_icon')).height(70)

        Tabs() {
          ForEach(this.tabTitles, (title: string) => {
            this.tabContentData(title)
          })
        }
        .borderWidth(2)
      }.width('90%')
      .alignItems(HorizontalAlign.Center)
    }.width('100%')
    .align(Alignment.Center)
    .scrollBar(BarState.Off)
  }
}
  • 实现List组件吸顶的效果
@Component
struct ScrollCeiling2 {
  scroller: Scroller = new Scroller()
  itemData: Array<number> = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  classList: Array<string> = ['class1', 'class2']

  build() {
    Scroll(this.scroller) {
      Column({space : 10}) {
        // 搜索框
        Stack({alignContent: Alignment.End}) {
          Row() {
            Image($r('sys.media.ohos_ic_public_search_filled')).height(20)
              .margin({left:5})

            TextInput({placeholder: '请输入'}).type(InputType.Normal).fontSize('10fp').backgroundColor(Color.Transparent)
          }.height(50).width('100%')
          .borderWidth('1')
          .borderRadius(24)
          .padding({left:5, right:5, top:5, bottom:5})

          Button('搜索').type(ButtonType.Capsule).width(80).margin({right:5})
        }

        Column() {
          // Class_List
          List() {
            ForEach(this.classList, (cls: string) => {
              ListItem() {
                Text(cls).fontSize('20fp').width(100).textAlign(TextAlign.Center)
                  .borderWidth(1).borderRadius(12).margin({left:5, right:5})
              }
            })
          }.listDirection(Axis.Horizontal).height(30)

          /*
          设置scrollForward的滚动模式为NestedScrollMode.PARENT_FIRST:
          当控制Data_List内元素向前滚动时,其父组件Column先滚动,覆盖Scroll组件嵌套的Column组件内的Stack组件(搜索框),随后Column组件触碰顶部边缘,触发边缘效果,从而将Class_List固定在顶部
          设置scrollBackward的滚动模式为NestedScrollMode.SELF_FIRST:
          当控制Data_List内元素向后滚动时,Data_List的内容先滚动,直至滚动到Data_List最顶部后,父组件Column开始滚动
          */

          // Data_List
          List() {
            ForEach(this.itemData, (item: number) => {
              ListItem() {
                Text(`${ item }`).height(80).width('100%').textAlign(TextAlign.Center).backgroundColor(0xDDDDDD).margin({bottom: 5})
              }
            })
          }.height('90%')
          .nestedScroll({
            scrollForward: NestedScrollMode.PARENT_FIRST,
            scrollBackward: NestedScrollMode.SELF_FIRST
          })
        }.height('100%')
      }
    }.scrollBar(BarState.Off)
  }
}
  • 错误示例
@Component
struct ScrollCeiling3 {
  scroller: Scroller = new Scroller()
  itemData: Array<number> = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  classList: Array<string> = ['class1', 'class2']

  build() {
    Scroll(this.scroller) {
      Column({space : 10}) {
        // 搜索框
        Stack({alignContent: Alignment.End}) {
          Row() {
            Image($r('sys.media.ohos_ic_public_search_filled')).height(20)
              .margin({left:5})

            TextInput({placeholder: '请输入'}).type(InputType.Normal).fontSize('10fp').backgroundColor(Color.Transparent)
          }.height(50).width('100%')
          .borderWidth('1')
          .borderRadius(24)
          .padding({left:5, right:5, top:5, bottom:5})

          Button('搜索').type(ButtonType.Capsule).width(80).margin({right:5})
        }

        // Class_List
        List() {
          ForEach(this.classList, (cls: string) => {
            ListItem() {
              Text(cls).fontSize('20fp').width(100).textAlign(TextAlign.Center)
                .borderWidth(1).borderRadius(12).margin({left:5, right:5})
            }
          })
        }.listDirection(Axis.Horizontal).height(30)

        /*
        Class_List和Data_List的外层没有另外嵌套一层Column组件,此时,Class_List和Data_List的父组件为Scroll嵌套的Column组件
        当控制Data_List内元素向前滚动时,基于nestedScroll的设置,父组件Column先滚动
        Column组件顶部已处于显示边缘,所以Column组件完成滚动,开始其子组件开始滚动,由于所有组件都在Column组件内,且Data_List的高度被设置为100%,所以Class_List会随着向前滚动而开始退出显示边缘,无法实现吸顶效果
        */
        // Data_List
        List() {
          ForEach(this.itemData, (item: number) => {
            ListItem() {
              Text(`${ item }`).height(80).width('100%').textAlign(TextAlign.Center).backgroundColor(0xDDDDDD).margin({bottom: 5})
            }
          })
        }.height('100%')
        .nestedScroll({
          scrollForward: NestedScrollMode.PARENT_FIRST,
          scrollBackward: NestedScrollMode.SELF_FIRST
        })
      }
    }.scrollBar(BarState.Off)
  }
}

有趣的是,假如将示例中的Data_List的高度设置为小于100%(比如93%),因为Data_List高度无法占满屏幕,Class_List会有部分露出甚至整个露出,可能出现与吸顶相似的效果。

【总结】

将Scroll组件中的某个元素固定在首位,可以通过对组件设置nestedScroll属性,与正确的父组件绑定嵌套滚动模式,并将scrollForward参数设置为NestedScrollMode.PARENT_FIRST,将scrollBackward设置为NestedScrollMode.SELF_FIRST,确定向前滚动和向后滚动的滚动模式,从而实现Scroll组件内某个元素的吸顶效果。

在HarmonyOS Next中,要实现Column始终从Scroll顶部开始显示,可以使用Scroll的alignContent属性。将alignContent设置为FlexAlign.Start即可强制内容顶部对齐。示例代码如下:

Scroll() {
  Column() {
    // 内容组件
  }
  .width('100%')
}
.scrollable(ScrollDirection.Vertical)
.alignContent(FlexAlign.Start)
.width('100%')
.height('100%')

该方法适用于API 9及以上版本,无需考虑Column内容高度。

在HarmonyOS Next中实现Column始终从Scroll顶部开始显示的效果,可以通过以下方式解决:

  1. 使用Stack布局包裹Scroll和Column,并设置Column的对齐方式:
Stack({ alignContent: Alignment.Top }) {
  Scroll() {
    Column() {
      // 内容组件
    }
    .width('100%')
  }
}
.width('100%')
.height('100%')
  1. 另一种方法是给Column设置alignItems属性:
Scroll() {
  Column() {
    // 内容组件
  }
  .width('100%')
  .alignItems(HorizontalAlign.Start)
}
  1. 如果内容可能动态变化,可以强制设置Column的最小高度:
Scroll() {
  Column() {
    // 内容组件
  }
  .width('100%')
  .minHeight(Scroll的高度值)
}

这些方法都能确保Column内容始终从Scroll顶部开始显示,而不受内容多少影响。第一种Stack方案是最可靠的实现方式。

回到顶部