返回博客首页
← 所有文章

使用 NativeScript 创建 Tinder 风格卡片 - 一见钟情

2017 年 4 月 13 日 — 作者:Jen Looper

任何了解我的人,都知道我的设计美学,以及我对 UI 和 UX 的想法,那就是

1. 我喜欢漂亮的颜色
2. 我喜欢在我的应用程序中使用大量的漂亮颜色
3. 我喜欢卡片
4. 如果有带有漂亮颜色的卡片,那就是 💰

关于我的另一个有趣的事实是,我之前在一家婚恋网站工作,我们为适婚单身人士进行匹配……有点像 Tinder,只不过更倾向于婚姻而不是其他。婚恋网站有有趣的 UI 挑战。毫不奇怪,婚恋应用程序应该开创真正革命性的 UI:可滑动卡片。在左右滑动来整理你的爱情生活时,有一种既在身体上又在情感上令人满意的感觉

screen696x696

几年前,著名的 Tinder 可滑动卡片界面席卷了应用程序商店,此后其他应用程序也采用了这种可排序卡片类型。较早的 Jelly 应用程序和 Pinterest 都是很好的例子
screen696x696-1

由于 NativeScript 布局支持手势和动画,因此创建这样的可滑动界面并不难。我第一次尝试创建 Tinder 风格的卡片界面是在去年我参加波士顿 Ignite 的闪电演讲时。我的应用程序背后的前提是,通过深入了解 23AndMe 基因 API,你可以根据你的尼安德特人 DNA 百分比找到你的完美匹配。请不要问为什么我对尼安德特人着迷,但我认为他们可能喜欢拥有自己的约会应用程序。尼安德特人也需要爱情。

neanderthal_love

最初,我创建了一个卡片滑动格式,其中一张卡片被放置到屏幕中,然后向左或向右滑动。这样,卡片会被回收,但一次只显示一张卡片。我在 NativeScriptSnacks.com 上分享了我的代码,但你可以立即看到布局存在一些问题。

test

首先,该应用程序的行为不像 Tinder,因为一次只显示一张卡片。其次,卡片有时会在图像呈现之前进入视图,导致界面不流畅。我们可以做得更好!仔细观察 Tinder 会发现,一堆卡片被展示给用户,作为视觉线索,表明有大量选择需要做出。

9-500-opt

还要注意,用户可以滑动顶部的卡片,并立即显示卡片以及“喜欢”或“不喜欢”的标记。这是一个线索,我们需要动态构建屏幕;必须在构建界面本身时将数据放置到卡片上。对于每个图像,绘制一张卡片并将其放置在布局中。此外,所有卡片必须彼此叠放在一起。这需要一个动态生成的绝对布局。

搭建界面

卡片界面 可以用于比简单地制作酷炫的约会应用程序多得多的用途。让我们学习如何通过创建食物匹配器来构建它们。想象一下,如果一个孩子可以通过在西兰花上向左滑动并在 PBJ 上向右滑动来在早上从爸爸妈妈那里预订午餐。或者,在这种情况下,通过选择晚餐后你想要的确切甜点,这始终是一个关键的选择时刻。

首先,我使用 Angular 和 NativeScript 搭建了一个基本的应用程序。由于这是一个甜点匹配应用程序,所以在我的服务中,我创建了一个基本的 emoji 数组。每个 emoji 都放置在彩色卡片上。

import { Injectable } from "@angular/core";
import { Emoji } from "./emoji";
@Injectable()
export class CardService {
    private emoji = new Array<Emoji>(
        { code: '🍮', color: 'b1' },
        { code: '🍡', color: 'b2' },
        { code: '🍨', color: 'b3' },
        { code: '🍩', color: 'b4' },
        { code: '🍪', color: 'b5' },
        { code: '🍰', color: 'b5' },
        { code: '🍬', color: 'b1' },
        { code: '🍭', color: 'b2' },
        { code: '🎂', color: 'b3' },
        { code: '🍧', color: 'b4' },
        { code: '🍫', color: 'b5' },
        { code: '🍦', color: 'b6' }
    );
    getEmoji(): Emoji[] {
        return this.emoji;
    }
}

在 `app.css` 文件中,我将颜色类与我在 colourlovers.com 上找到的一些漂亮颜色相关联

.b1 {
    background-color: #FFCCDA;
}
.b2 {
    background-color: #FAEEC3;
}
.b3 {
    background-color: #F67982;
}
.b4 {
    background-color: #FFA1A1;
}
.b5 {
    background-color: #FACBAA;
}
.b6 {
    background-color: #F67982;
}

然后,我创建了一个 XML 布局标记的“外壳”来容纳卡片。

<StackLayout>
    <AbsoluteLayout horizontalAlignment="center" paddingTop="30" #absolutelayout>
        <GridLayout width="100%" style="z-index:1" columns="*,*" horizontalAlignment="center">
            <Label #no col="0" verticalAlignment="center" text="不,谢谢!" class="no"></Label>
            <Label #yes col="1" text="是的,请!" class="yes"></Label>
        </GridLayout>
    </AbsoluteLayout>
</StackLayout>

这个 StackLayout 包含一个 AbsoluteLayout,卡片被放置在其中。叠加在这个布局上的是两个“贴纸”或“印章”,当用户向右滑动时显示绿色印章,当用户向左滑动时显示红色印章。它们作为滑动例程的第一步出现。

动画滑动

接下来,我们需要完成卡片作为堆栈的布局,并准备好将数据放置到卡片中。使用 `@ViewChild` 获取元素的 id

@ViewChild("absolutelayout") al: ElementRef;
@ViewChild("yes") yes: ElementRef;
@ViewChild("no") no: ElementRef;
...
ngOnInit() {
this.emoji = this.cardService.getEmoji();
// 初始化卡片
this.code = this.emoji[this.i].code;
// 为滑动做好准备!
for (var key in this.emoji) {
     this.handleSwipe(key);
}
}


然后,构建动态组件 - 网格和标签 - 并利用 `addChild` 将它们放置到 AbsoluteLayout 中。

handleSwipe(key: any) {
        this.i--;
        let grid = new GridLayout();
        let emoji = new Label();
        let yes = <Label>this.yes.nativeElement;
        let no = <Label>this.no.nativeElement;
        let absolutelayout = <AbsoluteLayout>this.al.nativeElement;
        let swipeleft = <Button>this.swipeleft.nativeElement;
        let swiperight = <Button>this.swiperight.nativeElement;
        // 在卡片上设置表情
        emoji.text = this.emoji[key].code;
        // 构建网格,即卡片
        grid.cssClass = 'card ' + this.emoji[key].color;
        grid.id = 'card' + Number(key);
        grid.marginTop = this.i;
        // 将表情添加到网格,并将网格添加到 AbsoluteLayout
        grid.addChild(emoji);
        absolutelayout.addChild(grid)
        ...
    }

最后,使用 NativeScript 的手势模块来处理实际的卡片滑动。此模块返回 args.direction,值为 1 或 0,分别代表向右或向左。因此,根据滑动方向,会发生一系列动画。首先,通常设置为 opacity=0 的戳记标签会短暂显示,显示红色或绿色的“谢谢”或“不谢”戳记。然后,卡片本身会使用 translate 进行动画处理,向右或向左以及向下推动,使其移动。这非常简单,您可以调整动画,使卡片向左或向右滑动,或弹回,具体取决于您希望界面响应的速度。

grid.on(GestureTypes.swipe, function (args: SwipeGestureEventData) {
            if (args.direction == 1) {
                // 向右
                yes.animate({ opacity: 0, duration: 100 })
                    .then(() => yes.animate({ opacity: 1, duration: 100 }))
                    .then(() => yes.animate({ opacity: 0, duration: 100 }))
                    .then(() =>
                        grid.animate({ translate: { x: 1000, y: 100 } })
                            .then(function () { return grid.animate({ translate: { x: 0, y: -2000 } }); })
                            .catch(function (e) {
                                console.log(e.message);
                            })
                    )
                    .catch((e) => {
                        console.log(e.message);
                    });
            }
            else {
                // 向左
                no.animate({ opacity: 0, duration: 100 })
                    .then(() => no.animate({ opacity: 1, duration: 100 }))
                    .then(() => no.animate({ opacity: 0, duration: 100 }))
                    .then(() =>
                        grid.animate({ translate: { x: -1000, y: 100 } })
                            .then(function () { return grid.animate({ translate: { x: 0, y: -2000 } }); })
                            .catch(function (e) {
                                console.log(e.message);
                            })
                    )
                    .catch((e) => {
                        console.log(e.message);
                    });
            }
        });

使用 NativeScript 的手势和动画模块来为这些卡片设置动画非常容易。为了确保此动态布局的性能,我建议一次性发送一批数据到卡片堆栈(例如,一次性布局十张卡片),并在滑动例程结束时,布局另外十张卡片。试试看!以下是最终结果。

swipe

这些卡片的源代码 在这里

您是否爱上了 NativeScript?请在评论中联系我们,或在 Twitter 上 @nativescript 告诉我们您的故事!