手势组合:
鸿蒙还提供了实现手势组合方式;同时也规定了手势组合的三方模式:如下:
GestureMode.Sequence:顺序识别,根据注册顺序依次进行手势识别,直到所有手势识别成功。如果任一手势识别失败,则后续手势识别均无法完成。在顺序识别手势组中,仅最后一个手势能响应onActionEnd事件。
GestureMode.Parallel:并发识别,注册的手势同时识别,直到所有手势识别结束,手势识别互相不影响。
GestureMode.Exclusive:互斥识别,注册的手势同时识别,若有一个手势识别成功,则结束手势识别,其他手势识别均失败。
Column() {
Text('验证手势的组合')
.fontSize(20)
.fontColor('#333')
}
.gesture(
GestureGroup(
GestureMode.Sequence, //组合手势识别的顺序
//双击触发
// TapGesture({ count: 2 }).onAction(() => {
// console.log('-----------> gesture group TapGesture ')
// }),
//长点击相应
LongPressGesture({
fingers:1,
})
.onAction(() => {
console.log('-----------> gesture group LongPressGesture ')
}),
PanGesture()
.onActionStart(() => {
console.log('-----------> gesture group PanGesture onActionStart')
})
.onActionUpdate(() => {
console.log('-----------> gesture group PanGesture onActionUpdate')
})
.onActionEnd(() => {
console.log('-----------> gesture group PanGesture onActionEnd')
})
)
)
.width('100%')
.height(50)
.alignItems(HorizontalAlign.Center)
.justifyContent(FlexAlign.Center)
.backgroundColor(Color.Gray)
注意:如果在组合手势中,模式顺序执行,如果第一个是TapGesture 那么将导致后续无法相应;因为只有松手时才响应onAction 事件;
手势的拦截处理:
手势拦截可以查看鸿蒙的拦截处理;
针对 Scroll 嵌套 list 滑动引发的冲突,可以借助于两种方式处理;
方案:1.onScrollFrameBegin() 在外部scroll 或者list 中设置;同时设置两者的的 scroller 通过scroller 实现滚动交替;可以设置在父容器中 将滚动事件传递给子容器,也可以设置到子容器中,将滚动事件传递到父容器中 ;来实现滚动事件的交互;例如设置嵌套滚动中的代码如下:
//当前在父容器 scroll 设置,也可以设置在内部
.onScrollFrameBegin((offset: number, state: ScrollState) => {
//如果想要将滚动消费交给子容器消费,可以将滑动交给 childScroll
if (offset < 0) {
//手指向下滑动
if (this.scrollerForChild.currentOffset().yOffset != 0 ){
//没有滚动到了 list 最顶部
this.scrollerForChild.scrollBy(0, offset);//将事件交给子组件滚动
return {offsetRemain:0}//表示当前父容器消费偏移量 为0
}
}
return { offsetRemain: offset };//表示滚动由父容器 scroll 消费
}
注意:onScrollFrameBegin:在该方法中手指向上滑动 offset>0 ,向下滑动 offset<0;这个地方和滑动手势中的 offsetY 是不同的;
方案2.nestedScroll 通过设置交替滚动来满足,但是这种情况,一般应用于scroll+tab+list,实现沉浸式效果;同样也能更好的实现scroll+list的滚动交互效果;常见的设置如下:
.nestedScroll({
//当可滚动组件向前滚动时,设置嵌套滚动模式
scrollForward: NestedScrollMode.PARENT_FIRST,
//当可滚动组件向后滚动时,设置嵌套滚动模式
scrollBackward: NestedScrollMode.SELF_FIRST
})
注意:在 Scroll+list时,如果 list 外部存在 Column 如果设置height('100%')的话,将列表将无法滚动;
实现下拉功能验证:
下面是一个demo,效果是实现下拉拖拽效果。类似下拉刷新;只作为验证功能使用,没有考虑到扩展,封装其他;
使用到了技能点:onScrollFrameBegin+nestedScroll处理交互冲突;onTouch 处理松手后操作;animateTo实现自动的回弹和展示效果;具体代码如下:
private scrollerForParent: Scroller = new Scroller();
private scrollerForChild: Scroller = new Scroller();
@Local list: number[] = new Array(50).fill(0)
.map((_: number, index) => index + 1)
@Local translateY: number = -200
@Builder
refresh() {
//编写一个功能,就是类似于下拉刷新,上拉加载功能;
Scroll(this.scrollerForParent) {
Column() {
Row()
.width('100%')
.height(200)
.backgroundColor(Color.Brown)
List({ space: 10, scroller: this.scrollerForChild }) {
Repeat(this.list)
.each((item: RepeatItem<number>) => {
ListItem() {
Text('这个是测试数据' + item.index)
.fontSize(20)
.fontColor('#333')
.width('100%')
.textAlign(TextAlign.Center)
.height(80);
};
})
.key((item: number, index: number) => {
return `${item}_${index}_${Math.random()}`;
});
}
.width('100%')
.height('100%')
.enableScrollInteraction(false) //禁止滚动
.scrollBar(BarState.Off)
.nestedScroll({
scrollForward: NestedScrollMode.PARENT_FIRST,
scrollBackward: NestedScrollMode.SELF_FIRST
});
}
.width('100%')
.translate({ y: this.translateY })
.onTouch((event: TouchEvent) => {
switch (event.type) {
case TouchType.Cancel:
case TouchType.Up:
//在松手的时候,判断当前展示同步的位置
/**
* 如果头部区域没有展示出来,就不做处理
* 如果头部区域展示出来,就判断展示出来的大小,如果超出指定的阈值就完全展示,如果小于,就进行隐藏
*/
if (this.translateY == -200) {
return;
}
if (this.translateY >= -120) {
//表示滑动超出了120的距离
this.getUIContext().animateTo({
delay: 0,
duration: 200,
curve: Curve.Linear,
iterations: 1,
onFinish: () => {
}
}, () => {
this.translateY = 0;
});
} else {
//表示没有超出一般的距离
this.getUIContext().animateTo({
delay: 0,
duration: 200,
curve: Curve.Linear,
iterations: 1,
onFinish: () => {
this.translateY = -200;
}
}, () => {
this.translateY = -200;
});
}
break;
}
});
}
.width('100%')
.height('100%')
.scrollable(ScrollDirection.Vertical)
.scrollBar(BarState.Off)
.onScrollFrameBegin((offset: number, state: ScrollState) => {
/**
* 想要实现的功能,就是在下滑的时候将因此区域的父容器跟随滚动显示出来
*1.在手指向下滑动的时候,获取到滑动的值,然后更改平移的值;但是最大的平移的值是200
*/
let resOffset: number = offset;
let currentHeight: number = this.scrollerForParent.currentOffset().yOffset;
let newOffset = currentHeight + offset;
console.log('--------- > PanGesture ' + currentHeight);
if (offset < 0) {
//手指向下滑动
console.log('--------- > PanGesture 1 ' + this.scrollerForChild.currentOffset().yOffset + ' ...state ' + state);
if (this.scrollerForChild.currentOffset().yOffset == 0 && state == ScrollState.Scroll) {
//如果子滚动器滑动到了顶部,同时是拖拽,那么就平移布局
this.translateY = (this.translateY - offset >= 0) ? 0 : this.translateY - offset;
return { offsetRemain: 0 };
}
//如果触发了 fling 效果后,如果不足以完整展示头部局,就将移动效果交给动画
if (this.scrollerForChild.currentOffset().yOffset == 0 && state == ScrollState.Fling) {
return { offsetRemain: 0 };
}
//如果没有滚动到最上方,就由子列表滚动
if (newOffset < 0) {
//表示当前头部区域还是没有展示出来
resOffset = 0 - currentHeight;
}
console.log('--------- > PanGesture 2' + (offset - resOffset));
this.scrollerForChild.scrollBy(0, offset - resOffset);
} else {
//手指向上移动
//在向上移动的时候,如果处于顶部,当前展示了头部局,同时处于手势滑动时
if (this.scrollerForChild.currentOffset().yOffset <= 0 && this.translateY > -200 &&
state == ScrollState.Scroll) {
this.translateY = (this.translateY - offset <= -200) ? -200 : this.translateY - offset;
return { offsetRemain: 0 };
}
//如果处于顶部,同时头部局可见,同时处于 fling 状态,那么就不仅移动,将移动效果交给动画处理
if (this.scrollerForChild.currentOffset().yOffset <= 0 && this.translateY > -200 &&
state == ScrollState.Fling) {
return { offsetRemain: 0 };
}
//这个地方不考虑 有脚布局
if (newOffset > 0) {
resOffset = 0 - currentHeight;
}
console.log('--------- > PanGesture 3 ' + resOffset + ' ....child ' + (offset - resOffset));
this.scrollerForChild.scrollBy(0, offset - resOffset);
}
return { offsetRemain: resOffset };
})
}
由于个人能力有限;如果存在问题,望各位大佬及时指正;