Flutter - изменение AppBar со страницы

Итак, у меня есть приложение Flutter с несколькими страницами, это делается через PageView. Перед просмотром этой страницы я создаю свой AppBar, чтобы он постоянно находился в верхней части приложения и не анимировался при прокрутке между страницами. Затем я хочу на одной из страниц создать нижнюю панель приложения, но для этого мне нужно получить доступ к элементу панели приложений, однако я понятия не имею, как это сделать.

Это основной класс, страница, на которой я пытаюсь редактировать панель приложения, - это PlanPage.

final GoogleSignIn googleSignIn = GoogleSignIn();
final FirebaseAuth auth = FirebaseAuth.instance;

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
    @override
    Widget build(BuildContext context) {
        return MaterialApp(
            title: '',
            home: _handleCurrentScreen()
        );
    }

    Widget _handleCurrentScreen() {
        return StreamBuilder<FirebaseUser>(
            stream: auth.onAuthStateChanged,
            builder: (BuildContext context, snapshot) {
                print(snapshot);
                if (snapshot.connectionState == ConnectionState.waiting) {
                    return SplashPage();
                } else {
                    if (snapshot.hasData) {
                        return Home();
                    }
                    return LoginPage();
                }
            }
        );
    }
}

class Home extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
      return HomeState();
  }
}

class HomeState extends State<Home> {
    PageController _pageController;

    PreferredSizeWidget bottomBar;

    int _page = 0;


  @override
  Widget build(BuildContext context) {
      return Scaffold(
          appBar: AppBar(
              bottom: bottomBar,
          ),
          body: PageView(
              children: [
                  Container(
                      child: SafeArea(
                          child: RecipesPage()
                      ),
                  ),
                  Container(
                      child: SafeArea(
                          child: PlanPage()
                      ),
                  ),
                  Container(
                      child: SafeArea(
                          child: ShoppingListPage()
                      ),
                  ),
                  Container(
                      child: SafeArea(
                          child: ExplorePage()
                      ),
                  ),
              ],

              /// Specify the page controller
              controller: _pageController,
              onPageChanged: onPageChanged
          ),
          bottomNavigationBar: BottomNavigationBar(
              type: BottomNavigationBarType.fixed,
              items: [
                  BottomNavigationBarItem(
                      icon: Icon(Icons.book),
                      title: Text('Recipes')
                  ),
                  BottomNavigationBarItem(
                      icon: Icon(Icons.event),
                      title: Text('Plan')
                  ),
                  BottomNavigationBarItem(
                      icon: Icon(Icons.shopping_cart),
                      title: Text('Shopping List')
                  ),
                  BottomNavigationBarItem(
                      icon: Icon(Icons.public),
                      title: Text("Explore"),
                  ),
              ],
              onTap: navigationTapped,
              currentIndex: _page,
          ),

      );
  }

    void onPageChanged(int page){
        setState((){
            this._page = page;
        });
    }

    void setBottomAppBar(PreferredSizeWidget appBar) {
        this.bottomBar = appBar;
        print("setBottomAppBar: "+ appBar.toString());
    }

    /// Called when the user presses on of the
    /// [BottomNavigationBarItem] with corresponding
    /// page index
    void navigationTapped(int page){

        // Animating to the page.
        // You can use whatever duration and curve you like
        _pageController.animateToPage(
            page,
            duration: const Duration(milliseconds: 300),
            curve: Curves.ease
        );
    }

    @override
    void initState() {
        super.initState();
        initializeDateFormatting();

        _pageController = PageController();
    }

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

Класс PlanPage выглядит так

class PlanPage extends StatefulWidget {
    var homeState;

    PlanPage(this.homeState);

    @override
    State<StatefulWidget> createState() {
        return _PlanState(homeState);
    }

}

class _PlanState extends State<PlanPage> with AutomaticKeepAliveClientMixin<PlanPage>, SingleTickerProviderStateMixin {
    var homeState;
    TabController _tabController;

    _PlanState(this.homeState);

    @override
    bool get wantKeepAlive => true;

    @override
    Widget build(BuildContext context) {
        //homeState.setBottomAppBar(_buildTabBar());

        return Scaffold(
            appBar: AppBar(
                bottom: _buildTabBar(),
            ),
            body: TabBarView(
                controller: _tabController,
                children: Plan.now().days.map((day) {
                    return ListView.builder(
                        itemCount: MealType.values.length,
                        itemBuilder: (BuildContext context, int index){
                            var mealType = MealType.values[index];
                            return Column(
                                children: <Widget>[
                                    Text(
                                        mealType.toString().substring(mealType.toString().indexOf('.')+1),
                                        style: TextStyle(
                                            //decoration: TextDecoration.underline,
                                            fontSize: 30.0,
                                            fontWeight: FontWeight.bold
                                        ),
                                    ),
                                    Column(
                                        children: day.meals.where((meal) => meal.mealType == mealType).map((meal) {
                                            return RecipeCard(meal.recipe);
                                        }).toList(),
                                    )

                                ],
                            );
                        }
                    );
                }).toList(),
            )
        );
    }

    Widget _buildTabBar() {
        return TabBar(
            controller: _tabController,
            isScrollable: true,
            tabs: List.generate(Plan.now().days.length,(index) {
                return Tab(
                    child: Column(
                        children: <Widget>[
                            Text(DateFormat.E().format(Plan.now().days[index].day)),
                            Text(DateFormat('d/M').format(Plan.now().days[index].day)),
                        ],
                    ),
                );
            }, growable: true),
        );
    }

    @override
    void initState() {
        super.initState();

        _tabController = new TabController(
            length: Plan.now().days.length,
            vsync: this,
            initialIndex: 1
        );
    }
}

Однако, как это работает сейчас, он показывает 2 панели приложений. [1


person Oliver Nybroe    schedule 24.11.2018    source источник


Ответы (1)


Обычно не рекомендуется иметь две вложенные области с прокруткой. То же самое для двух вложенных лесов.

Тем не менее, вы можете прослушивать изменения страницы (_pageController.addListener(listener)), чтобы обновить свойство состояния page, и построить другое AppBar.bottom (в виджете Home, поэтому вы можете удалить Scaffold в PlanPage) в зависимости от страницы, которую просматривает пользователь.

-РЕДАКТИРОВАТЬ-

В своем виджете Home вы можете добавить слушателя к _pageController следующим образом:

void initState() {
    super.initState();
    _pageController = PageController()
        ..addListener(() {
            setState(() {});
        });
}

чтобы ваш виджет перестраивался каждый раз, когда пользователь прокручивает ваш PageView. Вызов setState с пустой функцией может показаться запутанным, но он просто позволяет вам восстановить виджет при _pageController.page изменении, что не является поведением по умолчанию. Вы также можете иметь свойство состояния page и обновлять его в вызове setState, чтобы отразить свойство _pageController.page, но результат будет таким же.

Таким образом, вы можете построить разные AppBar.bottom в зависимости от _pageController.page:

// in your build function

final bottomAppBar = _pageController.page == 2 ? TabBar(...) : null;

final appBar = AppBar(
    bottom: bottomAppBar,
    ...
);

return Scaffold(
    appBar: appBar,
    ...
);
person Dario Ielardi    schedule 24.11.2018
comment
Я не могу заставить его работать tbh. Я не могу понять, где мне создать свой TabController, чтобы я мог передать его AppBar.bottom. - person Oliver Nybroe; 25.11.2018
comment
Вы можете просто переместить его в виджет Home и передать его PlanPage в качестве параметра конструктора. - person Dario Ielardi; 25.11.2018
comment
Не кажется таким элегантным иметь всю эту логику в моем виджете Home, когда эта логика специфична для PlanPage, также для другой страницы потребуется Tabcontroller. Так что не имеет значения, что Tabcontroller находится в другом объекте состояния, а не в том, кто его фактически использует? - person Oliver Nybroe; 25.11.2018
comment
Нет, подождите, вам нужно отображать разные AppBar.bottom в зависимости от того, какую страницу из PageView в виджете Home пользователь правильно просматривает? Таким образом, в виджете Home у вас будет pageController со слушателем, а tabController останется в PlanPage. - person Dario Ielardi; 25.11.2018