www.2527.com_澳门新葡8455手机版_新京葡娱乐场网址_
做最好的网站

之MaterialApp和Scaffold组件使用详细明白,局地路由

2020-01-25 18:39 来源:未知

时间: 2019-06-11阅读: 396标签: Flutter

一,前言:

  MaterialApp和Scaffold是Flutter提供的两个Widget,其中:

  • MaterialApp是一个方便的Widget,它封装了应用程序实现Material Design所需要的一些Widget。
  • Scaffold组件是Material Design布局结构的基本实现。此类提供了用于显示drawer、snackbar和底部sheet的API。MaterialApp 的 child 是 Scaffold Widget。

React Native 进阶(四)--导航器

Flutter是借鉴React的开发思想实现的,在子组件的插槽上,React有this.props.children,Vue有slot/slot。当然Flutter也有类似的Widget,那就是Navigator,不过是以router的形式实现(像router-view/router-view)。

二,MaterialApp属性介绍                      

 * 22个参数
字段 类型
navigatorKey GlobalKey<NavigatorState>
home Widget
routes Map<String, WidgetBuilder>
initialRoute String
onGenerateRoute RouteFactory
onUnknownRoute RouteFactory
navigatorObservers List<NavigatorObserver>
builder TransitionBuilder
title String
onGenerateTitle GenerateAppTitle
color Color
theme ThemeData
locale Locale
localizationsDelegates Iterable<LocalizationsDelegate<dynamic>>
localeResolutionCallback LocaleResolutionCallback
supportedLocales Iterable<Locale>
debugShowMaterialGrid bool
showPerformanceOverlay bool
checkerboardRasterCacheImages(棋盘格光栅缓存图像) bool
checkerboardOffscreenLayers bool
showSemanticsDebugger bool
debugShowCheckedModeBanner(调试显示检查模式横幅) bool

导航器对比

如果你刚开始接触,那么直接选择Navigator就好。如果你只针对iOS平台开发,并且想和系统原生外观一致,那么可以选择NavigatorIOS。如果你想更好地管理导航栈,那么应该尝试一下NavigationExperimental。

Navigator的使用无非3个属性

1. navigatorKey

navigatorKey.currentState
相当于
Navigator.of

使用

 GlobalKey<NavigatorState> _navigatorKey=new GlobalKey();  new MaterialApp(      navigatorKey: _navigatorKey,    );

Navigator

Navigator使用纯JavaScript实现了一个导航栈,因此可以跨平台工作,同时也便于定制。

Navigator可以在renderScene方法中根据当前路由渲染不同的组件。默认情况下新的场景会从屏幕右侧滑进来,但你也可以通过configureScene方法来管理这一行为。你还可以通过navigationBar属性来配置一个跨场景的导航栏。但我们不推荐使用跨场景的navigationBar,它的代码逻辑维护起来很困难!建议自己在场景中用View实现自定义的导航栏。

initialRoute: 初始路由onGenerateRoute: 匹配路由onUnknownRoute: 404

2. home

style="font-size: 12px;">进入程序后显示的第一个页面,传入的是一个Widget,但实际上这个Widget需要包裹一个Scaffold以显示该程序使用Material Design风格

  注意:这个是一个 Widget 对象,用来定义当前应用打开的时候,所显示的界面。

使用

class MyApp extends StatelessWidget {  // This widget is the root of your application.  @override  Widget build(BuildContext context) {    return new MaterialApp(      home: new MyHomePage;  }}

//这是一个可改变的Widgetclass MyHomePage extends StatefulWidget {  @override  _MyHomePageState createState() => new _MyHomePageState();}

class _MyHomePageState extends State<MyHomePage> {  @override  Widget build(BuildContext context) {    return new Scaffold(      appBar: new AppBar(        title: Text('Title'),      ),      body: Center(        child: Text('Home'),      ),    );  }}

方法

如果你得到了一个navigator对象的引用,则可以调用许多方法来进行导航:

  • getCurrentRoutes() - 获取当前栈里的路由,也就是push进来,没有pop掉的那些。
  • jumpBack() - 跳回之前的路由,当然前提是保留现在的,还可以再跳回来,会给你保留原样。
  • jumpForward() - 上一个方法不是调到之前的路由了么,用这个跳回来就好了。
  • jumpTo(route) - 跳转到已有的场景并且不卸载。
  • push(route) - 跳转到新的场景,并且将场景入栈,你可以稍后跳转过去
  • pop() - 跳转回去并且卸载掉当前场景
  • replace(route) - 用一个新的路由替换掉当前场景
  • replaceAtIndex(route, index) - 替换掉指定序列的路由场景
  • replacePrevious(route) - 替换掉之前的场景
  • resetTo(route) - 跳转到新的场景,并且重置整个路由栈
  • immediatelyResetRouteStack(routeStack) - 用新的路由数组来重置路由栈
  • popToRoute(route) - pop到路由指定的场景,在整个路由栈中,处于指定场景之后的场景将会被卸载。
  • popToTop() - pop到栈中的第一个场景,卸载掉所有的其他场景。

这些都是navigator可以用的public method,就是跳转用的,里面有些带参数的XXX(route),这个route参数是什么呢,这个route就是:
renderScene={(route, navigator) =>
这里的route,最基本的route就是:

let route = {
  component: SampleComponent
}

在实现层面

3. routes

style="font-size: 12px;">声明程序中有哪个通过Navigation.of.pushNamed跳转的路由
参数以键值对的形式传递
key:路由名字
value:对应的Widget

注意:
  定义应用中页面跳转规则。 该对象是一个 Map<String, WidgetBuilder>。当使用 Navigator.pushNamed 来路由的时候,会在 routes 查找路由名字,然后使用 对应的 WidgetBuilder 来构造一个带有页面切换动画的 MaterialPageRoute。如果应用只有一个界面,则不用设置这个属性,使用 home 设置这个界面即可。如果 home 不为 null,当 routes 中包含 Navigator.defaultRouteName('/') 的时候会出错,两个都是 home 冲突了。如果所查找的路由在 routes 中不存在,则会通过 onGenerateRoute 来查找。

属性

configureScene function

可选的函数,用来配置场景动画和手势。会带有两个参数调用,一个是当前的路由,一个是当前的路由栈。然后它应当返回一个场景配置对象

(route, routeStack) => Navigator.SceneConfigs.FloatFromRight

  • Navigator.SceneConfigs.PushFromRight (默认)
  • Navigator.SceneConfigs.FloatFromRight
  • Navigator.SceneConfigs.FloatFromLeft
  • Navigator.SceneConfigs.FloatFromBottom
  • Navigator.SceneConfigs.FloatFromBottomAndroid
  • Navigator.SceneConfigs.FadeAndroid
  • Navigator.SceneConfigs.HorizontalSwipeJump
  • Navigator.SceneConfigs.HorizontalSwipeJumpFromRight
  • Navigator.SceneConfigs.VerticalUpSwipeJump
  • Navigator.SceneConfigs.VerticalDownSwipeJump

initialRoute object

定义启动时加载的路由。路由是导航栏用来识别渲染场景的一个对象。initialRoute必须是initialRouteStack中的一个路由。initialRoute默认为initialRouteStack中最后一项。

initialRouteStack [object]

提供一个路由集合用来初始化。如果没有设置初始路由的话则必须设置该属性。如果没有提供该属性,它将被默认设置成一个只含有initialRoute的数组。

navigationBar node

可选参数,提供一个在场景切换的时候保持的导航栏。

navigator object

可选参数,提供从父导航器获得的导航器对象。

onDidFocus function

每当导航切换完成或初始化之后,调用此回调,参数为新场景的路由。

onWillFocus function

会在导航切换之前调用,参数为目标路由。

renderScene function

必要参数。用来渲染指定路由的场景。调用的参数是路由和导航器。

(route, navigator) =><MySceneComponent title={route.title} navigator={navigator} />

sceneStyle View#style

将会应用在每个场景的容器上的样式。

首先:Navigator的高度为infinity。如果直接父级非最上级也是infinity会产生异常,例如,Scaffold

使用

new MaterialApp(      routes: {       '/home':(BuildContext context) => HomePage(),       '/home/one':(BuildContext context) => OnePage(),       //....      },    );

例子

Navigator.js

import React from 'react';
import {
  View,
  Navigator
} from 'react-native';
import FirstPageComponent from './FirstPageComponent';

export default class Sample extends React.Component {
  render() {
    let defaultName = 'FirstPageComponent';
    let defaultComponent = FirstPageComponent;
    return (
      <Navigator
        initialRoute={{ name: defaultName, component: defaultComponent }}
        configureScene={(route) => { // 跳转动画
          return Navigator.SceneConfigs.VerticalDownSwipeJump;
        }}
        renderScene={(route, navigator) => {
          let Component = route.component;
          // 这里有个 { ...route.params },这个语法是把 routes.params 里的每个key 作为props的一个属性
          return <Component {...route.params} navigator={navigator} />
        }} 
      />
    );
  }
} 

第10行: 一个初始首页的component名字,比如我写了一个component叫HomeComponent,
        那么这个name就是这个组件的名字[HomeComponent]了。  
第11行: 这个组件的Class,用来一会儿实例化成 <Component />标签
第14行: initialRoute={{ name: defaultName, component: defaultComponent }} 这个指定了默认的页面,
        也就是启动app之后会看到界面的第一屏。 需要填写两个参数: name 跟 component。(注意这里填什么参数(参数名)纯粹是自定义的,
        因为这个参数也是你自己发自己收,自己在renderScene方法中处理。这个示例用了两个参数,但其实真正使用的参数只有component)  
第15,16,17行: configureScene={() => {return Navigator.SceneConfigs.VerticalDownSwipeJump;}} 这个是页面之间跳转时候的动画,
        具体有哪些动画可以看node_modules/react-native/Libraries/CustomComponents/Navigator/NavigatorSceneConfigs.js下的源代码。
最后的几行: renderScene={(route, navigator) => {let Component = route.component;return <Component {...route.params} navigator={navigator} />}},
        这里是每个人最疑惑的,我们先看到回调里的两个参数:route, navigator。通过打印我们发现route里其实就是我们传递的name,component这两个货,
        navigator是一个Navigator的对象,为什么呢,因为它有push pop jump...等方法,这是我们等下用来跳转页面用的那个navigator对象。
        return <Component {...route.params} navigator={navigator} />  
        这里有一个判断,也就是如果传递进来的component存在,那我们就是返回一个这个component,结合前面 initialRoute 的参数,我们就知道,
        这是一个会被render出来给用户看到的component,然后navigator作为props传递给了这个component。

所以下一步,在这个FirstPageComponent里面,我们可以直接拿到这个 props.navigator:

FirstPageComponent.js

import React from 'react';
import {
  View,
  Navigator,
  TouchableOpacity,
  Text
} from 'react-native';

import SecondPageComponent from './SecondPageComponent';

export default class FirstPageComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      id: 2,
      user: null,
    };
  }
  _pressButton() {
    let _this = this;
    const { navigator } = this.props;
    //为什么这里可以取得 props.navigator?请注意Navigator.js中:
    //<Component {...route.params} navigator={navigator} />
    //这里传递了navigator作为props
    if (navigator) {
      // 入栈~ 把SecondPageComponent页面push进栈,接着跳转到SecondPageComponent
      navigator.push({
        name: 'SecondPageComponent',
        component: SecondPageComponent,
        //这个 params 其实来自于Navigator 里的一个方法的参数
        params: {  //routes.params
          id: this.state.id,
          //从SecondPageComponent获取user
          getUser: function (user) {
            _this.setState({
              user: user
            })
          }
        }
      })
    }
  }
  render() {
    if (this.state.user) {
      return (
        <View>
          <Text>用户信息: {JSON.stringify(this.state.user)}</Text>
        </View>
      );
    } else {
      return (
        <View>
          <TouchableOpacity onPress={this._pressButton.bind(this)}>
            <Text>查询ID为{this.state.id}的用户信息</Text>
          </TouchableOpacity>
        </View>
      );
    }
  }
}

这个里面创建了一个可以点击的区域,点击可以跳到SecondPageComponent这个页面,实现页面的跳转。
现在来创建SecondPageComponent,并且让它可以再跳回FirstPageComponent:

SecondPageComponent.js

import React from 'react';
import {
  View,
  Navigator,
  Text,
  TouchableOpacity
} from 'react-native';

import FirstPageComponent from './FirstPageComponent';

const USER_MODELS = {
  1: { name: '小李', age: 18 },
  2: { name: '小明', age: 20 }
};

export default class SecondPageComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      id: null
    };
  }
  componentDidMount() {
    //这里获取从FirstPageComponent传递过来的参数: id
    this.setState({
      id: this.props.id
    });
  }
  _pressButton() {
    const { navigator } = this.props;
    if (this.props.getUser) {
      let user = USER_MODELS[this.props.id];
      this.props.getUser(user);
    }
    //出栈~ 把当前的页面pop掉,这里就返回到了上一个页面:FirstPageComponent了
    if (navigator) {
      navigator.pop();
    }
  }

  render() {
    return (
      <View>
        <Text>获得的参数(id): id={this.state.id}</Text>
        <TouchableOpacity onPress={this._pressButton.bind(this)}>
          <Text>点我跳回去</Text>
        </TouchableOpacity>
      </View>
    );
  }
}
  • Column - Navigator。所以:Navigator需要附件限制高度,例如:Scaffold - Column - Container(height: 300) - Navigator
4 . initialRoute

style="font-size: 12px;">初始路由,当用户进入程序时,自动打开对应的路由。
(home还是位于一级)
传入的是上面routes的key
style="font-size: 12px;">跳转的是对应的Widget(如果该Widget有Scaffold.AppBar,并不做任何修改,左上角有返回键)

注意:用指定默认显示的路由名字,默认值为 Window.defaultRouteName

使用

new MaterialApp(      routes: {       '/home':(BuildContext context) => HomePage(),       '/home/one':(BuildContext context) => OnePage(),       //....      },      initialRoute: '/home/one',    );

传递参数和获取参数

然后:在onGenerateRoute属性中,使用第一个BuildContext参数,能够在MaterialApp未设置route的情况下使用Navigator.pushNamed(nContext, '/efg');跳到对应的子路由中。

5 . onGenerateRoute

style="font-size: 12px;">当通过Navigation.of.pushNamed跳转路由时,

在routes查找不到时,会调用该方法

使用

new MaterialApp(      routes: {       '/home':(BuildContext context) => HomePage(),       '/home/one':(BuildContext context) => OnePage(),       //....      },      onGenerateRoute: {        //setting.isInitialRoute; bool类型 是否初始路由        //setting.name; 要跳转的路由名key        return new PageRouteBuilder(            pageBuilder: (BuildContext context, _, __) {        //这里为返回的Widget              return HomePage();            },            opaque: false,        //跳转动画            transitionDuration: new Duration(milliseconds: 200),            transitionsBuilder:                (___, Animation<double> animation, ____, Widget child) {              return new FadeTransition(                opacity: animation,                child: new ScaleTransition(                  scale: new Tween<double>(begin: 0.5, end: 1.0)                      .animate(animation),                  child: child,                ),              );            });      }    );
传递参数到新页面

传递参数到新页面,可以通过push。比如在一个 press的事件里:

FirstPageComponent.js

...
constructor(props) {
  super(props);
  this.state = {
    id: 2
  };
}
_pressButton() {
  ...
    navigator.push({
      name: 'SecondPageComponent',
      component: SecondPageComponent,
      //这个 params 其实来自于Navigator 里的一个方法的参数
      params: {
        id: this.state.id
      }
    })
  ...
}

params的来历:

//Navigator.js
...
<Navigator
  initialRoute={{ name: defaultName, component: defaultComponent }}
  configureScene={() => {
    return Navigator.SceneConfigs.VerticalDownSwipeJump;
  }}
  renderScene={(route, navigator) => {
    let Component = route.component;
    if(route.component) {
        //这里有个 { ...route.params }
        return <Component {...route.params} navigator={navigator} />
    }
  }} 
/>
...

{ ...route.params }语法是把 routes.params 里的每个key 作为props的一个属性:

//FirstPageComponent.js
...
navigator.push({
    name: 'SecondPageComponent',
    component: SecondPageComponent,
    params: {  //routes.params
        id: this.state.id
    }
});
...

这里的 params.id 就变成了 <Component id={routes.params.id} navigator={navigator}> 里的id属性(props)传递给了下一个页面。

//SecondPageComponent.js
...
componentDidMount() {
  //这里获取从FirstPageComponent传递过来的参数: id
  this.setState({
    id: this.props.id
  });
}
...
render() {
  return (
    <View>
      <Text>获得的参数: id={ this.state.id }</Text>
      <TouchableOpacity onPress={this._pressButton.bind(this)}>
        <Text>点我跳回去</Text>
      </TouchableOpacity>
    </View>
  );
}

最后:Navigator执行寻找路由顺序是 initialRoute - onGenerateRoute - onUnknownRoute,这个和React的Route是类似的。

6 . onUnknownRoute

效果跟onGenerateRoute一样
调用顺序为onGenerateRoute ==> onUnknownRoute

返回参数到之前页面

返回的时候,也需要传递参数回上一个页面。但是navigator.pop()并没有提供参数,因为pop()只是从 [路由1,路由2,路由3。。。]里把最后一个路由踢出去的操作,并不支持传递参数给倒数第二个路由,这里要用到一个概念,把上一个页面的实例或者回调方法,作为参数传递到当前页面来,在当前页面操作上一个页面的state:

比如FirstPageComponent传递id到SecondPageComponent,然后SecondPageComponent返回user信息给FirstPageComponent:

//FirstPageComponent.js
...
constructor(props) {
  super(props);
  this.state = {
    id: 2,
    user: null,
  }
}
...
_pressButton() {
    let _this = this;
    ...
      params: {  //routes.params
        id: this.state.id,
        //从SecondPageComponent获取user
        getUser: function (user) {
          _this.setState({
            user: user
          })
        }
      }
...
render() {
  if (this.state.user) {
    return (
      <View>
        <Text>用户信息: {JSON.stringify(this.state.user)}</Text>
      </View>
    );
  } else {
    return (
      <View>
        <TouchableOpacity onPress={this._pressButton.bind(this)}>
          <Text>查询ID为{this.state.id}的用户信息</Text>
        </TouchableOpacity>
      </View>
    );
  }
}

//SecondPageComponent.js
...
const USER_MODELS = {
  1: { name: '小李', age: 18 },
  2: { name: '小明', age: 20 }
};

export default class SecondPageComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      id: null
    };
  }
  componentDidMount() {
    //这里获取从FirstPageComponent传递过来的参数: id
    this.setState({
      id: this.props.id
    });
  }
  _pressButton() {
    const { navigator } = this.props;
    if (this.props.getUser) {
      let user = USER_MODELS[this.props.id];
      this.props.getUser(user);
    }
    //出栈~ 把当前的页面pop掉,这里就返回到了上一个页面:FirstPageComponent了
    if (navigator) {
      navigator.pop();
    }
  }
}
...

最后附上源码

7 . navigatorObservers

style="font-size: 12px;">路由观察器,当调用Navigator的相关方法时,会回调相关的操作

使用

new MaterialApp(      navigatorObservers: [        MyObserver(),      ],    );//继承NavigatorObserverclass MyObserver extends NavigatorObserver{  @override  void didPush(Route route, Route previousRoute) {    // 当调用Navigator.push时回调    super.didPush(route, previousRoute);    //可通过route.settings获取路由相关内容    //route.currentResult获取返回内容    //....等等    print(route.settings.name);  }}

NavigatorIOS

如果你只针对iOS平台开发,那么可以考虑使用NavigatorIOS。它是基于 UINavigationController封装的。

<NavigatorIOS
  initialRoute={{
    component: MyScene,
    title: 'My Initial Scene',
    passProps: { myProp: 'foo' },
  }}
/>

用法类似Navigator,NavigatorIOS也使用路由对象来描述场景,但有一些重要区别。其中要渲染的组件在路由对象的component字段中指定,要给目标组件传递的参数则写在passProps中。被渲染的component都会自动接受到一个名为navigator的属性,你可以直接调用此对象(this.props.navigator)的push和pop方法。
由于NavigatorIOS使用的是原生的UIKit导航,所以它会自动渲染一个带有返回按钮和标题的导航栏。

你还可以看看react-native-navigation,这是一个第三方的组件,旨在于提供原生的跨平台的导航组件。

import 'package:flutter/material.dart';class NavigatorPage extends StatefulWidget { @override _NavigatorPageState createState() = _NavigatorPageState();}class _NavigatorPageState extends StateNavigatorPage { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Navigator'), ), body: Column( children: Widget[ Text('Navigator的高度为infinity'), Text('如果直接父级非最上级也是infinity会产生异常'), Container( height: 333, color: Colors.amber.withAlpha(111), child: Navigator( // Navigator initialRoute: '/abc', onGenerateRoute: (val) { RoutePageBuilder builder; switch (val.name) { case '/abc': builder = (BuildContext nContext, Animationdouble animation, Animationdouble secondaryAnimation) = Column( // 并没有在 MaterialApp 中设定 /efg 路由 // 因为Navigator的特性 使用nContext 可以跳转 /efg children: Widget[ Text('呵呵呵'), RaisedButton( child: Text('去 /efg'), onPressed: () { Navigator.pushNamed(nContext, '/efg'); }, ) ], ); break; case '/efg': builder = (BuildContext nContext, Animationdouble animation, Animationdouble secondaryAnimation) = Row( children: Widget[ RaisedButton( child: Text('去 /hhh'), onPressed: () { Navigator.pushNamed(nContext, '/hhh'); }, ) ], ); break; default: builder = (BuildContext nContext, Animationdouble animation, Animationdouble secondaryAnimation) = Center( child: RaisedButton( child: Text('去 /abc'), onPressed: () { Navigator.pushNamed(nContext, '/abc'); }, ) ); } return PageRouteBuilder( pageBuilder: builder, // transitionDuration: const Duration(milliseconds: 0), ); }, onUnknownRoute: (val) { print(val); }, observers: NavigatorObserver[] ), ), Text('Navigator执行寻找路由顺序'), Text('initialRoute'), Text('onGenerateRoute'), Text('onUnknownRoute'), ], ), ); }}
8 . builder

当构建一个Widget前调用

一般做字体大小,方向,主题颜色等配置

使用

new MaterialApp(      builder: (BuildContext context, Widget child) {        return MediaQuery(          data: MediaQuery.of.copyWith(            //字体大小                textScaleFactor: 1.4,              ),          child: child,        );      },    );

NavigationExperimental

Navigator和NavigatorIOS都是有状态的组件。如果你在app中多处使用这些组件,那么维护工作就会变得非常麻烦。NavigationExperimental以不同的方式实现了导航,它可以使用任何视图来作为导航视图,同时还用到了规约函数(reducer)自顶向下地管理状态。正如名字中的Experimental(实验)所示,这一组件的整体实现具有一定的实验性,但仍然建议你尝试一下用它去更好地管理应用的导航。

<NavigationCardStack
  onNavigateBack={onPopRouteFunc}
  navigationState={myNavigationState}
  renderScene={renderSceneFun}
/>

引入NavigationExperimental的步骤和React Native中的其他组件一样。在引入此组件之后,还可以进一步解构其中一些有用的子组件,比如这里我们会从中解构NavigationCardStack和 NavigationStateUtils这两个子组件。

import React, { Component } from 'react';
import { NavigationExperimental } from 'react-native';

const {
  CardStack: NavigationCardStack,
  StateUtils: NavigationStateUtils,
} = NavigationExperimental;

NavigationExperimental的实现机制与Navigator和NavigatorIOS有所不同,用它来构建导航栈还需要一些额外的步骤。

项目地址:-example-app

9 . title

该标题出现在

style="font-size: 12px;">Android:任务管理器的程序快照之上

IOS: 程序切换管理器中

注意:这个和启动图标名字是不一样的,和当前 Activity 的名字也是不一样的。 这个 Title 是用来定义任务管理窗口界面所看到应用名字的。在原生 Android 系统中点击圆圈 Home 按钮右边的方块按钮就会打开多任务切换窗口。

使用

new MaterialApp(      title: 'Flutter应用',    );

第一步:定义初始状态和根容器

首先创建一个新组件,我们会把它作为根容器,并在这里定义初始状态。导航栈会定义在navigationState字段中,其中也包含了初始的路由定义:

import React, { Component } from 'react';
import { NavigationExperimental } from 'react-native';

const {
  CardStack: NavigationCardStack,
  StateUtils: NavigationStateUtils,
} = NavigationExperimental;

class Sample extends Component {
  constructor(props, context) {
    super(props, context);
    this.state = {
      // 定义初始的导航状态
      navigationState: {
        index: 0, // 现在是第一页(索引从0开始)
        routes: [{key: '最初的场景'}], // 初始仅设定一个路由
      },
    };
    // 稍后再补充此函数的实现细节
    this._onNavigationChange = this._onNavigationChange.bind(this);
  }
  _onNavigationChange(type) {
    // 稍后再补充此函数的实现细节
  }
  _exit() {
    //exit()实现
  }
  render() {
    return (
      <Text>这是一段占位的文字。稍后会在这里渲染导航。</Text>
    );
  }
}

现在我们定义了一个有状态的组件,暂时是无用的。我们的初始状态包含了一个路由对象,以及当前页面的索引值。但是这看起来跟Navigator的初始路由定义好像没什么区别!回忆一下navigator对象提供了push和pop操作,看起来也非常直观。但是前面我们说过了,现在我们会在根容器上使用规约函数来管理状态,下面继续。

撸完代码记得给颗星哦。

10 . onGenerateTitle

跟上面的tiitle一样,但含有一个context参数
用于做本地化

使用

new MaterialApp(      onGenerateTitle: {        return 'Flutter应用';      },    );

第二步:规约导航状态

NavigationExperimental内置了一些有用的规约函数(reducer),都放在NavigationStateUtils中。我们现在要用的两个就是push和pop了。它们接受一个navigationState对象参数,然后返回新的navigationState对象。

据此我们可以这样来编写_onNavigationChange函数,在其中判断"push"和"pop"的行为,并分别规约对应的状态。

_onNavigationChange(type) {
  // 从state中解构出navigationState
  let {navigationState} = this.state;
  switch (type) {
    case 'push':
      // push一个新路由,在这里就是一个带有key属性的对象。
      // key必须要确保唯一性
      const route = {key: 'Route-'   Date.now()};
      // 调用NavigationStateUtils提供的push规约函数
      navigationState = NavigationStateUtils.push(navigationState, route);
      break;
    case 'pop':
      // 使用pop函数来弹出当前路由
      navigationState = NavigationStateUtils.pop(navigationState);
      break;
  }
  // 如果没有实际变化,则NavigationStateUtils会返回同样的`navigationState`
  // 我们只会更新确实发生变化的状态
  if (this.state.navigationState !== navigationState) {
    // 请记住更新状态必须通过setState()方法!
    this.setState({navigationState});
    // 简单讲解一下上面那一句ES6语法
    // 如果key和value的字面一样,那么可以简写成一个,等同于下面的写法:
    // this.setState({navigationState: navigationState});
  }
}

到这里,我们已经触碰到了NavigationExperimental的精髓所在。这里我们只处理了两种行为,实际开发中行为可能更复杂,比如可能会考虑后退(back)行为,又或者是tab间的切换过渡行为等等。

我们现在还没写初始场景和实际的导航器,不过别急,我们一步一步来。

11 . color

style="font-size: 12px;">该颜色为Android中程序切换中应用图标背景的颜色,当应用图标背景为透明时

使用

new MaterialApp(      color: Colors.blue,    )

第三步:定义场景

为方便起见我们先定义一个Row(行)组件。其中显示了一些文字,并带有点击事件。

class TappableRow extends Component {
  render() {
    return (
      <TouchableHighlight
        style={styles.row}
        underlayColor="#D0D0D0"
        onPress={this.props.onPress}>
        <Text style={styles.buttonText}>
          {this.props.text}
        </Text>
      </TouchableHighlight>
    );
  }
}

现在来定义实际的场景。其中用到了一个ScrollView来显示一个垂直列表,第一行显示当前路由对象的key字段值,后两行用来点击后调用导航器的push和pop方法。

class MyVeryComplexScene extends Component {
  render() {
    return (
      <ScrollView style={styles.scrollView}>
        <Text style={styles.row}>
          路由: {this.props.route.key}
        </Text>
        <TappableRow
          text="加载下一个场景"
          onPress={this.props.onPushRoute}/>
        <TappableRow
          text="返回上一个场景"
          onPress={this.props.onPopRoute}/>
      </ScrollView>
    );
  }
}
12 . theme

style="font-size: 12px;">应用程序的主题,各种的定制颜色都可以设置,用于程序主题切换

使用

new MaterialApp(      theme: new ThemeData(       //主题色        primarySwatch: Colors.blue,      ),    );

第四步:创建导航栈

我们之前已经定义了状态和管理状态的规约函数,现在可以创建导航器组件了。在写导航器的同时,我们可以使用当前路由的属性来配置场景并渲染它了。

class MyVerySampleNavigator extends Component {
  // 在这里绑定一些导航用的方法
  constructor(props, context) {
    super(props, context);
    this._onPushRoute = this.props.onNavigationChange.bind(null, 'push');
    this._onPopRoute = this.props.onNavigationChange.bind(null, 'pop');
    this._renderScene = this._renderScene.bind(this);
  }
  // 现在我们终于可以使用“NavigationCardStack”来渲染场景。
  render() {
    return (
      <NavigationCardStack
        onNavigateBack={this._onPopRoute}
        navigationState={this.props.navigationState}
        renderScene={this._renderScene}
        style={styles.navigator}
      />
    );
  }
  // 根据路由来渲染场景
  // `sceneProps`的具体结构定义在`NavigationTypeDefinition`的`NavigationSceneRendererProps`中
  // 这里你可以根据路由的不同来返回不同的场景组件,我们这里为了简要说明,始终只返回这一个场景组件
  _renderScene(sceneProps) {
    return (
      <MyVeryComplexScene
        route={sceneProps.scene.route}
        onPushRoute={this._onPushRoute}
        onPopRoute={this._onPopRoute}
        onExit={this.props.onExit}
      />
    );
  }
}

最后把我们新做的导航器放到根容器中:

class Sample extends Component {
  // 这里省略了constructor和其他的方法
  render() {
    return (
      <MyVerySampleNavigator
        navigationState={this.state.navigationState}
        onNavigationChange={this._onNavigationChange}
        onExit={this._exit}
      />
    );
  }
}

别忘了引入组件和样式

import { NavigationExperimental, PixelRatio, ScrollView, StyleSheet, Text, TouchableHighlight } from 'react-native';

const styles = StyleSheet.create({
  navigator: {
    flex: 1,
  },
  scrollView: {
    marginTop: 64
  },
  row: {
    padding: 15,
    backgroundColor: 'white',
    borderBottomWidth: 1 / PixelRatio.get(),
    borderBottomColor: '#CDCDCD',
  },
  rowText: {
    fontSize: 17,
  },
  buttonText: {
    fontSize: 17,
    fontWeight: '500',
  },
});

React Native学习笔记--进阶(一)--嵌入到Android原生应用中、组件的生命周期、颜色、图片、触摸事件
React Native学习笔记--进阶(二)--动画
React Native学习笔记--进阶(三)--定时器、直接操作(setNativeProps)、调试
React Native学习笔记--进阶(四)--导航器
React Native学习笔记--进阶(五)--性能、升级、特定平台代码

13 . locale

style="font-size: 12px;">当前区域,如果为null则使用系统区域 style="font-size: 12px;">一般用于语言切换

使用

//传入两个参数,语言代码,国家代码new MaterialApp(      Locale('yy','zh'),    );//源码纠正,一些曾经用过的代码(注释后面的日期为不再使用的日期),源码会自动纠正//来看一下源码//languageCode 第一个参数switch (languageCode) {      case 'in': return 'id'; // Indonesian; deprecated 1989-01-01      case 'iw': return 'he'; // Hebrew; deprecated 1989-01-01      case 'ji': return 'yi'; // Yiddish; deprecated 1989-01-01      case 'jw': return 'jv'; // Javanese; deprecated 2001-08-13      case 'mo': return 'ro'; // Moldavian, Moldovan; deprecated 2008-11-22      case 'aam': return 'aas'; // Aramanik; deprecated 2015-02-12      case 'adp': return 'dz'; // Adap; deprecated 2015-02-12      case 'aue': return 'ktz'; // =/Kx'au//'ein; deprecated 2015-02-12      case 'ayx': return 'nun'; // Ayi ; deprecated 2011-08-16      case 'bgm': return 'bcg'; // Baga Mboteni; deprecated 2016-05-30      case 'bjd': return 'drl'; // Bandjigali; deprecated 2012-08-12      case 'ccq': return 'rki'; // Chaungtha; deprecated 2012-08-12      case 'cjr': return 'mom'; // Chorotega; deprecated 2010-03-11      case 'cka': return 'cmr'; // Khumi Awa Chin; deprecated 2012-08-12      case 'cmk': return 'xch'; // Chimakum; deprecated 2010-03-11      case 'coy': return 'pij'; // Coyaima; deprecated 2016-05-30      case 'cqu': return 'quh'; // Chilean Quechua; deprecated 2016-05-30      case 'drh': return 'khk'; // Darkhat; deprecated 2010-03-11      case 'drw': return 'prs'; // Darwazi; deprecated 2010-03-11      case 'gav': return 'dev'; // Gabutamon; deprecated 2010-03-11      case 'gfx': return 'vaj'; // Mangetti Dune !Xung; deprecated 2015-02-12      case 'ggn': return 'gvr'; // Eastern Gurung; deprecated 2016-05-30      case 'gti': return 'nyc'; // Gbati-ri; deprecated 2015-02-12      case 'guv': return 'duz'; // Gey; deprecated 2016-05-30      case 'hrr': return 'jal'; // Horuru; deprecated 2012-08-12      case 'ibi': return 'opa'; // Ibilo; deprecated 2012-08-12      case 'ilw': return 'gal'; // Talur; deprecated 2013-09-10      case 'jeg': return 'oyb'; // Jeng; deprecated 2017-02-23      case 'kgc': return 'tdf'; // Kasseng; deprecated 2016-05-30      case 'kgh': return 'kml'; // Upper Tanudan Kalinga; deprecated 2012-08-12      case 'koj': return 'kwv'; // Sara Dunjo; deprecated 2015-02-12      case 'krm': return 'bmf'; // Krim; deprecated 2017-02-23      case 'ktr': return 'dtp'; // Kota Marudu Tinagas; deprecated 2016-05-30      case 'kvs': return 'gdj'; // Kunggara; deprecated 2016-05-30      case 'kwq': return 'yam'; // Kwak; deprecated 2015-02-12      case 'kxe': return 'tvd'; // Kakihum; deprecated 2015-02-12      case 'kzj': return 'dtp'; // Coastal Kadazan; deprecated 2016-05-30      case 'kzt': return 'dtp'; // Tambunan Dusun; deprecated 2016-05-30      case 'lii': return 'raq'; // Lingkhim; deprecated 2015-02-12      case 'lmm': return 'rmx'; // Lamam; deprecated 2014-02-28      case 'meg': return 'cir'; // Mea; deprecated 2013-09-10      case 'mst': return 'mry'; // Cataelano Mandaya; deprecated 2010-03-11      case 'mwj': return 'vaj'; // Maligo; deprecated 2015-02-12      case 'myt': return 'mry'; // Sangab Mandaya; deprecated 2010-03-11      case 'nad': return 'xny'; // Nijadali; deprecated 2016-05-30      case 'nnx': return 'ngv'; // Ngong; deprecated 2015-02-12      case 'nts': return 'pij'; // Natagaimas; deprecated 2016-05-30      case 'oun': return 'vaj'; // !O!ung; deprecated 2015-02-12      case 'pcr': return 'adx'; // Panang; deprecated 2013-09-10      case 'pmc': return 'huw'; // Palumata; deprecated 2016-05-30      case 'pmu': return 'phr'; // Mirpur Panjabi; deprecated 2015-02-12      case 'ppa': return 'bfy'; // Pao; deprecated 2016-05-30      case 'ppr': return 'lcq'; // Piru; deprecated 2013-09-10      case 'pry': return 'prt'; // Pray 3; deprecated 2016-05-30      case 'puz': return 'pub'; // Purum Naga; deprecated 2014-02-28      case 'sca': return 'hle'; // Sansu; deprecated 2012-08-12      case 'skk': return 'oyb'; // Sok; deprecated 2017-02-23      case 'tdu': return 'dtp'; // Tempasuk Dusun; deprecated 2016-05-30      case 'thc': return 'tpo'; // Tai Hang Tong; deprecated 2016-05-30      case 'thx': return 'oyb'; // The; deprecated 2015-02-12      case 'tie': return 'ras'; // Tingal; deprecated 2011-08-16      case 'tkk': return 'twm'; // Takpa; deprecated 2011-08-16      case 'tlw': return 'weo'; // South Wemale; deprecated 2012-08-12      case 'tmp': return 'tyj'; // Tai Mène; deprecated 2016-05-30      case 'tne': return 'kak'; // Tinoc Kallahan; deprecated 2016-05-30      case 'tnf': return 'prs'; // Tangshewi; deprecated 2010-03-11      case 'tsf': return 'taj'; // Southwestern Tamang; deprecated 2015-02-12      case 'uok': return 'ema'; // Uokha; deprecated 2015-02-12      case 'xba': return 'cax'; // Kamba ; deprecated 2016-05-30      case 'xia': return 'acn'; // Xiandao; deprecated 2013-09-10      case 'xkh': return 'waw'; // Karahawyana; deprecated 2016-05-30      case 'xsj': return 'suj'; // Subi; deprecated 2015-02-12      case 'ybd': return 'rki'; // Yangbye; deprecated 2012-08-12      case 'yma': return 'lrr'; // Yamphe; deprecated 2012-08-12      case 'ymt': return 'mtm'; // Mator-Taygi-Karagas; deprecated 2015-02-12      case 'yos': return 'zom'; // Yos; deprecated 2013-09-10      case 'yuu': return 'yug'; // Yugh; deprecated 2014-02-28      default: return languageCode;    }//_countryCode 第二个参数switch (_countryCode) {      case 'BU': return 'MM'; // Burma; deprecated 1989-12-05      case 'DD': return 'DE'; // German Democratic Republic; deprecated 1990-10-30      case 'FX': return 'FR'; // Metropolitan France; deprecated 1997-07-14      case 'TP': return 'TL'; // East Timor; deprecated 2002-05-20      case 'YD': return 'YE'; // Democratic Yemen; deprecated 1990-08-14      case 'ZR': return 'CD'; // Zaire; deprecated 1997-07-14      default: return regionCode;    }
14 . localizationsDelegates

本地化委托,用于更改Flutter Widget默认的提示语,按钮text等

使用

new MaterialApp(      localizationsDelegates: [          MyLocalizationsDelegates(),      ],      locale: Locale('zh','cn'),    );class MyLocalizationsDelegates extends LocalizationsDelegate<MaterialLocalizations>{  @override  bool isSupported(Locale locale) {//是否支持该locale,如果不支持会报异常    if(locale == const Locale('zh','cn')){      return true;    }    return false;  }  @override//是否需要重载  bool shouldReload(LocalizationsDelegate old)  => false;  @override  Future<MaterialLocalizations> load(Locale locale) {//加载本地化    return new SynchronousFuture(new MyLocalizations;  }}//本地化实现,继承DefaultMaterialLocalizationsclass MyLocalizations extends DefaultMaterialLocalizations{  final Locale locale;  MyLocalizations(this.locale, );  @override  String get okButtonLabel {    if(locale == const Locale('zh','cn')){      return '好的';    }else{      return super.okButtonLabel;    }  }  @override  String get backButtonTooltip {    if(locale == const Locale('zh','cn')){      return '返回';    }else{      return super.okButtonLabel;    }  }}
15 . localeResolutionCallback

style="font-size: 12px;">当传入的是不支持的语种,可以根据这个回调,返回相近,并且支持的语种

使用

16 . supportedLocales

传入支持的语种数组

17 . debugShowMaterialGrid

style="font-size: 12px;">debug模式下是否显示材质网格,传入bool类型,使用就不写了

18 . showPerformanceOverlay

style="font-size: 12px;">当为true时应用程序顶部覆盖一层GPU和UI曲线图,可即时查看当前流畅度情况

19 . checkerboardRasterCacheImages

style="font-size: 12px;">当为true时,打开光栅缓存图像的棋盘格

20 . checkerboardOffscreenLayers

style="font-size: 12px;">当为true时,打开呈现到屏幕位图的层的棋盘格

21 . showSemanticsDebugger

style="font-size: 12px;">当为true时,打开Widget边框,类似Android开发者模式中显示布局边界

22 . debugShowCheckedModeBanner

style="font-size: 12px;">当为true时,在debug模式下显示右上角的debug字样的横幅,false即为不显示

 

三,Scaffold属性介绍

1. appBar:
显示在界面顶部的一个 AppBar,也就是 Android 中的 ActionBar 、Toolbar
2. body:
当前界面所显示的主要内容 Widget
3. floatingActionButton:
纸墨设计中所定义的 FAB,界面的主要功能按钮
4. persistentFooterButtons:
固定在下方显示的按钮,比如对话框下方的确定、取消按钮
5. drawer:
侧边栏控件
6 .backgroundColor:
内容的背景颜色,默认使用的是 ThemeData.scaffoldBackgroundColor 的值
7. bottomNavigationBar:
显示在页面底部的导航栏
8. resizeToAvoidBottomPadding

类似于 Android 中的 android:windowSoftInputMode=”adjustResize”,控制界面内容 body 是否重新布局来避免底部被覆盖了,比如当键盘显示的时候,重新布局避免被键盘盖住内容。默认值为 true。

  显示 snackbar 或者 bottom sheet 的时候,需要使用当前的 BuildContext 参数调用Scaffold.of函数来获取 ScaffoldState 对象,然后使用 ScaffoldState.showSnackBar 和 ScaffoldState.showBottomSheet 函数来显示。

  要特别注意 Scaffold.of 的参数 BuildContext, 如果包含该 BuildContext 的 Widget 是 Scaffold 的父 Widget,则 Scaffold.of 是无法查找到对应的 ScaffoldState 对象的,Scaffold.of 返回的是父对象中最近的 Scaffold 中的 ScaffoldState 对象。 比如,如果在 Scaffold 的 build 函数中,使用 build 的 BuildContext 参数是可以的:

@overrideWidget build(BuildContext context) {  return new RaisedButton(    child: new Text('SHOW A SNACKBAR'),    onPressed: () {      Scaffold.of.showSnackBar(new SnackBar(        content: new Text('Hello!'),      ));    },  );} 

  如果 build 函数返回一个 Scaffold 对象,则由于 Scaffold 对象是这个 Widget 的子对象,所以使用这个 build 的 BuildContext 参数是不能查找到 ScaffoldState 对象的,这个时候,通过在 Scaffold 中使用一个 Builder 来提供一个新的 BuildConext :

@overrideWidget build(BuildContext context) {  return new Scaffold(    appBar: new AppBar(      title: new Text('Demo')    ),    body: new Builder(      // Create an inner BuildContext so that the onPressed methods      // can refer to the Scaffold with Scaffold.of().      builder: (BuildContext context) {        return new Center(          child: new RaisedButton(            child: new Text('SHOW A SNACKBAR'),            onPressed: () {              Scaffold.of.showSnackBar(new SnackBar(                content: new Text('Hello!'),              ));            },          ),        );      },    ),  );}

  另外还可以把 build 函数中的 Widget 分别创建,分别引入新的 BuildContext 来获取 Scaffold。

关于 Scaffold 的示例,请查看示例项目中的使用方式,另外在后面介绍 Scaffold 里面的各个子 Widget 的时候会演示具体使用代码,比如 演示 AppBar 的示例。

TAG标签:
版权声明:本文由澳门新葡8455手机版发布于Web前端,转载请注明出处:之MaterialApp和Scaffold组件使用详细明白,局地路由