8.7 已发布—WinterCG 合规性第 1 部分
了解更多

本教程通过指导你使用一些基本功能构建一个示例应用,向你介绍 NativeScript 的基础知识。

本教程将教你以下内容

  • 使用 NativeScript 组件构建布局
  • 使用手势为你的应用添加交互性
  • 使用路由在不同视图之间进行导航

前提条件

NativeScript Core 使用 JavaScript 或 TypeScript 以及 XML 来创建应用。为了充分利用本教程,你应已对 JavaScriptTypeScript 有基本了解。

示例应用概览

你将构建一个主从结构的应用,其显示一个音乐剧列表,还可以导航到详细信息页面来查看每个音乐剧的详细信息。

Example app preview

你可以在 GitHub 上找到该应用的完整源代码

设置你的环境

要设置你的开发环境,请按照文档的 环境设置 部分中的说明进行操作。

创建一个新的 NativeScript 应用

本教程中我们会使用 TypeScript。要创建一个新的 NativeScript TypeScript 应用,请使用应用名称以及 --ts 运行 CLI 命令 ns create

cli
ns create example-app --ts

NativeScript CLI 使用根文件夹名称为 example-app 创建一个新目录,其中有一个初始的骨架应用项目,并且会安装必需的包和依赖项。这可能需要几分钟时间,完成后将准备就绪供运行。

运行应用

转到项目的目录,并运行以下命令以在各个平台上运行该项目。

cli
cd example-app

// run on iOS
ns run ios

// run on Android
ns run android

ns run 命令会构建该应用,并在连接好的安卓设备或安卓模拟器上启动该应用(安卓系统),或连接好的 iOS 设备或 iOS 模拟器上启动该应用(iOS 系统)。默认情况下,它侦听代码中的更改,同步这些更改,并刷新所有选定的设备。

文件夹结构

我们将基于 TypeScript starter 应用为我们的应用创建以下文件/文件夹结构。

app
  |- assets
      |- anastasia.png
      |- beetlejuicemusical.png
      |- bookofmormon.png
  |- home
      |- home-page.ts
      |- home-page.xml
      |- home-view-model.ts
  |- details
      |- details-page.ts
      |- details-page.xml
      |- details-view-model.ts
  |- models
      |- flick.model.ts
  |- services
      |- flick.service.ts
  |- app-root.xml
  |- app.css
  |- app.ts

创建主页

让我们通过以下内容开始创建主页功能的文件

typescript
// app/home/home-page.ts

import { NavigatedData, Page } from '@nativescript/core'
import { HomeViewModel } from './home-view-model'

export function navigatingTo(args: NavigatedData): void {
  if (args.isBackNavigation) {
    return
  }

  const page = <Page>args.object
  page.bindingContext = new HomeViewModel()
}
typescript
// app/home/home-view-model.ts

import { Observable } from '@nativescript/core'

export class HomeViewModel extends Observable {}
xml
<!-- app/home/home-page.xml -->
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" />

路由设置

我们将设置主页作为应用启动时的默认路由。我们可以通过将主页文件中的路径传递至根组件的 Frame 的 defaultPage 属性来设置默认路由。打开 app-root.xml 并添加以下代码

xml
<!-- app/app-root.xml -->
<Frame defaultPage="home/home-page" />

主页 UI

在创建主界面的 UI 之前,让我们先创建 `FlickModel` 和 `FlickService`。这将允许我们在模板中直接使用数据。

`FlickModel` 中包含每个 Flick 对象的形状。在 `app` 中创建一个 `models` 目录,并在其中创建一个名为 `flick.model.ts` 的新文件。打开新的 `flick.model.ts`,添加以下 `interface`

typescript
// app/models/flick.model.ts

export interface FlickModel {
  id: number
  genre: string
  title: string
  image: string
  url: string
  description: string
  details: {
    title: string
    body: string
  }[]
}

然后,我们将在 `FlickService` 中使用 `FlickModel` 返回 Flick 数据。在 `app` 中创建一个 `services` 目录,并在其中创建一个名为 `flick.service.ts` 的新文件。打开新的 `flick.service.ts`,并添加以下内容

typescript
// app/services/flick.service.ts

import { FlickModel } from '../models'

export class FlickService {
  private flicks: FlickModel[] = [
    {
      id: 1,
      genre: 'Musical',
      title: 'Book of Mormon',
      image: '~/assets/bookofmormon.png',
      url: 'https://nativescript.cn/images/ngconf/book-of-mormon.mov',
      description: `A satirical examination of the beliefs and practices of The Church of Jesus Christ of Latter-day Saints.`,
      details: [
        {
          title: 'Music, Lyrics and Book by',
          body: 'Trey Parker, Robert Lopez, and Matt Stone',
        },
        {
          title: 'First showing on Broadway',
          body: 'March 2011 after nearly seven years of development.',
        },
        {
          title: 'Revenue',
          body: 'Grossed over $500 million, making it one of the most successful musicals of all time.',
        },
        {
          title: 'History',
          body: 'The Book of Mormon was conceived by Trey Parker, Matt Stone and Robert Lopez. Parker and Stone grew up in Colorado, and were familiar with The Church of Jesus Christ of Latter-day Saints and its members. They became friends at the University of Colorado Boulder and collaborated on a musical film, Cannibal! The Musical (1993), their first experience with movie musicals. In 1997, they created the TV series South Park for Comedy Central and in 1999, the musical film South Park: Bigger, Longer & Uncut. The two had first thought of a fictionalized Joseph Smith, religious leader and founder of the Latter Day Saint movement, while working on an aborted Fox series about historical characters. Their 1997 film, Orgazmo, and a 2003 episode of South Park, "All About Mormons", both gave comic treatment to Mormonism. Smith was also included as one of South Park\'s "Super Best Friends", a Justice League parody team of religious figures like Jesus and Buddha.',
        },
        {
          title: 'Development',
          body: `During the summer of 2003, Parker and Stone flew to New York City to discuss the script of their new film, Team America: World Police, with friend and producer Scott Rudin (who also produced South Park: Bigger, Longer & Uncut). Rudin advised the duo to see the musical Avenue Q on Broadway, finding the cast of marionettes in Team America similar to the puppets of Avenue Q. Parker and Stone went to see the production during that summer and the writer-composers of Avenue Q, Lopez and Jeff Marx, noticed them in the audience and introduced themselves. Lopez revealed that South Park: Bigger, Longer & Uncut was highly influential in the creation of Avenue Q. The quartet went for drinks afterwards, and soon found that each camp wanted to write something involving Joseph Smith. The four began working out details nearly immediately, with the idea to create a modern story formulated early on. For research purposes, the quartet took a road trip to Salt Lake City where they "interviewed a bunch of missionaries—or ex-missionaries." They had to work around Parker and Stone\'s South Park schedule. In 2006, Parker and Stone flew to London where they spent three weeks with Lopez, who was working on the West End production of Avenue Q. There, the three wrote "four or five songs" and came up with the basic idea of the story. After an argument between Parker and Marx, who felt he was not getting enough creative control, Marx was separated from the project.[10] For the next few years, the remaining trio met frequently to develop what they initially called The Book of Mormon: The Musical of the Church of Jesus Christ of Latter-day Saints. "There was a lot of hopping back and forth between L.A. and New York," Parker recalled.`,
        },
      ],
    },
    {
      id: 2,
      genre: 'Musical',
      title: 'Beetlejuice',
      image: '~/assets/beetlejuicemusical.png',
      url: 'https://nativescript.cn/images/ngconf/beetlejuice.mov',
      description: `A deceased couple looks for help from a devious bio-exorcist to handle their haunted house.`,
      details: [
        {
          title: 'Music and Lyrics',
          body: 'Eddie Perfect',
        },
        {
          title: 'Book by',
          body: 'Scott Brown and Anthony King',
        },
        {
          title: 'Based on',
          body: 'A 1988 film of the same name.',
        },
        {
          title: 'First showing on Broadway',
          body: 'April 25, 2019',
        },
        {
          title: 'Background',
          body: `In 2016, a musical adaptation of the 1988 film Beetlejuice (directed by Tim Burton and starring Geena Davis as Barbara Maitland, Alec Baldwin as Adam Maitland, Winona Ryder as Lydia Deetz and Michael Keaton as Betelgeuse) was reported to be in the works, directed by Alex Timbers and produced by Warner Bros., following a reading with Christopher Fitzgerald in the title role. In March 2017, it was reported that Australian musical comedian Eddie Perfect would be writing the music and lyrics and Scott Brown and Anthony King would be writing the book of the musical, and that another reading would take place in May, featuring Kris Kukul as musical director. The musical has had three readings and two laboratory workshops with Alex Brightman in the title role, Sophia Anne Caruso as Lydia Deetz, Kerry Butler and Rob McClure as Barbara and Adam Maitland.`,
        },
      ],
    },
    {
      id: 3,
      genre: 'Musical',
      title: 'Anastasia',
      image: '~/assets/anastasia.png',
      url: 'https://nativescript.cn/images/ngconf/anastasia.mov',
      description: `The legend of Grand Duchess Anastasia Nikolaevna of Russia.`,
      details: [
        { title: 'Music and Lyrics', body: 'Lynn Ahrens and Stephen Flaherty' },
        {
          title: 'Book by',
          body: 'Terrence McNally',
        },
        {
          title: 'Based on',
          body: 'A 1997 film of the same name.',
        },
        {
          title: 'Background',
          body: `A reading was held in 2012, featuring Kelli Barret as Anya (Anastasia), Aaron Tveit as Dmitry, Patrick Page as Vladimir, and Angela Lansbury as the Empress Maria. A workshop was held on June 12, 2015, in New York City, and included Elena Shaddow as Anya, Ramin Karimloo as Gleb Vaganov, a new role, and Douglas Sills as Vlad.
        The original stage production of Anastasia premiered at the Hartford Stage in Hartford, Connecticut on May 13, 2016 (previews). The show was directed by Darko Tresnjak and choreography by Peggy Hickey, with Christy Altomare and Derek Klena starring as Anya and Dmitry, respectively.
        Director Tresnjak explained: "We've kept, I think, six songs from the movie, but there are 16 new numbers. We've kept the best parts of the animated movie, but it really is a new musical." The musical also adds characters not in the film. Additionally, Act 1 is set in Russia and Act 2 in Paris, "which was everything modern Soviet Russia was not: free, expressive, creative, no barriers," according to McNally.
        The musical also omits the supernatural elements from the original film, including the character of Rasputin and his musical number "In the Dark of the Night", (although that song’s melody is repurposed in the new number "Stay, I Pray You"), and introduces instead a new villain called Gleb, a general for the Bolsheviks who receives orders to kill Anya.`,
        },
      ],
    },
  ]

  static getInstance(): FlickService {
    return FlickService._instance
  }

  private static _instance: FlickService = new FlickService()

  getFlicks(): FlickModel[] {
    return this.flicks
  }

  getFlickById(id: number): FlickModel | undefined {
    return this.flicks.find((flick) => flick.id === id) || undefined
  }
}

添加一个 `/app/assets/` 目录到项目中,并从示例项目中复制 3 张静态图像 此处.

接下来,让我们分解主页的布局和 UI 元素。

Home page layout breakdown

主页可以分成两部分,带有标题的 ActionBar 和带有卡片的可滚动主内容区域(我们将在下一节讨论卡片)。让我们从创建带标题的 ActionBar 开始。打开 `home-page.xml`,添加以下代码

xml
<!-- app/home/home-page.xml -->
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo">
  <ActionBar title="NativeFlix" />
</Page>

由于我们有一个要显示的 flick 数组,因此可以使用 NativeScript 的 ListView 组件。`ListView` 是一个 NativeScript UI 组件,可以高效地在垂直或水平滚动列表中呈现项目。让我们首先在主页组件中创建一个名为 flick 的变量,我们将把它用作 `ListView` 的数据源。打开 `home-view-model.ts`,添加以下内容

typescript
// app/home/home-view-model.ts

import { Observable, ObservableArray } from '@nativescript/core'
import { FlickModel } from '../models'
import { FlickService } from '../services'

// Add the contents of HomeViewModel class 👇
export class HomeViewModel extends Observable {
  private _flicks: FlickModel[]

  constructor() {
    super()
    this.populateFlicks()
  }

  // this will be used as the data source of our ListView
  get flicks(): ObservableArray<FlickModel> {
    return new ObservableArray(this._flicks)
  }

  populateFlicks(): void {
    this._flicks = FlickService.getInstance().getFlicks()
  }
}

接下来,添加 `ListView` 组件

xml
<!-- app/home-page/home-page.xml -->
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo">
  <ActionBar title="NativeFlix" />

  <!-- Add this 👇 -->
  <StackLayout height="100%">
    <ListView height="100%" separatorColor="transparent" items="{{ flicks }}">
      <ListView.itemTemplate>
        <Label text="{{ title }}" />
      </ListView.itemTemplate>
    </ListView>
  </StackLayout>
</Page>

NativeScript 中的 `ListView` 使用 `items` 属性作为其数据源。在上面的代码片段中,我们将 `items` 属性设置为 `flicks`。这会循环浏览 `flicks` 可观察数组,并为每个条目呈现 `ListView.itemTemplate` 中的内容。如果你现在运行该应用,你应该会看到一个 flick 标题列表。

创建 flick 卡片

在我们开始创建下面的卡片之前,让我们为将在应用程序中使用的背景和文本颜色创建一些类。由于这将在整个应用程序中共享,因此让我们将其添加到 `app.css`。打开 `app.css`,添加以下内容

css
/* app/app.css */

/* applied when device is in light mode */
.ns-light .bg-primary {
  background-color: #fdfdfd;
}
.ns-light .bg-secondary {
  background-color: #ffffff;
}
.ns-light.text-primary {
  color: #444;
}
.ns-light.text-secondary {
  color: #777;
}

/* applied when device is in dark mode */
.ns-dark .bg-primary {
  background-color: #212121;
}
.ns-dark .bg-secondary {
  background-color: #383838;
}
.ns-dark .text-primary {
  color: #eee;
}
.ns-dark .text-secondary {
  color: #ccc;
}

Home page cards breakdown

如上图所示,每张卡片由 3 个组件组成,即预览图像、标题和描述。我们将使用 `GridLayout` 作为容器,并为预览图像和文本使用 `Image` 和 `Label` 组件。打开 `home-page.xml`,并添加以下内容

xml
<!-- app/home/home-page.xml -->
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo">
  <ActionBar title="NativeFlix" />
  <StackLayout height="100%">
    <ListView
      height="100%"
      separatorColor="transparent"
      items="{{ flicks }}"
      itemTap="{{ onFlickTap }}"
    >
      <ListView.itemTemplate>
        <!-- Add this 👇 -->
        <GridLayout
          height="280"
          borderRadius="10"
          class="bg-secondary"
          rows="*, auto, auto"
          columns="*"
          margin="5 10"
          padding="0"
        >
          <Image row="0" margin="0" stretch="aspectFill" src="{{ image }}" />
          <Label
            row="1"
            margin="10 10 0 10"
            fontWeight="700"
            class="text-primary"
            fontSize="18"
            text="{{ title }}"
          />
          <Label
            row="2"
            margin="0 10 10 10"
            class="text-secondary"
            fontSize="14"
            textWrap="true"
            text="{{ description }}"
          />
        </GridLayout>
      </ListView.itemTemplate>
    </ListView>
  </StackLayout>
</Page>

检查点

如果你已经按照这么远的内容进行操作,则在任一平台上运行应用都会产生一个应用,它类似于该屏幕截图中的应用,同时该列表可以在垂直方向上滚动。

Home page

创建详细信息页面

让我们从创建文件开始,为我们的详细信息功能指定以下内容

typescript
// app/details/details-page.ts

import { NavigatedData, Page } from '@nativescript/core'
import { DetailsViewModel } from './details-view-model'

export function navigatingTo(args: NavigatedData): void {
  const page = <Page>args.object
  page.bindingContext = new DetailsViewModel()
}
typescript
// app/details/details-view-model.ts

import { Observable } from '@nativescript/core'

export class DetailsViewModel extends Observable {}
xml
<!-- app/details/details-page.xml -->
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo" />

从主页设置导航到详细信息组件

我们将使用 Frame 类的 navigate 函数从主页组件导航到详细信息组件。除了路由名称之外,我们还将把该演步的 id 作为 navigate 函数的 context 对象的一部分进行传递。我们将使用此 id 在详细组件中访问有关该演步的更多信息。打开 home-view-model.ts 并添加以下内容

typescript
// app/home/home-view-model.ts

// Update this 👇
import { Frame, Observable, ObservableArray, ItemEventData } from '@nativescript/core'
import { FlickModel } from '../models'
import { FlickService } from '../services'

export class HomeViewModel extends Observable {
  private _flicks: FlickModel[]

  constructor() {
    super()
    this.populateFlicks()
  }

  get flicks(): ObservableArray<FlickModel> {
    return new ObservableArray(this._flicks)
  }

  populateFlicks(): void {
    this._flicks = FlickService.getInstance().getFlicks()
  }

  // Add this 👇
  onFlickTap(args: ItemEventData): void {
    Frame.topmost().navigate({
      moduleName: 'details/details-page',
      context: { flickId: this._flicks[args.index].id }
    })
  }
}

接下来,让我们向 ListView 项添加轻触事件。打开 home-page.xml 并添加以下内容

xml
<!-- app/home/home-page.xml -->
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo">
  <ActionBar title="NativeFlix" />
  <StackLayout height="100%">
    <!-- Update this 👇 -->
    <ListView
      height="100%"
      separatorColor="transparent"
      items="{{ flicks }}"
      itemTap="{{ onFlickTap }}"
    >
      <ListView.itemTemplate>
        <GridLayout
          height="280"
          borderRadius="10"
          class="bg-secondary"
          rows="*, auto, auto"
          columns="*"
          margin="5 10"
          padding="0"
        >
          <Image row="0" margin="0" stretch="aspectFill" src="{{ image }}" />
          <Label
            row="1"
            margin="10 10 0 10"
            fontWeight="700"
            class="text-primary"
            fontSize="18"
            text="{{ title }}"
          />
          <Label
            row="2"
            margin="0 10 10 10"
            class="text-secondary"
            fontSize="14"
            textWrap="true"
            text="{{ description }}"
          />
        </GridLayout>
      </ListView.itemTemplate>
    </ListView>
  </StackLayout>
</Page>

访问导航道具

我们在导航到详细信息页面时传入轻触该演步卡片的用户 id。我们可以通过页面的 navigationContext 访问传入的 id。我们首先将在详细信息页面上获取 navigationContext,然后将其传递到 DetailsViewModel。然后我们可以使用 id 获取所选演步信息,以便在详细信息组件的模板中进行显示。打开 details-page.ts 并添加以下内容

typescript
// app/details/details-page.ts

import { EventData, Page } from '@nativescript/core'
import { DetailsViewModel } from './details-view-model'

export function navigatingTo(args: EventData): void {
  const page = <Page>args.object
  // Update this 👇
  page.bindingContext = new DetailsViewModel(page.navigationContext)
}

接下来,让我们访问此属性,并获取 DetailsViewModel 中的演步信息。打开 details-view-model.ts 并添加以下内容

typescript
// app/details/details-view-model.ts

import { Observable } from '@nativescript/core'
import { FlickService } from '../services'
import { FlickModel } from '../models'

// Add the contents of HomeViewModel class 👇
export class DetailsViewModel extends Observable {
  private _flick: FlickModel

  // the passed-in context object during the navigation will be here
  constructor(private _context: { flickId: number }) {
    super()

    this._flick = FlickService.getInstance().getFlickById(this._context.flickId)
  }

  get flick(): FlickModel {
    return this._flick
  }
}

详细 UI

让我们分析详细信息页面的布局和 UI 元素。

Details page layout breakdown

详细信息页面可分为三个主要部分,即带演步标题的 ActionBar、英雄图片和带演步详细信息的主内容。我们将使用 flicks 对象中的 details 阵列填充演步详细信息部分。details 阵列包含对象,其中含有 titlebody,而这两个对象将统一呈现,各自具有自己的样式。我们可以使用 NativeScript 的 Repeater 组件遍历阵列,并创建 UI 元素或创建一系列元素来处理阵列中的每个条目。打开 details-page.xml 并添加以下代码

xml
<!-- app/details/details-page.xml -->
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="navigatingTo">
  <!-- Add this 👇 -->
  <ActionBar title="{{ flick.title }}" />

  <!-- Add this 👇 -->
  <ScrollView>
    <StackLayout>
      <Image margin="0" stretch="aspectFill" src="{{ flick.image }}" />
      <StackLayout padding="10 20">
        <Repeater items="{{ flick.details }}">
          <Repeater.itemTemplate>
            <StackLayout>
              <Label
                marginTop="15"
                fontSize="16"
                fontWeight="700"
                class="text-primary"
                textWrap="true"
                text="{{ $value.title }}"
              />
              <Label
                fontSize="14"
                class="text-secondary"
                textWrap="true"
                text="{{ $value.body }}"
              />
            </StackLayout>
          </Repeater.itemTemplate>
        </Repeater>
      </StackLayout>
    </StackLayout>
  </ScrollView>
</Page>

检查点

现在,在任一平台上运行的这款应用,应该与本截图中示例的一致,能够在主页和详情页面间导航。

Details page

后续:

恭喜!你打造出了你的首款 NativeScript 应用,可在 iOS 和 Android 上运行。你可以继续添加更多NativeScript UI 组件(或打造自己的定制 UI 组件),或者可添加一些本机功能。可能性无限!