HarmonyOS应用开发者的宝藏APP-HMOS代码工坊-组件堆叠滑动折叠效果(二)

前言

在前一篇文章中,我们分析了组件堆叠效果的模块划分和实现思路,并且实现了前两个最简单的部分。接下来我们继续实现剩余部分

第三部分物品栏

由于向上滑动的时候需要底部第四部分的商品列表需要被第三部分遮盖,我们同样使用Stack来做父布局,使用zIndex属性来调整Z轴层级来实现覆盖效果。

基础布局

typescript 复制代码
build() {
    Stack({alignContent:Alignment.Top}) {
      Row(){
        Image($r("app.media.user_portrait"))
          .width(30)
          .aspectRatio(1)
          .borderRadius(15)
        Blank().height(0)
        Image($r("app.media.stack_scan"))
          .width(30)
          .aspectRatio(1)

      }.width("100%").height(50).alignItems(VerticalAlign.Center).padding({left:15,right:15})

      Scroll(this.outSideScrollController){
        Column(){
          //搜索框
          Row() {
            Image($r('app.media.search'))
              .width(20)
              .aspectRatio(1)
              .margin({ left: 20 })

            Text('搜索提示语')
              .opacity(0.6)
              .fontColor(Color.Black)
              .fontColor(Color.Black)
              .fontSize(16)
              .margin({ left: 10 })
          }
          .height(this.searchAreaHeight)
          .backgroundColor(Color.White)
          .width("100%")
          .borderRadius(12)
          .onClick((_) => {
            this.getUIContext().getPromptAction().showToast({ message: "跳转到搜索页面" })
          })
          Stack({alignContent:Alignment.Top}){
            if(this.showHorizontalList){
              Row(){
                Text("横向滑动列表").fontColor(Color.White)
              }.height(100).width("100%").borderRadius(12).backgroundColor(Color.Red).justifyContent(FlexAlign.Center)
            }else{
              Row(){
                Text("不可滑动布局").fontColor(Color.White)
              }.height(200).width("100%").borderRadius(12).backgroundColor(Color.Blue).justifyContent(FlexAlign.Center)
            }
            Scroll(this.inSideScrollController){
              Column(){
                Row({space:10}){
                  Text("分类1").fontColor(Color.White).layoutWeight(1).textAlign(TextAlign.Center).backgroundColor(Color.Pink)
                  Text("分类2").fontColor(Color.White).layoutWeight(1).textAlign(TextAlign.Center).backgroundColor(Color.Pink)
                  Text("分类3").fontColor(Color.White).layoutWeight(1).textAlign(TextAlign.Center).backgroundColor(Color.Pink)
                  Text("分类4").fontColor(Color.White).layoutWeight(1).textAlign(TextAlign.Center).backgroundColor(Color.Pink)
                  Text("分类5").fontColor(Color.White).layoutWeight(1).textAlign(TextAlign.Center).backgroundColor(Color.Pink)
                }.height(100).width("100%").backgroundColor(Color.Green).justifyContent(FlexAlign.SpaceAround)
                ForEach(this.fakeProduct,(item:string,index:number)=>{
                  ListItem(){
                    Row(){
                      Text(item)
                    }.width("100%").height(50).justifyContent(FlexAlign.Center).backgroundColor(Color.White).borderRadius(12)
                  }
                })
              }

            }.onScrollFrameBegin((offset:number)=>{
              let yOffset = this.inSideScrollController.currentOffset().yOffset
              return {offsetRemain:offset}
            })
          }
        }.margin({top:50}).height("100%").justifyContent(FlexAlign.Start)
      }.padding({left:15,right:15}).height("100%").width("100%")
      .onScrollFrameBegin((offset:number,state:ScrollState)=>{
        let yOffset = this.outSideScrollController.currentOffset().yOffset
        this.searchAreaHeight = 100 - yOffset * 0.6
        return {offsetRemain:offset}
      })
    }.backgroundColor("#f1f1f1")
    .height('100%')
    .width('100%')
  }

滑动冲突

发现了两个个问题:

  1. 嵌套滑动的滑动冲突
    从图上来看,当手指在内部的Scroll滑动时只能滑动内部的Scroll,在外部Scroll滑动时,只能滑动外部的Scroll,这和我们的需求不符合。
  2. 第三部分的物品卡片没有展示出来。

对于问题1我们可以通过设置nestedScroll属性来解决。

对于问题2我们可以对内部Scroll的直接子组件Colum加个margin属性解决。

typescript 复制代码
            Scroll(this.inSideScrollController){
              Column(){
              //分类
                Row({space:10}){
}.height(100).width("100%").backgroundColor(Color.Green).justifyContent(FlexAlign.SpaceAround).borderRadius(12)
                ForEach(this.fakeProduct,(item:string,index:number)=>{
                  ListItem(){
                    Row(){
                      Text(item)
                    }.width("100%").height(50).justifyContent(FlexAlign.Center).backgroundColor(Color.White).borderRadius(12)
                  }
                })
              }
//和顶部的距离为第三部分物品卡片的高度
.margin({top:200})

            }.nestedScroll({
              scrollForward: NestedScrollMode.PARENT_FIRST,
              scrollBackward: NestedScrollMode.SELF_FIRST
            })

现在来看效果就正常了。

物品卡片透明度效果

为了方便后面的计算,我们定义几个常量和变量

typescript 复制代码
const SEARCH_AREA_HEIGHT = 100
const ITEM_CARDS_AREA_HEIGHT = 200
const HORIZONTAL_LIST_HEIGHT = 100
typescript 复制代码
  outSideScrollController:Scroller = new Scroller()
  inSideScrollController:Scroller = new Scroller()

  @State searchAreaHeight:number = SEARCH_AREA_HEIGHT
  @State showHorizontalList:boolean = false

  @State itemCardsAreaOpacity:number = 1
  @State horizontalListOpacity:number = 0
  @State classifyAreaOpacity:number = 1

然后在横向滑动列表不可滑动布局分类上添加不透明度属性。 在内部Scroll的onScrollFrameBegin事件的回调中,获取滑动距离并且计算不透明度属性

typescript 复制代码
          Stack({alignContent:Alignment.Top}){
            if(this.showHorizontalList){
              Row(){
                Text("横向滑动列表").fontColor(Color.White)
              }.zIndex(1).height(HORIZONTAL_LIST_HEIGHT).width("100%").borderRadius(12).backgroundColor(Color.Red).justifyContent(FlexAlign.Center).opacity(this.horizontalListOpacity)//添加不透明度属性
            }else{
              Row(){
                Text("不可滑动布局").fontColor(Color.White)
              }.height(ITEM_CARDS_AREA_HEIGHT).width("100%").borderRadius(12).backgroundColor(Color.Blue).justifyContent(FlexAlign.Center).opacity(this.itemCardsAreaOpacity)//添加不透明度属性
            }
            Scroll(this.inSideScrollController){
              Column(){
              //分类
                Row({space:10}){
                }.height(100).width("100%").backgroundColor(Color.Green).justifyContent(FlexAlign.SpaceAround).borderRadius(12).opacity(this.classifyAreaOpacity)//添加不透明度属性
                ForEach(this.fakeProduct,(item:string,index:number)=>{
                  ListItem(){
                    Row(){
                      Text(item)
                    }.width("100%").height(50).justifyContent(FlexAlign.Center).backgroundColor(Color.White).borderRadius(12)
                  }
                })
              }.margin({top:ITEM_CARDS_AREA_HEIGHT})

            }.nestedScroll({
              scrollForward: NestedScrollMode.PARENT_FIRST,
              scrollBackward: NestedScrollMode.SELF_FIRST
            }).onScrollFrameBegin((offset:number)=>{
              let yOffset = this.inSideScrollController.currentOffset().yOffset
              console.error(`inSideScroll ${yOffset}`)

             let maxDiff = ITEM_CARDS_AREA_HEIGHT - HORIZONTAL_LIST_HEIGHT
             let diff =maxDiff - yOffset
             //滑动具体超过二者的差值,则展示横向滑动列表;否则展示物品卡片
              if(diff>= 0){
                this.itemCardsAreaOpacity = diff/maxDiff
                this.classifyAreaOpacity = diff/maxDiff
                this.showHorizontalList =false
              }else{
              	//横向滑动列表的不透明度
                this.horizontalListOpacity = -diff/maxDiff
                this.showHorizontalList = true
              }
              return {offsetRemain:offset}
            })
          }

效果看起来也还可以,我们替换一下素材看看

第四部分

接下来我们来实现底部第四部分的分类缩放和横向滑动列表的间距变化。

分类

这里我们做的简单点,分类项的缩放值和不透明度取相同值,就不再额外计算了。如果需要修改效果,根据滑动偏移量重新计算一个值就好了。

typescript 复制代码
 Column({ space: 10 }) {
   Image($r('app.media.ic_gallery_puzzle')).width(40)
     .aspectRatio(1)
     .objectFit(ImageFit.Contain)
   Text("拼图")
     .fontSize(16)
     .fontColor(Color.Black)
     .textAlign(TextAlign.Center)
 }
 .scale({x:this.classifyAreaOpacity,y:this.classifyAreaOpacity})
 .layoutWeight(1)
 .justifyContent(FlexAlign.Center)

这里是其中一个分类项的布局

横向滑动列表

横向滑动列表刚出现时,列表项间距是最大的,随着向上滑动,列表项间距逐渐变小,最终固定为一个最小值。这里我们设置最大间距为50 ,最小间距为10 。同样的在内部ScrollonScrollFrameBegin的回调中进行计算

typescript 复制代码
const MIN_HORIZONTAL_LIST_SPACE:number = 10
const MAX_HORIZONTAL_LIST_SPACE:number = 50

间距初始值为最大间距

typescript 复制代码
@State horizontalListSpace:number= MAX_HORIZONTAL_LIST_SPACE

重新写一下布局,直接替换为素材。这里的FakeHorizontalData是自定义数据类,包含nameicon属性。

typescript 复制代码
if(this.showHorizontalList){
  Scroll(){
    Row({space:this.horizontalListSpace}){
      ForEach(this.fakeHorizontalDataList,(item:FakeHorizontalData,index:number)=>{
        Column(){
          Image(item.icon).width(40).aspectRatio(1)
            .objectFit(ImageFit.Contain)
          Blank().width(15).height(0)
          Text(item.name).fontSize(16)
        }.width(80).backgroundColor(Color.White).borderRadius(10).height(90).justifyContent(FlexAlign.Center)
      })
    }.justifyContent(FlexAlign.Center)
  }.scrollable(ScrollDirection.Horizontal).zIndex(1).height(HORIZONTAL_LIST_HEIGHT).width("100%").opacity(this.horizontalListOpacity).backgroundColor("#f1f1f1")

}

计算对应的属性值

在内部ScrollonScrollFrameBegin的回调中进行计算

typescript 复制代码
.onScrollFrameBegin((offset:number)=>{
   let yOffset = this.inSideScrollController.currentOffset().yOffset
   console.error(`inSideScroll ${yOffset}`)

  let maxDiff = (ITEM_CARDS_AREA_HEIGHT - HORIZONTAL_LIST_HEIGHT)
  let diff =maxDiff - yOffset
   if(diff>= 0){
     this.itemCardsAreaOpacity = diff/maxDiff
     this.classifyAreaOpacity = diff/maxDiff
     this.showHorizontalList =false
   }else{
     this.horizontalListOpacity = -diff/maxDiff
     this.showHorizontalList = true


     let calcSpace:number = MAX_HORIZONTAL_LIST_SPACE + diff
     if(calcSpace > MIN_HORIZONTAL_LIST_SPACE){
       this.horizontalListSpace = calcSpace
     }else{
       this.horizontalListSpace = MIN_HORIZONTAL_LIST_SPACE
     }

   }
   return {offsetRemain:offset}
 })

来看下效果 这样我们就大致复刻了HMOS代码工坊应用中的组件堆叠效果。

最下面的商品列表没有实现,没啥难度,自己替换一下就好了

后记

HMOS代码工坊的组件堆叠效果源码在这里 gitee.com/harmonyos_s...

自己实现的效果在这里gitcode.com/huangyuan_x...

对比代码后发现,HMOS代码工坊中对于第三部分实现是使用GridColGridRow进行实现的。滑动时的效果也比较舒服,自己实现的滑动效果还需要进行数值上的调整。 不过大体来看,效果还算可以


【 更多精彩内容,请关注公众号:【名称:HarmonyOS开发者技术,ID:HarmonyOS_Dev】;也欢迎加入鸿蒙开发者交流群:work.weixin.qq.com/gm/48f89e7a...

相关推荐
国服第二切图仔1 小时前
Electron for 鸿蒙pc项目实战之右键菜单组件
javascript·electron·harmonyos·鸿蒙pc
Huang兄1 小时前
HarmonyOS应用开发者的宝藏APP-HMOS代码工坊-组件堆叠滑动折叠效果(一)
harmonyos
国服第二切图仔2 小时前
Electron for 鸿蒙PC项目实战之拖拽组件示例
javascript·electron·harmonyos
国服第二切图仔3 小时前
Electron for鸿蒙PC项目实战之天气预报应用
javascript·electron·harmonyos·鸿蒙pc
国服第二切图仔3 小时前
Electron for鸿蒙PC项目之侧边栏组件示例
javascript·electron·harmonyos·鸿蒙pc
RisunJan4 小时前
HarmonyOS 系统概述
华为·harmonyos
泓博4 小时前
鸿蒙网络请求流式返回实现方法
华为·harmonyos
国服第二切图仔5 小时前
Electron for鸿蒙pc项目实战之下拉菜单组件
javascript·electron·harmonyos·鸿蒙pc