Так что в Интернете есть много примеров, где вы можете использовать SliverAppBar
, который скрывается при прокрутке, а TabBar
ниже все еще отображается. Я не могу найти ничего, что делало бы это наоборот: когда я прокручиваю Я хочу скрыть только TabBar
вверх, постоянное отображение AppBar
постоянно отображается. Кто-нибудь знает, как этого добиться?
Вот пример со скрытым AppBar (Это не то, что я хочу, просто помогает лучше понять, чего я хочу).
ОБНОВИТЬ
Это то, что я пробовал до сих пор, и я думал, что это работает, но проблема в том, что я не могу получить AppBar
в поле Positioned
, чтобы иметь правильную высоту (например, iPhone X, его высота намного больше и перекрывается с панелью вкладок) .
// this sliver app bar is only use to hide/show the tabBar, the AppBar
// is invisible at all times. The to the user visible AppBar is below
return Scaffold(
body: Stack(
children: <Widget>[
NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
floating: true,
snap: true,
pinned: false,
bottom: TabBar(
tabs: [
Tab(
child: Text(
"1",
textAlign: TextAlign.center,
),
),
Tab(
child: Text(
"2",
textAlign: TextAlign.center,
),
),
Tab(
child: Text(
"3",
textAlign: TextAlign.center,
),
),
],
controller: _tabController,
),
),
];
},
body: TabBarView(
children: [
MyScreen1(),
MyScreen2(),
MyScreen3(),
],
controller: _tabController,
physics: new NeverScrollableScrollPhysics(),
),
),
// Here is the AppBar the user actually sees. The SliverAppBar
// above will slide the TabBar underneath this one. However,
// I can´t figure out how to give it the correct height.
Container(
child: Positioned(
top: 0.0,
left: 0.0,
right: 0.0,
child: AppBar(
iconTheme: IconThemeData(
color: Colors.red, //change your color here
),
automaticallyImplyLeading: true,
elevation: 0,
title: Text("My Title"),
centerTitle: true,
),
),
),
],
),
);
Здесь - это то, как вы можете это сделать, идея в том использовать postframecallback
с помощью GlobalKey
для предварительного расчета appBar height
и добавить exapandedHeight
, как показано ниже,
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
TabController _tabController;
GlobalKey _appBarKey;
double _appBarHight;
@override
void initState() {
_appBarKey = GlobalKey();
_tabController = TabController(length: 3, vsync: this);
SchedulerBinding.instance.addPostFrameCallback(_calculateAppBarHeight);
super.initState();
}
_calculateAppBarHeight(_){
final RenderBox renderBoxRed = _appBarKey.currentContext.findRenderObject();
setState(() {
_appBarHight = renderBoxRed.size.height;
});
print("AppbarHieght = $_appBarHight");
}
@override
Widget build(BuildContext context) {
// this sliver app bar is only use to hide/show the tabBar, the AppBar
// is invisible at all times. The to the user visible AppBar is below
return Scaffold(
body: Stack(
children: <Widget>[
NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
floating: true,
expandedHeight: _appBarHight,
snap: true,
pinned: false,
bottom: TabBar(
tabs: [
Tab(
child: Text(
"1",
textAlign: TextAlign.center,
),
),
Tab(
child: Text(
"2",
textAlign: TextAlign.center,
),
),
Tab(
child: Text(
"3",
textAlign: TextAlign.center,
),
),
],
controller: _tabController,
),
),
];
},
body: TabBarView(
children: [
MyScreen1(),
MyScreen2(),
MyScreen3(),
],
controller: _tabController,
physics: new NeverScrollableScrollPhysics(),
),
),
// Here is the AppBar the user actually sees. The SliverAppBar
// above will slide the TabBar underneath this one. However,
// I can¥t figure out how to give it the correct height.
Container(
key: _appBarKey,
child: Positioned(
top: 0.0,
left: 0.0,
right: 0.0,
child: AppBar(
backgroundColor: Colors.red,
iconTheme: IconThemeData(
color: Colors.red, //change your color here
),
automaticallyImplyLeading: true,
elevation: 0,
title: Text("My Title"),
centerTitle: true,
),
),
),
],
),
);
}
}
class MyScreen1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Text("My Screen 1"),
),
);
}
}
class MyScreen2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Text("My Screen 2"),
),
);
}
}
class MyScreen3 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Text("My Screen 3"),
),
);
}
}
Редактировать:
После больше расследования я нашел решение без ключей или «вещей» MediaQuery, используя только виджет SafeArea
. проверьте следующий полный код:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
TabController _tabController;
@override
void initState() {
_tabController = TabController(length: 3, vsync: this);
super.initState();
}
@override
Widget build(BuildContext context) {
// this sliver app bar is only use to hide/show the tabBar, the AppBar
// is invisible at all times. The to the user visible AppBar is below
return Scaffold(
body: Stack(
children: <Widget>[
NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
primary: true,
floating: true,
backgroundColor: Colors.blue,//.withOpacity(0.3),
snap: true,
pinned: false,
bottom: TabBar(
tabs: [
Tab(
child: Text(
"1",
textAlign: TextAlign.center,
),
),
Tab(
child: Text(
"2",
textAlign: TextAlign.center,
),
),
Tab(
child: Text(
"3",
textAlign: TextAlign.center,
),
),
],
controller: _tabController,
),
),
];
},
body: TabBarView(
children: [
MyScreen1(),
MyScreen2(),
MyScreen3(),
],
controller: _tabController,
physics: new NeverScrollableScrollPhysics(),
),
),
// Here is the AppBar the user actually sees. The SliverAppBar
// above will slide the TabBar underneath this one.
// by using SafeArea it will.
Positioned(
top: 0.0,
left: 0.0,
right: 0.0,
child: Container(
child: SafeArea(
top: false,
child: AppBar(
backgroundColor: Colors.blue,
// iconTheme: IconThemeData(
// color: Colors.red, //change your color here
// ),
automaticallyImplyLeading: true,
elevation: 0,
title: Text("My Title",),
centerTitle: true,
),
),
),
),
],
),
);
}
}
class MyScreen1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
color: Colors.yellow,
child: Center(
child: Text("My Screen 1"),
),
);
}
}
class MyScreen2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Text("My Screen 2"),
),
);
}
}
class MyScreen3 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Text("My Screen 3"),
),
);
}
}
Я думаю, что это довольно легко использовать вложенные леса. где вам не нужно вычислять любую высоту. Просто поместите вкладку в SilverAppBar не ниже SilverAppBar.
не стесняйтесь комментировать, если это не решит вашу проблему.
Пример:
return Scaffold(
appBar: AppBar(), //your appbar that doesnt need to hide
body: Scaffold(
appBar: SilverAppBar(
pinned: false,
floating: false,
flexibleSpace: new Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
new TabBar() //your tabbar that need to hide when scrolling
])
)
body: //your content goes here
)
);
ты сам пробовал? Выдает ошибку The argument type 'SliverAppBar' can't be assigned to the parameter type 'PreferredSizeWidget?'.
Вы были очень близки, я только что изменил пару строк. Я сделал это без использования GlobalKey
и прочего (postFrameCallback
и т.д.). Это очень простой и понятный подход.
Все, что вам нужно сделать, это заменить FlutterLogo
своими собственными виджетами, а именно MyScreen1
, MyScreen2
и MyScreen3
.
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
floating: true,
snap: true,
pinned: true,
bottom: PreferredSize(
preferredSize: Size(0, kToolbarHeight),
child: TabBar(
controller: _tabController,
tabs: [
Tab(child: Text("1")),
Tab(child: Text("2")),
Tab(child: Text("3")),
],
),
),
),
];
},
body: TabBarView(
controller: _tabController,
children: [
FlutterLogo(size: 300, colors: Colors.blue), // use MyScreen1()
FlutterLogo(size: 300, colors: Colors.orange), // use MyScreen2()
FlutterLogo(size: 300, colors: Colors.red), // use MyScreen3()
],
physics: NeverScrollableScrollPhysics(),
),
),
Positioned(
top: 0.0,
left: 0.0,
right: 0.0,
child: MediaQuery.removePadding(
context: context,
removeBottom: true,
child: AppBar(
iconTheme: IconThemeData(color: Colors.red),
automaticallyImplyLeading: true,
elevation: 0,
title: Text("My Title"),
centerTitle: true,
),
),
),
],
),
);
}
}
Хорошая работа :) но, если мы знаем, что высота панели приложения равна 56, то с самого начала проблем не возникает. если вы попробуете мое решение, вы обнаружите, что высота в iPhone X составляет 134, а не 56, отсюда и вопрос: я думаю, что однажды у нас была похожая дискуссия!
Рад видеть вас снова, ну, я не пробовал ваше решение, но, насколько я могу судить, высота в iPhone X не будет 134
, а будет 100
(44
для строки состояния + 56
для панели навигации), но не уверен, какой рост вы имеете в виду, когда говорите 134
,
Я тоже, ваше решение действительно только в идеальном мире :), iPhone X имеет другие размеры дизайна, и 56 намного короче для панели приложений, чем должно, проверьте этот вопрос stackoverflow.com/questions/46197660/…
Я не уверен по термину pt
там, я пришел из Android, где у нас есть dp
, (не уверен, что это то, что pt
в iOS), но сейчас я могу проверить в iPhone XR (и iPhone X не должен отличаться ) строка состояния имеет высоту 44
логических пикселей, а панель навигации имеет высоту 56
логических пикселей, что делает ее 100
@SaedNabil Я запустил тот же код в iPhone X, и кажется, что ваше так называемое «решение для идеального мира» сработало и здесь безупречно с 56
.
пожалуйста, сравните эти два изображения image1 imgur.com/CNZjfSG image two imgur.com/w2Iey55 вы можете заметить разницу в высоте панели приложений, красная панель приложений является стандартной высотой для iPhone X, и я знаю, что это работает, но проблема в том, что это не стандартная высота, как я объяснял ранее .
Да, вы правы, разница есть, и я не тестировал его на iOS, когда писал код, я запускал его на Android (скриншот которого я загрузил первым), но опять же, вам не нужно иметь дело с 134
, Я сделал тот же эффект с 100
в обновленном коде, и теперь он работает и для iOS.
Я вообще не имею дело с какими-либо константами, ни 134, ни 56, код динамически вычисляет высоту и адаптируется к любым размерам устройства, я проверил ваш код, и, похоже, ничего не изменилось в отношении высоты, пожалуйста, проверьте это изображение imgur.com/ciXHjC7
Даже я больше не играл с 56
, я использовал kToolbarHeight
(надеюсь, вы не будете указывать на это сейчас), и скриншот, который вы прислали, такой же, как и при использовании appBar: AppBar()
, так что в этом нет ничего плохого.
Это было очень интересно :), я думаю, что нашел правильное решение, пожалуйста, проверьте раздел «Правка» моего ответа.
Я в отпуске пару дней и не могу понять ни одной части кода (использую телефон для комментариев), если вам понравилось мое решение, я буду ждать +1 с вашей стороны :)
Извините, что долго не отвечал. Тем временем я пришел к точно такому же решению: SafeArea решает все мои проблемы. Молодец!