Flutter—— 路由(Route)和導(dǎo)航(Navigator),實(shí)現(xiàn)頁面管理

Flutter的頁面,怎么進(jìn)行跳轉(zhuǎn)的呢?通過路由和導(dǎo)航呢。

一、路由和導(dǎo)航,初認(rèn)識

言簡意賅!

  • 路由(Route) : route是一個屏幕或頁面的抽象(可以大概理解為安卓的Activity)
  • 導(dǎo)航(Navigator) : Navigator是管理route的Widget。導(dǎo)航器管理著路由對象的堆棧并提供管理堆棧的方法,如 Navigator.push入棧 和 Navigator.pop出棧

Navigator可以通過route入棧和出棧來實(shí)現(xiàn)頁面之間的跳轉(zhuǎn)。


一1、最簡單的頁面跳轉(zhuǎn)

一個頁面,在Flutter里面,被理解為一個路由。
多個路由,可以存在與同一個dart文件中的。

  • 使用Navigator.pushNamed方法首先需要在 MaterialApp 中定義routes。
import 'package:flutter/material.dart';

void main() {
  runApp(new MaterialApp(title:'導(dǎo)航頁面示例', home: new Demo()));
}

class Demo extends StatelessWidget{
  @override
  Widget build(BuildContext context){
    return new Scaffold(
      appBar: new AppBar(
        title: Text('導(dǎo)航頁面示例'),
      ),
      body: new Center(
          child:RaisedButton(
            child: Text('查看詳情頁面'),
            onPressed: (){
              Navigator.push(context, MaterialPageRoute(builder: (context)=>new SecondScreen()));
            },
          )
      ),
    );
  }
}

class SecondScreen extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('導(dǎo)航頁面第二屏'),
      ),
      body: new Center(
        child: new RaisedButton(
          onPressed: (){
            Navigator.pop(context);
          },
          child: new Text('返回頁面'),
        ),
      ),
    );
  }
}

.
.

從上面的例子,我們看到進(jìn)入新頁面,用push(入棧),返回上個頁面,用pop(出棧)。

MaterialPageRoute

  • MaterialPageRoute 是Material組件庫的一個Widget,它可以針對不同平臺,實(shí)現(xiàn)與平臺頁面切換動畫風(fēng)格一致的路由切換動畫

    • 對于Android,當(dāng)打開新頁面時,新的頁面會從屏幕底部滑動到屏幕頂部;當(dāng)關(guān)閉頁面時,當(dāng)前頁面會從屏幕頂部滑動到屏幕底部后消失,同時上一個頁面會顯示到屏幕上。

    • 對于iOS,當(dāng)打開頁面時,新的頁面會從屏幕右側(cè)邊緣一致滑動到屏幕左邊,直到新頁面全部顯示到屏幕上,而上一個頁面則會從當(dāng)前屏幕滑動到屏幕左側(cè)而消失;當(dāng)關(guān)閉頁面時,正好相反,當(dāng)前頁面會從屏幕右側(cè)滑出,同時上一個頁面會從屏幕左側(cè)滑入。

  • 默認(rèn)情況下,當(dāng)一個模態(tài)路由被另一個替換時,上一個路由將保留在內(nèi)存中,如果想釋放所有資源,可以將 maintainState 設(shè)置為 false。

MaterialPageRoute的構(gòu)造函數(shù)

 MaterialPageRoute({
    WidgetBuilder builder,
    RouteSettings settings,
    bool maintainState = true,
    bool fullscreenDialog = false,
  })
  • builder 是一個WidgetBuilder類型的回調(diào)函數(shù),它的作用是構(gòu)建路由頁面的具體內(nèi)容,返回值是一個widget。我們通常要實(shí)現(xiàn)此回調(diào),返回新路由的實(shí)例。

  • settings 包含路由的配置信息,如路由名稱、是否初始路由(首頁)。

  • maintainState:默認(rèn)情況下,當(dāng)入棧一個新路由時,原來的路由仍然會被保存在內(nèi)存中,如果想在路由沒用的時候釋放其所占用的所有資源,可以設(shè)置maintainState為false。

  • fullscreenDialog表示新的路由頁面是否是一個全屏的模態(tài)對話框,在iOS中,如果fullscreenDialog為true,新頁面將會從屏幕底部滑入(而不是水平方向)。

.
.
示例:

1.gif

一.2、跳轉(zhuǎn)頁面時傳遞數(shù)據(jù)

  • 使用Navigator.push接收參數(shù)的重點(diǎn)在構(gòu)造函數(shù),通過在構(gòu)造函數(shù)中接受參數(shù)進(jìn)行傳遞

傳遞數(shù)據(jù),其實(shí)也很簡單。P1跳轉(zhuǎn)到P2,P2的構(gòu)造函數(shù)預(yù)留好參數(shù),P1跳轉(zhuǎn)到P2的時候,傳遞一下即可。


import 'package:flutter/material.dart';

class Product {
  final String title;
  final String description;
  Product(this.title,this.description);
} //Product 類 屬性

void main(){
  runApp(new MaterialApp(
      title:'傳遞數(shù)據(jù)示例',
      home:new ProductList(
          products:new List.generate(20, (i)=>new Product('商品 $i', '這是一個商品的詳情 $i')) //父子傳值
      )
  ));
}


class ProductList extends StatelessWidget{
  final List<Product> products;
  ProductList({Key key,@required this.products}):super(key:key);
  @override
  Widget build(BuildContext context){
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('商品列表'),
      ),
      body: new ListView.builder(
          itemCount:products.length,
          itemBuilder:(context,index){
            return new ListTile(
              title:new Text(products[index].title),
              onTap: (){
                Navigator.push(
                    context,
                    // 傳遞數(shù)據(jù)
                    new MaterialPageRoute(
                        builder: (context)=>new ProductDetail(product:products[index])
                    )
                );
              },
            );
          }
      ),
    );
  }
}

class ProductDetail extends StatelessWidget {
  final Product product;
  ProductDetail({Key key,@required this.product}):super(key:key);
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('${product.title}'),
      ),
      body: new Padding(
        padding: new EdgeInsets.all(16.0),
        child: new Text('${product.description}'),
      ),
    );
  }
}

.
.
效果:

2.gif

一3、頁面返回數(shù)據(jù)

import 'package:flutter/material.dart';

void main() {
  runApp(new MaterialApp(title:'導(dǎo)航頁面示例', home: new ArticleListScreen()));
}

class Article {
  String title;
  String content;

  Article({this.title, this.content});
}
class ArticleListScreen extends StatelessWidget {
  final List<Article> articles = new List.generate(
    10,
        (i) => new Article(
      title: 'Article $i',
      content: '文章 $i: 你喜歡這個文章嗎親.',
    ),
  );

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Article List'),
      ),
      body: new ListView.builder(
        itemCount: articles.length,
        itemBuilder: (context, index) {
          // 當(dāng)用戶點(diǎn)擊列表中的文章時將跳轉(zhuǎn)到ContentScreen,并將 article 傳遞給 ContentScreen。
          //為了實(shí)現(xiàn)這一點(diǎn),我們將實(shí)現(xiàn) ListTile 的 onTap 回調(diào)。 在的 onTap 回調(diào)中,再次調(diào)用Navigator.push方法。

          return new ListTile(
              title: new Text(articles[index].title),
              // 列表項的 onTap 回調(diào),處理內(nèi)容頁面返回的數(shù)據(jù)并顯示。
              /*onTap: () {
                Navigator.push(
                  context,
                  new MaterialPageRoute(
                    builder: (context) => new ContentScreen(articles[index]),
                  ),
                );
              },*/

              onTap: () async {
                // 接受頁面返回值
                String result = await Navigator.push(
                  context,
                  new MaterialPageRoute(
                    // 文章頁面跳轉(zhuǎn)到內(nèi)容頁面,傳遞個值
                    builder: (context) => new ContentScreen(articles[index]),
                  ),
                );

                if (result != null) {
                  Scaffold.of(context).showSnackBar(
                    new SnackBar(
                      // 顯示一下從別人頁面?zhèn)鬟f過來的值,如果有值的話
                      content: new Text("$result"),
                      duration: const Duration(seconds: 1),
                    ),
                  );
                }
              },
          );
        },
      ),
    );
  }
}



class ContentScreen extends StatelessWidget {
  final Article article;

  ContentScreen(this.article);

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('${article.title}'),
      ),
      body: new Padding(
          padding: new EdgeInsets.all(15.0),
          child: new Column(
            children: <Widget>[
              new Text('${article.content}'),
              new Row(
                mainAxisAlignment: MainAxisAlignment.spaceAround,
                children: <Widget>[
                  new RaisedButton(
                    onPressed: () {
                      // pop 第二個參數(shù),就是表示給上個頁面?zhèn)鬟f
                      Navigator.pop(context, 'Like');
                    },
                    child: new Text('Like'),
                  ),
                  new RaisedButton(
                    onPressed: () {
                      // pop 第二個參數(shù),就是表示給上個頁面?zhèn)鬟f
                      Navigator.pop(context, 'Unlike');
                    },
                    child: new Text('Unlike'),
                  ),
                ],
              )
            ],
          ),

      ),
    );
  }
}

  • 返回數(shù)據(jù),主要就是pop的時候返回,然后想接受的,在push的時候,就接受下

.
.
效果

3.gif

二、定制路由

通常,我們可能需要定制路由以實(shí)現(xiàn)自定義的過渡效果等。定制路由有兩種方式:

  • 繼承路由子類,如:PopupRoute、ModalRoute 等。
  • 使用 PageRouteBuilder 類通過回調(diào)函數(shù)定義路由。
  • 下面使用 PageRouteBuilder 實(shí)現(xiàn)一個頁面旋轉(zhuǎn)淡出的效果。
onTap: () async {
  String result = await Navigator.push(
      context,
      new PageRouteBuilder(
        transitionDuration: const Duration(milliseconds: 1000),
        pageBuilder: (context, _, __) =>
            new ContentScreen(articles[index]),
        transitionsBuilder:
            (_, Animation<double> animation, __, Widget child) =>
                new FadeTransition(
                  opacity: animation,
                  child: new RotationTransition(
                    turns: new Tween<double>(begin: 0.0, end: 1.0)
                        .animate(animation),
                    child: child,
                  ),
                ),
      ));

  if (result != null) {
    Scaffold.of(context).showSnackBar(
      new SnackBar(
        content: new Text("$result"),
        duration: const Duration(seconds: 1),
      ),
    );
  }
},

三、命名路由

在Flutter最初的版本中,命名路由是不能傳遞參數(shù)的,后來才支持了參數(shù).

當(dāng)使用 initialRoute 時,需要確保你沒有同時定義 home 屬性。

通常,移動應(yīng)用管理著大量的路由,并且最容易的是使用名稱來引用它們。路由名稱通常使用路徑結(jié)構(gòu):“/a/b/c”,主頁默認(rèn)為 “/”。

MaterialApp(
  // Start the app with the "/" named route. In this case, the app starts
  // on the FirstScreen widget.
  
  // 使用“/”命名路由來啟動應(yīng)用(Start the app with the "/" named route. In our case, the app will start)
  // 在這里,應(yīng)用將從 FirstScreen Widget 啟動(on the FirstScreen Widget)
  
  initialRoute: '/',
  routes: {
    // When navigating to the "/" route, build the FirstScreen widget.
    // 當(dāng)我們跳轉(zhuǎn)到“/”時,構(gòu)建 FirstScreen Widget(When we navigate to the "/" route, build the FirstScreen Widget)
    '/': (context) => FirstScreen(),
    // When navigating to the "/second" route, build the SecondScreen widget.
    // 當(dāng)我們跳轉(zhuǎn)到“/second”時,構(gòu)建 SecondScreen Widget(When we navigate to the "/second" route, build the SecondScreen Widget)
    '/second': (context) => SecondScreen(),
  },
);

創(chuàng)建 MaterialApp 時可以指定 routes 參數(shù),該參數(shù)是一個映射路由名稱和構(gòu)造器的 Map。MaterialApp 使用此映射為導(dǎo)航器的 onGenerateRoute 回調(diào)參數(shù)提供路由。

注冊路由

      routes: {
        // 注冊路由
        "parameters_page":(context)=>ParametersRoute(),
      },

在路由頁通過RouteSetting對象獲取路由參數(shù):

class ParametersRoute extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    //獲取路由參數(shù)  
    var args = ModalRoute.of(context).settings.arguments
    //...省略無關(guān)代碼
  }
}
 

在打開路由時傳遞參數(shù)

onPressed: () {
    //導(dǎo)航到一個新的路由頁面
    //Navigator.pushNamed(context, "third_page");
    Navigator.of(context).pushNamed("parameters_page",arguments:"命名路由傳遞的參數(shù)");
},

指定給 parameters_page路由 傳遞參數(shù) "命名路由傳遞的參數(shù)"

更加常見的,是弄個路由表

本部分和示例無關(guān)。

  • 提供一個路由表,這是一個Map,是字符串和WidgetBuilder的對應(yīng)關(guān)系。比如:
/// 路由表
final Map<String, WidgetBuilder> routeTable = {
  '/' : (content) => Home(),
  '/page1' : (content) => Page1(),
  '/page2' : (content) => Page2(),
  '/page3' : (content) => Page3(),
};
  • 把這個路由表放在MaterialApp的routes參數(shù)中
void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      routes: routeTable,
    );
  }
}
  • 使用的例子如下,比如跳轉(zhuǎn)到Page1頁面:
onPressed: (){
    Navigator.of(context).pushNamed('/page1');
}

一個示例代碼

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: '路由測試',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      routes: {
        // 注冊路由
        "home": (context) => MyHomePage(),
        "parameters_page": (context) => ParametersRoute(),
      },
      home: new MyHomePage(title: '路由測試'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            FlatButton(
              child: Text(
                "攜帶參數(shù)打開新頁面",
                style: TextStyle(fontWeight: FontWeight.bold),
              ),
              textColor: Colors.blue,
              onPressed: ()  {
                //導(dǎo)航到一個新的路由頁面
//                Navigator.pushNamed(context, "third_page");
                 Navigator.of(context)
                    .pushNamed("parameters_page", arguments: "命名路由傳遞的參數(shù)");
              },
            )
          ],
        ),
      ),
    );
  }
}

class ParametersRoute extends StatelessWidget {
  final Topic = Text("路由測試");

  @override
  Widget build(BuildContext context) {
    // TODO: implement build

    var args = ModalRoute.of(context).settings.arguments;
    return Scaffold(
      appBar: AppBar(
        title: Text("路由測試"),
      ),
      body: new ListView(

        children: <Widget>[
          
          Center(
            child: Text("路由到此頁面獲取到的數(shù)據(jù)為:\n\n" + args),
          ),
        ],

      )
    );
  }
}

.
.

4.gif

四、onGenerateRoute 攔截器

假設(shè)我們要開發(fā)一個電商APP,當(dāng)用戶沒有登錄時可以看店鋪、商品等信息.
但交易記錄、購物車、用戶個人信息等頁面需要登錄后才能看。為了實(shí)現(xiàn)上述功能,我們需要在打開每一個路由頁前判斷用戶登錄狀態(tài)!如果每次打開路由前我們都需要去判斷一下將會非常麻煩,那有什么更好的辦法嗎?答案是有! —— onGenerateRoute

  • onGenerateRoute 可以做攔截器
  • onGenerateRoute可以變相的接受參數(shù)
  • 可以在onGenerateRoute()函數(shù)中提取參數(shù)并將它們傳遞給widget,而不是直接在窗口小部件中提取參數(shù)。
  • 注意,onGenerateRoute只會對命名路由生效

.
.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // 提供處理命名路由的函數(shù)。使用此功能可識別要推送的命名路徑,并創(chuàng)建正確的頁面。
      onGenerateRoute: (settings) {
        // 如果您要 打開 PassArguments 路由
        if (settings.name == PassArgumentsScreen.routeName) {
          // 將參數(shù)轉(zhuǎn)換為正確的類型:ScreenArguments。
          final ScreenArguments args = settings.arguments;

          // 從參數(shù)中提取所需數(shù)據(jù)并將數(shù)據(jù)傳遞到正確的屏幕。
          return MaterialPageRoute(
            builder: (context) {
              return PassArgumentsScreen(
                title: args.title,
                message: args.message,
              );
            },
          );
        }
      },
      title: 'Navigation with Arguments',
      home: HomeScreen(),
    );
  }
}

class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home Screen'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            // A button that navigates to a named route that. The named route
            // extracts the arguments by itself.
            RaisedButton(
              child: Text("帶參數(shù) push 方式"),
              onPressed: () {
                // When the user taps the button, navigate to the specific route
                // and provide the arguments as part of the RouteSettings.
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => ExtractArgumentsScreen(),
                    // Pass the arguments as part of the RouteSettings. The
                    // ExtractArgumentScreen reads the arguments from these
                    // settings.
                    settings: RouteSettings(
                      arguments: ScreenArguments(
                        'tag1  來自HomeScreen的 push',
                        'tag1  這個消息將被  build 方法 提取',
                      ),
                    ),
                  ),
                );
              },
            ),
            // A button that navigates to a named route. For this route, extract
            // the arguments in the onGenerateRoute function and pass them
            // to the screen.
            RaisedButton(
              child: Text("帶參 pushNamed onGenerateRoute 方式"),
              onPressed: () {
                // When the user taps the button, navigate to a named route
                // and provide the arguments as an optional parameter.
                Navigator.pushNamed(
                  context,
                  PassArgumentsScreen.routeName,
                  arguments: ScreenArguments(
                    'tag2  來自HomeScreen的 pushNamed',
                    'tag2  這個消息來自 將被 onGenerateRoute 函數(shù)提取',
                  ),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

// 一個Widget,它從ModalRoute中提取必要的參數(shù)。
class ExtractArgumentsScreen extends StatelessWidget {
  static const routeName = '/extractArguments';

  @override
  Widget build(BuildContext context) {
    // 從當(dāng)前ModalRoute設(shè)置中提取參數(shù)并將其轉(zhuǎn)換為ScreenArguments。
    final ScreenArguments args = ModalRoute.of(context).settings.arguments;

    return Scaffold(
      appBar: AppBar(
        title: Text(args.title),
      ),
      body: Center(
        child: Text(args.message),
      ),
    );
  }
}

// Widget,通過構(gòu)造函數(shù)接受必要的參數(shù)。
class PassArgumentsScreen extends StatelessWidget {
  static const routeName = '/passArguments';

  final String title;
  final String message;

  // 此Widget接受參數(shù)作為構(gòu)造函數(shù)參數(shù)。它不從ModalRoute中提取參數(shù)。
  //
  // 參數(shù)由提供給MaterialApp小部件的onGenerateRoute函數(shù)提取。
  const PassArgumentsScreen({
    Key key,
    @required this.title,
    @required this.message,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Text(message),
      ),
    );
  }
}

// 您可以將任何對象傳遞給arguments參數(shù)。在此示例中,創(chuàng)建一個包含可自定義標(biāo)題和消息的類。
class ScreenArguments {
  final String title;
  final String message;

  ScreenArguments(this.title, this.message);
}

這是官方的例子,稍微改了點(diǎn)文字

5.gif

.
.
END

.
.
.

參數(shù):
Flutter (十五) 路由及導(dǎo)航

6.2.初識Flutter應(yīng)用之路由管理

Flutter 路由和導(dǎo)航

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容