I tried to acheive from NestedScrollView by creating a sample code. But its not exactly which I want. Please help me.
I want to achieve this ui. When scrolling appbar should dock at the top. Image top should start from status bar to the fixed height 300.
After Image there would be some dynamic content like description.
Then I have to add TabBar, which will dock just below the Appbar when scroll.
Body will scroll on top of Header Image, like a overlay.
Image Attachments
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class StickyTabbarExample extends StatefulWidget {
#override
_StickyTabbarExampleState createState() => _StickyTabbarExampleState();
}
class _StickyTabbarExampleState extends State<StickyTabbarExample>
with SingleTickerProviderStateMixin {
// TabController? _tabController;
final List<String> _tabs = <String>[
"Featured",
"Popular",
];
#override
void initState() {
super.initState();
// _tabController = TabController(vsync: this, length: _tabs.length);
}
#override
void dispose() {
// _tabController?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) => Scaffold(
body: DefaultTabController(
length: _tabs.length,
child: NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) => <Widget>[
SliverAppBar(
pinned: true,
// floating: true,
toolbarHeight: 70,
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Icon(
Icons.arrow_back_ios_new_sharp,
color: Colors.white,
size: 15,
),
Icon(
Icons.shop,
color: Colors.white,
size: 15,
)
]),
backgroundColor: Colors.green,
expandedHeight: 300,
flexibleSpace: FlexibleSpaceBar(
background: Image.network(
"https://images.unsplash.com/photo-1673942393203-fe61f45b4479?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=870&q=80%20870w",
fit: BoxFit.cover,
width: double.maxFinite,
),
),
),
SliverToBoxAdapter(
child: Positioned.fill(
child: Transform.translate(
offset: Offset(0, -10.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: 30,
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.only(
topRight: Radius.circular(100),
topLeft: Radius.circular(100),
),
),
),
Text("Challenge description"),
],
),
),
),
),
SliverPersistentHeader(
delegate: _SliverAppBarDelegate(
TabBar(
indicator: UnderlineTabIndicator(
borderSide: BorderSide(
width: 4,
color: Color(0xFF646464),
),
insets: EdgeInsets.only(left: 0, right: 8, bottom: 4)),
isScrollable: true,
labelPadding: EdgeInsets.only(left: 0, right: 0),
tabs: [
Tab(
child: Text(
"Tab 1",
style: TextStyle(color: Colors.black),
),
),
Tab(
child: Text(
"Tab 2",
style: TextStyle(color: Colors.black),
)),
],
),
),
pinned: true,
),
],
body: TabBarView(children: [TabA(), TabB()]),
),
),
);
}
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate(this._tabBar);
final TabBar _tabBar;
#override
double get minExtent => _tabBar.preferredSize.height;
#override
double get maxExtent => _tabBar.preferredSize.height;
#override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) =>
Container(
color: Colors.white,
child: _tabBar,
);
#override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) => false;
}
class TabA extends StatelessWidget {
const TabA({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) => CustomScrollView(
// primary: false,
key: PageStorageKey<String>("Tab1"),
slivers: [
SliverPadding(
padding: EdgeInsets.symmetric(vertical: 24, horizontal: 16),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) => Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
color: index % 2 == 0 ? Colors.green : Colors.greenAccent,
height: 80,
alignment: Alignment.center,
child: Text(
"Item $index",
style: const TextStyle(fontSize: 30),
),
),
),
// 40 list items
childCount: 40,
),
),
)
],
);
}
class TabB extends StatelessWidget {
const TabB({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) => CustomScrollView(
key: PageStorageKey<String>("Tab2"),
slivers: [
SliverPadding(
padding: EdgeInsets.symmetric(vertical: 24, horizontal: 16),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) => Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
color: index % 2 == 0 ? Colors.green : Colors.greenAccent,
height: 80,
alignment: Alignment.center,
child: Text(
"Item $index",
style: const TextStyle(fontSize: 30),
),
),
),
childCount: 40,
),
),
)
],
);
}
Related
I'm making a Pomodoro app and I don't know how to implement Get and Obx if the timer is over and make that the tab bar change automatically.
This is my code:
Tab bar:
#override
Widget build(BuildContext context) {
return SizedBox(
height: MediaQuery.of(context).size.height,
width: double.infinity,
child: AnimatedBuilder(
animation: _countDownController.controller,
builder: (context, child) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.transparent,
bottom: PreferredSize(
preferredSize: const Size.fromHeight(22),
child: Container(
color: Colors.transparent,
child: SafeArea(
child: ResponsiveWeb(
child: Column(children: [
TabBar(
controller: _tabController,
indicator: const UnderlineTabIndicator(
borderSide: BorderSide(
color: Color(0xff3B3B3B), width: 2.0),
insets: EdgeInsets.fromLTRB(
12.0, 12.0, 12.0, 12.0)),
indicatorWeight: 5,
indicatorSize: TabBarIndicatorSize.label,
labelColor: const Color(0xff3B3B3B),
labelStyle: GoogleFonts.nunito(
fontSize: 16.0,
// letterSpacing: 1,
fontWeight: FontWeight.w500),
unselectedLabelColor: const Color(0xffD7D7D7),
tabs: const [
Tab(
text: "Pomodoro",
icon: Icon(Icons.work_history_outlined,
size: 24),
),
Tab(
text: "Short break",
icon: Icon(Icons.ramen_dining_outlined,
size: 24),
),
Tab(
text: "Long break",
icon: Icon(
Icons.battery_charging_full_outlined,
size: 24),
),
]),
]),
),
),
),
),
),
),
);
},
),
);
}
}
And my timer:
This is an example from an animation which means that if the timer starts the animation starts as well
createAnimationController(TickerProvider ticker) {
currentRoundType = typeRound.pomodoro;
_changeCurrentRoundTypeString();
tickerProvider = ticker;
currentRoundSeconds.value = currentRoundType == typeRound.pomodoro
? _settingsController.secondsWork.value
: currentRoundNumber.value < _settingsController.rounds.value
? _settingsController.secondsBreak.value
: _settingsController.secondsBreakAfterRound.value;
restartTimers();
controller = AnimationController(
vsync: tickerProvider,
duration: currentDuration,
);
logger.d(controller.value);
painter = CustomTimePainter(
backgroundColor: const Color.fromARGB(0, 33, 149, 243),
color: const Color(0xffD94530),
animation: controller,
);
timerString.value =
'${(currentDuration.inHours).toString().padLeft(2, '0')}:${(currentDuration.inMinutes % 60).toString().padLeft(2, '0')}:${(currentDuration.inSeconds % 60).toString().padLeft(2, '0')}';
listRounds.value = List.generate(
_settingsController.rounds.value + 1, (index) => Rx(stateRound.undone));
}
This is another example, which means that if the timer ends the empty image is colored with red color
import 'package:flutter/material.dart';
import 'package:get/get.dart%20';
import 'package:pomodoro/3.tomatoes_interval_UI/countdown_controller.dart';
import 'package:pomodoro/3.tomatoes_interval_UI/tomato_icon.dart';
class TomatoesIcons extends StatefulWidget {
const TomatoesIcons({super.key});
#override
State<TomatoesIcons> createState() => _TomatoesIconsState();
}
class _TomatoesIconsState extends State<TomatoesIcons>
with TickerProviderStateMixin {
final CountDownController _countDownController = Get.find();
final ScrollController _horizontal = ScrollController();
#override
void initState() {
super.initState();
_countDownController.createAnimationController(this);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: AnimatedBuilder(
animation: _countDownController.controller,
builder: (context, child) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
color: const Color.fromARGB(255, 255, 202, 55),
height: 65,
width: MediaQuery.of(context).size.width,
child: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Obx(
() => Scrollbar(
controller: _horizontal,
child: SingleChildScrollView(
controller: _horizontal,
scrollDirection: Axis.horizontal,
child: Row(
children: _countDownController.listRounds
.map(
(e) => MouseRegion(
cursor: SystemMouseCursors.click,
child: TomatoIcon(e),
),
)
.toList(),
),
),
),
),
),
),
),
],
);
},
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pomodoro/3.tomatoes_interval_UI/countdown_controller.dart';
class TomatoIcon extends StatefulWidget {
final Rx<stateRound> state;
const TomatoIcon(this.state, {Key? key}) : super(key: key);
#override
State<TomatoIcon> createState() => _TomatoIconState();
}
class _TomatoIconState extends State<TomatoIcon> {
#override
Widget build(BuildContext context) {
return Obx(
() => IconButton(
onPressed: null,
icon: widget.state.value == stateRound.done
? Image.asset('assets/icons/tomatoDone.png')
: Image.asset('assets/icons/tomatoUndone.png')),
);
}
}
With these examples, I would like to create a function or " if statement" which triggers the timer is over, select automatically a tab bar.
Thanks for any help you can provide
Hey I m new in flutter now m stuck with the tab bar I have four files (Class), the first one is the parent file and the other three files(Class) are the child.
Now I want to change tabbarview when I clicked the button from the child class.
I also shared my sample code please help me.
This is My Parent Class
class AddItemTab extends StatefulWidget {
const AddItemTab({Key? key}) : super(key: key);
#override
_AddItemTabState createState() => _AddItemTabState();
}
class _AddItemTabState extends State<AddItemTab> {
final List<Widget> _fragments = [
const ProductPurchase(),
const ProtectionProduct(),
const RoomProduct()
];
int _page = 0;
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
backgroundColor: MyColor.backgroundColor,
body: Padding(
padding: const EdgeInsets.only(
top: 50.0, right: 20.0, left: 20.0, bottom: 20.0),
child: Container(
child: Column(
children: [
Row(
children: [
Align(
alignment: Alignment.centerLeft,
child: IconButton(
padding: EdgeInsets.zero,
constraints: BoxConstraints(),
onPressed: () {
Navigator.of(context).pop();
},
icon: const Icon(Icons.arrow_back_ios),
),
),
Text("Back"),
],
),
SizedBox(
height: 15,
),
const Align(
alignment: Alignment.centerLeft,
child: Text(
'Add an item',
style: TextStyle(
color: Colors.black,
fontSize: 34,
fontFamily: 'Inter',
fontWeight: FontWeight.w700,
),
)),
const SizedBox(
height: 15,
),
Container(
height: 55,
width: double.infinity,
child: const TabBar(
indicator: BoxDecoration(
color: MyColor.buttonColor,
borderRadius: BorderRadius.all(
Radius.circular(5),
),
),
indicatorWeight: 5,
indicatorPadding: EdgeInsets.only(top:50),
// controller: _tabController,
labelColor: Colors.black,
tabs: [
Tab(
child: Text(
"Purchase",
textAlign: TextAlign.center,
),
),
Tab(
text: 'Protection',
),
Tab(
text: 'Room',
),
],
),
),
const SizedBox(height: 20),
Expanded(
child: TabBarView(
children: [
_fragments[0],
_fragments[1],
_fragments[2],
],
))
],
),
),
)),
);
}
}
This is My Child Class
class ProductPurchase extends StatefulWidget {
const ProductPurchase({Key? key}) : super(key: key);
#override
_ProductPurchaseState createState() => _ProductPurchaseState();
}
class _ProductPurchaseState extends State<ProductPurchase> {
final List<Widget> _fragments = [
const ProtectionProduct(),
];
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: MyColor.backgroundColor,
body: Stack(
children: [
Padding(
padding: EdgeInsets.only(bottom: 50),
child: Align(
alignment: Alignment.bottomCenter,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
elevation: 5,
),
onPressed: () {
// Navigator.of(context).push(MaterialPageRoute(
// builder: (context) => ProductView()));
// _fragments[0];
},
child: Ink(
decoration: BoxDecoration(
color: MyColor.buttonColor,
borderRadius: BorderRadius.circular(10)),
child: Container(
width: 250,
padding: const EdgeInsets.all(15),
constraints: const BoxConstraints(minWidth: 88.0),
child: const Text('Go To Next Tabbar View',
textAlign: TextAlign.center,`enter code here`
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white)),
),
),
),
),
),
],
),
);
}
}
You need to use TabController for this , while you already have tab Bar and tab Bar view, you can do it like
class _AddItemTabState extends State<AddItemTab>
with SingleTickerProviderStateMixin {
final List<Widget> _fragments = [
.....
];
late final TabController controller = TabController(length: 3, vsync: this);
#override
Widget build(BuildContext context) {
........
child: TabBar(
controller: controller,
......
Expanded(
child: TabBarView(
controller: controller,
And to move n index, here 2
onPressed: () {
controller.animateTo(2);
},
To call from different widget using callback method
class ProductPurchase extends StatefulWidget {
final VoidCallback callback;
const ProductPurchase({Key? key, required this.callback}) : super(key: key);
.....
onPressed: (){
widget.callback();
},
Once you used this widget, provide
ProductPurchase(callback: (){
controller.animateTo(2);
},);
class ProductPurchase extends StatefulWidget {
final VoidCallback callback;
const ProductPurchase({Key? key, required this.callback}) : super(key: key);
#override
_ProductPurchaseState createState() => _ProductPurchaseState();
}
class _ProductPurchaseState extends State<ProductPurchase> {
#override
Widget build(BuildContext context) {
return Scaffold(
// backgroundColor: MyColor.backgroundColor,
body: Stack(
children: [
Padding(
padding: EdgeInsets.only(bottom: 50),
child: Align(
alignment: Alignment.bottomCenter,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
elevation: 5,
),
onPressed: () {
widget.callback(); //this
},
child: Ink(
decoration: BoxDecoration(
color: MyColor.buttonColor,
borderRadius: BorderRadius.circular(10)),
child: Container(
width: 250,
padding: const EdgeInsets.all(15),
constraints: const BoxConstraints(minWidth: 88.0),
child: const Text('Go To Next Tabbar View',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white)),
),
),
),
),
),
],
),
);
}
}
And fragments
late final List<Widget> _fragments = [
ProductPurchase(
callback: () {
controller.animateTo(3);
},
),
Container(color: Colors.cyanAccent, child: Stack(children: [Text("fsA")])),
Text("2A")
];
More about TabBar
How to create this kind of UI in flutter. with expanded pageview??
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:movie_app/components/bottom_tabs.dart';
import 'package:movie_app/components/card_image.dart';
import 'package:movie_app/components/expandable_text.dart';
import 'package:movie_app/components/heading.dart';
import 'package:movie_app/constants.dart';
import 'package:movie_app/screens/card_details/components/card_details_cast.dart';
import 'package:movie_app/screens/card_details/components/card_details_photos.dart';
import 'package:movie_app/screens/card_details/components/card_details_reviews.dart';
import 'package:movie_app/screens/card_details/components/card_details_tabs.dart';
import '../../../components/my_text.dart';
import '../card_details.dart';
class Body extends StatefulWidget {
const Body({Key key}) : super(key: key);
#override
State<Body> createState() => _BodyState();
}
class _BodyState extends State<Body> {
PageController _tabsPageController;
int _selectedTab = 0;
#override
void initState() {
_tabsPageController = PageController();
super.initState();
}
#override
void dispose() {
_tabsPageController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Stack(
clipBehavior: Clip.none,
children: [
Image.network(
"https://static.wikia.nocookie.net/film-vault/images/c/c8/Furypost.jpg/revision/latest?cb=20171202094520",
height: 260,
fit: BoxFit.cover,
),
Positioned(
top: 100,
left: 180,
child: SvgPicture.asset(
"assets/icons/play icon.svg",
color: kwhitecolor,
height: 50,
),
),
Container(
margin: EdgeInsets.only(top: 200, left: 15, right: 15, bottom: 20),
child: Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
const CardImage(
image:
"https://m.media-amazon.com/images/M/MV5BMjA4MDU0NTUyN15BMl5BanBnXkFtZTgwMzQxMzY4MjE#._V1_.jpg",
width: 120,
),
const SizedBox(width: 15),
// Text("data"),
Column(
// mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: const [
MyText(
"FURY",
fontSize: 22,
isBold: true,
color: Colors.black,
),
SizedBox(width: 2),
MyText(
"(2014)",
fontSize: 14,
isBold: true,
color: Colors.black,
),
],
),
const MyText(
"Action/Drama/War",
fontWeight: FontWeight.w600,
),
const SizedBox(height: 40),
Row(
children: [
const Icon(Icons.remove_red_eye_outlined,
color: klightgreyColor),
const SizedBox(width: 2),
const MyText("1.5M", isBold: true),
const SizedBox(width: 10),
SvgPicture.asset(
"assets/icons/comment.svg",
height: 21,
color: klightgreyColor,
),
const SizedBox(width: 4),
const MyText("1K", isBold: true),
const SizedBox(width: 10),
SvgPicture.asset(
"assets/icons/clock.svg",
height: 18,
color: klightgreyColor,
),
const SizedBox(width: 4),
const MyText("2h 30min", isBold: true),
],
),
],
),
],
),
const SizedBox(height: 30),
const Heading(
"STORYLINE",
fontSize: 18,
padding: 0,
lineColor: Color.fromARGB(255, 255, 68, 68),
// fontWeight: FontWeight.w800,
),
const SizedBox(height: 10),
const ExpandableText(
text:
"1945, the Allies are making their final push in the European theater. A battle-hardened Army sergeant nallier (Brad Pitt), leading a Sherman tank and a five-man crew, undertakes a deadly mission behind enemy lines. Hopelessly outnumbered, outgunned and saddled with an inexperienced soldier (Logan Lerman) in their midst, Wardaddy and his men face overwhelming odds as they move to strike at the heart of Nazi Germany.",
max: 4,
),
SizedBox(height: 10),
CardDetailsBottomTabs(
selectedTab: _selectedTab,
tabPressed: (num) {
_tabsPageController.animateToPage(num,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOutCubic);
},
),
// HERE I WANT TO USE EXPANDED WIDGET INSTEAD OF SIZEDBOX, BUT IT GIVES ME ERROR
SizedBox(
height: 300,
child: PageView(
controller: _tabsPageController,
onPageChanged: (num) {
setState(() {
_selectedTab = num;
});
},
children: const [
CardDetailsCast(),
CardDetailsPhoto(),
CardDetailsReview(),
],
),
),
],
),
),
],
),
);
}
}
this is all what I did so far, when I wrap pageview inside expanded widget it gives me error,
So is there any one who could find a way to wrap the pageview with expanded widget???
this is all what I did so far, when I wrap pageview inside expanded widget it gives me error,
So is there any one who could find a way to wrap the pageview with expanded widget???
this is all what I did so far, when I wrap pageview inside expanded widget it gives me error,
So is there any one who could find a way to wrap the pageview with expanded widget???
You can follow CustomScrollView with SliverAppBar. Example from SliverAppBar
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
bool _pinned = true;
bool _snap = false;
bool _floating = false;
// [SliverAppBar]s are typically used in [CustomScrollView.slivers], which in
// turn can be placed in a [Scaffold.body].
#override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
pinned: _pinned,
snap: _snap,
floating: _floating,
expandedHeight: 160.0,
flexibleSpace: const FlexibleSpaceBar(
title: Text('SliverAppBar'),
background: FlutterLogo(),
),
),
const SliverToBoxAdapter(
child: SizedBox(
height: 20,
child: Center(
child: Text('Scroll to see the SliverAppBar in effect.'),
),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
color: index.isOdd ? Colors.white : Colors.black12,
height: 100.0,
child: Center(
child: Text('$index', textScaleFactor: 5),
),
);
},
childCount: 20,
),
),
],
),
bottomNavigationBar: BottomAppBar(
child: Padding(
padding: const EdgeInsets.all(8),
child: OverflowBar(
overflowAlignment: OverflowBarAlignment.center,
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('pinned'),
Switch(
onChanged: (bool val) {
setState(() {
_pinned = val;
});
},
value: _pinned,
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('snap'),
Switch(
onChanged: (bool val) {
setState(() {
_snap = val;
// Snapping only applies when the app bar is floating.
_floating = _floating || _snap;
});
},
value: _snap,
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('floating'),
Switch(
onChanged: (bool val) {
setState(() {
_floating = val;
_snap = _snap && _floating;
});
},
value: _floating,
),
],
),
],
),
),
),
);
}
}
i want to implement the sliver app bar as shown in the the 2 pictures given below. After much googling , I fount about the CustomScrollView widget and the SliverAppBar widget but all the tutorials and blogs online about sliver app bars show a simple one where an image disappears into an app bar with a text as title on scrolling. However here what I want to achieve is slightly different and I am having a hard time trying to figure out how to do it. Can anyone help me with it?
You can try this:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
ScrollController? _scrollController;
bool lastStatus = true;
double height = 200;
void _scrollListener() {
if (_isShrink != lastStatus) {
setState(() {
lastStatus = _isShrink;
});
}
}
bool get _isShrink {
return _scrollController != null &&
_scrollController!.hasClients &&
_scrollController!.offset > (height - kToolbarHeight);
}
#override
void initState() {
super.initState();
_scrollController = ScrollController()..addListener(_scrollListener);
}
#override
void dispose() {
_scrollController?.removeListener(_scrollListener);
_scrollController?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final TextTheme textTheme = Theme.of(context).textTheme;
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData.dark(),
title: 'Horizons Weather',
home: Scaffold(
body: NestedScrollView(
controller: _scrollController,
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
SliverAppBar(
elevation: 0,
backgroundColor: Colors.blueGrey,
pinned: true,
expandedHeight: 275,
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.parallax,
title: _isShrink
? const Text(
"Profile",
)
: null,
background: SafeArea(
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 48),
child: ClipRRect(
borderRadius: BorderRadius.circular(100),
child: Image.network(
headerImage,
fit: BoxFit.cover,
height: 100,
width: 100,
),
),
),
const SizedBox(
height: 16,
),
Text(
"Flipkart",
style: textTheme.headline4,
),
const SizedBox(
height: 8,
),
const Text(
"flipkart.com",
),
const SizedBox(
height: 5,
),
const Text(
"Info about the company",
),
],
),
),
),
actions: _isShrink
? [
Padding(
padding: const EdgeInsets.only(left: 8, right: 12),
child: Row(
children: [
Padding(
padding:
const EdgeInsets.only(left: 8, right: 8),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Text(
"Flipkart",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
Text(
"flipkart.com",
style: TextStyle(
fontSize: 12,
),
),
],
),
),
ClipRRect(
borderRadius: BorderRadius.circular(100),
child: Image.network(
headerImage,
fit: BoxFit.cover,
height: 30,
width: 30,
),
),
],
),
),
]
: null,
),
];
},
body: CustomScrollView(
scrollBehavior: const ConstantScrollBehavior(),
slivers: [
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Center(child: Text("Item: $index")),
);
},
childCount: 50,
),
),
],
),
),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
const UserAccountsDrawerHeader(
accountName: Text("Zakaria Hossain"),
accountEmail: Text("zakariaaltime#gmail.com"),
currentAccountPicture: CircleAvatar(
backgroundColor: Colors.orange,
child: Text(
"A",
style: TextStyle(fontSize: 40.0),
),
),
),
ListTile(
leading: Icon(Icons.home),
title: Text("Home"),
onTap: () {
Navigator.pop(context);
},
),
ListTile(
leading: Icon(Icons.settings),
title: Text("Settings"),
onTap: () {
Navigator.pop(context);
},
),
ListTile(
leading: Icon(Icons.contacts),
title: Text("Contact Us"),
onTap: () {
Navigator.pop(context);
},
),
],
),
),
),
);
}
}
Demo
I have a tab bar with a PageView inside each TabBarView. Each PageView has a ListView and when I scroll it, how can I scroll from the Scaffold's SingleChildScrollView rather than just scrolling the ListView inside the TabBarView?
Currently, the tab bar stays on the same position when I scroll the TabBarView, which looks terrible. How can I scroll individual TabBarView from SingleChildScrollView?
I tried tweaking with physics, but didn't turn out the way I wanted it to.
IMAGE :
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
int currIndex = 0;
TabController _tabController;
#override
void initState() {
super.initState();
_tabController =
TabController(vsync: this, length: 3, initialIndex: currIndex);
_tabController.addListener(() {
_handleTabSelection();
});
}
void _handleTabSelection() {
setState(() {
currIndex = _tabController.index == null ? 0 : _tabController.index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: DefaultTabController(
length: 3,
initialIndex: 0,
child: SingleChildScrollView(
child: Container(
height: MediaQuery.of(context).size.height,
child: Column(
children: [
Container(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8),
child: Container(
child: Text(
"HOME",
style: TextStyle(fontSize: 25),
)),
),
_buildTabBar(context),
],
),
),
Expanded(
child: _buildTabBarView(
context,
),
)
],
),
),
),
),
),
);
}
TabBarView _buildTabBarView(BuildContext context) {
return TabBarView(
controller: _tabController,
children: List.generate(
3,
(index) => Container(
color: Colors.red,
child: ListView.builder(
itemCount: 50,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: 60,
color: Colors.blue,
child: Center(child: Text("$index"))),
);
},
),
)));
}
Widget _buildTabBar(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 8.0, right: 8),
child: Container(
height: 45,
width: double.infinity,
decoration: buildTabBarStyle(),
child: TabBar(
controller: _tabController,
isScrollable: false,
tabs: List.generate(
3,
(index) => Center(
child: Text(
"$index",
style: TextStyle(color: Colors.black),
),
)),
),
),
);
}
BoxDecoration buildTabBarStyle() {
return BoxDecoration(
color: Color.fromARGB(255, 230, 248, 255),
border: Border.all(
width: 1,
color: Colors.black,
),
borderRadius: BorderRadius.all(Radius.circular(10)),
);
}
}
Here is a different approach to do it simply.
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with SingleTickerProviderStateMixin {
late final TabController controller;
#override
void initState() {
super.initState();
controller = TabController(length: 3, vsync: this);
}
BoxDecoration buildTabBarStyle() {
return BoxDecoration(
color: Color.fromARGB(255, 230, 248, 255),
border: Border.all(
width: 1,
color: Colors.black,
),
borderRadius: BorderRadius.all(Radius.circular(10)),
);
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: NestedScrollView(
headerSliverBuilder: (context, innerBoxIsScrolled) => [
SliverAppBar(
snap: true,
floating: true,
pinned: false,
toolbarHeight: 80,
title: Text("Home"),
// title: Search(),
centerTitle: true,
bottom: PreferredSize(
preferredSize: Size(0.0, 48.0),
child: Container(
decoration: buildTabBarStyle(),
alignment: Alignment.center,
width: double.infinity,
child: TabBar(
controller: controller,
isScrollable: true,
labelColor: Colors.green,
unselectedLabelColor: Colors.grey,
labelStyle:
TextStyle(fontWeight: FontWeight.bold, fontSize: 16.0),
tabs: [
Tab(text: "1"),
Tab(text: "2"),
Tab(text: "3"),
],
),
),
),
),
],
body: TabBarView(
controller: controller,
children: [
...List.generate(
3,
(t) => Container(
color: Colors.red,
child: ListView.builder(
itemCount: 50,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: 60,
color: Colors.blue,
child: Center(
child: Text("tab $t index $index"),
),
),
);
},
),
),
).toList(),
],
),
),
),
);
}
}