Как реализовать SliverAppBar с помощью TabBar
Флаттер документ показывает демо для SliverAppBar
+ TabBar
+ TabBarView with ListView
использование NestedScrollView
и это немного сложно, поэтому мне интересно, есть ли простой и понятный способ реализовать это. Я попробовал это:
CustomScrollView
slivers:
SliverAPPBar
bottom: TabBar
TabBarView
children: MyWidget(list or plain widget)
получил ошибку:
flutter: было выброшено следующее утверждение при построении Scrollable(axisDirection: right, физика:
flutter: RenderViewport ожидал дочернего типа RenderSliver, но получил дочерний тип _RenderExcludableScrollSemantics.
flutter: RenderObjects ожидает определенных типов дочерних элементов, потому что они координируют свои действия с дочерними элементами во время разметки и рисования. Например, RenderSliver не может быть дочерним элементом RenderBox, потому что RenderSliver не понимает протокол компоновки RenderBox.
а также
flutter: было сгенерировано другое исключение: 'package:flutter/src/widgets/framework.dart': ошибочное утверждение: строка 3497, позиция 14: 'owner._debugCurrentBuildTarget == this': не соответствует действительности.
ЗДЕСЬ МОЙ КОД:
import 'package:flutter/material.dart';
main(List<String> args) {
runApp(MyScrollTabListApp());
}
class MyScrollTabListApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(title: "aa", home: MyScrollTabListHomePage());
}
}
class MyScrollTabListHomePage extends StatefulWidget {
@override
MyScrollTabListHomePageState createState() {
return new MyScrollTabListHomePageState();
}
}
class MyScrollTabListHomePageState extends State<MyScrollTabListHomePage>
with SingleTickerProviderStateMixin {
final int _listItemCount = 300;
final int _tabCount = 8;
TabController _tabController;
@override
void initState() {
_tabController = TabController(length: _tabCount, vsync: this);
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
expandedHeight: 240.0,
title: Text("Title"),
pinned: true,
bottom: TabBar(
controller: _tabController,
isScrollable: true,
tabs: List<Tab>.generate(_tabCount, (int i) {
return Tab(text: "TAB$i");
}),
),
),
TabBarView(
controller: _tabController,
children: List<Widget>.generate(_tabCount, (int i) {
return Text('line $i');
}),
),
],
),
);
}
}
и для официального демо, он использует структуру, как это
DefaultTabController
NestedScrollView
headerSliverBuilder
SliverOverlapAbsorber
handle
SliverAppBar
TabBarView
CustomScrollView
SliverOverlapInjector
handle
SliverPadding
6 ответов
Использование NestedScrollView
Вот рабочий код.
@override
Widget build(BuildContext context) {
return Scaffold(
body: DefaultTabController(
length: 2,
child: NestedScrollView(
headerSliverBuilder: (context, value) {
return [
SliverAppBar(
bottom: TabBar(
tabs: [
Tab(icon: Icon(Icons.call), text: "Call"),
Tab(icon: Icon(Icons.message), text: "Message"),
],
),
),
];
},
body: TabBarView(
children: [
CallPage(),
MessagePage(),
],
),
),
),
);
}
Вот пример для TabView с SilverAppBar
class SilverAppBarWithTabBarScreen extends StatefulWidget {
@override
_SilverAppBarWithTabBarState createState() => _SilverAppBarWithTabBarState();
}
class _SilverAppBarWithTabBarState extends State<SilverAppBarWithTabBarScreen>
with SingleTickerProviderStateMixin {
TabController controller;
@override
void initState() {
super.initState();
controller = new TabController(length: 3, vsync: this);
}
@override
Widget build(BuildContext context) {
return new Scaffold(
body: new CustomScrollView(
slivers: <Widget>[
new SliverAppBar(
title: Text("Silver AppBar With ToolBar"),
pinned: true,
expandedHeight: 160.0,
bottom: new TabBar(
tabs: [
new Tab(text: 'Tab 1'),
new Tab(text: 'Tab 2'),
new Tab(text: 'Tab 3'),
],
controller: controller,
),
),
new SliverList(
new SliverFillRemaining(
child: TabBarView(
controller: controller,
children: <Widget>[
Text("Tab 1"),
Text("Tab 2"),
Text("Tab 3"),
],
),
),
],
),
);
}
}
Да. Вы можете использовать NestedScrollView для достижения вкладок. Вот дополнительный код к этому.
class AppView extends StatelessWidget {
final double _minValue = 8.0;
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return Scaffold(
appBar: MyAppBar(),
drawer: DrawerDialog(),
body: DefaultTabController(
length: 3,
child: SafeArea(
child: NestedScrollView(
body: TabBarView(
children: [Text("Page 1"), Text("Page 2"), Text("Page 3")],
),
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) => [
SliverPadding(
padding: EdgeInsets.all(_minValue * 2.5),
sliver: SliverToBoxAdapter(
child: Text(
"Hiding Header",
style: textTheme.headline6,
textAlign: TextAlign.center,
),
),
),
SliverAppBar(
backgroundColor: Colors.grey[100],
pinned: true,
elevation: 12.0,
leading: Container(),
titleSpacing: 0.0,
toolbarHeight: 10,
bottom: TabBar(tabs: [
Tab(
child: Text(
"All",
style: textTheme.subtitle2,
),
),
Tab(
child: Text(
"Categories",
style: textTheme.subtitle2,
),
),
Tab(
child: Text(
"Upcoming",
style: textTheme.subtitle2,
),
),
]),
),
],
),
),
),
);
}}
Если вы хотите использовать
CustomScrollView
, вы можете воспользоваться
SliverToBoxAdapter
:
Widget build(BuildContext context) {
return Scaffold(
body: DefaultTabController(
length: 2,
child: CustomScrollView(
slivers: [
SliverAppBar(
title: Text('SliverAppBar'),
bottom: TabBar(
tabs: [
Tab(icon: Icon(Icons.call), text: "Call"),
Tab(icon: Icon(Icons.message), text: "Message"),
],
),
),
SliverToBoxAdapter(
child: SizedBox(
height: MediaQuery.of(context).size.height,
child: TabBarView(
children: [
Container(color: Colors.red),
Container(color: Colors.blue),
],
),
),
),
],
),
),
);
}
Вы также можете реализовать его, предоставив _tabController как для TabBar(), так и для TabBarView, чтобы он был привязан. И для дочерних элементов TabBarView, если вы используете ListView, дайте ему физику: NeverScrollablePhysics(), чтобы он не двигался, пожалуйста, не надо. Вы должны задать динамическую высоту контейнеру ListView, чтобы он загрузил все дочерние элементы.
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
TabController _tabController;
@override
void initState() {
_tabController = TabController(length: 2, vsync: this);
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
floating: true,
expandedHeight: 50,
title: Column(
children: [
Row(
children: [
Text('Hello, User'),
Spacer(),
InkWell(
child: Icon(Icons.map_rounded),
),
],
),
],
),
),
SliverList(
delegate: SliverChildListDelegate([
_tabSection(context),
])),
],
));
}
Widget _tabSection(BuildContext context) {
final height = MediaQuery.of(context).size.height;
final width = MediaQuery.of(context).size.width;
double mainAxisHeight = height > width ? height : width;
return DefaultTabController(
length: 2,
child: Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
Container(
height: 48,
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(10),
bottomLeft: Radius.circular(10))),
child: TabBar(
indicatorColor: Colors.white,
indicator: UnderlineTabIndicator(
borderSide: BorderSide(color: Colors.white, width: 5.0),
insets: EdgeInsets.symmetric(horizontal: 40),
),
labelColor: Colors.white,
unselectedLabelColor: Colors.grey[300],
tabs: [
Tab(
iconMargin: EdgeInsets.only(top: 5),
text: "Tab Bar 1",
),
Tab(
iconMargin: EdgeInsets.only(top: 5),
text: "Tab bar 2",
),
]),
),
Container(
height: 200 * 6 // 200 will be Card Size and 6 is number of Cards
child: TabBarView(controller: _tabController, children: [
tabDetails(),
tabDetails(),
]))
]));
}
tabDetails() {
final height = MediaQuery.of(context).size.height;
final width = MediaQuery.of(context).size.width;
double mainAxisHeight = height > width ? height : width;
return Container(
padding: EdgeInsets.symmetric(horizontal: 15),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.red[100],
Colors.red[200],
])),
child: ListView(
physics: NeverScrollableScrollPhysics(), // This will disable LitView'Scroll so only Scroll is possible by TabBarView Scroll.
children: [
SizedBox(height: 10),
Container(
height:140,
width: width,
child: ListView.builder(
scrollDirection: Axis.vertical,
itemCount: 6,
itemBuilder: (BuildContext context, int indexChild) {
return Row(
children: [
MyListTile(
name: "Name",
),
SizedBox(width: 5),
],
);
},
),
),
SizedBox(height: 1000),
],
),
);
}
}
Отличное видео здесь объясняет все это. Оказывается, вам нужен контроллер scrollView: https://youtu.be/3Cm7WzH3gb8