鸿蒙开发实践——ArkUI组件嵌套滚动优化实践

1. 典型场景

新闻类页面:内容页为Web组件实现,评论区使用ArkUI原生List实现。

主题等应用主页:上半区为推荐页,下半区为分类推荐页(分类栏滑动到顶部后不会消失)。

2. 实现思路(以新闻类为例)

使用Scroll嵌套Web和List组件实现。Scroll作为父组件响应滚动手势,Web和List组件禁用滚动手势,滚动偏移量由父组件Scroll给Web和List组件派发。

2.1 派发逻辑:

手势向上滑动

1)如果web没有滚动到底部,则Scroll将滚动偏移量派发给web,Scroll组件本身不滚动

2)如果web滚动到底部,Scroll没有滚动到底部,则Scroll自身滚动,不给Web和List派发滚动偏移量

3)如果Scroll滚动到底部,则滚动偏移量派发给List,Scroll组件本身不滚动

手势向下滑动

1)如果List没有滚动到顶部,则Scroll将滚动偏移量派发给List,Scroll组件本身不滚动

2)如果List滚动到顶部,Scroll没有滚动到顶部,则Scroll自身滚动,不给Web和List派发滚动偏移量

3)如果Scroll滚动到顶部,则滚动偏移量派发给Web,Scroll组件本身不滚动

3. 关键实现

3.1 禁用Web滚动手势

.onGestureRecognizerJudgeBegin((event: BaseGestureEvent, current: GestureRecognizer, others: Array<GestureRecognizer>) => {
  if (current.isBuiltIn() && current.getType() == GestureControl.GestureType.PAN_GESTURE) {
    return GestureJudgeResult.REJECT
  }
  return GestureJudgeResult.CONTINUE
})

 3.2 禁用List组件滚动手势

enableScrollInteraction(false)

3.3 List和Scroll组件滚动边界判断

滚动到上边界:

scroller.currentOffset().yOffset <= 0

滚动到下边界: 

scroller.isAtEnd() == true

3.4 Web组件滚动边界判断

获取Web组件自身高度:

webController.getPageHeight()

获取Web组件内容高度:

webController?.runJavaScriptExt('window.innerHeight')

获取Web组件的滚动偏移量:

webController?.runJavaScriptExt('document.documentElement.scrollTop || document.body.scrollTop')

3.5 禁用Scroll组件滚动

Scroll组件绑定onScrollFrameBegin事件,将剩余偏移量设置为0

3.6 滚动偏移量派发

通过对应组件滚动控制器的scrollBy方法设置

4. 实例代码

import webview from '@ohos.web.webview'
@Entry
@ComponentV2
struct MyComponent {
  scroller:Scroller = new Scroller()
  listScroller:Scroller = new Scroller()
  webController:webview.WebviewController = new webview.WebviewController()
  isWebEnd:boolean = false
  webHeight:number = 0
  scrollTop:number = 0
  @Local arr:Array<number> = []

  aboutToAppear() {
    for (let i = 0; i <= 100; i++) {
      this.arr.push(i)
    }
  }

  getWebHeight() {
    try{
      this.webController?.runJavaScriptExt('window.innerHeight',
        (error, result) => {
          if (error || !result) {
            return
          }
          if (result.getType() === webview.JsMessageType.NUMBER) {
            this.webHeight = result.getNumber()
          }
        })
    } catch (error) {
    }
  }

  getWebScrollTop() {
    try{
      this.webController?.runJavaScriptExt('document.documentElement.scrollTop || document.body.scrollTop',
        (error, result) => {
          if (error || !result) {
            return
          }
          if (result.getType() === webview.JsMessageType.NUMBER) {
            this.scrollTop = result.getNumber()
            this.isWebEnd = false
            if (this.scrollTop + this.webHeight >= this.webController.getPageHeight()) {
              this.isWebEnd = true
            }
          }
        })
    } catch (error) {

    }
  }

  build() {
    Scroll(this.scroller) {
      Column() {
        Web({
          src: "https://gitee.com",
          controller: this.webController
        }).height('100%')
          .onPageEnd(() => {
            this.webController.setScrollable(false, webview.ScrollType.EVENT)
            this.getWebHeight()
          })
          .onGestureRecognizerJudgeBegin((event: BaseGestureEvent, current: GestureRecognizer, others: Array<GestureRecognizer>) => {
            if (current.isBuiltIn() && current.getType() == GestureControl.GestureType.PAN_GESTURE) {
              return GestureJudgeResult.REJECT
            }
            return GestureJudgeResult.CONTINUE
          })
        List({ scroller: this.listScroller}) {
          Repeat<number>(this.arr).each((item: RepeatItem<number>) => {
            ListItem() {
              Text("item" + item.item.toString())
            }
          })
        }.height('100%')
        .maintainVisibleContentPosition(true)
        .enableScrollInteraction(false)
      }
    }
    .onScrollFrameBegin((offset: number, state: ScrollState) => {
      this.getWebScrollTop()
      if (offset > 0) {
        if (!this.isWebEnd) {
          this.webController.scrollBy(0, offset)
          return {offsetRemain: 0}
        } else if (this.scroller.isAtEnd()) {
          this.listScroller.scrollBy(0, offset)
          return {offsetRemain: 0}
        }
      } else if (offset < 0) {
        if (this.listScroller.currentOffset().yOffset > 0) {
          this.listScroller.scrollBy(0, offset)
          return {offsetRemain: 0}
        } else if (this.scroller.currentOffset().yOffset <= 0) {
          this.webController.scrollBy(0, offset)
          return {offsetRemain: 0}
        }
      }
      return {offsetRemain: offset}
    })
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值