HarmonyOS list嵌套scroll+list布局,如果解决滑动冲突

在原本scroll+list的布局页面中添加自定义下拉刷新布局,但是有滑动冲突

HarmonyOS
1天前
浏览
收藏 0
回答 1
待解决
回答 1
按赞同
/
按时间
Heiang

参考demo:

HomePage.ets

/**
 * 重点nestedScroll,edgeEffect属性设置
 * 在滚动嵌套时,要注意nestedScroll,edgeEffect属性设置,可以尝试进行不同设置来看效果
 *
 * */
import SearchBar, { SEARCHBAR_HEIGHT } from './SearchBar'
import Refresher, { RefreshState, REFRESH_HEAD_HEIGHT } from './Refresher'

export const LIST_TAB_BAR_HEIGHT = 56 // 内容页面tab bar高度

export const LIST_BOTTOM_MARGIN = 20 // list距离顶部距离

const SCREEN_HEIGHT = 720 // 屏幕高度,实际应用应从api获取

@Component
export default struct HomeViewController {
  @State fontColor: string = '#182431'
  @State selectedFontColor: string = '#007DFF'
  @State currentIndex: number = 0
  private scrollController: Scroller = new Scroller();
  private tabController: TabsController = new TabsController()
  @State private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  @State rComplete: number = 0
  @State lComplete: number = 0

  @Builder
  tabBuilder(index: number, name: string) {
    Column() {
      Text(name)
        .fontColor(this.currentIndex === index ? this.selectedFontColor : this.fontColor)
        .fontSize(16)
        .fontWeight(this.currentIndex === index ? 500 : 400)
        .lineHeight(22)
        .margin({ top: 17, bottom: 7 })
    }.width('100%')
  }

  @Builder
  tabControllerBuilder() {
    Scroll() {
      Column() {
        Text("占位图区域")
          .fontColor("#fff")
          .textAlign(TextAlign.Center)
          .width('100%')
          .height(200)
          .backgroundColor("#f87654")
        Tabs({ barPosition: BarPosition.Start, controller: this.tabController }) {
          TabContent() {
            this.listControllerBuilder()
          }.tabBar(this.tabBuilder(0, '手机'))

          TabContent() {
            this.listControllerBuilder()
          }.tabBar(this.tabBuilder(1, '宽带'))

          TabContent() {
            this.listControllerBuilder()
          }.tabBar(this.tabBuilder(2, '家庭'))

          TabContent() {
            this.listControllerBuilder()
          }.tabBar(this.tabBuilder(3, '附近'))

          TabContent() {
            this.listControllerBuilder()
          }.tabBar(this.tabBuilder(4, '政企'))
        }
        .vertical(false)
        .barMode(BarMode.Fixed)
        .barHeight(LIST_TAB_BAR_HEIGHT)
        .animationDuration(400)
        .onChange((index: number) => {
          this.currentIndex = index
        })
      }.justifyContent(FlexAlign.Start)
    }

    .width('100%')
    .height(SCREEN_HEIGHT - SEARCHBAR_HEIGHT)
    .backgroundColor('#F1F3F5')
    .edgeEffect(EdgeEffect.None)
    .nestedScroll({ scrollForward: NestedScrollMode.SELF_ONLY,//SELF_ONLY
      scrollBackward: NestedScrollMode.PARENT_FIRST })//NestedScrollMode.SELF_FIRST配合EdgeEffect.None可以触发上拉加载,但是整个页面会上移,看使用场景
  }

  @Builder
  listControllerBuilder() {
    List({ space: 20, initialIndex: 0 }) {
      ForEach(this.arr, (item: number) => {
        ListItem() {
          Text('' + item)
            .width('100%')
            .height(100)
            .fontSize(16)
            .textAlign(TextAlign.Center)
            .borderRadius(10)
            .backgroundColor(0xFFFFFF)
        }
      }, (item: string) => item)
    }
    .listDirection(Axis.Vertical) // 排列方向
    .scrollBar(BarState.Off)
    .friction(0.6)
    .chainAnimation(false)
    .edgeEffect(EdgeEffect.Spring) //配合EdgeEffect.Spring可以触发list回弹效果
    .nestedScroll({ scrollForward: NestedScrollMode.PARENT_FIRST,
      scrollBackward: NestedScrollMode.SELF_FIRST })
    .margin({ bottom: LIST_BOTTOM_MARGIN })
    .onScrollIndex((firstIndex: number, lastIndex: number, centerIndex: number) => {
    })
    .onScroll((scrollOffset: number, scrollState: ScrollState) => {
    })
    .onAppear(() => {

    })
    .width('90%')
    .height(SCREEN_HEIGHT - SEARCHBAR_HEIGHT - LIST_TAB_BAR_HEIGHT - LIST_BOTTOM_MARGIN)
  }

  @Builder
  searchBarBuilder(){
    SearchBar()
  }

  build() {
    Column() {
      Refresher({
        scrollHeight: SCREEN_HEIGHT,
        refreshComplete: this.rComplete,
        loadComplete: this.lComplete,
        rScroller: this.scrollController, // 绑定scroller
        onRefresh: () => {
          setTimeout(() => {
            this.rComplete = 1
          }, 2000)
        },
        onCustomList: () => {
          // this.tabControllerBuilder()
          this.searchBarBuilder()
          this.tabControllerBuilder()
        },
        onLoad: () => {
          setTimeout(() => {
            this.arr.push(21)
            this.arr.push(22)
            this.arr.push(23)
            this.lComplete = 1
          }, 2000)
        }
      })
    }
  }
}

ListWithRefreshPage.ets 代码如下(分两条):
import Refresher, { RefreshState, REFRESH_HEAD_HEIGHT } from './Refresher'

import SearchBar, { SEARCHBAR_HEIGHT } from './SearchBar'

export const LIST_TAB_BAR_HEIGHT = 56 // 内容页面tabbar高度

export const LIST_TOP_MARGIN = 10 // list距离顶部距离

export const SCREEN_HEIGHT = 720 // 屏幕高度,实际应用应从api获取

@Component
export default struct ListWithRefreshController {
  @State private arr: number[] = [0, 1, 2, 3, 4, 5]
  @State rComplete: number = 0
  @State lComplete: number = 0
  scroller: Scroller = new Scroller()

  @Builder
  refreshListBuilder() {
    Refresher({
      refreshComplete: this.rComplete,
      loadComplete: this.lComplete,
      scrollHeight: SCREEN_HEIGHT - SEARCHBAR_HEIGHT - LIST_TAB_BAR_HEIGHT,
      onCustomList: () => {
        this.listControllerBuilder()
      },
      onRefresh: () => {
        setTimeout(() => {
          this.arr = [0, 1, 2, 3, 4, 5];
          this.rComplete = 1;
        }, 2000)
      },
      onLoad: () => {
        setTimeout(() => {
          let count = this.arr.length;
          this.arr.push(this.arr[this.arr.length -1] + 1)
          this.arr.push(this.arr[this.arr.length -1] + 1)
          this.arr.push(this.arr[this.arr.length -1] + 1)
          this.arr.push(this.arr[this.arr.length -1] + 1)

          // this.scroller.scrollToIndex(count + 1,false)
          this.scroller.scrollTo({xOffset:0,yOffset:this.scroller.currentOffset().yOffset + 60,animation:true})

          // setTimeout(()=>{
          this.lComplete = 1;
          //
          // },110)


        }, 2000)
      }
    })}

  @Builder
  listControllerBuilder() {
    List({ space: 20, initialIndex: 0, scroller: this.scroller }) {
      ForEach(this.arr, (item: number) => {
        ListItem() {
          Text('' + item)
            .width('100%')
            .height(100)
            .fontSize(16)
            .textAlign(TextAlign.Center)
            .borderRadius(10)
            .backgroundColor(0xFFFFFF)
        }
      }, (item: string) => item)
    }
    .listDirection(Axis.Vertical) // 排列方向
    .scrollBar(BarState.On)
    .friction(0.6)
    .chainAnimation(false)
    .edgeEffect(EdgeEffect.None) // 边缘效果设置为Spring
    .nestedScroll({ scrollForward: NestedScrollMode.PARENT_FIRST, scrollBackward: NestedScrollMode.PARENT_FIRST })
    .onScrollIndex((firstIndex: number, lastIndex: number, centerIndex: number) => {
    })
    .onScroll((scrollOffset: number, scrollState: ScrollState) => {
      console.info(`列表视图scrollOffset = ` + this.scroller.currentOffset().yOffset)
    })
    .width('90%')
    .height(SCREEN_HEIGHT - SEARCHBAR_HEIGHT - LIST_TAB_BAR_HEIGHT)
  }

  build() {
    this.refreshListBuilder()
  }
}

MinePage.ets 代码如下(分多条):
/**
 * 此页面可以单独刷新list,下拉list
 * 也可单独刷新整个scroll,下拉searchbar
 *
 * */
import ListWithRefreshController, { LIST_TAB_BAR_HEIGHT, SCREEN_HEIGHT } from './ListWithRefreshPage'
import SearchBar, { SEARCHBAR_HEIGHT } from './SearchBar'
import Refresher, { RefreshState, REFRESH_HEAD_HEIGHT } from './Refresher'

@Component
export default struct MineViewController {
  @State fontColor: string = '#182431'
  @State selectedFontColor: string = '#007DFF'
  @State currentIndex: number = 0
  private tabController: TabsController = new TabsController()
  private scrollController: Scroller = new Scroller();
  @State private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
    10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

  @Builder
  tabBuilder(index: number, name: string) {
    Column() {
      Text(name)
        .fontColor(this.currentIndex === index ? this.selectedFontColor : this.fontColor)
        .fontSize(16)
        .fontWeight(this.currentIndex === index ? 500 : 400)
        .lineHeight(22)
        .margin({ top: 17, bottom: 7 })
    }.width('100%')
  }

  @Builder
  tabControllerBuilder() {
    Tabs({ barPosition: BarPosition.Start, controller: this.tabController }) {
      TabContent() {
        ListWithRefreshController()
      }.tabBar(this.tabBuilder(0, '手机'))

      TabContent() {
        ListWithRefreshController()
      }.tabBar(this.tabBuilder(1, '宽带'))

      TabContent() {
        ListWithRefreshController()
      }.tabBar(this.tabBuilder(2, '家庭'))

      TabContent() {
        ListWithRefreshController()
      }.tabBar(this.tabBuilder(3, '附近'))

      TabContent() {
        ListWithRefreshController()
      }.tabBar(this.tabBuilder(4, '政企'))
    }
    .position({ x: 0, y: SEARCHBAR_HEIGHT })
    .vertical(false)
    .barMode(BarMode.Fixed)
    .barWidth(800 - 60)
    .barHeight(LIST_TAB_BAR_HEIGHT)
    .animationDuration(400)
    .onChange((index: number) => {
      this.currentIndex = index
    })
    .width('100%')
    .height(SCREEN_HEIGHT - SEARCHBAR_HEIGHT)
    .backgroundColor('#F1F3F5')
  }

  build() {
    Column() {
      Scroll(this.scrollController) {
        Column() {
          SearchBar()
          this.tabControllerBuilder()
        }.height('100%')
      }
      .scrollable(ScrollDirection.Vertical)
      .edgeEffect(EdgeEffect.Spring)
      .backgroundColor(0xdddddd)
      .height(SCREEN_HEIGHT)
      .onScroll((xOffset: number, yOffset: number) => {
        console.info(`滚动视图scrollOffset = ` + yOffset)
      })
      .onTouch((event?: TouchEvent) => {
      })
    }
    .justifyContent(FlexAlign.Start)
    .backgroundColor(0xffffff)
    .width('100%')
    .height(SCREEN_HEIGHT)
  }

}

Refresher.ets 代码如下(分多条):
/**
 * 简单的下拉,上拉组件,基于scroll
 * 还需完善
 */

export const REFRESH_HEAD_HEIGHT = 60 // 刷新组件的高度
const REFRESH_TEXT_PULL_DOWN = '下拉刷新'
const REFRESH_TEXT_REFRESH_ABLE = '松开立即刷新'
const REFRESH_TEXT_REFRESHING = '正在刷新'
const REFRESH_TEXT_REFRESH_COMPLETE = '刷新完成'


const REFRESH_TEXT_PULL_UP = '上拉加载'
const REFRESH_TEXT_LOAD_ABLE = '松开加载更多'
const REFRESH_TEXT_LOADING = '正在加载'
const REFRESH_TEXT_LOAD_COMPLETE = '加载完成'

export enum RefreshState {
  REFRESH_IDLE = 0, // 初始化状态
  REFRESH_DRAGGING = 1, // 下拉状态
  REFRESH_ABLE = 2, // 下拉至可刷新状态
  REFRESHING = 3, // 正在刷新状态
  REFRESH_COMPLETE = 4 // 刷新完成状态
}

export enum LoadState {
  LOAD_IDLE = 0, // 初始化状态
  LOAD_DRAGGING = 1, // 下拉状态
  LOAD_ABLE = 2, // 下拉至可刷新状态
  LOADING = 3, // 正在刷新状态
  LOAD_COMPLETE = 4 // 刷新完成状态
}

@Component
export default struct Refresher {
  @State rText: string = REFRESH_TEXT_PULL_DOWN; // 下拉刷新初始文本
  @State lText: string = REFRESH_TEXT_PULL_UP; // 上拉加载初始文本
  // 初始化刷新组件必须传入以下参数,rState与父组件进行状态绑定
  @Watch('refreshCompleteChanged') @Link refreshComplete:number ; // 刷新完成通知
  @Watch('loadCompleteChanged') @Link loadComplete:number ; // 加载完成通知
  @Watch('refreshStateChanged') @State rState: RefreshState = RefreshState.REFRESH_IDLE // 下拉监测状态刷新
  @Watch('loadStateChanged') @State lState: LoadState = LoadState.LOAD_IDLE // 上拉监测状态刷新
  private scrollHeight?: number; // 传入scroll高度
  @BuilderParam onCustomList?: () => void; // 传入自定义布局
  private onRefresh?: () => void; // 正在刷新,进行网络请求或者其他逻辑
  private onLoad?: () => void; // 正在加载,进行网络请求或者其他逻辑
  rScroller: Scroller = new Scroller(); // 基于scroll 绑定scroller

  // 上拉加载完成
  loadCompleteChanged(){
    if(this.loadComplete == 1){
      this.lState = LoadState.LOAD_COMPLETE;
      this.loadComplete = 0;
      console.log('load')
    }
  }
  // 下拉刷新完成
  refreshCompleteChanged(){
    if(this.refreshComplete == 1){
      this.rState = RefreshState.REFRESH_COMPLETE;
      this.refreshComplete = 0;
      console.log('refresh')
    }
  }

  // 下拉刷新状态改变
  refreshStateChanged() {
    switch (this.rState) {
    // 刷新完成,状态和文本回到初始化状态
      case RefreshState.REFRESH_COMPLETE: {
        this.rText = REFRESH_TEXT_REFRESH_COMPLETE
        if (this.rScroller) {
          this.rScroller.scrollTo({ xOffset: 0, yOffset: 0, animation: { duration: 250, curve: Curve.Linear } })
          setTimeout(() => {
            this.rState = RefreshState.REFRESH_IDLE
          }, 250)
        }
        break
      }
    // 下拉状态,修改文本/或者自定义其他动作
      case RefreshState.REFRESH_DRAGGING: {
        this.rText = REFRESH_TEXT_PULL_DOWN
        break
      }
    // 可刷新状态,修改文本/或者自定义其他动作
      case RefreshState.REFRESH_ABLE: {
        this.rText = REFRESH_TEXT_REFRESH_ABLE
        break
      }
    //正在刷新状态
      case RefreshState.REFRESHING: {
        if (this.onRefresh) {
          this.onRefresh()
        }
        this.rText = REFRESH_TEXT_REFRESHING
        this.lText = REFRESH_TEXT_LOADING
        if (this.rScroller)
          this.rScroller.scrollTo({ xOffset: 0, yOffset: -REFRESH_HEAD_HEIGHT, animation: true })
        break
      }
      default:
        this.rText = REFRESH_TEXT_PULL_DOWN
        this.lText = REFRESH_TEXT_PULL_UP
        break
    }
  }

  // 上拉加载状态更新
  loadStateChanged(){
    switch (this.lState) {
    // 刷新完成,状态和文本回到初始化状态
      case LoadState.LOAD_COMPLETE: {
        this.lText = REFRESH_TEXT_LOAD_COMPLETE
        if (this.rScroller) {
          this.rScroller.scrollTo({ xOffset: 0, yOffset: 0, animation: false })
          setTimeout(() => {
            this.lState = LoadState.LOAD_IDLE
          }, 250)
        }
        break
      }
    // 上拉状态,修改文本/或者自定义其他动作
      case LoadState.LOAD_COMPLETE: {
        this.rText = REFRESH_TEXT_PULL_DOWN
        break
      }
    // 可加载状态,修改文本/或者自定义其他动作
      case LoadState.LOAD_ABLE: {
        this.lText = REFRESH_TEXT_LOAD_ABLE
        break
      }
    //正在加载状态
      case LoadState.LOADING: {
        if (this.onLoad) {
          this.onLoad()
        }
        this.lText = REFRESH_TEXT_LOADING
        if (this.rScroller)
          this.rScroller.scrollTo({ xOffset: 0, yOffset: REFRESH_HEAD_HEIGHT, animation: true })
        break
      }
      default:
        this.rText = REFRESH_TEXT_PULL_DOWN
        this.lText = REFRESH_TEXT_PULL_UP
        break
    }
  }

  @Builder
  headerRefresh() {
    Row() {
      // 自定义子组件,根据实际业务需求可做修改
      Text(this.rText)
        .fontSize(16)
    }
    .height(REFRESH_HEAD_HEIGHT)
    .width('100%')
    .justifyContent(FlexAlign.SpaceEvenly)
    .alignItems(VerticalAlign.Center)
    .position({ x: 0, y: -REFRESH_HEAD_HEIGHT }) // stack布局位置,一般是-REFRESH_HEAD_HEIGHT,下拉才会显现
  }

  @Builder
  footerRefresh() {
    Row() {
      // 自定义子组件,根据实际业务需求可做修改
      Text(this.lText)
        .fontSize(16)
    }
    .height(REFRESH_HEAD_HEIGHT)
    .width('100%')
    .justifyContent(FlexAlign.SpaceEvenly)
    .alignItems(VerticalAlign.Center)
    .position({ x: 0, y: this.scrollHeight })
  }

  build() {
    Scroll(this.rScroller) {
      Stack({ alignContent: Alignment.Top }) {
        this.headerRefresh()
        Column() {
          if (this.onCustomList != undefined) {
            this.onCustomList()
          }
        }
        .height(this.scrollHeight)
        .justifyContent(FlexAlign.Start)
        .alignItems(HorizontalAlign.Center)
        this.footerRefresh()
      }
    }
    .width('100%')
    .height(this.scrollHeight)
    .scrollable(ScrollDirection.Vertical)
    .edgeEffect(EdgeEffect.Spring)
    .onTouch((event: TouchEvent) => {
      this.refreshEvent(event)
    })
  }

  // 下拉刷新组件,scroll类的组件进行触摸事件监听
  private refreshEvent(event?: TouchEvent) {
    if (!event) {
      return;
    }
    let yOffset: number = this.rScroller.currentOffset().yOffset
    // 下拉刷新
    if(yOffset < 0) {
      // 下拉
      if (event.type === TouchType.Move) {
        if (yOffset > -REFRESH_HEAD_HEIGHT) {
          if (this.rState == RefreshState.REFRESH_IDLE) this.rState = RefreshState.REFRESH_DRAGGING
        }
        else {
          if (this.rState == RefreshState.REFRESH_DRAGGING) this.rState = RefreshState.REFRESH_ABLE
        }
      }
      // 松开
      if (event.type === TouchType.Up) {
        if (this.rState == RefreshState.REFRESH_ABLE) {
          this.rState = RefreshState.REFRESHING
          return
        }
        if(this.rState == RefreshState.REFRESHING) {
          this.rScroller.scrollTo({ xOffset: 0, yOffset: -REFRESH_HEAD_HEIGHT, animation: true })
        }

      }
    } else {
      console.log('12121221')
      // 上拉
      if (event.type === TouchType.Move) {
        if (yOffset < REFRESH_HEAD_HEIGHT) {
          if (this.lState == LoadState.LOAD_IDLE) this.lState = LoadState.LOAD_DRAGGING
        }
        else {
          if (this.lState == LoadState.LOAD_DRAGGING) this.lState = LoadState.LOAD_ABLE
        }
      }
      // 松开
      if (event.type === TouchType.Up) {
        if (this.lState == LoadState.LOAD_ABLE) {
          this.lState = LoadState.LOADING
          return
        }
        if (this.lState == LoadState.LOADING) {
          this.rScroller.scrollTo({ xOffset: 0, yOffset: REFRESH_HEAD_HEIGHT, animation: true })
        }
      }
    }
  }
}
分享
微博
QQ
微信
回复
1天前
相关问题
refresh + scroll+list嵌套问题
403浏览 • 1回复 待解决
scrolllist嵌套滑动
1559浏览 • 1回复 待解决
HarmonyOS scroll嵌套List不能整体滑动
521浏览 • 1回复 待解决
HarmonyOS scroll嵌套list页面无法滑动
54浏览 • 1回复 待解决
HarmonyOS scrolllist滚动冲突
439浏览 • 1回复 待解决
HarmonyOS List嵌套list中的布局
41浏览 • 1回复 待解决
Web和List嵌套手势冲突问题
1043浏览 • 1回复 待解决
HarmonyOS List+Swipe+web滑动冲突
224浏览 • 1回复 待解决
HarmonyOS Scroll+web+list嵌套滑行
39浏览 • 1回复 待解决
HarmonyOS list 嵌套web滑动切换问题
511浏览 • 1回复 待解决
HarmonyOS List嵌套waterflow滑动卡顿
306浏览 • 1回复 待解决
使用List嵌套实现表格布局
1032浏览 • 1回复 待解决
HarmonyOS Scroll嵌套web手势冲突
18浏览 • 1回复 待解决