开天辟地 HarmonyOS(鸿蒙) - 组件(布局类): Scroll(可滚动容器)

源码 https://github.com/webabcd/HarmonyDemo
作者 webabcd

开天辟地 HarmonyOS(鸿蒙) - 组件(布局类): Scroll(可滚动容器)

示例如下:

pages\component\layout\ScrollDemo.ets

/*
 * Scroll - 可滚动容器
 */

import { MyLog, TitleBar } from '../../TitleBar';

@Entry
@Component
struct ScrollDemo {

  build() {
    Column() {
      TitleBar()
      Tabs() {
        TabContent() { MySample1() }.tabBar('基础').align(Alignment.Top)
        TabContent() { MySample2() }.tabBar('嵌套 Scroll 的滚动逻辑').align(Alignment.Top)
        TabContent() { MySample3() }.tabBar('snap').align(Alignment.Top)
        TabContent() { MySample4() }.tabBar('paging').align(Alignment.Top)
        TabContent() { MySample5() }.tabBar('事件相关').align(Alignment.Top)
        TabContent() { MySample6() }.tabBar('Scroller 相关').align(Alignment.Top)
        TabContent() { MySample7() }.tabBar('自定义 ScrollBar').align(Alignment.Top)
      }
      .scrollable(true)
      .barMode(BarMode.Scrollable)
      .layoutWeight(1)
    }
  }
}

@Component
struct MySample1 {

  private array: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

  build() {
    /*
     * Scroll - 可滚动容器
     *   enableScrollInteraction() - 是否支持手动滚动
     *   scrollable() - 滚动方向(ScrollDirection 枚举)
     *     Vertical, Horizontal, None
     *   scrollBar() - 滚动条状态(BarState 枚举)
     *     Off - 不显示
     *     On - 显示
     *     Auto - 触摸时显示(2 秒后消失)
     *   scrollBarColor() - 滚动条颜色
     *   scrollBarWidth() - 滚动条宽度
     *   initialOffset() - 滚动的初始偏移量
     *   friction() - 手动滑动时,滚动的摩擦系数,值越大越难滚
     *     非可穿戴设备默认值为 0.6,可穿戴设备默认值为 0.9
     *   flingSpeedLimit() - 跟手滑动时,松手后的的最大初始速度,默认值 12000
     *   edgeEffect() - 滚动到边缘时的效果
     *     edgeEffect - 效果(EdgeEffect 枚举)
     *       Spring - 弹性效果
     *       Fade - 圆弧阴影效果
     *       None - 无效果
     *     options - 选项
     *       alwaysEnabled - 当组件内容小于组件自身的大小时,是否开启 edgeEffect 效果
     */
    Scroll() {
      Column() {
        ForEach(this.array, (item: number) => {
          Text(item.toString()).width('80%').height(200)
            .fontSize(48).textAlign(TextAlign.Center).margin(10)
            .backgroundColor(Color.Orange).borderRadius(20)
        })
      }
    }
    .enableScrollInteraction(true)
    .scrollable(ScrollDirection.Vertical)
    .scrollBar(BarState.On)
    .scrollBarColor(Color.Blue)
    .scrollBarWidth(10)
    .initialOffset({
      xOffset: 0,
      yOffset: 100,
    })
    .friction(0.6)
    .flingSpeedLimit(12000)
    .edgeEffect(EdgeEffect.Spring, {
      alwaysEnabled: false
    })
    .width('100%')
    .backgroundColor(Color.Yellow)
  }
}

@Component
struct MySample2 {

  private array: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

  build() {
    /*
     * Scroll - 可滚动容器
     *   nestedScroll() - 嵌套 Scroll 时,在子 Scroll 指定父子的滚动逻辑
     *     scrollForward, scrollBackward - 通过手势往下滚动和往上滚动时的滚动逻辑(NestedScrollMode 枚举)
     *       SELF_ONLY - 只滚动自己,不滚动父 Scroll
     *       SELF_FIRST - 先滚动自己,滚动到边缘后,再滚动父 Scroll
     *       PARENT_FIRST - 先滚动父 Scroll,滚动到边缘后,再滚动自己
     *       PARALLEL - 自己和父 Scroll 一起滚动
     */
    Scroll() {
      Column() {
        Text("aaa").width("100%").height(200).textAlign(TextAlign.Center)
          .backgroundColor(Color.Red).fontColor(Color.White)

        Column() {
          Text("bbb").width("100%").height(50).textAlign(TextAlign.Center)
            .backgroundColor(Color.Green).fontColor(Color.White)

          Scroll() {
            Column() {
              ForEach(this.array, (item: number) => {
                Text(item.toString()).width('80%').height(200)
                  .fontSize(48).textAlign(TextAlign.Center).margin(10)
                  .backgroundColor(Color.Orange).borderRadius(20)
              })
            }
          }
          .scrollBar(BarState.Off)
          // 向下滚动时(手势向上),先是父 Scroll 整体滚动,当文本框 aaa 滚出屏幕后,子 Scroll 继续滚动
          // 向上滚动时(手势向下),先是子 Scroll 滚动,当滚动到子 Scroll 的边缘后,再父 Scroll 整体滚动
          .nestedScroll({
            scrollForward: NestedScrollMode.PARENT_FIRST,
            scrollBackward: NestedScrollMode.SELF_FIRST
          })
        }
        // 这里要指定一下高度
        // 父 Scroll 向下滚动时,文本框 aaa 滚出屏幕后,文本框 bbb 会固定在顶端,然后子 Scroll 继续滚动
        .height('100%')
      }
    }
    .scrollBar(BarState.Off)
    .backgroundColor(Color.Yellow)
  }
}

@Component
struct MySample3 {

  private array: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

  build() {
    /*
     * Scroll - 可滚动容器
     *   scrollSnap() - 滚动后的停靠逻辑(一个 ScrollSnapOptions 对象)
     *     snapPagination - 停靠点位置
     *       指定一个值,比如 100,则每隔 100 为一个停靠点
     *       指定一个数组,比如 [50, 88, 120],则一共 3 个停靠点,分别为 50, 88, 120
     *     snapAlign - 当 snapPagination 指定为一个值时的停靠方式(ScrollSnapAlign 枚举)
     *       NONE - 无停靠效果
     *       START - 每个 item 的顶端与 Scroll 的顶端对齐
     *       CENTER - 每个 item 的中间与 Scroll 的中间对齐
     *       END - 每个 item 的底端与 Scroll 的底端对齐
     *     enableSnapToStart - 当 snapPagination 指定为一个数组时,允许 Scroll 在顶端与第一个停靠点之间自由滚动
     *     enableSnapToEnd - 当 snapPagination 指定为一个数组时,允许 Scroll 在底端与最后一个停靠点之间自由滚动
     */
    Scroll() {
      Column() {
        ForEach(this.array, (item: number) => {
          Text(item.toString()).width('80%').height(150)
            .fontSize(48).textAlign(TextAlign.Center).margin(10)
            .backgroundColor(Color.Orange).borderRadius(20)
        })
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor(Color.Yellow)
    .edgeEffect(EdgeEffect.Fade)
    .scrollSnap({
      // 本例中,每个 item 是一个 Text,高度是 150,上 margin 是 10,下 margin 是 10,所以每个 item 的高度是 170
      snapPagination: 170,
      snapAlign: ScrollSnapAlign.CENTER,
      enableSnapToStart: true,
      enableSnapToEnd: true
    })
  }
}

@Component
struct MySample4 {

  private array: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

  build() {
    /*
     * Scroll - 可滚动容器
     *   enablePaging() - 是否支持翻页模式
     */
    Scroll() {
      Column() {
        ForEach(this.array, (item: number) => {
          Text(item.toString()).width('100%').height('100%')
            .fontSize(128).fontColor(Color.Orange).textAlign(TextAlign.Center)
            .backgroundColor(`#${item}${item}${item}${item}${item}${item}`)
        })
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor(Color.Yellow)
    .edgeEffect(EdgeEffect.None)
    .enablePaging(true)
  }
}

@Component
struct MySample5 {

  @State message: string = ""

  private array: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

  build() {
    Stack({ alignContent: Alignment.TopStart }) {

      /*
       * Scroll - 可滚动容器
       *   onScrollStart - 滚动开始时的回调
       *   onScrollStop - 滚动结束时的回调
       *   onScrollFrameBegin - 滚动过程中,每一帧开始前的回调
       *     offset - 即将发生的滑动量
       *     scrollState - 当前滚动状态(ScrollState 枚举)
       *       Idle - 无滚动(onScrollFrameBegin 不会进入此状态)
       *       Scroll - 滚动中且手指触摸中
       *       Fling - 惯性滚动中,手指不在触摸中
       *     返回值 { offsetRemain: offset } 用于指定实际滚动量,允许在这里自定义实际滚动量
       *   onWillScroll - 滚动过程中的回调(在每一帧滚动之前)
       *     xOffset - 水平滚动量
       *     yOffset - 垂直滚动量
       *     scrollState - 当前滚动状态(ScrollState 枚举)
       *       Idle - 无滚动(onWillScroll 不会进入此状态)
       *       Scroll - 滚动中且手指触摸中
       *       Fling - 惯性滚动中,手指不在触摸中
       *     scrollSource - 产生滚动的原因
       *   onDidScroll - 滚动过程中的回调(在每一帧滚动之后)
       *     xOffset - 水平滚动量
       *     yOffset - 垂直滚动量
       *     scrollState - 当前滚动状态(ScrollState 枚举)
       *       Idle - 无滚动(onDidScroll 可能会进入此状态)
       *       Scroll - 滚动中且手指触摸中
       *       Fling - 惯性滚动中,手指不在触摸中
       *   onScrollEdge - 滚动到边缘时的回调
       *     side - 边缘类型(Edge 枚举)
       *       Top, Bottom, Start, End
       *   onReachStart - 滚动到顶部时的回调
       *   onReachEnd - 滚动到底部时的回调
       */
      Scroll() {
        Column() {
          ForEach(this.array, (item: number) => {
            Text(item.toString()).width('80%').height(200)
              .fontSize(48).textAlign(TextAlign.Center).margin(10)
              .backgroundColor(Color.Orange).borderRadius(20)
          })
        }
      }
      .onScrollFrameBegin((offset: number, scrollState: ScrollState) => {
        this.message = `onScrollFrameBegin offset:${offset}, scrollState:${scrollState}`
        MyLog.d(this.message)
        // 指定实际滚动量,允许在这里自定义实际滚动量
        return { offsetRemain: offset };
      })
      .onWillScroll((xOffset: number, yOffset: number, scrollState: ScrollState, scrollSource: ScrollSource) => {
        this.message = `onWillScroll xOffset:${xOffset}, yOffset:${yOffset}, scrollState:${scrollState}, scrollSource:${scrollSource}`
        MyLog.d(this.message)
      })
      .onDidScroll((xOffset: number, yOffset: number, scrollState: ScrollState) => {
        this.message = `onDidScroll xOffset:${xOffset}, yOffset:${yOffset}, scrollState:${scrollState}`
        MyLog.d(this.message)
      })
      .onScrollStart(() => {
        this.message = `onScrollStart`
        MyLog.d(this.message)
      })
      .onScrollStop(() => {
        this.message = `onScrollStop`
        MyLog.d(this.message)
      })
      .onScrollEdge((side: Edge) => {
        this.message = `onScrollEdge side:${side}`
        MyLog.d(this.message)
      })
      .onReachStart(() => {
        this.message = `onReachStart`
        MyLog.d(this.message)
      })
      .onReachEnd(() => {
        this.message = `onReachEnd`
        MyLog.d(this.message)
      })
      .width('100%')
      .height('100%')

      Text(this.message).fontSize(16).fontColor(Color.White).backgroundColor(Color.Blue)
    }
    .width('100%')
    .height('100%')
    .backgroundColor(Color.Yellow)
    .alignContent(Alignment.Center)
  }
}

@Component
struct MySample6 {

  @State message: string = ""

  /*
   * Scroller 是一个 controller,是用于和 Scroll 交互的,声明式编程通常都会用这种方式
   */
  scroller: Scroller = new Scroller()
  private array: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

  build() {
    Stack({ alignContent: Alignment.TopStart }) {
      // 指定 Scroll 需要绑定的 Scroller
      Scroll(this.scroller) {
        Column() {
          ForEach(this.array, (item: number) => {
            Text(item.toString()).width('80%').height(200)
              .fontSize(48).textAlign(TextAlign.Center).margin(10)
              .backgroundColor(Color.Orange).borderRadius(20)
          })
        }
      }
      .width('100%')
      .height('100%')

      Column({space:10}) {

        /*
         * Scroller - 用于和绑定的 Scroll 之间的交互
         *   scrollBy() - 滚动到指定的位置(无动画)
         *   scrollEdge() - 滚动到指定的边缘位置(Edge 枚举),可以指定滚动速度(默认值是 0 会自动计算滚动速度)
         *     Top, Bottom, Start, End
         *   scrollPage() - 按页滚动
         *     next: true 则滚动到下一页
         *     next: false 则滚动到上一页
         *   scrollTo() - 滚动到指定的位置(有动画)
         *   scrollToIndex() - 滚动到指定索引位置的 item(仅 Grid, List, WaterFlow 有效)
         *   fling() - 指定初始加速度并滚动
         *     负数则往下滚动,正数则往上滚动
         *   currentOffset() - 获取当前的滚动位置
         *     xOffset, yOffset
         *   isAtEnd() - 是否滚动到底部了
         *   getItemRect() - 获取指定索引位置的 item 的位置和大小(仅 Grid, List, WaterFlow 有效)
         */

        Button('scrollBy 150')
          .onClick(() => {
            this.scroller.scrollBy(0, 150)
          })
        Button('scrollEdge top')
          .onClick(() => {
            this.scroller.scrollEdge(Edge.Top, { velocity: 0 })
          })
        Button('scrollPage next')
          .onClick(() => {
            this.scroller.scrollPage({ next: true })
          })
        Button('scrollTo 500')
          .onClick(() => {
            this.scroller.scrollTo({
              xOffset: 0,
              yOffset: 500,
              animation: {
                duration: 1000,
                curve: Curve.Ease
              }
            })
          })
        Button('fling(-1000)')
          .onClick(() => {
            this.scroller.fling(-1000)
          })
        Button('currentOffset()')
          .onClick(() => {
            const currentOffset = this.scroller.currentOffset()
            this.message = `currentOffset() xOffset:${currentOffset.xOffset}, yOffset:${currentOffset.yOffset}`
          })

        Text(this.message).fontSize(16).fontColor(Color.White).backgroundColor(Color.Blue)
      }
      .hitTestBehavior(HitTestMode.None)
      .alignItems(HorizontalAlign.Start)
    }
    .width('100%')
    .height('100%')
    .backgroundColor(Color.Yellow)
  }
}

@Component
struct MySample7 {

  scroller: Scroller = new Scroller()
  private array: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

  build() {
    Column() {
      Stack({ alignContent: Alignment.TopStart }) {
        Scroll(this.scroller) {
          Column() {
            ForEach(this.array, (item: number) => {
              Text(item.toString()).width('80%').height(200)
                .fontSize(48).textAlign(TextAlign.Center).margin(10)
                .backgroundColor(Color.Orange).borderRadius(20)
            })
          }
        }
        .scrollBar(BarState.Off)
        .width('100%')
        .height('100%')

        /*
         * ScrollBar - 自定义滚动条(List, Grid, Scroll 等可滚动组件均可使用此方法自定义滚动条)
         *   scroller - 需要绑定的 Scroller(关联的可滚动组件也要绑定同一个 Scroller)
         *   direction - 滚动方向(ScrollBarDirection 枚举)
         *     Vertical, Horizontal
         *   state - 滚动条状态(BarState 枚举)
         *     Off - 不显示
         *     On - 显示
         *     Auto - 触摸时显示(2 秒后消失)
         *   width() - 滚动条宽度
         *   backgroundColor() - 滚动条的轨道颜色
         *   子组件 - 自定义滚动条滑块
         */
        ScrollBar({
          scroller: this.scroller,
          direction: ScrollBarDirection.Vertical,
          state: BarState.On
        }) {
          Column()
            .width(20)
            .height(100)
            .borderRadius(10)
            .backgroundColor(Color.Blue)
        }
        .width(20)
        .backgroundColor(Color.Gray)
      }
      .width('100%')
      .height('100%')
      .backgroundColor(Color.Yellow)
    }
  }
}

源码 https://github.com/webabcd/HarmonyDemo
作者 webabcd

posted @ 2025-02-05 14:23  webabcd  阅读(124)  评论(0)    收藏  举报