Слияние кода завершено, страница обновится автоматически
/*
* Copyright (c) 2024 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { IconList1, IconList2, IconList3 } from '../view/IconView';
import { ProductList } from '../view/ProductList';
import { promptAction } from '@kit.ArkUI';
/**
* 功能描述:本示例介绍运用Stack组件以构建多层次堆叠的视觉效果。通过绑定Scroll组件的onScroll滚动事件回调函数,精准捕获滚动动作的发生。当滚动时,实时地调节组件的透明度、高度等属性,从而成功实现了嵌套滚动效果、透明度动态变化以及平滑的组件切换。其中,搜索框能够实现“吸顶”效果,在用户滚动页面时始终保持在顶部。
*
* 推荐场景:内容丰富的网站、购物商城、新闻网站
*
* 核心组件:
* 1.ProductList
*
* 实现步骤:
* 1.在设计布局时,考虑到头部组件位于底部且其他组件需在其之上层叠展示,选用Stack组件以达成这种堆叠效果,确保各组件间具有清晰的前后层级关系。
* 2.为了实现顶部可滚动区域的内容堆叠和滚动效果,我们采用Scroll组件,确保用户可以顺畅地浏览滚动内容。
* 3.在处理滚动过程中动态调整文本框高度及组件透明度的需求时,通过对Scroll组件的滚动事件回调函数onScroll处理,使其在滚动过程中实时监测并适时修改文本框的高度及组件透明度。
* 4.在处理多层嵌套滚动场景时,保证正确的滚动顺序(即先滚动父组件再滚动子组件),只需在内层的Scroll组件中设置其nestedScroll属性,确保滚动行为符合预期。
* 5.商品列表部分采用瀑布流(WaterFlow)布局容器进行设计,将商品信息动态分布并分成两列呈现,每列商品自上而下排列,使用了LazyForEach进行数据懒加载,WaterFlow布局时会根据可视区域按需创建FlowItem组件,并在FlowItem滑出可视区域外时销毁以降低内存占用。
*/
/** 为便于后续描述与交流,现对应用界面中的相关区域进行命名说明:
* 1.底部白色背景区域:位于搜索栏正下方,呈现纯白色背景的区域,将此区域命名为“快捷图标区域1”。
* 2.商品上方无背景色横条列表:紧邻商品布局之上,未设置独立背景颜色的一组横向排列的图标,将其定义为“快捷图标区域2”。
* 3.向上滑动后显现的列表:当用户在界面中向上滑动操作时,会逐渐露出的另一组图标,此部分称为“快捷图标区域3”。
* 以上定义旨在清晰、准确地标识出应用界面中涉及的三个快捷图标区域,以利于后续描述。
*/
const ASPECTRATIO: number = 1; // 图片的宽高比
const OPACITY: number = 0.6; // 字体设置透明度
const ZINDEX: number = 1; // 快截图标区域3放在最上层
const LAYOUT_WEIGHT: number = 1; // 分配剩余空间
const SCROLL_OFFSET: number = 90; // 向上偏移的距离
@Component
export struct ComponentStackComponent {
build() {
// TODO: 知识点:堆叠容器,子组件按照顺序依次入栈,后一个子组件覆盖前一个子组件。
Stack({ alignContent: Alignment.Top }) {
Flex({ justifyContent: FlexAlign.SpaceBetween }) {
Image($r("app.media.component_stack_user_portrait"))
.width($r("app.integer.component_stack_user_portrait_width"))
.aspectRatio(ASPECTRATIO)
.borderRadius($r("app.integer.component_stack_user_portrait_border_radius"))
.onClick(() => {
promptAction.showToast({ message: $r('app.string.component_stack_other_function') });
});
Image($r("app.media.component_stack_stack_scan"))
.width($r("app.integer.component_stack_scan_width"))
.aspectRatio(ASPECTRATIO)
.onClick(() => {
promptAction.showToast({ message: $r('app.string.component_stack_other_function') });
});
}
.padding({
left: $r("app.integer.component_stack_flex_padding_left"),
right: $r("app.integer.component_stack_flex_padding_right"),
top: $r("app.integer.component_stack_flex_padding_top")
})
.width('100%').height($r("app.integer.component_stack_flex_height"))
ScrollView()
// 自身和子节点都响应触摸测试,不会阻塞兄弟节点的触摸测试。
.hitTestBehavior(HitTestMode.Transparent)
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.component_stack_product_background'))
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
}
}
@Component
struct ScrollView {
@State searchHeight: number = 100; // 搜索框原始高度
readonly searchHeightRaw: number = 100; // 备份搜索框初始高度
@State marginTop: number = 200; // 快截图标区域2 顶部偏移量
@State opacity2: number = 1; // 快截图标区域2 透明度
@State ratio: number = 1; // 快截图标区域2 缩小比例
@State height2: number = 100; // 快截图标区域2 高度
readonly height2Raw: number = 100; // 快截图标区域2 备份高度
@State isChange: boolean = false; // 改变快截图标区域1的组件
@State opacity1: number = 1; // 快截图标区域1的透明度
@State marginSpace: number = 25; // 快截图标区域3 左右之间间隔
readonly maxMarginSpace: number = 25; // IconList3默认最大间距
readonly minMarginSpace: number = 12; // IconList3默认最小间距
readonly IconList1Raw: number = 100; // 计算IconList1的透明度
readonly IconList2Raw: number = 120; // 计算IconList2的透明度
readonly IconList3Raw: number = 140; // 计算IconList3的透明度
scroller: Scroller = new Scroller();
scroller2: Scroller = new Scroller();
build() {
// Scroll滚动父组件
Scroll(this.scroller) {
Column() {
SearchView({ searchHeight: this.searchHeight })
// Stack堆叠组件
Stack({ alignContent: Alignment.Top }) {
IconView({
isChange: this.isChange,
marginSpace: this.marginSpace,
opacity1: this.opacity1
})
// Scroll滚动子组件
Scroll(this.scroller2) {
BottomView({
ratio: this.ratio,
opacity2: this.opacity2,
height2: this.height2,
marginTop: this.marginTop
})
}
// 自身和子节点都响应触摸测试,不会阻塞兄弟节点的触摸测试。
.hitTestBehavior(HitTestMode.Transparent)
.width('100%')
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.scrollBar(BarState.Off)
// TODO: 知识点:嵌套滚动选项。设置向前向后两个方向上的嵌套滚动模式,实现与父组件的滚动联动。
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST, // 可滚动组件往末尾端滚动时的嵌套滚动选项,父组件先滚动,父组件滚动到边缘以后自身滚动。
scrollBackward: NestedScrollMode.SELF_FIRST // 可滚动组件往起始端滚动时的嵌套滚动选项,自身先滚动,自身滚动到边缘以后父组件滚动。
})
// Scroll滚动子组件函数回调
.onDidScroll(() => {
// TODO: 知识点: Scroll组件绑定onScroll事件,然后在此方法里改变该组件的margin和opacity属性值的大小实现组件移动和隐显
// 性能知识点: onScroll属于频繁回调,不建议在onScroll做耗时和冗余操作
const yOffset: number = this.scroller2.currentOffset().yOffset;
this.height2 = this.height2Raw - yOffset * 0.5;
// 根据yOffset的偏移量来设置IconList2的透明度,当偏移量大于等于IconList2原始高度就是透明的。
if (1 - yOffset / this.IconList2Raw >= 0) {
this.opacity2 = 1 - yOffset / this.IconList2Raw; // IconList2的透明度
} else {
this.opacity2 = 0;
}
// 巧妙利用IconList2的透明度的值opacity2来设置IconList2的缩放。
this.ratio = this.opacity2;
// 根据yOffset的偏移量来设置IconList1的透明度和IconList3的间距,当偏移量大于等于IconList1原始高度就是透明的同时IconList3的间距也是最小的。
if (1 - yOffset / this.IconList1Raw > 0) {
this.isChange = false;
this.opacity1 = 1 - yOffset / this.IconList1Raw; // IconList1的透明度
this.marginSpace = this.maxMarginSpace; // IconList3默认间距
} else {
this.isChange = true;
this.opacity1 = (yOffset - this.IconList1Raw) / this.maxMarginSpace; // IconList1的透明度
this.marginSpace = this.IconList3Raw - yOffset > this.minMarginSpace ?
(this.IconList3Raw - yOffset) : this.minMarginSpace; // IconList3的间距
}
})
}
.layoutWeight(LAYOUT_WEIGHT)
}
.height('100%')
.width('100%')
.margin({ top: $r("app.integer.component_stack_margin_search_view") }) // 不遮挡头像和扫码按钮
}
.padding({
left: $r("app.integer.component_stack_scroll_padding"),
right: $r("app.integer.component_stack_scroll_padding")
})
.width('100%')
.height('100%')
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.BOTTOM])
.scrollBar(BarState.Off)
// Scroll滚动父组件函数回调
.onDidScroll(() => {
// 获取滑动距离
const yOffset: number = this.scroller.currentOffset().yOffset;
// this.searchHeight 随 yOffset变化的公式。按需调整。
this.searchHeight = this.searchHeightRaw - yOffset * 0.6;
})
}
}
@Component
struct SearchView {
@Prop searchHeight: number;
build() {
Row() {
Image($r("app.media.component_stack_search"))
.width($r("app.integer.component_stack_search_width"))
.aspectRatio(ASPECTRATIO)
.margin({ left: $r("app.integer.component_stack_search_icon_margin_left") })
Text($r("app.string.component_stack_search_title"))
.opacity(OPACITY)
.fontColor(Color.Black)
.fontSize($r("app.integer.component_stack_search_font_size"))
.margin({ left: $r("app.integer.component_stack_search_text_margin_left") })
}
.id('component_stack_search')
.width('100%')
.height(this.searchHeight)
.backgroundColor(Color.White)
.borderRadius($r("app.integer.component_stack_search_border_radius"))
.onClick(() => {
promptAction.showToast({ message: $r('app.string.component_stack_other_function') });
});
}
}
@Component
struct IconView {
@Prop isChange: boolean; // 改变快截图标区域1的组件
@Prop opacity1: number; // 快截图标区域1的透明度
@Prop marginSpace: number; // 快截图标区域3 左右之间间隔
build() {
if (this.isChange) {
Row() {
IconList3({ marginSpace: this.marginSpace })
}
.backgroundColor($r('app.color.component_stack_product_background'))
.width('100%')
.height($r("app.integer.component_stack_icon_list_height3"))
.opacity(this.opacity1)
// TODO: 知识点:组件的Z序,设置组件的堆叠顺序,zIndex值越大,显示层级越高。
.zIndex(ZINDEX)
} else {
Row() {
IconList1()
}
.width('100%')
.height($r("app.integer.component_stack_icon_list_height1"))
.opacity(this.opacity1)
}
}
}
@Component
struct BottomView {
@Prop opacity2: number; // 快截图标区域2 透明度
@Prop ratio: number; // 快截图标区域2 缩小比例
@Prop height2: number; // 快截图标区域2 高度
@Prop marginTop: number; // 快截图标区域2 顶部偏移量
build() {
Column() {
Row() {
// 上图下文字透明背景样式
IconList2({ ratio: this.ratio })
}
.width('100%')
.height(this.height2)
.opacity(this.opacity2)
// 商品列表组件
ProductList()
.width('100%')
.height(`calc((100% + ${SCROLL_OFFSET}vp ))`)
}
.margin({ top: this.marginTop }) // 防止遮挡IconList1
}
}
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )