Flutter it up [02]

The last post we talk about building a title bar, and this time, we focus on the content view.

Let's check out the app structure: it contains a title bar which we already had, a scrollable content area and a floating action button. We should first think about how to organize those things up.

01-2

First of all, Flutter provides a widget that implements the basic material design visual layout structure as it says named Scaffold. Its name is quite strange but it's indeed powerful. Since we use our custom title bar, we don't need its AppBar field. It has a floatingActionButton field so we can assign it with a new FloatingActionButton object:

floatingActionButton: new FloatingActionButton(
  onPressed: _onPressedFAB,
  tooltip: 'Search images',
  child: new Icon(Icons.search, color: Colors.white),
),

Just remember that Flutter has builtin material design icons saving us from finding them.

Currently we have _MainPageState like this:

class _MainPageState extends State<MainPage>
    with SingleTickerProviderStateMixin {
  int _selectedIndex = 1;

  void _onPressedFAB() {
  }

  void _handleOnClickTitle(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new Column(
        mainAxisSize: MainAxisSize.max,
        children: <Widget>[
          new TitleBar(_handleOnClickTitle, _selectedIndex),
        ],
      ),
      backgroundColor: Colors.black,
      floatingActionButton: new FloatingActionButton(
        onPressed: _onPressedFAB,
        tooltip: 'Search images',
        child: new Icon(Icons.search, color: Colors.white),
      ),
    );
  }
}

We stack the title bar and the content area in a Column widget, which is the body of Scaffold. Now we need a content area right next to the TitleBar, and it should take up all the leaving space in Column. In this case, a Expand widget is needed:

A widget that expands a child of a Row, Column, or Flex.

Inside the Expand, it should be ViewPager equivalent widget. In Flutter, it's TabBarView. By constructing TabBarView, we must assign its children of course and its controller.

What's a tab controller? Well, it coordinates tab selection between a TabBar and a TabBarView. The TabBarView normally works together with TabBar, and those widgets are connected by TabController. If you are using the two widget both, DefaultTabController is the best and simple choice.

However, again, we use our own title bar, thus, we have to manage the tab view and its tab controller on our own.

By reading the comments on TabController, we should know the typical way to deal with it: construct the controller in State's initState() method and dispose it dispose(), and while building the TabBarView widget, we assign the controller to it.


class _MainPageState extends State<MainPage>
    with SingleTickerProviderStateMixin {
  int _selectedIndex = 1;
  TabController _controller;

  @override
  void initState() {
    super.initState();
    _controller = new TabController(
        length: 3,
        vsync: this,
        initialIndex: _selectedIndex);
    _controller.addListener(() {
      _handleOnClickTitle(_controller.index);
    });
  }

  @override
  void dispose() {
    _controller?.dispose();
    super.dispose();
  }

  void _onPressedFAB() {
  }

  void _handleOnClickTitle(int index) {
    setState(() {
      _selectedIndex = index;
    });
    _controller?.animateTo(index);
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new Column(
        mainAxisSize: MainAxisSize.max,
        children: <Widget>[
          new TitleBar(_handleOnClickTitle, _selectedIndex),
          new Expanded(
              child: new TabBarView(
                controller: _controller,
                children: <Widget>[
                ],
              )
          )
        ],
      )
      backgroundColor: Colors.black,
      floatingActionButton: new FloatingActionButton(
        onPressed: _onPressedFAB,
        tooltip: 'Search images',
        child: new Icon(Icons.search, color: Colors.white),
      ),
    );
  }
}

You may have noticed the new mixin feature in dart:

class _MainPageState extends State<MainPage> with SingleTickerProviderStateMixin

I am not going to give a long talk about it since it's also new to me. In this case, you can understand by this: _MainPageState can use any methods defined in SingleTickerProviderStateMixin, and _MainPageState is also a TickerProvider since SingleTickerProviderStateMixin implements it. It's like SingleTickerProviderStateMixin is a companion to _MainPageState.

To know more about the mixin feature, refer to the official document.

NOTE: in this case the fact that we don't use DefaultTabController is for a reason. DefaultTabController is a stateful widget managing its own state. And of course it maintains a TabController inside, which means that we are not about to listen to the index changed of TabController unless this class provides to us, however it doesn't. Thus we create our own TabController and add listener to it:

_controller.addListener(() {
  //todo
});

Inside the listener, we should update the title bar by calling setState() method, the build(BuildContext context) method will be called in the future and the title bar's visual state will be up-to-date.

Above we talked about how to build a TabBarView, however, the children field in TabBarView is still empty. The next post we are about to build a ListView and send a http request to obtain images to display. Stay tone and come back soon to see hot it's done.