返回博客首页
← 所有文章

构建 NativeScript 滑动卡片插件:大使故事

2017 年 8 月 24 日 — 作者:Rachid Al-Khayyat 和 Jean-Baptiste Aniel

1- 介绍 Native Baguette 团队

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 的应用程序,但你也可以构建任何你想要的东西。

我们决定在共同努力改进彼此用例的同时构建两个独立的插件。

2- 构建插件

构建 NativeScript 插件的最佳实践是使用官方插件种子。这个种子为你提供了一个坚实的设置,并且我们发布插件所需的一切都变得轻而易举。所以我们从这里开始。

3- Rachid 的滑动卡片插件


swipeCard

如前所述,想法是构建一个容器,该容器包含一叠类型为(布局)的卡片,由消费者来构建和设计不同的卡片,然后通过一个类型为布局的数组将它们分配给插件。

有趣的事实!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');
        }
    });


该插件已成功在纯 Nativescript 和 Angular Nativescript 中进行了测试。关于安装和使用的所有说明都可以在我的 github 仓库中找到:https://github.com/rkhayyat/nativescript-swipe-card

 

4- Jean-Baptiste 的滑动布局插件

Jean-Baptiste:虽然这是插件的不同版本,但很多核心代码都是相同的。由于我们都处理滑动手势,所以我不会过多地谈论核心内容,而更多地谈论插件之间的差异。

demo

该插件已发布在 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

5- 结论

我们发现,为 Nativescript 构建插件并不像我们想象的那么难。一旦我们掌握了基础知识,它就真的很有趣也很容易。我们的学习曲线平稳快速,并且我们也从Nathan Walker 的一个很棒的课程中获益良多,因此,在很短的时间内(两个月),并且在时间非常有限的情况下,我们设法构建了两种不同的滑动卡片插件概念,而不是一个。

我们很享受我们在 NativeScript 大使计划中进行的短暂实验,这绝对不是最后一次。