Related
I use a default Tabbar. I have two tab .When I change tabview by clicking, onTab method call finely. But when I change tabview by swiping or scrolling, how I can call onTab method?. How I can listen my onTab changing value when I change my tabview by swiping or scrolling? I need change tabIndex value in controller when I change tabView by swiping or scroling.
UI Part here
#override
Widget build(BuildContext context) {
return DefaultTabController(
initialIndex: 0,
length: 2,
child: Scaffold(
appBar: AppBar(
backgroundColor: AllColors.deepPurple,
leading: InkWell(
onTap: () => Get.back(),
child: Icon(
Icons.arrow_back,
color: AllColors.whiteColor,
),
),
elevation: 0.0,
title: Text(
"Categories",
style: AllStyles.titleTextStyle,
),
actions: [
InkWell(
child: Padding(
padding: const EdgeInsets.only(right: 12.0),
child: Icon(Icons.add),
),
onTap: () {
},
)
],
bottom: TabBar(
controller: categoriesController.tabController,
onTap: (value) {
categoriesController.changeTabValue(value);
print("Value " + value.toString());
},
isScrollable: false,
indicatorColor: AllColors.whiteColor,
indicatorSize: TabBarIndicatorSize.label,
tabs: [Tab(text: "Income"), Tab(text: "Expense")],
),
),
body: TabBarView(
children: [
IncoomeTabCategories(),
ExpenseTabCategories()
],
),
),
);
}
Controller part here:
class CategoriesController extends GetxController with GetSingleTickerProviderStateMixin {
TabController? tabController;
int tabIndex=0;
#override
void onInit() {
super.onInit();
tabController = TabController(length: 2, vsync: this,initialIndex: 0)
}
#override
void dispose() {
super.dispose();
tabController!.dispose();
}
void changeTabValue(int index){
tabIndex=index;
update();
}
}
I want to use sliver app bar with tab bar and grid view.
I tried this code:
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxScrolled) {
return <Widget>[
SliverAppBar(
backgroundColor: Colors.white,
centerTitle: true,
pinned: true,
snap: false,
floating: true,
title: Image(
image: AssetImage('assets/images/appbar_logo.png'),
width: 152,
height: 42,
),
bottom: PreferredSize(
preferredSize: Size.fromHeight(48),
child: TabBar(
isScrollable: true,
automaticIndicatorColorAdjustment: false,
indicatorSize: TabBarIndicatorSize.tab,
tabs: STR_TAB_TITLE_LIST.map((e) => Container(
padding: EdgeInsets.only(left: 4, right: 4),
child: Tab(text: e),
),
).toList(),
controller: _tabController,
),
),
),
];
},
body: TabBarView(
children: []..addAll(STR_TAB_TITLE_LIST.map((e) {
if (e == 'myWork') {
return MyWorkPage(e);
} else if (e == 'character') {
return CharactersPage(onCharacterPageItemSelected, e);
}
return TabPage(e);
})),
controller: _tabController,
),
),
),
);
}
And pages:
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.topCenter,
child: GridView.builder(
shrinkWrap: true,
padding: EdgeInsets.all(4.0),
physics: BouncingScrollPhysics(),
itemCount: SAMPLE_CARD_LIST.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
itemBuilder: (context, index) {
return ItemCard(SAMPLE_CARD_LIST[index]);
},
),
);
}
The problems I faced are two
It does not save page's scroll position
like this: https://github.com/flutter/flutter/issues/40740
it cuts top of page when swipe to next tab
like this: Flutter TabBar and SliverAppBar that hides when you scroll down
I tried all suggestions above links but it did not worked
How can I fix this?
Use of code:
You can make your custom appbar. I think this code will help you.
appBar: CustomTab(
onDone: (tabNo) {
},
),)
Where we have used:
import 'package:filepath/md2indicator.dart';
class CustomTab extends StatefulWidget implements PreferredSizeWidget {
final void Function(int) onDone;
CustomTab({
Key? key,
required this.onDone,
}) : super(key: key);
#override
State<CustomTab> createState() => _CustomTabState();
#override
// TODO: implement preferredSize
final Size preferredSize = const Size.fromHeight(kToolbarHeight);
}
class _CustomTabState extends State<CustomTab>
with SingleTickerProviderStateMixin {
late TabController tabcontroller;
// final GlobalKey<ScaffoldState> _key = GlobalKey<ScaffoldState>();
#override
void initState() {
// TODO: implement initState
super.initState();
tabcontroller = TabController(length: 9, vsync: this);
tabcontroller.addListener(() {
setState(() {
widget.onDone(tabcontroller.index);
tabcontroller.animateTo(tabcontroller.index);
});
});
}
#override
void dispose() {
tabcontroller.dispose();
// TODO: implement dispose
super.dispose();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: TabBar(
controller: tabcontroller,
labelStyle: TextStyle(
fontWeight: FontWeight.w700,
),
indicatorSize: TabBarIndicatorSize.label,
labelColor: Colors.green,
unselectedLabelColor: Colors.black,
isScrollable: true,
indicator: MD2Indicator(
indicatorHeight: 3,
indicatorColor: Colors.green.shade700,
indicatorSize: MD2IndicatorSize.full,
),
tabs: [
Tab(
text: 'Trending',
),
Tab(
text: 'Sports',
),
Tab(
text: 'Economy',
),
Tab(
text: 'Fashion',
),
Tab(
text: 'Entertainment',
),
Tab(
text: 'Technology',
),
Tab(
text: 'POLITICS',
),
Tab(
text: 'Viral',
),
Tab(
text: 'Videos',
)
],
),
);
}
}
And custom tab bar md2_tab_indicator style can be designed as:
import 'package:flutter/widgets.dart';
enum MD2IndicatorSize {
tiny,
normal,
full,
}
class MD2Indicator extends Decoration {
final double indicatorHeight;
final Color indicatorColor;
final MD2IndicatorSize indicatorSize;
const MD2Indicator({
required this.indicatorHeight,
required this.indicatorColor,
required this.indicatorSize,
});
#override
_MD2Painter createBoxPainter([VoidCallback? onChanged]) {
return _MD2Painter(this, onChanged);
}
}
class _MD2Painter extends BoxPainter {
final MD2Indicator decoration;
_MD2Painter(this.decoration, VoidCallback? onChanged) : super(onChanged);
#override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
assert(configuration.size != null);
Rect rect;
if (decoration.indicatorSize == MD2IndicatorSize.full) {
rect = Offset(
offset.dx,
configuration.size!.height - decoration.indicatorHeight,
) &
Size(configuration.size!.width, decoration.indicatorHeight);
} else if (decoration.indicatorSize == MD2IndicatorSize.tiny) {
rect = Offset(
offset.dx + configuration.size!.width / 2 - 8,
configuration.size!.height - decoration.indicatorHeight,
) &
Size(16, decoration.indicatorHeight);
} else {
rect = Offset(
offset.dx + 6,
configuration.size!.height - decoration.indicatorHeight,
) &
Size(configuration.size!.width - 12, decoration.indicatorHeight);
}
final Paint paint = Paint()
..color = decoration.indicatorColor
..style = PaintingStyle.fill;
canvas.drawRRect(
RRect.fromRectAndCorners(
rect,
topRight: Radius.circular(8),
topLeft: Radius.circular(8),
),
paint,
);
}
}```
I want to use DefaultTabController in the middle of the screen along with other widgets. There will be a widget before the TabBar. I managed to find an answer for this in SO. But the problem is I want to scroll the entire page. Provided answer only scroll the tab section. I added SingleChildScrollView by wrapping the entire thing. But that didn't work as that conflict with the Expanded widget.
Then I found another answer which uses CustomScrollView with slivers but again that only scroll the tab section not the entire screen.
Erroneous code with SingleChildScrollView: https://dartpad.dev/d380be0134d0c4e84b5dca87a58b3022
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: SingleChildScrollView(
child: Column(
children: [
SizedBox(height: 150, child: Text('Hey')),
Expanded(child: ProfileTabBarNavigation()),
],
),
),
);
}
}
const String kArtwork = "Left";
const String kPastJobs = "Right";
const EdgeInsets kPaddingTabBar = EdgeInsets.all(5.0);
const Color kLightGrey = Colors.grey;
class ProfileTabBarNavigation extends StatelessWidget {
final List<Tab> myTabs = <Tab>[
const Tab(text: kArtwork),
const Tab(text: kPastJobs)
];
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
initialIndex: 0,
child: Scaffold(
appBar: TabBar(
tabs: myTabs,
unselectedLabelColor: Colors.black54,
labelColor: Colors.black,
indicatorSize: TabBarIndicatorSize.tab,
indicator: BoxDecoration(
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(50),
color: Colors.white,
),
),
body: TabBarView(
children: myTabs.map((Tab tab) {
final String label = tab.text.toLowerCase();
return ListView.builder(
physics: NeverScrollableScrollPhysics(),
itemCount: 100,
itemBuilder: (context, index) {
return ListTile(
title: Text(label),
);
},
);
}).toList(),
),
),
);
}
}
CustomScrollView: https://dartpad.dev/335f9e0d134a72232200bc8bfd493670
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: CustomScrollView(
slivers: [
SliverFillRemaining(
hasScrollBody: true,
child: Column(
children: [
SizedBox(height: 150, child: Text('Hey')),
Expanded(child: ProfileTabBarNavigation()),
],
),
)
],
),
);
}
}
const String kArtwork = "Left";
const String kPastJobs = "Right";
const EdgeInsets kPaddingTabBar = EdgeInsets.all(5.0);
const Color kLightGrey = Colors.grey;
class ProfileTabBarNavigation extends StatelessWidget {
final List<Tab> myTabs = <Tab>[
const Tab(text: kArtwork),
const Tab(text: kPastJobs)
];
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
initialIndex: 0,
child: Scaffold(
appBar: TabBar(
tabs: myTabs,
unselectedLabelColor: Colors.black54,
labelColor: Colors.black,
indicatorSize: TabBarIndicatorSize.tab,
),
body: TabBarView(
physics: NeverScrollableScrollPhysics(),
children: myTabs.map((Tab tab) {
final String label = tab.text.toLowerCase();
return ListView.builder(
// physics: NeverScrollableScrollPhysics(),
itemCount: 50,
itemBuilder: (context, index) {
return ListTile(
title: Text(label),
);
},
);
}).toList(),
),
),
);
}
}
Got the CustomScrollView solution from here: How to use Expanded in SingleChildScrollView?
Short answer: you need to use NestedScrollView. You also need to adjust a few things in your current code, so longer answer:
const String kArtwork = "Left";
const String kPastJobs = "Right";
const EdgeInsets kPaddingTabBar = EdgeInsets.all(5.0);
const Color kLightGrey = Colors.grey;
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
final String title = 'Multi Sliver Scrollable';
return MaterialApp(
title: title,
theme: ThemeData.dark(),
debugShowCheckedModeBanner: false,
home: Home(title),
);
}
}
class Home extends StatelessWidget {
Home(this.title);
final String title;
final List<Tab> myTabs = <Tab>[
const Tab(text: kArtwork),
const Tab(text: kPastJobs)
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: DefaultTabController(
length: myTabs.length,
child: NestedScrollView(
headerSliverBuilder: (context, innerBoxIsScrolled) {
return <Widget>[
SliverToBoxAdapter(
child: SizedBox(height: 150, child: Text('Hey'))),
SliverAppBar(
toolbarHeight: 0,
bottom: TabBar(
tabs: myTabs,
indicatorSize: TabBarIndicatorSize.tab,
),
),
];
},
body: TabBarView(
children: myTabs.map((Tab tab) {
final String label = tab.text.toLowerCase();
return CustomScrollView(
key: PageStorageKey<String>(label),
slivers: [
SliverList(
delegate: SliverChildListDelegate.fixed(
List.filled(50, Text(label)),
),
),
],
);
}).toList(),
),
),
),
);
}
}
I have made a flutter project in which I am facing issue with two pages.
Plugin used for two pages- https://pub.dev/packages/flutter_inappwebview
One Page that is "Single App page" render a Webpage & WillPopScope is working here absolutely fine.
Another Page that is "Compare App Page" render a different Webpages into different tabs & here WillPopScope is only working for 1st tab and not working for the rest of tab.
I want to implement WillPopScope for each tab so that each tab have its own history and when a person present on a particular tab & hitting back button(I want to do this inbuilt back button & not via a created back button) takes him to back in history.
Note- A common widget is used in both Single & Compare App as children.
Below are the Main code
class NewCompareApp extends StatefulWidget {
#override
_NewCompareAppState createState() => _NewCompareAppState();
}
class _NewCompareAppState extends State<NewCompareApp> {
List apps;
#override
void initState() {
super.initState();
apps = getCompareApps();
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: apps.length,
child: Scaffold(
appBar: AppBar(
titleSpacing: 0,
title: Card(
elevation: 10,
child: Container(
height: 35,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(8.0)),
),
padding: EdgeInsets.only(left: 5),
child: TextField(
autofocus: false,
cursorColor: Colors.grey,
decoration: InputDecoration(
hintText: 'Search', border: InputBorder.none),
),
),
),
bottom: TabBar(
indicatorWeight: 1,
labelColor: Colors.white,
unselectedLabelColor: Colors.black,
indicatorColor: Colors.white,
isScrollable: true,
tabs: apps
.map((ca) => Tab(
text: ca.name,
))
.toList(),
),
),
resizeToAvoidBottomInset: false,
body: TabBarView(
physics: NeverScrollableScrollPhysics(),
children: apps
.map((ca) => WebApp(
url: ca.url,
forWidget: 'cmp',
))
.toList(),
)),
);
}
}
class WebApp extends StatefulWidget {
final String url;
final String forWidget;
WebApp({Key key, #required this.url, #required this.forWidget})
: super(key: key);
#override
_WebAppState createState() => _WebAppState();
}
class _WebAppState extends State<WebApp>
with AutomaticKeepAliveClientMixin<WebApp> {
#override
bool get wantKeepAlive => true;
var currentUrl = '';
InAppWebViewController controller;
Future<bool> _handleBack(context) async {
var status = await controller.canGoBack();
if (status) {
controller.goBack();
} else {
getExitDialog(context, extra: {
"in_app": true,
});
}
return false;
}
#override
Widget build(BuildContext context) {
Widget mainWidget = Column(
children: <Widget>[
Expanded(
child: WillPopScope(
onWillPop: () => _handleBack(context),
child: InAppWebView(
initialUrl: widget.url,
onWebViewCreated: (InAppWebViewController webViewController) {
controller = webViewController;
},
onLoadStart: (InAppWebViewController controller, String url) {
this.currentUrl = url;
},
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
horizontalScrollBarEnabled: false,
verticalScrollBarEnabled: false),
),
),
),
),
Container(
color: Colors.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
FlatButton(
padding: EdgeInsets.zero,
child: Icon(Icons.arrow_back),
onPressed: () => _handleBack(context),
),
FlatButton(
padding: EdgeInsets.zero,
child: Icon(Icons.refresh),
onPressed: () {
if (controller != null) {
controller.reload();
}
},
),
FlatButton(
padding: EdgeInsets.zero,
child: Icon(Icons.arrow_forward),
onPressed: () {
if (controller != null) {
controller.goForward();
}
},
),
FlatButton(
padding: EdgeInsets.zero,
child: Icon(Icons.share),
onPressed: null,
),
],
),
),
],
);
return widget.forWidget == 'single_app'
? Scaffold(body: SafeArea(top: true, child: mainWidget))
: mainWidget;
}
}
Code in detail- https://gist.github.com/ycv005/13dec1df2b57535271eb346e132c6775
Thanks in advance.
After struggling I found below solution where a global controller that keep changing on tab change and plus have a local controller as well to handle other stuff.
In bottom, selecting global controller and TabBarView(wrapping with WillPopScope)
bottom: TabBar(
onTap: (int index) async {
currentIndex = index;
print('here is index- $index');
if (tabWebControllerMap.containsKey(currentIndex)) {
globalController = tabWebControllerMap[currentIndex];
final hereUrl = await globalController.getUrl();
print('here url- $hereUrl');
}
},
controller: _tabController,
indicatorWeight: 1,
labelColor: Colors.white,
unselectedLabelColor: Colors.black,
indicatorColor: Colors.white,
isScrollable: true,
tabs: apps
.map((ca) => Tab(
child: Text(
ca.name,
style: TextStyle(fontSize: 12),
),
))
.toList(),
),
),
Iam trying to implement a appbar like this
When scrolling down I need to hide the search bar alone and pin the row and the tabs on the device top. Which is like
And when we scroll down the all the three rows needs to be displayed.
Using SliverAppBar with bottom property tabs are placed and pinned when scrolling, but a row above it should be pinned at the top above the tabbar. Im not able to add a column with the row and tabbar because of preferedSizeWidget in bottom property. Flexible space bar also hides with the appbar so I cannot use it. Does anyone know how to make this layout in flutter.
Please try this.
body: Container(
child: Column(
children: <Widget>[
Container(
// Here will be your AppBar/Any Widget.
),
Expanded(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
// All your scroll views
Container(),
Container(),
],
),
),
),
],
),
),
You could create your own SliverAppBar or you can divide them in 2 items, a SliverAppBar and a SliverPersistentHeader
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home>
with SingleTickerProviderStateMixin {
TabController controller;
TextEditingController textController = TextEditingController();
#override
void initState() {
super.initState();
controller = TabController(
length: 3,
vsync: this,
);
}
#override
void dispose(){
super.dispose();
controller.dispose();
textController.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
leading: const Icon(Icons.menu),
title: TextField(
controller: textController,
textInputAction: TextInputAction.search,
decoration: InputDecoration(
isDense: true,
hintText: 'Search Bar',
hintStyle: TextStyle(color: Colors.black.withOpacity(.5), fontSize: 16),
border: InputBorder.none
)
),
snap: true,
floating: true,
actions: [
IconButton(
icon: const Icon(Icons.search),
onPressed: () => print('searching for: ${textController.text}'),
)
]
),
//This is Where you create the row and your tabBar
SliverPersistentHeader(
delegate: MyHeader(
top: Row(
children: [
for(int i = 0; i < 4; i++)
Expanded(
child: OutlineButton(
child: Text('button $i'),
onPressed: () => print('button $i pressed'),
)
)
]
),
bottom: TabBar(
indicatorColor: Colors.white,
tabs: [
Tab(text: 'Tab 1'),
Tab(text: 'Tab 2'),
Tab(text: 'Tab 3'),
],
controller: controller,
),
),
pinned: true,
),
SliverFillRemaining(
child: TabBarView(
controller: controller,
children: <Widget>[
Center(child: Text("Tab one")),
Center(child: Text("Tab two")),
Center(child: Text("Tab three")),
],
),
),
],
),
);
}
}
//Your class should extend SliverPersistentHeaderDelegate to use
class MyHeader extends SliverPersistentHeaderDelegate {
final TabBar bottom;
final Widget top;
MyHeader({this.bottom, this.top});
#override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
return Container(
color: Theme.of(context).accentColor,
height: math.max(minExtent, maxExtent - shrinkOffset),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if(top != null)
SizedBox(
height: kToolbarHeight,
child: top
),
if(bottom != null)
bottom
]
)
);
}
/*
kToolbarHeight = 56.0, you override the max and min extent with the height of a
normal toolBar plus the height of the tabBar.preferredSize
so you can fit your row and your tabBar, you give them the same value so it
shouldn't shrink when scrolling
*/
#override
double get maxExtent => kToolbarHeight + bottom.preferredSize.height;
#override
double get minExtent => kToolbarHeight + bottom.preferredSize.height;
#override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) => false;
}
UPDATE
A NestedScollView let you have 2 ScrollViews so you can control the inner scroll with the outer (just like you want with a TabBar)
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
TextEditingController textController = TextEditingController();
List<String> _tabs = ['Tab 1', 'Tab 2', 'Tab 3'];
// Your tabs, or you can ignore this and build your list
// on TabBar and the TabView like my previous example.
// I don't create a TabController now because I wrap the whole widget with a DefaultTabController
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
textController.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: DefaultTabController(
length: _tabs.length, // This is the number of tabs.
child: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled){
return <Widget>[
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverAppBar(
elevation: 0.0,
leading: const Icon(Icons.menu),
title: TextField(
controller: textController,
textInputAction: TextInputAction.search,
decoration: InputDecoration(
isDense: true,
hintText: 'Search Bar',
hintStyle: TextStyle(
color: Colors.black.withOpacity(.5),
fontSize: 16),
border: InputBorder.none)
),
snap: true,
floating: true,
actions: [
IconButton(
icon: const Icon(Icons.search),
onPressed: () => print('searching for: ${textController.text}'),
)
]
),
),
SliverPersistentHeader(
delegate: MyHeader(
top: Row(children: [
for (int i = 0; i < 4; i++)
Expanded(
child: OutlineButton(
child: Text('button $i'),
onPressed: () => print('button $i pressed'),
))
]),
bottom: TabBar(
indicatorColor: Colors.white,
tabs: _tabs.map((String name) => Tab(text: name)).toList(),
),
),
pinned: true,
),
];
},
body: TabBarView(
children: _tabs.map((String name) {
return SafeArea(
child: Builder(
// This Builder is needed to provide a BuildContext that is
// "inside" the NestedScrollView, so that
// sliverOverlapAbsorberHandleFor() can find the
// NestedScrollView.
// You can ignore it if you're going to build your
// widgets in another Stateless/Stateful class.
builder: (BuildContext context) {
return CustomScrollView(
// The "controller" and "primary" members should be left
// unset, so that the NestedScrollView can control this
// inner scroll view.
// If the "controller" property is set, then this scroll
// view will not be associated with the NestedScrollView.
// The PageStorageKey should be unique to this ScrollView;
// it allows the list to remember its scroll position when
// the tab view is not on the screen.
key: PageStorageKey<String>(name),
slivers: <Widget>[
SliverOverlapInjector(
// This is the flip side of the SliverOverlapAbsorber
// above.
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
),
SliverPadding(
padding: const EdgeInsets.all(8.0),
sliver: SliverFixedExtentList(
itemExtent: 48.0,
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ListTile(
title: Text('Item $index'),
onTap: () => print('$name at index $index'),
);
},
childCount: 30,
),
),
),
],
);
},
),
);
}).toList(),
),
),
));
}
}
import 'dart:io';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primaryColor: Colors.white,
),
home: NewsScreen(),
debugShowCheckedModeBanner: false,
);
}
}
class NewsScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() => _NewsScreenState();
}
class _NewsScreenState extends State<NewsScreen> {
final List<String> _tabs = <String>[
"Featured",
"Popular",
"Latest",
];
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
body: DefaultTabController(
length: _tabs.length,
child: NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverOverlapAbsorber(
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverSafeArea(
top: false,
bottom: Platform.isIOS ? false : true,
sliver: SliverAppBar(
title: Text('Tab Demo'),
elevation: 0.0,
floating: true,
pinned: true,
snap: true,
forceElevated: innerBoxIsScrolled,
bottom: TabBar(
tabs: _tabs.map((String name) => Tab(text: name)).toList(),
),
),
),
),
];
},
body: TabBarView(
children: [
Icon(Icons.directions_car),
Icon(Icons.directions_transit),
Icon(Icons.directions_bike),
],
),
),
),
),
);
}
}