Native Baguette 团队是 2017 年夏季团队中的几个 NativeScript 大使团队之一。在这篇文章中,团队成员 Rachid Al-Khayyat 和 Jean-Baptiste Aniel 讨论了他们构建滑动卡片插件的过程,这是大使团队发布的第一个项目!
Rachid:今年早些时候,我正在为法国的难民构建一个由 Nativescript 提供支持的应用程序,我需要一叠可滑动卡片来显示双语的一些短语。我浏览了NativeScript 插件的官方来源,但遗憾的是找不到我需要的。后来我发现了一篇文章,解释了如何构建一个简单的插件,但代码不是插件。在成为大使之前,我就开始着手卡片插件的开发,最终加入了由 Brad Martin 和 Osei Fortune 指导的团队,并与 Jean-Baptiste Aniel 合作。我们决定将我们的团队命名为 Native Baguette,因为 Jean-Baptiste 和我都住在法国。我的想法是构建一个容器,该容器包含一叠类型为(布局)的卡片,让消费者来构建和设计不同的卡片,并将它们通过一个类型为布局的数组分配给插件。
Jean-Baptiste:我设想这个插件的方式有点不同。我不认为它只是一个类似 Tinder 的滑动卡片插件。我把它想象成一个简单的容器,它可以帮助你轻松地处理滑动事件、滑动动画和拖动手势,但尽可能地进行定制。因此,使用此版本的插件,你将能够创建一个类似 Tinder 的应用程序,但你也可以构建任何你想要的东西。
我们决定在共同努力改进彼此用例的同时构建两个独立的插件。
构建 NativeScript 插件的最佳实践是使用官方插件种子。这个种子为你提供了一个坚实的设置,并且我们发布插件所需的一切都变得轻而易举。所以我们从这里开始。
如前所述,想法是构建一个容器,该容器包含一叠类型为(布局)的卡片,由消费者来构建和设计不同的卡片,然后通过一个类型为布局的数组将它们分配给插件。
有趣的事实!Time-Travel 团队(Luna Kang、Stefan Medjo 和导师 Jen Looper)使用 Rachid 的滑动卡片插件完成了他们的“Fetching”应用程序——一个为狗狗提供 Tinder 的应用程序,它使用 Petfinder API 来帮助狗狗在其所在的区域找到小狗玩伴!
为了拖放卡片,我决定根据这篇优秀的文章的建议,用 Pan 手势代替 Swipe 手势。
Pan 手势将返回平移状态,其中
args.state === 1 (finger is pressed down)
args.state === 2 (finger is pressed and moving)
args.state === 3 (finger is up).
layout.on(GestureTypes.pan, (args: PanGestureEventData) => {
if (args.state === 1) // down
{
prevDeltaX = 0;
prevDeltaY = 0;
}
else if (args.state === 2) { // currently panning
layout.translateX += args.deltaX - prevDeltaX;
layout.translateY += args.deltaY - prevDeltaY;
prevDeltaX = args.deltaX;
prevDeltaY = args.deltaY;
}
}
当手指抬起(args.state === 3)时,代码应该决定是将卡片滑动开(分别向左或向右)还是滑动回其原始位置。原则是如果卡片的宽度超过容器边缘的 50%,则卡片将根据其位置向左或向右滑动,否则将弹回。
public static swipeEvent:string = 'swipeEvent';
//. ...
else if (args.state === 3) // up
{
let currLocationX =
layout.getLocationOnScreen().x-(screen.mainScreen.widthDIPs-layout.width)/2;
let isToLeft:boolean;
let swipeX:number;
if (currLocationX<0) {
currLocationX = currLocationX*(-1);
isToLeft = true;
}
let shiftX = (containerWidth*screen.mainScreen.widthDIPs) - currLocationX;
let movPerc = shiftX/(containerWidth*screen.mainScreen.widthDIPs);
if (movPerc < 0.5) {
if (isToLeft) {
swipeX = -2000;
}
else {
swipeX = 2000;
}
layout.animate({
translate: {
x:swipeX,
y:0
},
duration: 500
});
} else {
layout.animate({
translate: {
x:0,
y:0
},
duration: 200
});
}
最好让插件在每次将卡片滑动到左边或右边时通知消费者。这可以使用 this.notify 来完成,它将向 Class SwipeEvent 发送通知。这样,消费者可以挂钩一个自定义的回调函数,该函数将在卡片被滑动开时执行。
export class SwipeEvent {
eventName: string;
object: any;
direction:number
// …
If (movPerc < 0.5) {
let eventData:SwipeEvent = {
eventName: SwipeCard.swipeEvent,
object: this,
direction:isToLeft?2:1
}
if (isToLeft) {
swipeX = -2000;
this.notify(eventData);
}
else {
swipeX = 2000;
this.notify(eventData);
}
}
消费者可以像这样监听 swipeEvent
swipeCard.on("swipeEvent", (args:SwipeEvent) => {
console.log(args.direction);
if (args.direction === 1) {
//right
console.log('Swiped to right');
} else {
//left
console.log('Swiped to left');
}
});
Jean-Baptiste:虽然这是插件的不同版本,但很多核心代码都是相同的。由于我们都处理滑动手势,所以我不会过多地谈论核心内容,而更多地谈论插件之间的差异。
该插件已发布在 https://github.com/rhanb/nativescript-swipe-layout/
主要区别之一是 `SwipeLayout` 扩展了 `ContentView`,这意味着由 `SwipeLayout` 生成的 `View` 将没有任何 `layout` 特性。
这意味着你可以在 `SwipeLayout` 中放入任何东西;它不会干扰子元素。例如,如果你想构建一个类似 Tinder 的应用程序,一种解决方案是使用 AbsoluteLayout 来重现“堆栈”效果。
另一个区别是视图和 JS/TS/NG 部分之间绑定滑动事件的方式。在这个插件中,你将有 4 个事件要绑定
swipeLeft
swipeRight
swipeUp
swipeDown
例如,在 Angular 中
<SwipeLayout height="300" width="300" row="0" colSpan="3" col="0" (loaded)="tabLoaded($event)" (swipeLeft)="swipeLeft($event)"
(swipeRight)="swipeRight($event)" (swipeDown)="swipeDown($event)" (swipeUp)="swipeUp($event)">
</SwipeLayout>
你将能够触发任何一个事件,也可以不触发任何一个事件,没有一个是必须的。
第二个区别是,你将能够更改动画,启用或禁用动画。
你将有一个名为 `animation_state` 的属性,它将是一个名为 `ANIMATION_STATE` 的枚举,默认值为 `ALWAYS`
export enum ANIMATION_STATE {
ALWAYS, // Will always animate on swipes event
ON_EVENTS, // Will animate on swipes event listened on the app side
NEVER // Will never animate on swipes event
}
正如你所料,将你的属性 `animation_state` 设置为 `ANIMATION_STATE.ALWAYS` 将在每次滑动事件时动画你的视图。
但是,如果你将其设置为 `ANIMATION_STATE.ON_EVENTS`,你的视图将在设置回调的滑动事件上动画。
例如
<SwipeLayout height="300" width="300" row="0" colSpan="3" col="0" (loaded)="tabLoaded($event)" (swipeLeft)="swipeLeft($event)"
(swipeRight)="swipeRight($event)" [animated]=”myAnimationState”> // with myAnimationState set to ANIMATION_STATE.ON_EVENTS
</SwipeLayout>
视图将仅在向右和向左滑动事件上动画。
将你的属性 `animated` 设置为 `ANIMATION_STATE.NEVER` 将停止你的视图动画。
第三个名为 `gestureMode` 的属性允许消费者在两种模式之间选择
export enum GESTURE_MODE {
DRAG, // Will allow to drag layout
SWIPE // Will animate on swipe events
}
DRAG 模式将允许用户拖动布局,但滑动事件仍将被触发并动画。另一方面,SWIPE 模式将不允许用户拖动布局。主要区别在于事件触发的方式:在 SWIPE 模式下,事件将通过 `swipe listener` 触发,而在 DRAG 模式下,滑动事件将通过 `pan listener` 触发。
最后一个区别与动画定制有关。因为所有的动画都将是包中的公共函数,这意味着你可以覆盖它们
animateSwipeLeft
animateSwipeRight
animateSwipeUp
animateSwipeDown
我们发现,为 Nativescript 构建插件并不像我们想象的那么难。一旦我们掌握了基础知识,它就真的很有趣也很容易。我们的学习曲线平稳快速,并且我们也从Nathan Walker 的一个很棒的课程中获益良多,因此,在很短的时间内(两个月),并且在时间非常有限的情况下,我们设法构建了两种不同的滑动卡片插件概念,而不是一个。
我们很享受我们在 NativeScript 大使计划中进行的短暂实验,这绝对不是最后一次。