跨PageView的持久性应用栏颤抖

时间:2018-06-29 05:11:37

标签: flutter

理想情况下,我想按以下步骤设置Flutter应用程序

  • PageView可在3页和底部导航栏之间向左/向右滑动,以用作标签并帮助导航
  • 永久性应用栏位于顶部,带有抽屉和上下文图标
  • 介于两者之间的页面内容

从图片中可以看出,我主要按照以下方式设置了自己的方式

main.dart - app entry point, set up appbar, set up pageview with children for new PeoplePage, new TimelinePage, new StatsPage

people_page.dart
timeline_page.dart
stats_page.dart

These three pages just deliver the content to the PageView children as required.

这是实现此目标的正确方法吗?在表面上它工作正常。我遇到的问题是,我要在人员页面上实现一个可选择的列表,以更改应用程序栏标题/颜色as in this example,但是该应用程序栏是在主页上设置的。我可以全局访问应用栏吗?

我可以为每个页面构建一个新的应用程序栏,但是我不希望在切换页面时刷入一个新的应用程序栏。我希望应用程序栏看起来持久,并且只刷入内容。

对于实现此目标的最佳方法的任何指导将不胜感激。

sample

2 个答案:

答案 0 :(得分:5)

我整理了一个简单的示例,说明您如何从屏幕向下浏览到页面,然后又返回。这应该可以解决您的问题。

https://gist.github.com/slightfoot/464fc225b9041c2d66ec8ab36fbdb935

import 'package:flutter/material.dart';

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

class TestApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primaryColor: Colors.green[900],
        scaffoldBackgroundColor: Colors.grey[200],
      ),
      home: MainScreen(),
    );
  }
}

class AppBarParams {
  final Widget title;
  final List<Widget> actions;
  final Color backgroundColor;

  AppBarParams({
    this.title,
    this.actions,
    this.backgroundColor,
  });
} 

class MainScreen extends StatefulWidget {
  final int initialPage;

  const MainScreen({
    Key key,
    this.initialPage = 0,
  }) : super(key: key);

  @override
  MainScreenState createState() => MainScreenState();

  static MainScreenState of(BuildContext context) {
    return context.ancestorStateOfType(TypeMatcher<MainScreenState>());
  }
}

class MainScreenState extends State<MainScreen> {
  final List<GlobalKey<MainPageStateMixin>> _pageKeys = [
    GlobalKey(),
    GlobalKey(),
    GlobalKey(),
  ];

  PageController _pageController;
  AppBarParams _params;
  int _page;

  set params(AppBarParams value) {
    setState(() => _params = value);
  }

  @override
  void initState() {
    super.initState();
    _page = widget.initialPage ?? 0;
    _pageController = PageController(initialPage: _page);
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _pageKeys[0].currentState.onPageVisible();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: _params?.title,
        actions: _params?.actions,
        backgroundColor: _params?.backgroundColor,
      ),
      body: PageView(
        controller: _pageController,
        onPageChanged: _onPageChanged,
        children: <Widget>[
          PeoplePage(key: _pageKeys[0]),
          TimelinePage(key: _pageKeys[1]),
          StatsPage(key: _pageKeys[2]),
        ],
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _page,
        onTap: _onBottomNavItemPressed,
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            title: Text('people'),
            icon: Icon(Icons.people),
          ),
          BottomNavigationBarItem(
            title: Text('timeline'),
            icon: Icon(Icons.history),
          ),
          BottomNavigationBarItem(
            title: Text('stats'),
            icon: Icon(Icons.pie_chart),
          ),
        ],
      ),
    );
  }

  @override
  void reassemble() {
    super.reassemble();
    _onPageChanged(_page);
  }

  void _onPageChanged(int page) {
    setState(() => _page = page);
    _pageKeys[_page].currentState.onPageVisible();
  }

  void _onBottomNavItemPressed(int index) {
    setState(() => _page = index);
    _pageController.animateToPage(
      index,
      duration: Duration(milliseconds: 400),
      curve: Curves.fastOutSlowIn,
    );
  }
}

abstract class MainPageStateMixin<T extends StatefulWidget> extends State<T> {
  void onPageVisible();
}

class PeoplePage extends StatefulWidget {
  const PeoplePage({Key key}) : super(key: key);

  @override
  PeoplePageState createState() => PeoplePageState();
}

class PeoplePageState extends State<PeoplePage> with MainPageStateMixin {
  final List<Color> _colors = [
    Colors.orange,
    Colors.purple,
    Colors.green,
  ];

  int _personCount = 3;

  @override
  void onPageVisible() {
    MainScreen.of(context).params = AppBarParams(
      title: Text('People'),
      actions: <Widget>[
        IconButton(
          icon: Icon(Icons.person_add),
          onPressed: () => setState(() => _personCount++),
        ),
      ],
      backgroundColor: Colors.green,
    );
  }

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: _personCount,
      itemBuilder: (BuildContext context, int index) {
        return Card(
          child: InkWell(
            onTap: () => _onTapCard(index),
            child: Padding(
              padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
              child: Row(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                  Material(
                    type: MaterialType.circle,
                    color: _colors[index % _colors.length],
                    child: Container(
                      width: 48.0,
                      height: 48.0,
                      alignment: Alignment.center,
                      child: Text('$index', style: TextStyle(color: Colors.white)),
                    ),
                  ),
                  SizedBox(width: 16.0),
                  Text(
                    'Item #$index',
                    style: TextStyle(
                      color: Colors.grey[600],
                      fontSize: 18.0,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      },
    );
  }

  void _onTapCard(int index) {
    Scaffold.of(context).showSnackBar(SnackBar(content: Text('Item #$index')));
  }
}

class TimelinePage extends StatefulWidget {
  const TimelinePage({Key key}) : super(key: key);

  @override
  TimelinePageState createState() => TimelinePageState();
}

class TimelinePageState extends State<TimelinePage> with MainPageStateMixin {
  @override
  void onPageVisible() {
    MainScreen.of(context).params = AppBarParams(
      title: Text('Timeline'),
      actions: <Widget>[
        IconButton(
          icon: Icon(Icons.alarm_add),
          onPressed: () {},
        ),
      ],
      backgroundColor: Colors.purple,
    );
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('Coming soon'),
    );
  }
}

class StatsPage extends StatefulWidget {
  const StatsPage({Key key}) : super(key: key);

  @override
  StatsPageState createState() => StatsPageState();
}

class StatsPageState extends State<StatsPage> with MainPageStateMixin {
  @override
  void onPageVisible() {
    MainScreen.of(context).params = AppBarParams(
      title: Text('Stats'),
      actions: <Widget>[
        IconButton(
          icon: Icon(Icons.add_box),
          onPressed: () {},
        ),
      ],
      backgroundColor: Colors.orange,
    );
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('Coming soon'),
    );
  }
}

答案 1 :(得分:2)

解决此问题的一种方法是将AppBar标题和背景色作为状态变量,然后在PageView中将onPageChanged设置为一个函数。此函数接受page int,并根据page int将标题和颜色的状态设置为所需的值。对于多选列表,可以将标题设置为保留所选值的变量,也可以将其作为状态变量保留在主页中,然后将其传递给子组件。您可以使用任何状态管理策略,并且应该可以正常工作。

onPageChanged函数示例:

void onPageChanged(int page) {
    String _temptitle = "";
    Color _tempColor;
    switch (page) {
      case 0:
        _temptitle = "People";
        _tempColor = Colors.pink;
        break;
      case 1:
        _temptitle = "Timeline";
        _tempColor = Colors.green;
        break;
      case 2:
        _temptitle = "Stats";
        _tempColor = Colors.deepPurple;
        break;
    }
    setState(() {
      this._page = page;
      this._title = _temptitle;
      this._appBarColor = _tempColor;
    });
  }

因此对于多选情况,无需将标题设置为某个常数,而是将标题设置为保存所选选项值的变量。

Sample here

完整代码在这里:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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> {
  PageController _pageController;
  int _page = 0;
  String _title = "MyApp";
  Color _appBarColor = Colors.pink;

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(_title),
        backgroundColor: _appBarColor,
      ),
      body: PageView(
        children: <Widget>[
          Container(
            child: Center(child: Text("People")),
          ),
          Container(
            child: Center(child: Text("Timeline")),
          ),
          Container(
            child: Center(child: Text("Stats")),
          ),
        ],
        controller: _pageController,
        onPageChanged: onPageChanged,
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.people),
            title: Text("People"),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.access_time),
            title: Text("Timeline"),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.pie_chart),
            title: Text("Stats"),
          ),
        ],
        onTap: navigateToPage,
        currentIndex: _page,
      ),
    );
  }

  void navigateToPage(int page) {
    _pageController.animateToPage(page,
        duration: Duration(milliseconds: 300), curve: Curves.ease);
  }

  void onPageChanged(int page) {
    String _temptitle = "";
    Color _tempColor;
    switch (page) {
      case 0:
        _temptitle = "People";
        _tempColor = Colors.pink;
        break;
      case 1:
        _temptitle = "Timeline";
        _tempColor = Colors.green;
        break;
      case 2:
        _temptitle = "Stats";
        _tempColor = Colors.deepPurple;
        break;
    }
    setState(() {
      this._page = page;
      this._title = _temptitle;
      this._appBarColor = _tempColor;
    });
  }

  @override
  void initState() {
    super.initState();
    _pageController = new PageController();
    _title = "People";
  }

  @override
  void dispose() {
    super.dispose();
    _pageController.dispose();
  }
}

您可以根据需要改进此代码。希望这对您有所帮助。让我知道我是否可以改善此答案。