Flutter 学习3:转场、导航

Categories: flutter

这个是我学习Flutter的一个系列文章:

  1. Flutter 学习1:开发环境、开发工具、初始化一个项目
  2. Flutter 学习2:从main.dart文件说起
  3. Flutter 学习3:转场、导航
  4. Flutter 学习4:集成到原有的项目中
  5. Flutter 学习5:开发Dart包和插件包
  6. Flutter 学习6:绘制动画
  7. Flutter 学习7:Dart语言基础
  8. Flutter 学习8:BottomSheet
  9. Flutter 学习9:Positioned、Transform等控件使用以及手势控制
  10. Flutter 学习10:NestedScrollView、SliverAppBar、TabBar

上次文章从main.dart文件开始分析,并做出来一个ListView的页面。我跟着官方的例子继续往下看,它写了一个功能,当前的列表数据可以多选,然后可以跳转到一个新的页面查看选中的值。这里就涉及到了页面跳转。具体来看看怎么做。

示例的页面跳转

前面列表页面已经出来,那要多选需要把选中的数据保留下来。

class RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];
  final _biggerFont = const TextStyle(fontSize: 18.0);
  final _saved = new Set<WordPair>(); //保存选中的值
  ...
  }

然后列表的Item我们添加了一个爱心,用来表示是否选中。这里我又要👍一个,Flutter自带的Material主题提供了各种Icon,可以直接拿来用,非常方便!

Widget _buildItem(WordPair pair) {
    //这里定义当前项是否已经选中
    final alreadySaved = _saved.contains(pair);
    return ListTile(
      title: Text(
        pair.asPascalCase,
        style: _biggerFont,
        ),
        //这里就是爱心图标,红色表示选中,空心表示未选中
      trailing: Icon(
        alreadySaved ? Icons.favorite : Icons.favorite_border,
        color: alreadySaved ? Colors.red : null,
      ),
      //ListTile的点击事件 选中还是取消选中
      onTap: (){
        setState(() {
                  if (alreadySaved) {
                    _saved.remove(pair);
                  }else {
                    _saved.add(pair);
                  } 
                });
      },
    );
  }

就是在原来的基础上,添加了一个爱心Icon和一个点击事件。现在可以点击了。

然后在导航栏添加一个菜单按钮,点击跳转到新页面查看选中的那几个值。

        appBar: new AppBar(
          title: new Text('Welcome to Flutter'),
          //添加菜单按钮
          actions: <Widget>[
            new IconButton(icon: const Icon(Icons.list), onPressed: _navigatePush)
          ],
        ),

IconButton绑定了点击事件_navigatePush 这是一个函数。

void _navigatePush() {
    ...
}

重点来了,这个函数里面应该是跳转页面的代码,如何写的呢? 官方的例子是这样写的:

    //这句是重点跳转到一个新页面。通过一个MaterialPageRoute对象进行的
    Navigator.of(context).push(
      new MaterialPageRoute<void>(
        builder: (BuildContext context) {
          final tiles = _saved.map((WordPair pair){
            return new ListTile(
              title: new Text(pair.asPascalCase, style:_biggerFont),
            );
          });
          final divided = ListTile.divideTiles(context: context, tiles: tiles).toList();
          return new Scaffold(         
            appBar: new AppBar(
              title: const Text('Saved Suggestions'),
            ),
            body: new ListView(children: divided),
          ); 
        }
      )
    );

现在点击右上角的列表图标的菜单按钮就会跳转一下,然后展现选中的值。

这个例子它把新页面的代码都写在了这个MaterialPageRoute的builder函数里面了。对于简单业务的页面这样写还是蛮方便的!但这种时候毕竟少,大部分情况是页面结构复杂,逻辑复杂,肯定是要专门一个分开的类来处理的。那这种情况怎么跳转呢?

真实的页面跳转

查询了官方文档后,发现跳转到一个新的页面代码也很简单,跟上面的类似:

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

那前面的例子有把选中的数据传递过去的,前面是因为都在class内部的所以直接用_saved这个Set就行了。那现在class分开了如何传递呢? 改写下这个SecondScreen的构建函数。

class SecondScreen extends StatelessWidget {
  final Set<WordPair> saved ;

 // 必须传入一个Set用来展现数据。
  SecondScreen({Key key, @required this.saved}) : super(key: key);
  ...

跳转的时候就把_saved这个Set传进去就行了。

Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => SecondScreen(saved: _saved)
      )
    );

页面关闭返回数据

现在页面跳转有了,有跳转有传入数据,那很容易联想到传出数据,就是把数据返回给前面的那个页面。 还是在这个Demo的基础上继续,比如我们第二个页面也是ListView,点击某一项把数据返回给前面那个页面。 先加点击事件:

        return new ListTile(
              title: new Text(pair.asPascalCase, style:_biggerFont),
              onTap: (){
                  _itemTapAndNavigationPop(context, pair.asPascalCase);
              },
            );

点击之后把名字传出去,并且关闭当前页面:

void _itemTapAndNavigationPop(BuildContext context, String title) {
    //只要执行pop函数就行了。
    Navigator.pop(context, title);
  }

但是前面的页面需要接收返回的数据啊,跳转的时候就肯定不是原来的写法了:

//这里这个调用函数加了异步标识async
void _navigatePush(BuildContext context) async {
    //页面返回的数据result
    final result = await Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => SecondScreen(saved: _saved)
      )
    );
    if (result != null) {
      //把接收到的值 用SnackBar显示一下
      Scaffold.of(context).showSnackBar(SnackBar(content: Text("$result")));
    }else {
      debugPrint('没有回传数据!!!!');
    }
  }

这里遇到一个问题,返回数据的时候showSnackBar这里一直报错!看文档的意思是当BuildContext在Scaffold之前调用Scaffold.of(context)会报错,因为它是在Scaffold的基础上显示的。所以它建议要新建一个Builder。所以前面的列表菜单按钮就重新Builder构建了一次。

    actions: <Widget>[
            new Builder(builder: (BuildContext context){
                return IconButton(
                  icon: const Icon(Icons.list), 
                  onPressed: () => this._navigatePush(context)  
                );
            })
          ],

页面跳转,数据传递基本就这样,跟原生的已经很类似了! 全部代码:https://github.com/fancylou/FlutterDemo/tree/2018-12-04

The End !