7

I have a PageView used with a BottomNavigationBar so that I can have swipeable and tappable tabs with a bottom bar rather than the normal navigation bar. Then I have two tabs/pages you can swipe between. One has a form with 4 UI elements and the other has no UI elements yet. Everything works fine but the performance of the PageView is very bad.

When I swipe between pages it is extremely slow and jumpy at first, definitely not the 60 frames per second promised by Flutter. Probably not even 30. After swiping several times though the performance gets better and better until its almost like a normal native app.

Below is my page class that includes the PageView, BottomNavigationBar, and logic connecting them. does anyone know how I can improve the performance of the PageView?

class _TabbarPageState extends State<TabbarPage> {

  int _index = 0;
  final controller = PageController(
    initialPage: 0,
    keepPage: true,
  );

  final _tabPages = <Widget>[
    StartPage(),
    OtherPage(),
  ];

  final _tabs = <BottomNavigationBarItem>[
    BottomNavigationBarItem(
      icon: Icon(Icons.play_arrow),
      title: Text('Start'),
    ),
    BottomNavigationBarItem(
      icon: Icon(Icons.accessibility_new),
      title: Text('Other'),
    )
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: PageView(
        controller: controller,
        children: _tabPages,
        onPageChanged: _onPageChanged,
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: _tabs,
        onTap: _onTabTapped,
        currentIndex: _index,
      ),
      floatingActionButton: _index != 1
          ? null
          : FloatingActionButton(
              onPressed: () {},
              tooltip: 'Test',
              child: Icon(Icons.add),
            ),
    );
  }

  void _onTabTapped(int index) {
    controller.animateToPage(
      index,
      duration: Duration(milliseconds: 300),
      curve: Curves.ease,
    );
    setState(() {
      _index = index;
    });
  }

  void _onPageChanged(int index) {
    setState(() {
      _index = index;
    });
  }
}
Graham
  • 5,488
  • 13
  • 57
  • 92
  • 3
    Ensure you performance test with profile or release builds only. Evaluating performance with debug builds is completely meaningless. – Günter Zöchbauer Jan 16 '19 at 06:15
  • 1
    Yup, should have run it in profile mode first. Performance is flawless in that mode and release mode. Thanks for the suggestion. If you would like to post running it in profile or release mode as an answer I'll mark it as accepted. – Graham Jan 16 '19 at 19:31

4 Answers4

3

Ensure you performance test with profile or release builds only. Evaluating performance with debug builds is completely meaningless.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
3

Sorry but Günter's answer didn't helped me! You have to set physics: AlwaysScrollableScrollPhysics() And your performance increases.

Worked for me

Rebar
  • 1,076
  • 10
  • 19
1

Try this hack, Apply viewportFraction to your controller with value 0.99(it can be 0.999 or 0.9999 hit and try until you get desired result)

final controller = PageController(
viewportFraction: 0.99 );
0

I am new to flutter, please tell me if this is wrong.

Have the same problem, here is my effort. Wors for me.

class HomePage extends StatefulWidget {
  @override
  State createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  var _currentIndex = 1;
  var _pageController = PageController(initialPage: 1);
  var _todoPage, _inProgressPage, _donePage, _userPage;

  @override
  void initState() {
    this._currentIndex = 1;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: PageView.builder(
        controller: this._pageController,
        onPageChanged: (index) {
          setState(() {
            this._currentIndex = index.clamp(0, 3);
          });
        },
        itemCount: 4,
        itemBuilder: (context, index) {
          if (index == 0) return this.todoPage();
          if (index == 1) return this.inProgressPage();
          if (index == 2) return this.donePage();
          if (index == 3) return this.userPage();
          return null;
        },
      ),
      bottomNavigationBar: buildBottomNavigationBar(),
    );
  }

  Widget buildBottomNavigationBar() {
    return BottomNavigationBar(
      showUnselectedLabels: false,
      items: [
        BottomNavigationBarItem(title: Text("待办"), icon: Icon(Icons.assignment)),
        BottomNavigationBarItem(title: Text("进行"), icon: Icon(Icons.blur_on)),
        BottomNavigationBarItem(title: Text("完成"), icon: Icon(Icons.date_range)),
        BottomNavigationBarItem(title: Text("我的"), icon: Icon(Icons.account_circle)),
      ],
      currentIndex: this._currentIndex,
      onTap: (index) {
        setState(() {
          this._currentIndex = index.clamp(0, 3);
        });
        _pageController.jumpToPage(this._currentIndex);
      },
    );
  }

  Widget todoPage() {
    if (this._todoPage == null) this._todoPage = TodoPage();
    return this._todoPage;
  }

  Widget inProgressPage() {
    if (this._inProgressPage == null) this._inProgressPage = InProgressPage();
    return this._inProgressPage;
  }

  Widget donePage() {
    if (this._donePage == null) this._donePage = DonePage();
    return this._donePage;
  }

  Widget userPage() {
    if (this._userPage == null) this._userPage = UserPage();
    return this._userPage;
  }
}

I just cache the pages that pageview hold. this REALLY smooth my pageview a lot, like native. but would prevent hotreload (ref: How to deal with unwanted widget build?),.

  • Did this actually work out in the end or was it just a temporary fix? I'm assuming it'll be a hassle to post updates on cached pages. – Badr B Jul 30 '20 at 11:39