从 RN 到 Flutter-路由器

我要死了,这个系列怎么这么长。

许多应用都有多个页面来展示不同信息。比方说,你可能在页面展示一个产品的图片,当用户点击时打开这个产品的详情。

在 Android,新的页面叫做 Activities。在 iOS 新的页面叫 ViewControllers,在 Flutter,新的页面也是部件!包括导航至这些页面的导航器也是导航器部件。

如何在页面中导航

在 React Native 里面,有三个导航器StackNavigatorTabNavigatorDrawerNavigator。都提供了配置的接口。

// React Native
const MyApp = TabNavigator(
  { Home: { screen: HomeScreen }, Notifications: { screen: tabNavScreen } },
  { tabBarOptions: { activeTintColor: "#e91e63" } }
);
const SimpleApp = StackNavigator({
  Home: { screen: MyApp },
  stackScreen: { screen: StackScreen },
});
export default MyApp1 = DrawerNavigator({
  Home: {
    screen: SimpleApp,
  },
  Screen2: {
    screen: drawerScreen,
  },
});

在 Flutter 中,有两个部件用于处理页面路由。

Navigator通过将一列页面以堆的方式覆盖,并提供方法管理这个堆,像是Navigator.pushNavigator.popMaterialApp 部件提供 routes 参数接受这些路由,这些路由可能会明确指出或者动态生成,比如标题动画。下面是一个指明路由的例子。

class App extends StatelessWidget{
    Widget build(BuildContext: context) {
        return MaterialApp(
           routes: <String, WidgetBuilder> {
                '/a': (BuildContext: context) => usualNavscreen(),
                '/b': (BuildContext: context) => drawerNavscreen(),
          },
       ),
    }
}

使用Navigator.of()并指定一个BuildContext来处理导航,比如前往某个特定页面,可以使用pushNamed

Navigator.of(context).pushNamed('/a');

你可以使用push方法调用已存在的Route,这样会动画打开 Route。下面的例子中MaterialPageRoute提供一个模态的页面,并适配系统动画。

Navigator.push(context, MaterialPageRoute(builder: (BuildContext context)
 => UsualNavscreen()));

如何使用选项卡和抽屉导航

在 Material 设计语言中,为 Flutter 提供了两种导航选项,tab 和 drawer。如果空间使用 tab 很紧张,则 drawer 是个不错的替代。

选项卡导航

在 React Native 中,createBottomTabNavigatorTabNavigation被用来处理选项卡和导航。

import { createBottomTabNavigator } from "react-navigation";

const MyApp = TabNavigator(
  { Home: { screen: HomeScreen }, Notifications: { screen: tabNavScreen } },
  { tabBarOptions: { activeTintColor: "#e91e63" } }
);

Flutter 提供特别的部件处理抽屉和选项卡导航。

TabController controller = TabController(length: 2, vsync: this);

TabBar(
    tabs: <Tab>[
        Tab(icon: Icon(Icons.person),),
        Tab(icon: Icon(Icons.email),),
    ],
    controller: controller,
),

一个TabController用于协调TabBarTabBarView。构造参数中的length属性,提供选项卡的个数。一个TickerProvider被用来处理框架状态变化的消息的,通过vsync传入。vsync:this参数是创建TabController必要的。

TickerProvider是可以产生Ticker对象的类的接口。Ticker 是可以用来接收框架消息的对象,但是他们基本上都是间接通过AnimationControllerAnimationController需要TickerProvider来获取Ticker对象。如果你正在从 State 里面创建一个 AnimationController,你可以使用TickerProviderStateMixin或者SingleTickerProviderStateMixin对象以得到TrickerProvider。如果你用的是flutter-hooks你可以使用useSingleTickerProvider获取Ticker对象。

Scaffold部件封装了一个新的TabBar部件并创建两个选项卡。TabBarView部件通过body参数传入。所有页面的TabBar都是TabBarView部件的子部件。

class Home extends StatefulWidget {
  Home() : super();
  @override
  State<StatefulWidget> createState() {
    return _Home();
  }
}

class _Home extends State<Home> with SingleTickerProviderStateMixin {
  @override
  Widget build(BuildContext context) {
    TabController controller = TabController(length: 2, vsync: this);
    return Scaffold(
        appBar: AppBar(
          title: Text('home'),
        ),
        bottomNavigationBar: Material(
          color: Colors.blue,
          child: TabBar(
            controller: controller,
            tabs: <Tab>[
              Tab(
                icon: Icon(Icons.person),
              ),
              Tab(
                icon: Icon(Icons.email),
              )
            ],
          ),
        ),
        body: TabBarView(
          children: <Widget>[
            Center(
              child: Text('person'),
            ),
            Center(
              child: Text('email'),
            )
          ],
          controller: controller,
        ));
  }
}

当然也可以使用flutter-hooks,其实如果收拾一下的话都要写两个类,就看你爱怎么写了。

class Home extends HookWidget {
  @override
  Widget build(BuildContext context) {
    final vsync = useSingleTickerProvider();
    final controller = useMemoized(() {
      return TabController(length: 2, vsync: vsync);
    });

    return Scaffold(
      appBar: AppBar(
        title: Text('Home'),
      ),
      bottomNavigationBar: Material(
        color: Colors.blue,
        child: TabBar(
          controller: controller,
          tabs: <Widget>[
            Tab(icon: Icon(Icons.person)),
            Tab(icon: Icon(Icons.email))
          ],
        ),
      ),
      body: TabBarView(
        controller: controller,
        children: <Widget>[
          Center(child: Text("person")),
          Center(
            child: Text("email"),
          )
        ],
      ),
    );
  }
}

抽屉导航

在 React Native 中,需要引入react-navigation包,并使用createDrawerNavigatorDrawerNavigation

export default MyApp = DrawerNavigator({
  Home: {
    screen: SimpleApp,
  },
  Screen2: {
    screen: drawScreen,
  },
});

在 Flutter 中,我们可以使用Drawer部件结合Scaffold部件创建 Material 设计语言的抽屉。想要创建一个带有抽屉的应用,需要将Drawer封装至Scaffold中。Scaffold部件提供一套基于Material 设计标准的样式结构,并且还支持许多特殊的 Material 设计组件,比如DrawerAppBarSnackBar

Drawer部件是一个基于 Material 设计的控制台,它在Scaffold水平方向的边缘,展示应用的导航链接。你可以在这个部件中使用RaisedButtonText组件,或者一个列表。在下面的例子中ListTile就提供了点击导航的功能。

// Flutter
Drawer(
  child:ListTile(
    leading: Icon(Icons.change_history),
    title: Text('Screen2'),
    onTap: () {
      Navigator.of(context).pushNamed('/b');
    },
  ),
  elevation: 20.0,
),

Scaffold部件的AppBar会自动适配给Drawer一个图标,并且Scaffold能够自动处理Drawer的左滑手势。

// Flutter
@override
Widget build(BuildContext context) {
  return Scaffold(
    drawer: Drawer(
      child: ListTile(
        leading: Icon(Icons.change_history),
        title: Text('Screen2'),
        onTap: () {
          Navigator.of(context).pushNamed('/b');
        },
      ),
      elevation: 20.0,
    ),
    appBar: AppBar(
      title: Text('Home'),
    ),
    body: Container(),
  );
}

另外,如果不爽页面转场动画单一,然后自己又懒得写动画,可以直接用page_transition包。

Android DraweriOS Drawer
Android DraweriOS Drawer