HarmonyOS 鸿蒙Next关于自定义tab点击滑动相关问题
HarmonyOS 鸿蒙Next关于自定义tab点击滑动相关问题
如何解决ListItem的高度较低时,切换tab不能精准定位问题,而且最后一个tab点击无法高亮显示;TabBar超出屏幕宽度时,如何点击屏幕栏边缘tab时自动滑动tab栏。
2 回复
参考以下demo
import curves from '[@ohos](/user/ohos).curves';
import display from '[@ohos](/user/ohos).display';
import componentUtils from '[@ohos](/user/ohos).arkui.componentUtils';
class ItemClass {
content: string = '';
color: Color = Color.White;
}
[@Entry](/user/Entry)
[@Component](/user/Component)
struct SwiperTab {
private displayInfo: display.Display | null = null;
private listScroller: ListScroller = new ListScroller();
private animationDuration: number = 300; //动画时间
private animationCurve: ICurve = curves.interpolatingSpring(7, 1, 328, 34); // 动画曲线
[@State](/user/State) indicatorIndex: number = 0; //当前页签Index
private scroller: Scroller = new Scroller();
private barArr: string[] = ['关注', '推荐', '热点','上海', '视频', '新时代','新歌', '新碟', '新片'];
[@State](/user/State) currentIndex: number = 0;
[@State](/user/State) endIndex: number = 0;
private arrList: ItemClass[] = [];
private colorArr: Color[] = [Color.White, Color.Blue, Color.Brown, Color.Green, Color.Gray];
aboutToAppear(): void {
this.displayInfo = display.getDefaultDisplaySync(); //获取屏幕实例
for (let i = 0; i < 9; i++) {
let data: ItemClass = {
content: i.toString(),
color: this.colorArr[i % 5]
}
this.arrList.push(data);
}
}
// 获取屏幕宽度,单位vp
private getDisplayWidth(): number {
return this.displayInfo != null ? px2vp(this.displayInfo.width) : 0;
}
// 获取组件大小、位置、平移缩放旋转及仿射矩阵属性信息。
private getTextInfo(index: number): Record<string, number> {
let modePosition :componentUtils.ComponentInfo = componentUtils.getRectangleById(index.toString());
try {
return { 'left': px2vp(modePosition.windowOffset.x), 'width': px2vp(modePosition.size.width) }
} catch (error) {
return { 'left': 0, 'width': 0 }
}
}
private scrollIntoView(currentIndex: number): void {
const indexInfo = this.getTextInfo(currentIndex);
let tabPositionLeft = indexInfo.left;
let tabWidth = indexInfo.width;
// 获取屏幕宽度,单位vp
const screenWidth = this.getDisplayWidth();
const currentOffsetX: number = this.scroller.currentOffset().xOffset;//当前滚动的偏移量
this.scroller.scrollTo({
// 将页签bar可滑动时候定位在正中间
xOffset: currentOffsetX + tabPositionLeft - screenWidth / 2 + tabWidth / 2,
yOffset: 0,
animation: {
duration: this.animationDuration,
curve: this.animationCurve, // 动画曲线
}
});
}
private startAnimateTo(duration: number, marginLeft: number, width: number): void {
animateTo({
duration: duration, // 动画时长
curve: this.animationCurve, // 动画曲线
onFinish: () => {
}
}, () => {
})
}
// 下划线动画
private underlineScrollAuto(duration: number, index: number): void {
let indexInfo = this.getTextInfo(index);
this.startAnimateTo(duration, indexInfo.left, indexInfo.width);
}
build() {
Column() {
Column(){
Scroll(this.scroller) {
Row() {
ForEach(this.barArr, (item: string, index: number) => {
Column() {
Text(item)
.fontSize(16)
.borderRadius(5)
.fontColor(this.indicatorIndex === index ? Color.Red : Color.Black)
.fontWeight(this.indicatorIndex === index ? FontWeight.Bold : FontWeight.Normal)
.margin({ left: 5, right: 5 })
.padding({left: 8, right: 8})
.id(index.toString())
.onClick(() => {
this.indicatorIndex = index;
this.scrollIntoView(index);
// 跟List进行联动
this.listScroller.scrollToIndex(index)
})
.border(this.indicatorIndex == index ? {
width: { left: 0, right: 0, top: 0, bottom: 2 },
color: { bottom: Color.Red },
radius: 0,
style: {
bottom: BorderStyle.Solid,
}
} : null)
}
}, (item: string) => item)
}
.height(32)
}
.width('100%')
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.edgeEffect(EdgeEffect.None)
.onScrollStop(() => {
this.underlineScrollAuto(0, this.indicatorIndex);
})
}
.width('90%')
.margin({ top: 15, bottom: 10})
List({ space: 10, scroller: this.listScroller }) {
ForEach(this.arrList, (item: ItemClass, index: number) => {
ListItem() {
Column() {
Text(item.content)
}
.width('100%')
.height(400)
.backgroundColor(item.color)
}
.onAreaChange((oldValue: Area, newValue: Area) => {
if (this.currentIndex === index) {
let posY = Number(newValue.position.y || 0)
let offsetY = Number(newValue.width) / 3
// 这里的距离根据自身业务来调整
if (posY>= 0 && posY< offsetY) {
this.indicatorIndex = this.currentIndex
}
}
})
}, (item: ItemClass) => item.content)
}
.edgeEffect(EdgeEffect.None)
.scrollBar(BarState.Off)
.onScrollIndex((start: number, end: number, center: number) => {
this.currentIndex = center
this.endIndex = end
})
.onReachEnd(() => {
// 处理最后一个页签
this.indicatorIndex = this.endIndex
})
.onScrollStop(() => {
this.scrollIntoView(this.indicatorIndex);
})
.width("100%")
.height("calc(100% - 50vp)")
.backgroundColor('#F1F3F5')
}
.width('100%')
}
}
可以根据自身业务来调整list的item滑动到某个top的某个范围时,显示对应的tabbar,
.onAreaChange((oldValue: Area, newValue: Area) => {
if (this.currentIndex === index) {
// 这里的距离根据自身业务来调整
let posY = Number(newValue.position.y || 0)
let offsetY = Number(newValue.width) / 3
if (posY>= 0 && posY< offsetY) {
this.indicatorIndex = this.currentIndex
}
}
})
修改后的demo:
import curves from '[@ohos](/user/ohos).curves';
import display from '[@ohos](/user/ohos).display';
import componentUtils from '[@ohos](/user/ohos).arkui.componentUtils';
class ItemClass {
content: string = '';
color: Color = Color.White;
}
[@Entry](/user/Entry)
[@Component](/user/Component)
struct SwiperTab {
private displayInfo: display.Display | null = null;
private listScroller: ListScroller = new ListScroller();
private animationDuration: number = 300; //动画时间
private animationCurve: ICurve = curves.interpolatingSpring(7, 1, 328, 34); // 动画曲线
[@State](/user/State) indicatorIndex: number = 0; //当前页签Index
private scroller: Scroller = new Scroller();
private barArr: string[] = ['关注', '推荐', '热点', '上海', '视频', '新时代', '新歌', '新碟', '新片'];
[@State](/user/State) currentIndex: number = 0;
[@State](/user/State) endIndex: number = 0;
private arrList: ItemClass[] = [];
private colorArr: Color[] = [Color.White, Color.Blue, Color.Brown, Color.Green, Color.Gray];
[@State](/user/State) isClickBar: boolean = false
aboutToAppear(): void {
//获取屏幕实例
this.displayInfo = display.getDefaultDisplaySync();
for (let i = 0; i < 9; i++) {
let data: ItemClass = {
content: i.toString(),
color: this.colorArr[i % 5]
}
this.arrList.push(data);
}
}
// 获取屏幕宽度,单位vp
private getDisplayWidth(): number {
return this.displayInfo != null ? px2vp(this.displayInfo.width) : 0;
}
// 获取组件大小、位置、平移缩放旋转及仿射矩阵属性信息。
private getTextInfo(index: number): Record<string, number> {
let modePosition: componentUtils.ComponentInfo = componentUtils.getRectangleById(index.toString());
try {
return { 'left': px2vp(modePosition.windowOffset.x), 'width': px2vp(modePosition.size.width) }
} catch (error) {
return { 'left': 0, 'width': 0 }
}
}
private scrollIntoView(currentIndex: number): void {
const indexInfo = this.getTextInfo(currentIndex);
let tabPositionLeft = indexInfo.left;
let tabWidth = indexInfo.width;
// 获取屏幕宽度,单位vp
const screenWidth = this.getDisplayWidth();
//当前滚动的偏移量
const currentOffsetX: number = this.scroller.currentOffset().xOffset;
// 将页签bar定位在正中间
this.scroller.scrollTo({
xOffset: currentOffsetX + tabPositionLeft - screenWidth / 2 + tabWidth / 2,
yOffset: 0,
animation: {
duration: this.animationDuration,
// 动画曲线
curve: this.animationCurve,
}
});
}
private startAnimateTo(duration: number, marginLeft: number, width: number): void {
animateTo({
duration: duration,
// 动画曲线
curve: this.animationCurve,
onFinish: () => {
}
}, () => {
})
}
// 下划线动画
private underlineScrollAuto(duration: number, index: number): void {
let indexInfo = this.getTextInfo(index);
this.startAnimateTo(duration, indexInfo.left, indexInfo.width);
}
build() {
Column() {
Column() {
Scroll(this.scroller) {
Row() {
ForEach(this.barArr, (item: string, index: number) => {
Column() {
Text(item)
.fontSize(16)
.borderRadius(5)
.fontColor(this.indicatorIndex === index ? Color.Red : Color.Black)
.fontWeight(this.indicatorIndex === index ? FontWeight.Bold : FontWeight.Normal)
.margin({ left: 5, right: 5 })
.padding({ left: 8, right: 8 })
.id(index.toString())
.onClick(() => {
this.indicatorIndex = index;
this.isClickBar = true
this.scrollIntoView(index);
// 跟List进行联动
this.listScroller.scrollToIndex(index)
})
.border(this.indicatorIndex == index ? {
width: {
left: 0,
right: 0,
top: 0,
bottom: 2
},
color: { bottom: Color.Red },
radius: 0,
style: {
bottom: BorderStyle.Solid,
}
} : null)
}
}, (item: string) => item)
}
.height(32)
}
.width('100%')
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
.edgeEffect(EdgeEffect.None)
.onScrollStop(() => {
this.underlineScrollAuto(0, this.indicatorIndex);
})
}
.width('90%')
.margin({ top: 15, bottom: 10 })
List({ space: 10, scroller: this.listScroller }) {
ForEach(this.arrList, (item: ItemClass, index: number) => {
ListItem() {
Column() {
Text(item.content)
}
.width('100%')
.height(200)
.backgroundColor(item.color)
}
.onAreaChange((oldValue: Area, newValue: Area) => {
if (this.currentIndex === index) {
// 这里的距离根据自身业务来调整
let posY = Number(newValue.position.y || 0)
let offsetY = Number(newValue.width) / 3
if (posY >= 0 && posY < offsetY) {
this.indicatorIndex = this.currentIndex
}
}
})
}, (item: ItemClass) => item.content)
}
.edgeEffect(EdgeEffect.None)
.scrollBar(BarState.Off)
.onScrollIndex((start: number, end: number, center: number) => {
this.currentIndex = start
this.endIndex = end
})
.onReachEnd(() => {
// 处理最后一个页签
if (!this.isClickBar) {
this.indicatorIndex = this.endIndex
}
})
.onScrollStop(() => {
this.scrollIntoView(this.indicatorIndex);
})
.onScrollFrameBegin((offset: number, state: ScrollState) => {
this.isClickBar = false
return { offsetRemain:offset }
})
.width("100%")
.height("calc(100% - 50vp)")
.backgroundColor('#F1F3F5')
}
.width('100%')
}
}
第二个问题是由于预览器不支持该动画效果,可以使用真机或者模拟器进行测试
更多关于HarmonyOS 鸿蒙Next关于自定义tab点击滑动相关问题的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
针对HarmonyOS 鸿蒙Next关于自定义tab点击滑动相关问题,以下提供直接解决方案:
-
自定义Tab组件实现:
- 利用HarmonyOS提供的
CustomComponent
或CustomLayout
来创建自定义Tab组件。 - 通过
ListContainer
或DirectionalLayout
来组织Tab项的显示。
- 利用HarmonyOS提供的
-
Tab点击事件处理:
- 为每个Tab项设置点击事件监听器,使用
Component.setClickedListener
方法。 - 在点击事件回调中,通过
Component
的getId
或自定义属性来识别被点击的Tab项。
- 为每个Tab项设置点击事件监听器,使用
-
实现滑动效果:
- 利用
Scroller
或ScrollComponent
来实现滑动效果。 - 根据Tab项的宽度和容器宽度,计算滑动距离和位置。
- 在Tab点击事件中,调用滑动组件的滑动方法,如
scrollTo
或smoothScrollTo
。
- 利用
-
同步Tab状态与滑动内容:
- 维护一个当前选中Tab的索引。
- 在滑动结束时,根据滑动位置更新选中Tab的索引,并更新UI以反映当前选中状态。
- 在Tab点击事件中,直接跳转到对应内容位置,并更新选中Tab索引和UI。
如果问题依旧没法解决请联系官网客服,官网地址是:https://www.itying.com/category-93-b0.html