首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >HarmonyOS 开发实践 —— 基于webview的嵌套滚动及与ArkUI组件的联动

HarmonyOS 开发实践 —— 基于webview的嵌套滚动及与ArkUI组件的联动

原创
作者头像
小帅聊鸿蒙
发布2024-12-17 15:00:44
发布2024-12-17 15:00:44
4060
举报
文章被收录于专栏:鸿蒙开发笔记鸿蒙开发笔记

场景描述

在一些应用的首页或者详情页上,需要原生组件与网页进行一些嵌套或者展开收起的逻辑。

  • 场景一:在滑动场景中原生组件与web页面嵌套,需要先让原生组件的高度变化,等原生组件到底后web页面高度随之变化。
  • 场景二:嵌套在列表的原生组件中的web页面,点击按钮可以展开或者收起。

方案描述

将web组件放置在List或者Scroll组件中,通过web的嵌套滚动属性nestedScroll和Scroll的onScrollFrameBegin属性实现场景一的场景效果。

封装一个web组件,在web的onpageEnd回调中拿到网页高度,当点击展开后将web的高度设置为拿到的高度即可实现场景二的效果。

场景一:在应用详情页面,上半部分展示简述,下方 web 页面展示详情内容

方案

在父组件Scroll里放置一个原生Image组件和一个web组件,给Image组件设置最大和最小高度。通过在Scroll组件每帧开始滚动时触发的回调onScrollFrameBegin中监听y轴高度,并通过一个变量传递到Image组件中,从而实现Image组件的高度动态变化。下半部分web组件使用控制嵌套滚动的方法nestedScroll和禁止滚动的方法setScrollable来实现一个吸顶的效果。

核心代码

  1. 在Scroll组件里设置Image和web组件,并将web组件nestedScroll属性的枚举值设置为变量。注意:Image组件最小高度为100vp,所以需要设置web的高度为calc('100%-100vp'),这样才能确保web页面完全展示出来。
代码语言:ts
复制
import { webview } from '@kit.ArkWeb'
import web_webview from '@ohos.web.webview'
 
@Entry
@Component
struct Index {
  controller: web_webview.WebviewController = new web_webview.WebviewController()
  @State mOffset: number = 0
  @State NestedScrollModeF: NestedScrollMode = NestedScrollMode.PARENT_FIRST
  @State NestedScrollModeB: NestedScrollMode = NestedScrollMode.SELF_FIRST
  private scroller: Scroller = new Scroller()
 
  build() {
    Scroll(this.scroller) {
      Column() {
        Image($rawfile('iimg.png'))
          .width('100%')
          .height(300 - this.mOffset)
        Web({
          src: 'https://developer.huawei.com/consumer/cn/?ha_linker=eyJ0cyI6MTcwODEyOTY4MDk5MywiaWQiOiI5NGE4Y2U3YzZkNWFjMzI1M2VlOWRkNjBhMWNhYjMwZCJ9',
          controller: this.controller
        })
          .nestedScroll({ scrollForward: this.NestedScrollModeF, scrollBackward: this.NestedScrollModeB })
          .height('calc(100% - 100vp)')
      }.height('100%')
    }
    .edgeEffect(EdgeEffect.Spring)
    .backgroundColor('#DCDCDC')
    .scrollBar(BarState.On)
    .height('100%')
    .width('100%')
    .onScrollFrameBegin((offset: number) => {
      this.mOffset = Math.min(this.mOffset + offset, 200)
      this.mOffset = Math.max(this.mOffset, -150)
      console.log('sdasd ' + this.mOffset)
      if (this.mOffset < 200) {
        this.NestedScrollModeF = NestedScrollMode.PARENT_FIRST
        this.NestedScrollModeB = NestedScrollMode.PARENT_FIRST
        this.controller.setScrollable(false)
      } else if (this.mOffset = 200) {
        this.NestedScrollModeF = NestedScrollMode.SELF_FIRST
        this.NestedScrollModeB = NestedScrollMode.SELF_FIRST
        this.controller.setScrollable(true)
      }
      return {
        offsetRemain: 0
      }
    })
  }
}
  1. 在onScrollFrameBegin回调里设置Image组件的最大高度和最小高度。
代码语言:ts
复制
this.mOffset = Math.min(this.mOffset + offset, 200)
this.mOffset = Math.max(this.mOffset, -150)
  1. 根据Image组件的动态高度设置scrollForward和scrollBackward的值来实现web组件的吸顶效果。
代码语言:ts
复制
if (this.mOffset < 200) {
  this.NestedScrollModeF = NestedScrollMode.PARENT_FIRST
  this.NestedScrollModeB = NestedScrollMode.PARENT_FIRST
  this.controller.setScrollable(false)
} else if (this.mOffset = 200) {
  this.NestedScrollModeF = NestedScrollMode.SELF_FIRST
  this.NestedScrollModeB = NestedScrollMode.SELF_FIRST
  this.controller.setScrollable(true)
}

场景二:在新闻类应用的资讯场景中,会有可以展开收起的 List 类型新闻页面

方案

封装一个web组件,给这个web组件设置一个最小高度,所有的List列表都是以这个高度展示,点击展开后即可浏览全部内容。

在以上dom树的示例中,可以看出来父组件List中有两个listItem子组件和两个封装的web组件,封装的类里面是有一个Stack父组件以及web和column两个子组件,分别加载web页面和展开更多的文本。给web页面一个最小高度,这样就可实现以上List列表的效果,当用户想查看详情的时候,就可以点击展开更多,通过这个点击事件将web加载时拿到的页面高度在变量里取出来,赋值到web组件的高度上,这样就可以实现点击展开详情页的效果,滑动到最下面后再点击收起把web的高度恢复到最小值即可。

核心代码

  1. 封装一个web组件,在这个page页内写上点击展开或收起的逻辑。
代码语言:ts
复制
import web_webview from '@ohos.web.webview';
 
@Preview
@Component
export default struct WebMoreComponent {
  private webSrc: string | Resource = ""
  wController: web_webview.WebviewController = new web_webview.WebviewController();
  @State webShowMore: boolean = false
  @State webHeight: number = 166
  @State webHeight1: number = 166
  bottomPadding = 33
  @State minShowHeight: number = 160
  refreshCurrent = 0
  listScroller: Scroller = new Scroller()
 
  build() {
    if (this.webSrc != null) {
      Stack({ alignContent: Alignment.Bottom }) {
        Web({
          src: this.webSrc,
          controller: this.wController,
          renderMode: RenderMode.SYNC_RENDER
        })
          .width('100%')
          .backgroundColor(Color.White)
          .layoutWeight(1)
          .fileAccess(true)
          .height(this.webHeight)
          .javaScriptAccess(true)
          .domStorageAccess(true)
          .overviewModeAccess(true)
          .onlineImageAccess(true)
          .nestedScroll({
            scrollForward: NestedScrollMode.SELF_FIRST,
            scrollBackward: NestedScrollMode.SELF_FIRST
          })
          .verticalScrollBarAccess(false)
          .id("webTest")
          .onPageEnd(() => {
            this.webHeight1 = this.wController.getPageHeight()
            if (this.wController.getPageHeight() == 0 && this.refreshCurrent < 6) {
              this.refreshCurrent++
              this.wController.refresh()
            }
          })
        Column() {
          Blank().layoutWeight(1)
            .visibility(this.webShowMore ? Visibility.None : Visibility.Visible)
          Column() {
            Text().height(33).width('100%')
              .linearGradient({
                direction: GradientDirection.Top,
                colors: [[0x00ffffff, 0.0], [0x0fffffff, 1]]
              }).visibility(this.webShowMore ? Visibility.None : Visibility.Visible)
            Row() {
              Text(this.webShowMore ? '收起' : '展开更多')
                .fontColor('#E91839')
                .fontSize(13)
              Image('')
                .width(9)
                .height(9)
                .margin({ left: 3 })
                .rotate({ angle: this.webShowMore ? 180 : 0 })
            }
            .height(33)
            .width('100%')
            .backgroundColor(Color.White)
            .padding({ bottom: 5.5 })
            .justifyContent(FlexAlign.Center)
            .onClick(() => {
              this.webShowMore = !this.webShowMore
              this.webHeight = this.webHeight1
              if (!this.webShowMore) {
              }
            })
          }.visibility(((this.webHeight - this.minShowHeight) < 5) ? Visibility.None : Visibility.Visible)
        }
      }.height(this.webShowMore ? this.webHeight + this.bottomPadding : this.minShowHeight)
      .clip(true)
    }
  }
}
  1. 当点击展开后,在web的页面加载完成后的回调onPageEnd中通过获取H5页面高度的方法getPageHeight来拿到整个H5页面高度,然后赋值给到变量webHeight1,当点击展开的时候,将webHeight1的值给到web的高度上,即可实现展开H5页面的效果。
代码语言:ts
复制
.onPageEnd(() => {
  this.webHeight1 = this.wController.getPageHeight()
  if (this.wController.getPageHeight() == 0 && this.refreshCurrent < 6) {
    this.refreshCurrent++
    this.wController.refresh()
  }
})

Row() {
  Text(this.webShowMore ? '收起' : '展开更多')
    .fontColor('#E91839')
    .fontSize(13)
  Image('')
    .width(9)
    .height(9)
    .margin({ left: 3 })
    .rotate({ angle: this.webShowMore ? 180 : 0 })
}
.height(33)
.width('100%')
.backgroundColor(Color.White)
.padding({ bottom: 5.5 })
.justifyContent(FlexAlign.Center)
.onClick(() => {
  this.webShowMore = !this.webShowMore
  this.webHeight=this.webHeight1
  if (!this.webShowMore) {
  }
})
  1. 新建一个page,设置父组件为List,将封装的web组件添加进去。
代码语言:ts
复制
import web_webview from '@ohos.web.webview'
import WebMoreComponent from './WebMoreComponent'
 
@Entry
@Component
struct Index {
  private listScroller: Scroller = new Scroller();
 
  build() {
    Column() {
      List({ scroller: this.listScroller }) {
        ListItem() {
          Image($rawfile('iimg.png'))
            .width('100%')
            .height(200)
        }
 
        WebMoreComponent({
          webSrc: 'https://developer.huawei.com/consumer/cn/?ha_linker=eyJ0cyI6MTcwODEyOTY4MDk5MywiaWQiOiI5NGE4Y2U3YzZkNWFjMzI1M2VlOWRkNjBhMWNhYjMwZCJ9'
        })
        WebMoreComponent({
          webSrc: 'https://developer.huawei.com/consumer/cn/training/'
        })
        ListItem() {
          Text('更多')
            .backgroundColor('#1890ff')
            .width('100%')
            .textAlign(TextAlign.Center)
            .height('30%')
        }
      }
      .scrollBar(BarState.Off)
      .height('100%')
    }
  }
}

常见问题

1.场景一中为什么要使用web的禁止滚动的方法setScrollable?

因为web组件嵌套在父组件Scroll中并且向上滑动时,最开始并不需要web页面滑动,只需要手势作用的量传递到Image组件上,使其高度减少,当达到最小高度后就可以滚动了。

2.场景二中为什么要设置两个高度值变量?

因为一个高度值是设置的web页面最小高度值,另外一个是web页面具体高度,拿到这个值后在点击展开的点击事件中改变页面高度时机正好。

写在最后

如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:

  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力;
  • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识;
  • 想要获取更多完整鸿蒙最新学习知识点,可关注B站:码牛课堂;

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 场景描述
  • 场景一:在应用详情页面,上半部分展示简述,下方 web 页面展示详情内容
    • 场景二:在新闻类应用的资讯场景中,会有可以展开收起的 List 类型新闻页面
  • 常见问题
  • 写在最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档