返回博客首页
← 所有文章

使用 NativeScript 4.0 结构化您的页面、侧边抽屉和选项卡视图

2018 年 5 月 15 日 — 作者:Sebastian Witalec

我们在NativeScript 4.0中引入的最重大变化之一是我们与应用程序主框架交互的方式。使其更加灵活(因此有此标题),并且更加直观。

问题

TL;DR:旧版 NativeScript 无法在页面之间共享内容 - 需要更多内存,更多 CPU 使用率和更慢的性能

在 NativeScript 的早期版本中,我们总是在应用程序的根目录中使用单个框架(注意:框架是负责导航的元素。它在导航到这些页面时托管页面)。由于此框架是应用程序的根目录,因此它始终为全屏,因此在其中加载的所有页面也是如此。

起初,这似乎没什么大不了。我在页面 A 上,因此主框架包含 A,然后我导航到页面 B,现在主框架包含 B。当您需要添加侧边抽屉(或选项卡视图)进行导航时,问题就出现了。由于每次应用程序导航时,其所有内容都会被替换,这意味着我们必须为每个页面添加一个侧边抽屉。结果,应用程序每次导航时都会创建一个新的侧边抽屉实例,这意味着使用了更多 RAM,花费了更多 CPU 周期,并且性能下降。

解决方案:灵活的框架组合

Flexible Frame Composition

TL;DR:NativeScript 4.0 可以在页面之间共享内容 - 不浪费 RAM 或 CPU 周期,性能更佳

好的,这过于简化,并不完全正确。在 NativeScript 4.0 中,您可以控制哪个元素应该是应用程序的根目录,以及在何处(以及是否)放置一个将托管页面的框架。因此,现在您可以将侧边抽屉设置为应用程序根目录,并将框架放在其主要内容内部。这实际上意味着框架将在同一个侧边抽屉实例内部交换不同的页面(实际上是重复使用它)。

乐趣不止于此。让我们关注选项卡视图示例。以前,如果您想在一个选项卡内深入导航 - 您必须创建一个类似的页面(其中包含整个选项卡视图定义),然后导航到该页面(这是因为每次导航都会交换整个屏幕内容)。现在,您可以使用更简单的方法 - 将选项卡视图作为根目录,并将框架放置在选项卡内容内部。当您在框架内导航页面时,框架之外的所有内容将保持不变。

因此,我们可以轻松地将页面的内容拆分为单独的框架

  • 静态 - 对于每个页面都保持不变 - 这是侧边抽屉(或选项卡视图)所在的位置
  • 动态 - 这是我们将用来托管每个页面内容的部分

通过这种方式,侧边抽屉(或选项卡视图)只需要创建一次。结果是什么?一个资源消耗更少的应用程序,具有更愉快的用户体验。

目录

在本文中,我们将介绍如何使用以下方法创建应用程序

  • 侧边抽屉导航 使用 NativeScript Core
  • 选项卡视图导航 使用 NativeScript Core
  • 侧边抽屉导航 使用 NativeScript Angular
  • 选项卡视图导航 使用 NativeScript Angular

如何在 NativeScript Core 中使用它 

使用NativeScript 4+创建的所有项目tns create都已准备好使用灵活的框架组合。

引入的更改之一是我们引导应用程序的方式。之前在app.ts中,我们用来调用app.start()(从 {N} 4 开始,它被标记为已弃用),现在我们需要调用app.run()

API 中还有另一个细微的差异。之前app.start() 方法接受一个moduleName ,它提供了我们在应用程序加载时要加载的页面。

app.start({ moduleName: 'home/home-page' });

现在,新的 app.run() 方法接受一个moduleName ,它指向我们称之为 app-root页面的内容,该页面需要包含一个导航框架

app.run({ moduleName: "app-root/app-root" });

正是这个框架提供了加载初始页面的路径。参见defaultPage

<!-- other content -->
  <Frame defaultPage="home/home-page"></Frame>
<!-- other content -->

如果您正在升级现有项目,请记住还要将nativescripttns-core-modules更新到"^4.0.0"

侧边抽屉

现在我们已经了解了基础知识,让我们看看如何将它与侧边抽屉一起使用。

app-root.xml中,我们只需要添加一个RadSideDrawer,其中包含通常的部分

  • drawerTransition - 指定抽屉出现时的行为
  • drawerContent - 指定抽屉内容(阅读。这里有您的导航)
  • mainContent - 这里是我们提供导航框架以及默认页面的位置

app-root.xml

<nsDrawer:RadSideDrawer id="sideDrawer" xmlns:nsDrawer="nativescript-ui-sidedrawer" loaded="onLoaded">
  <nsDrawer:RadSideDrawer.drawerTransition>
    <nsDrawer:SlideInOnTopTransition/>
  </nsDrawer:RadSideDrawer.drawerTransition>

  <nsDrawer:RadSideDrawer.drawerContent>
    <!-- Here go the side drawer items -->
    <Button text="Home" tap="goHome" />
    <Button text="Browse" tap="goBrowse" />
    <Button text="Search" tap="goSearch" />
  </nsDrawer:RadSideDrawer.drawerContent>

  <nsDrawer:RadSideDrawer.mainContent>
    <!-- This is the navigation frame -->
    <Frame defaultPage="home/home-page"></Frame> 
  </nsDrawer:RadSideDrawer.mainContent>
</nsDrawer:RadSideDrawer>

这是结构化app-root的第一步。接下来,我们需要一种方法从主页导航到另一个页面。

这实际上非常简单。您只需要调用topmost()来获取主框架,然后调用navigate()并提供组件的路径作为modulePath

之后,我们需要获取drawerComponent并使用closeDrawer()将其隐藏。

app-root.ts

import * as app from "application";
import { RadSideDrawer } from "nativescript-ui-sidedrawer";
import { topmost } from "ui/frame";

export function goBrowse() {
  topmost().navigate({
    moduleName: "browse/browse-page"
  });

  const drawerComponent = <RadSideDrawer>app.getRootView();
  drawerComponent.closeDrawer();
}

示例

您可以在 Playground 中看到它的实际效果

模板

为了方便起见,已经有一个项目模板,其中已配置了侧边抽屉。您可以在 市场中找到它

要使用侧边抽屉创建一个新项目,请调用tns create my-drawer-ts --template tns-template-drawer-navigation-ts

选项卡视图

如果选项卡视图是您首选的应用程序导航方式,则过程略有不同。

app-root.xml中,我们需要一个TabView组件,其androidTabsPosition属性设置为bottom

<TabView androidTabsPosition="bottom">

androidTabsPosition不仅更改了选项卡视图的位置(从 Android 的顶部到底部),还更改了其行为。因此,当选项卡视图加载时,只有当前可见选项卡的内容被加载到内存中。

然后,对于每个选项卡,我们需要一个TabViewItem,其中包含一个Frame组件 - 请注意,每个框架都有自己的默认页面。

app-root.xml

<TabView androidTabsPosition="bottom">
  <TabViewItem title="Home">
    <Frame defaultPage="home/home-page"></Frame>
  </TabViewItem>

  <TabViewItem title="Browse">
    <Frame defaultPage="browse/browse-page"></Frame>
  </TabViewItem>

  <TabViewItem title="Search">
    <Frame defaultPage="search/search-page"></Frame>
  </TabViewItem>
</TabView>

这将负责在不同选项卡之间导航,并且每个框架将在需要时加载其页面。

在框架中导航

您还可以独立于所有其他框架在每个框架中导航。

我们需要做的就是获取当前页面对象,然后获取其框架。然后,一旦我们有了框架,就可以像这样调用navigate

<Button text="Navigate" tap="onItemTap"></Button>
export function onItemTap(args: ItemEventData) {
  const frame = args.view.page.frame;
    
  frame.navigate({
    moduleName: "home/another-page",
  })
}

现在,如果您运行包含此代码的应用程序,您将能够导航到another-page,当您移动到BrowseSearch选项卡并返回Home选项卡时,Home选项卡仍然会显示another-page

在 iOS 上,点击当前打开的选项卡会自动导航到其defaultPage。因此,如果您在Home选项卡中,并且您再次点击Home选项卡,则该选项卡将返回到home/home-page

返回导航

iOS 会自动将一个后退按钮添加到操作栏。但是,对于 Android,您需要自己处理它。

这可以通过将一个导航按钮添加到您的操作栏来轻松解决

<ActionBar class="action-bar">
  <NavigationButton tap="onBackButtonTap" android.systemIcon="ic_menu_back" />
  <Label class="action-bar-title" text="{{ name }}"></Label>
</ActionBar>

然后,在onBackButtonTap上,您需要获取框架并调用goBack()。就像这样

export function onBackButtonTap(args: EventData) {
  const view = args.object as View;
  const frame = view.page.frame;

  frame.goBack();
}

示例

您可以在 Playground 中看到它的实际效果。

模板

或者,您可以使用 来自市场上的现成模板

要使用选项卡视图创建一个新项目,请调用tns create my-tab-ts --template tns-template-tab-navigation-ts

框架检索 API

在 4.0 之前,所有 NativeScript 应用程序都有一个由application.start()方法隐式创建的顶级框架。frameModule.topmost()方法返回此框架。在 4.0 中,我们现在允许您的应用程序具有多个框架实例。这需要不同的框架检索 API。以下是在 4.0 中获取所需框架的方法

  • frameModule.topmost() - 旧方法仍然存在,不仅是为了向后兼容。它现在返回最后导航的框架,或者如果您在选项卡视图中,则返回当前选定选项卡项的框架。这意味着,在简单的情况下,您应该仍然能够依赖此方法。但是,对于更复杂的情况或仅仅是为了获得更多控制,您应该使用以下方法。

    frameModule.topmost()非常适合选项卡视图导航

  • eventArgs.object.page.frame - 这可以在事件处理程序中使用。所有事件参数对象都应该包含对其页面的引用,该页面反过来又包含对其框架实例的引用。这使您能够检索显示事件对象的精确框架。

    eventArgs.object.page.frame非常适合侧边抽屉导航

  • frameModule.getFrameById(id) - 这是一个新方法。它允许您通过您在元素上指定的 ID 获取对框架的引用。请注意,这将搜索已导航的框架,而不会找到尚未显示的框架,例如在模态视图中。

    frameModule.getFrameById(id)在页面上有多个框架时非常有用,例如在拆分屏幕情况下。

如何在 Angular 中使用它

为了将灵活的框架组合与 Angular 一起使用,您需要使用

  • nativescript-angular: 5.3.0nativescript-angular: next或更新版本(6.0.0即将推出)
  • @angular/x: 6.0.0
  • 所有 NativeScript 4 先决条件

侧边抽屉

与在 NativeScript 的早期版本中使用侧边抽屉相比,我们需要做的唯一更改是将所有侧边抽屉实例移动到您的主组件(默认情况下是AppComponent),该组件应该包含

  • 一个使用tkDrawerContent指令标记的布局容器 - 它包含抽屉内容
  • 使用tkMainContent指令标记的page-router-outlet - 它包含应用程序主内容

    请注意,page-router-outlet等同于 NativeScript Core 的Frame 组件

就像这样

app.component.html

<RadSideDrawer [drawerTransition]="sideDrawerTransition">
  <GridLayout tkDrawerContent rows="auto, *" class="sidedrawer sidedrawer-left">
    <StackLayout row="0" class="sidedrawer-header">
      <Label class="sidedrawer-header-image fa" text="&#xf2bd;"></Label>
      <Label class="sidedrawer-header-brand" text="User Name"></Label>
      <Label class="footnote" text="[email protected]"></Label>
    </StackLayout>

    <ScrollView row="1">
      <StackLayout class="sidedrawer-content">
        <Button text="home" [nsRouterLink]="['/home']" (tap)="hideDrawer()" class="btn btn-primary"></Button>
        <Button text="browse" [nsRouterLink]="['/browse']" (tap)="hideDrawer()" class="btn btn-primary"></Button>
        <Button text="search" (tap)="goSearch()" class="btn btn-primary"></Button>
      </StackLayout>
    </ScrollView>
  </GridLayout>

  <page-router-outlet tkMainContent class="page page-content"></page-router-outlet>
</RadSideDrawer>

导航

现在导航是相当直观的 Angular 体验。

您可以从以下位置进行

  • TypeScript

    通过将RouterExtensions注入到AppComponent,然后调用this.routerExtensions.navigate()。最后,为了整理,我们需要获取抽屉并将其关闭。就像这样

constructor(private routerExtensions: RouterExtensions) {}

goSearch() {
  // navigate
  this.routerExtensions.navigate(['/search'], {
    transition: { name: "flip" }
  });
    
  // hide drawer
  const sideDrawer = <RadSideDrawer>app.getRootView();
  sideDrawer.closeDrawer();
}
  • 或 HTML

通过使用nsRouterLink提供导航路径,以及(可选)pageTransition提供过渡类型。最后,为了整理,我们需要关闭抽屉。

<Button
  text="browse"
  [nsRouterLink]="['/browse']"
  (tap)="hideDrawer()"
  pageTransition="flip">
</Button>
hideDrawer() {
  const sideDrawer = <RadSideDrawer>app.getRootView();
  sideDrawer.closeDrawer();
}

示例

您可以在 Playground 中看到它的实际效果

参见AppComponent以了解所有神奇之处 😉

模板

或者,您可以使用 来自市场上的现成模板

要使用侧边抽屉创建一个新项目,请调用tns create my-drawer-ng --template tns-template-drawer-navigation-ng

选项卡视图

在侧边抽屉示例中,我们只需要一个页面路由出口。但是,对于选项卡视图导航,我们需要多个命名页面路由出口,此功能不是nativescript-angular: 5.3的一部分,该版本是撰写本文时该 npm 模块的最新官方版本。这将在nativescript-angular: 6.0中正式提供,因此,目前我们将使用nativescript-angular@next

命名页面路由出口

命名页面路由出口只是一个带有name属性的page-router-outlet,就像这样

<page-router-outlet name="myTab"></page-router-outlet>

名称允许我们将每个页面路由出口链接到我们**路由配置**中的一个outlet。(有关详细信息,请参阅app.routing.ts部分)

app.component.html

例如,如果我们设想一个使用两个标签的应用程序

  • playerTab
  • teamTab

那么我们需要在app.component.html中添加一个带有两个**命名**的page-router-outlet组件的TabView。

<TabView androidTabsPosition="bottom">
  <page-router-outlet 
    *tabItem="{title: 'Players'}" 
    name="playerTab">
  </page-router-outlet>

  <page-router-outlet
    *tabItem="{title: 'Teams'}" 
    name="teamTab">
  </page-router-outlet>
</TabView>

app.routing.ts

现在我们需要配置我们的路由。这与常规的**路由配置**非常相似。

以下是我们的路径,没有**多个**page-router-outlet

const routes: Routes = [
  { path: '', redirectTo: '/players', pathMatch: 'full' },
  
  { path: 'players', component: PlayerComponent },
  { path: 'player/:id', component: PlayerDetailComponent },
  
  { path: 'teams', component: TeamsComponent},
  { path: 'team/:id', component: TeamDetailComponent },
];

首先,我们需要为每个路径添加**outlet**属性,将其分配给特定的路由出口。例如,对于**players**路径,我们需要将其分配给**playerTab**,就像这样

{ path: 'players', component: PlayerComponent, outlet: 'playerTab' },

然后我们需要更新默认的(**redirectTo**)路径。像这样

{ path: '', redirectTo: '/(playerTab:players//teamTab:teams)', pathMatch: 'full' },

注意,

  • 首先,我们导航到'/',这是AppComponent的路径,
  • 然后在( )中,我们定义
    • 每个出口的默认值,使用语法:tabname:path
    • 每个出口默认值用//分隔

以下是示例的完整配置

const routes: Routes = [
  { path: '', redirectTo: '/(playerTab:players//teamTab:teams)', pathMatch: 'full' },
  
  { path: 'players', component: PlayerComponent, outlet: 'playerTab'  },
  { path: 'player/:id', component: PlayerDetailComponent, outlet: 'playerTab'  },
  
  { path: 'teams', component: TeamsComponent, outlet: 'teamTab' },
  { path: 'team/:id', component: TeamDetailComponent, outlet: 'teamTab' },
];

现在,我们只需要创建所有四个组件,我们就可以开始运行了。

app.routing.ts - (另一个示例)

如果我们有一个带有**登录页面**和**标签页面**的应用程序,那么我们的配置看起来像这样

const routes: Routes = [
  { path: '', redirectTo: '/login', pathMatch: 'full' },
  //{ path: '', redirectTo: '/tabs/(playerTab:players//teamTab:teams)', pathMatch: 'full' },

  { path: 'login', component: LoginComponent },
  { path: 'tabs', component: TabsComponent, children: [
    { path: 'players', component: PlayerComponent, outlet: 'playerTab'  },
    { path: 'player/:id', component: PlayerDetailComponent, outlet: 'playerTab'  },
  
    { path: 'teams', component: TeamsComponent, outlet: 'teamTab' },
    { path: 'team/:id', component: TeamDetailComponent, outlet: 'teamTab' },
  ]}
];

导航 - 从组件类

拼图的最后一块是导航。

要从组件类导航,我们需要注入RouterExtensionsActivateRoute

import { RouterExtensions } from 'nativescript-angular/router';
import { ActivatedRoute } from '@angular/router';
...
export class PlayerComponent {
...
  constructor(
    private router: RouterExtensions,
    private route: ActivatedRoute
  ) { }
...
}
  • 导航 - 使用相对路径 - 从playersplayer/:id

当我们导航时,我们可以提供{ relativeTo: this.route },这会告诉路由器它应该导航到哪个page-router-outlet。因此,我们可以简单地通过调用以下内容来导航

navigateToPlayer(id: number) {
  this.router.navigate(['../player', id], { relativeTo: this.route });
}

注意:../<sibling>是一种引用兄弟路径的方法。这正是您从home/ahome/b导航的方式,方法是提供../b路径。

  • 导航 - 清除历史记录 - 从player/xplayers

每次您导航时,**导航堆栈**都会更新。因此,iOS 在 ActionBar 中为您提供了一个后退按钮,而 Android 允许您使用系统后退按钮后退。

当您只是导航回主页或默认页面时,这没有用。幸运的是,您可以通过添加clearHistory: true属性来避免这种情况。 navigateToPlayers() { this.router.navigate(['../../players'], { relativeTo: this.route, clearHistory: true }); }

注意#1:clearHistory只会清除被导航的**page-router-outlets**的历史记录。因此,当您在**playersTab outlet**中导航时,**teamsTab outlet**保持不变。

注意#2:当您尝试从pageA/param导航到../pageB时,将会带您到pageA/pageB并失败。由于我们当前路径中有一个参数,因此我们还需要通过导航到../../pageB来转义它。

  • 导航 - 使用绝对路径

我们也可以使用绝对路径进行导航。这使用**outlets**属性以及我们需要的出口名称以及我们希望它导航到的路径来完成,就像这样

this.router.navigate([{
  outlets: {
    outletName: ['path']
  }
}]);
  • 导航 - 使用绝对路径 - 从playersplayer/x
navigatePlayerOutlet(id: number) {
  this.router.navigate([{
    outlets: {
      playerTab: ['player', id] 
    }
  }]);
}

注意:路径和参数必须分别提供

好的:playerTab: ['player', id]

不好:playerTab: ['player/id']

  • 导航 - 使用绝对路径 - 在另一个标签中 - 从playersteam/x

您也可以在另一个出口中进行导航,就像这样

navigateTeamOutlet(id: number) {
  this.router.navigate([{
    outlets: {
      teamTab: ['team', id] 
    }
  }]);
}

注意:这不会切换标签到**Team Tab**,但是您导航到那里后,**Team Tab**将显示team/id

  • 导航 - 使用绝对路径 - 多个出口:从playersplayer/xteam/x
navigateOutlets(playerId: number, teamId: number) {
  this.router.navigate([{
    outlets: { 
      playerTab: ['player', playerId],
      teamTab: ['team', teamId],
    }
  }]);
}
  • 使用 Url 进行导航 - 从playersteam/x

这种方法通常用于更新所有出口。语法与我们在app.routing.ts中为redirectTo使用的语法相同。

在这种情况下,它允许我们在teamTab中导航到team/id,同时将**playerTab**保持在players

navigateTeamUrl(id: number) {
  this.router.navigateByUrl(`/(playerTab:players//teamTab:team/${id})`);
}
  • 导航 - 使用相对路径 - 到相同的路径,但具有不同的参数 - 从player/xplayer/y

要做到这一点,我们只需要后退一步../,这将带我们到/player,然后提供新的 id。像这样

navigateToPlayer(id: number) {
  this.router.navigate(['../', id], { relativeTo: this.route });
}
  • 导航 - 使用绝对路径 - 到相同的路径,但具有不同的参数 - 从player/xplayer/x+1

当使用绝对路径导航时,我们无法告诉路由器保持在相同的路径上。因此,我们必须每次都提供完整的路径。

navigateNext() {
  this.router.navigate([{
    outlets: {
      playerTab: ['player', this.player.id + 1]
    }
  }]);
}

导航 - 从组件模板

我们也可以直接从 html 导航。这是通过nsRouterLink属性的支持来实现的。

注意:**nsRouterLink**可以用于任何组件:**Button**、**Label**甚至**StackLayout**。

  • 导航 - 使用固定相对路径 - 从teamsteam/1

当我们使用相对路径时,路由器将自动假设您希望在相同的**page-router-outlet**中导航。

以下是我们从teamsteam/1导航的方式。首先,我们需要../后退一步,然后添加team/1,就像这样

<Button
  text="Open Team One"
  nsRouterLink="../team/1">
</Button>
  • 导航 - 使用页面过渡 - 从teamsteam/2
<Button
  text="Open Team Two with Animation"
  nsRouterLink="../team/2"
  pageTransition="flip"
  pageTransitionDuration="3000">
</Button>
  • 导航 - 使用相对路径 - 带有参数 - 从teamsteam/x

当您想要添加一个应该从变量中提取的参数时,我们需要在nsRouterLink周围使用[ ],然后将导航作为项目数组提供。

要从teamsteam/id导航,首先我们需要['../team'路径到 team,然后是参数值team.id],就像这样

<ListView [items]="items" class="list-group">
  <ng-template let-team="item">
    <Label
      [text]="team.name"
      [nsRouterLink]="['../team', team.id]">
    </Label>
  </ng-template>
</ListView>
  • 导航 - 使用绝对路径 - 从teamsteam/5

要使用相对路径导航,我们需要提供一个带有以下内容的数组:

  1. 带有我们 tabview 的组件的路径:'/'
  2. 包含outlets配置的对象,其中包含我们需要导航到的出口名称
<Button
  text="Navigate using Outlet"
  [nsRouterLink]="['/', { outlets: { teamTab: ['team', 5] } }]">
</Button>
  • 导航 - 使用绝对路径 - 在另一个出口中 - 从teamsplayer/5

当我们希望在另一个标签中的**page-router-outlet**中导航时,这也有效,就像这样

<Button
  text="Navigate in Players Tab"
  [nsRouterLink]="['/', { outlets: { playerTab: ['player', 5] } }]">
</Button>

注意:这将更改**playerTab**的内容,但不会将当前标签更改为**Players Tab**。

  • 导航 - 使用绝对路径 - 带有多个出口 - 从teamsteam/6player/6

为了使它更好,您甚至可以一次性导航多个出口。像这样

<Button
  text="Navigate in Two Outlets"
  [nsRouterLink]="[
'/',
  { outlets:
{
teamTab: ['team', 6],
playerTab: ['player', 6]
}
}]"> </Button>
  • 导航 - 使用相对路径 - 到相同的路径,但具有不同的参数 - 从team/xteam/x+1

当您需要重新导航到同一页面,但具有不同的参数时,您只需要使用../后退一步,然后提供新的参数即可。像这样

<Button
  text="prev"
  [nsRouterLink]="['../', team.id - 1]">
</Button>
  • 导航 - 使用绝对路径 - 到相同的路径,但具有不同的参数 - 从team/xteam/x+1

当您需要使用绝对路径重新导航到同一页面时,您需要每次都提供完整的路径。像这样

<Button
  text="next"
  [nsRouterLink]="['/', { outlets: { teamTab: ['team', team.id + 1] } }]">
</Button>
  • 导航 - 返回主页 - 清除历史记录 - 从team/xteams

最后,当您需要导航回主页组件时,您可能还想清除导航堆栈,以便 iOS 会删除后退按钮。这是通过添加clearHistory="true"来完成的,就像这样

<Button
  text="Back to Teams"
  [nsRouterLink]="['../../teams']"
  clearHistory="true">
</Button>

示例

在撰写本文时,Playground 使用的是nativescript-angular: 5.3,它不支持Named Page Router Outlets。因此没有 Playground 演示。

您可以在GitHub - frame-tabview-example中查看我的示例项目。

模板

NativeScript 团队正在努力更新当前的tab-navigation-ng模板,您应该能够从市场中获取它。

要检查模板是否已更新,只需检查GitHub 中的 app.component.html即可。它应该很快准备好。

要使用 TabView 创建新项目,请调用tns create my-tab-ng --template tns-template-tab-navigation-ng

其他用法

您还可以使用其他场景

  • 在**模态**中导航 - 查看示例此处
  • 将屏幕拆分为多个框架 - 这可以通过以下方法实现
    • frameModule.getFrameById(id) 用于 TS,以及
    • **命名页面路由出口** 用于 NG

最终

希望您觉得它有用,并且我成功涵盖了您可能需要的多数场景。

如果您有任何未涵盖的场景,或在 GitHub 或 Playground 中共享您使用灵活框架组合的项目,请告诉我。

或在评论中分享您的反馈。