鸿蒙开发协调布局CollapsibleLayout
鸿蒙开发协调布局CollapsibleLayout
首先鸿蒙我暂时没找到官方提供的协调布局,所以得自己自定义。
一、思路
可滚动头部、粘性头部、可滚动内容布局
可折叠区域高度=可滚动头部高度-粘性头部高度
二、效果图
鸿蒙开发协调布局CollapsibleLayout
三、关键代码
// 联系:893151960
@Component
export struct CollapsibleLayout {
@Builder AppBarInitBuilder() {}
// 标题栏
@BuilderParam AppBarBuilder: () => void = this.AppBarInitBuilder
@Builder ScrollableHeaderInitBuilder() {}
// 可滚动头部
@BuilderParam ScrollableHeaderBuilder: () => void = this.ScrollableHeaderInitBuilder
@Builder StickyHeaderInitBuilder() {}
// 粘性头部
@BuilderParam StickyHeaderBuilder: () => void = this.StickyHeaderInitBuilder
// 页面内容部分,通常为列表
@Builder ContentInitBuilder() {}
@BuilderParam ContentBuilder: () => void = this.ContentInitBuilder
@ObjectLink mediator: CollapsibleMediator
// 标题栏高度
@State appBarHeight: number = 0
// 可滚动头部高度
@State scrollableHeaderHeight: number = 0
// 粘性头部高度
@State stickyHeaderHeight: number = 0
// 组件高度
@State totalHeight: number = 0
lastProgress = 0
showAppBar:boolean = true
calculateContentHeight() {
if (this.totalHeight != 0 && this.stickyHeaderHeight != 0 ) {
return this.totalHeight - this.stickyHeaderHeight - this.appBarHeight
}
return 2000
}
/**
* 计算可折叠区域高度、内容区域高度
*/
calculateCoordinatorScrollableHeight() {
if ( this.scrollableHeaderHeight != 0 && this.totalHeight != 0 && this.stickyHeaderHeight != 0) {
this.mediator.coordinatorScrollableHeight = this.scrollableHeaderHeight - this.appBarHeight
}
}
updateCollapsibleScrollProgress() {
if (this.mediator.collapsibleScrollProgressCallback) {
const progress = this.mediator.curCoordinatorOffset() / this.mediator.coordinatorScrollableHeight
if (this.lastProgress != progress && Math.abs(this.lastProgress - progress) >= 0.01 || progress == 1) {
// if (this.lastProgress != progress) {
this.mediator.collapsibleScrollProgressCallback(progress)
this.lastProgress = progress
}
}
}
@Builder BodyBuilder() {
// 粘性头部
Stack() {
this.StickyHeaderBuilder()
}.onAreaChange((_, newValue: Area) => {
this.stickyHeaderHeight = newValue.height as number
this.calculateCoordinatorScrollableHeight()
})
// .zIndex(100)
Stack() {
this.ContentBuilder()
}.height(this.calculateContentHeight())
}
build() {
Stack({ alignContent: Alignment.Top }) {
Scroll(this.mediator.outScroller) {
Column() {
// 可滚动容器
Stack() {
this.ScrollableHeaderBuilder()
}.onAreaChange((_, newValue: Area) => {
this.scrollableHeaderHeight = newValue.height as number
this.calculateCoordinatorScrollableHeight()
})
this.BodyBuilder()
}
}
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Off)
.edgeEffect(EdgeEffect.None)
.height(this.totalHeight != 0 ? this.totalHeight : 10000)
.onWillScroll(() => {
this.updateCollapsibleScrollProgress()
})
.onScrollFrameBegin((offset: number, _) => {
if (offset > 0) { // 向上
if (this.mediator.isShrink()) { // 可折叠区域完全折叠
return { offsetRemain: 0 }
} else {
return { offsetRemain: offset }
}
}
if (offset < 0) { // 向下
if (this.mediator.isExpand()) { // 可折叠区域完全张开
return { offsetRemain: 0 }
} else {
return { offsetRemain: offset }
}
}
return { offsetRemain: offset }
})
// 固定标题栏
if (this.showAppBar){
Stack() {
this.AppBarBuilder()
}.width("100%")
.onAreaChange((_, newValue: Area) => {
this.appBarHeight = newValue.height as number
this.calculateCoordinatorScrollableHeight()
})
}
}
.width("100%")
.onAreaChange((_, newValue: Area) => {
this.totalHeight = newValue.height as number
this.calculateCoordinatorScrollableHeight()
})
}
}
@Observed
export class CollapsibleMediator {
// 内部可滚动容器集合
innerScrollerArrays: Scroller[] = new Array<Scroller>()
// 可折叠区域的高度(scrollableHeader-appBarHeight-stickyHeaderHeight)
coordinatorScrollableHeight: number = 0
curInnerScrollerIndex = 0
// 可折叠区域滑动进度回调
collapsibleScrollProgressCallback?: (progress: number) => void
outScroller = new Scroller()
constructor(coordinatorScrollProgressCallback?: (progress: number) => void ) {
this.collapsibleScrollProgressCallback = coordinatorScrollProgressCallback
}
getCurrentInnerScroller(index?: number) {
const curIndex = index ? index : 0
if (this.innerScrollerArrays[curIndex] == null) {
this.innerScrollerArrays[curIndex] = new Scroller()
}
return this.innerScrollerArrays[curIndex]
}
getCurrentInnerScrollerOffset() {
let y = this.innerScrollerArrays?.[this.curInnerScrollerIndex]?.currentOffset()?.yOffset
return (y ? y : 0) | 0
}
curCoordinatorOffset() {
return this.outScroller.currentOffset().yOffset
}
getScrollerFrameRemainOffset(offset: number): number {
if (!this.isShrink() && offset > 0) { // 向上
this.outScroller.scrollBy(0, offset)
return 0
}
if (this.getCurrentInnerScrollerOffset() == 0 && offset < 0) { // 向下
this.outScroller.scrollBy(0, offset)
return 0
}
return offset
}
setCurInnerScrollerIndex(index: number) {
if (this.curInnerScrollerIndex !== index) {
this.curInnerScrollerIndex = index
}
}
isExpand() {
return this.curCoordinatorOffset() === 0
}
isShrink() {
return this.coordinatorScrollableHeight !== 0 && Math.abs(this.curCoordinatorOffset() - this.coordinatorScrollableHeight) <= 0.0001
}
expand() {
this.outScroller.scrollTo({ xOffset: 0, yOffset: 0,
animation: { duration: 300, curve: Curve.Linear } })
}
shrink() {
this.outScroller.scrollTo({ xOffset: 0, yOffset: this.coordinatorScrollableHeight,
animation: { duration: 300, curve: Curve.Linear } })
}
}
四、项目demo结构图
有需要完整源码可私信或者看昵称联系我