rafalbednarczuk / curved_navigation_bar

Animated Curved Navigation Bar in Flutter
BSD 2-Clause "Simplified" License
694 stars 242 forks source link

How change the bottom-bar-index on page change? #6

Closed LiveLikeCounter closed 5 years ago

LiveLikeCounter commented 5 years ago

Great job, looks great!

I just tried the CurvedNavigationBar. I stuck with one question/problem: I use Pageview to show the content. When you swipe to another page, I want to change the selected bottombar also, but there is only a initialIndex. How can I change the selected bottombaritem afterwards?

rafalbednarczuk commented 5 years ago

thanks, In order to do this it's necessary to add some kind of ScrollController to widget. It's important feature

rafalbednarczuk commented 5 years ago

fixed in https://github.com/rafalbednarczuk/curved_navigation_bar/commit/a55b81913ffbfcee9aa5c08465425b6847485e0f

stargazing-dino commented 5 years ago

Hi, sorry, dumb question but is this how one would make it swipable? I mean, the bottom bar successfully changes index if someone swipes but it also seems like it significantly lags behind the swipe action. Not sure if it's just the emulator though or I'm messing up with the addListener or something else.

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

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

class _HomePageState extends State<HomePage>
    with SingleTickerProviderStateMixin {
  final List<Widget> _tabs = <Widget>[
    /* tabs */
  ];

  TabController _controller;
  int _currentIndex = 0;

  @override
  void initState() {
    super.initState();
    _controller = TabController(
      vsync: this,
      length: _tabs.length,
      initialIndex: 0,
    );
    _controller.addListener(() {
      setState(() {
        _currentIndex = _controller.index;
      });
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      extendBody: true,
      body: TabBarView(
          controller: _controller,
          children: _tabs.map<Widget>((Widget tab) {
            return SafeArea(
              top: false,
              bottom: false,
              child: Container(
                key: ObjectKey(tab),
                child: tab,
              ),
            );
          }).toList()),
      bottomNavigationBar: CurvedNavigationBar(
        index: _currentIndex,
        items: <Widget>[
          /* icons */
        ],
        backgroundColor: Colors.transparent,
        animationCurve: Curves.easeInOut,
        animationDuration: Duration(milliseconds: 600),
        onTap: (index) {
          _controller.animateTo(index);
        },
      ),
    );
  }
}
rafalbednarczuk commented 5 years ago

It should work with _controller.jumpTo(index) instead of _controller.animateTo(index), but you won't get animation effect

stargazing-dino commented 5 years ago

I really like that animation though.

I've done a lot of searching and it doesn't look like there's a any advice on this. However, I think the tabs source code successfully does something like this. I am very new to Flutter and Dart so maybe this will make more sense to you @rafalbednarczuk but in their tabs they add some listeners like

if (_controller != null) {
  _controller.animation.addListener(_handleTabControllerAnimationTick);
  _controller.addListener(_handleTabControllerTick);
  _currentIndex = _controller.index;
}

And the listeners look like this:

void _handleTabControllerAnimationTick() {
  assert(mounted);
  if (!_controller.indexIsChanging && widget.isScrollable) {
    // Sync the TabBar's scroll position with the TabBarView's PageView.
    _currentIndex = _controller.index;
    _scrollToControllerValue();
  }
}

void _handleTabControllerTick() {
  if (_controller.index != _currentIndex) {
    _currentIndex = _controller.index;
    if (widget.isScrollable)
      _scrollToCurrentIndex();
  }
  setState(() {
    // Rebuild the tabs after a (potentially animated) index change
    // has completed.
  });
}

Maybe this isn't the whole picture but what do you make of it? I'll do some testing later too and let you know how it goes.

[Edit] Oh, now I know why this would be difficult. You'd need to animate the bottom bar based off the controller's value as they do it in their code and not based off any one event given off by a listener (because you can't preemptively change the bottombar's index until you know for sure they've swiped to the other screen.)

void _scrollToControllerValue() {
  final double leadingPosition = _currentIndex > 0 ? _tabCenteredScrollOffset(_currentIndex - 1) : null;
  final double middlePosition = _tabCenteredScrollOffset(_currentIndex);
  final double trailingPosition = _currentIndex < maxTabIndex ? _tabCenteredScrollOffset(_currentIndex + 1) : null;

  final double index = _controller.index.toDouble();
  final double value = _controller.animation.value;
  double offset;
  if (value == index - 1.0)
    offset = leadingPosition ?? middlePosition;
  else if (value == index + 1.0)
    offset = trailingPosition ?? middlePosition;
  else if (value == index)
    offset = middlePosition;
  else if (value < index)
    offset = leadingPosition == null ? middlePosition : lerpDouble(middlePosition, leadingPosition, index - value);
  else
    offset = trailingPosition == null ? middlePosition : lerpDouble(middlePosition, trailingPosition, value - index);

  _scrollController.jumpTo(offset);
}