TabBar and TabView without Scaffold and with fixed Widget - flutter

I Created TabBar Without Appbar and Scaffold and fixed Widget above theme.
Now when I added TabBarView nothing show in tabs, So
I created method that gets Index from TabController and should show the Tabs.But I can not see anything again:
#override
Widget build(BuildContext context) {
return Material(
color: Theme.of(context).backgroundColor,
child: ListView(children: <Widget>[
Container(
height: 320,
child: new Carousel(
boxFit: BoxFit.cover,
images: [
photoUrl != null
? NetworkImage(photoUrl)
: NetworkImage(photoUrl),
],
autoplay: true,
dotSize: 6,
indicatorBgPadding: 10,
dotColor: Theme.of(context).backgroundColor,
dotBgColor: Theme.of(context).scaffoldBackgroundColor,
animationCurve: Curves.fastOutSlowIn,
animationDuration: Duration(microseconds: 1000),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child:
Text(name, style: Theme.of(context).textTheme.headline),
),
Padding(
padding: const EdgeInsets.fromLTRB(8, 4, 0, 6),
child:
Text(subtitle, style: Theme.of(context).textTheme.body2),
),
],
),
),
Padding(
padding: const EdgeInsets.fromLTRB(3, 8, 3, 4),
child: Divider(),
),
TabBar(
indicatorColor: Theme.of(context).primaryColor,
labelColor: Theme.of(context).buttonColor,
labelStyle: Theme.of(context).textTheme.headline,
unselectedLabelColor: Theme.of(context).splashColor,
controller: _tabController,
tabs: tabs.map((e) => Tab(text: e)).toList(),
),
_tabsContent(),
]));
}
THE METHOD _tabsContent():
_tabsContent() {
if (_tabController.index == 0) {
return MenuTab();
} else if (_tabController.index == 1) {
return AboutTab(cuisines, dayOfWeek, start, end, desc);
} else if (_tabController.index == 2) {
return ContactTab(
website,
email,
workPhone,
cellPhone,
info,
address1,
address2,
address3,
city,
country,
state,
zipCode,
latitude,
longitude,
socialId,
socialName,
url,
thumbnailsImage);
}
}
THE TABCONTROLLER:
TabController _tabController;
List tabs;
#override
void dispose() {
SystemChrome.restoreSystemUIOverlays();
SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);
_tabController.dispose();
super.dispose();
}
void initState() {
super.initState();
SystemChrome.setEnabledSystemUIOverlays([]);
tabs = ['Menu', 'About', 'Contact'];
_tabController = TabController(length: tabs.length, vsync: this);
}

First: Add this to initState()
_tabController.addListener(_handleTabControllerTick);
Second: create method
void _handleTabControllerTick() {
setState(() {
_currentIndex = _tabController.index;
});
}
Third: change this to:
_tabsContent() {
if (_currentIndex == 0) {
return Container( child: Text("Menu Tab"));
} else if (_currentIndex == 1) {
return Container( child: Text("About Tab"));
} else if (_currentIndex == 2) {
return Container( child: Text("Contact Tab"));
}
}
Also please make sure your _tabsContent() have return the correct content.
Try below code
import 'package:flutter/material.dart';
class testing extends StatefulWidget {
#override
_testingState createState() {
return _testingState();
}
}
class _testingState extends State<testing> with SingleTickerProviderStateMixin {
TabController _tabController;
List tabs;
int _currentIndex = 0;
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
void initState() {
super.initState();
tabs = ['Menu', 'About', 'Contact'];
_tabController = TabController(length: tabs.length, vsync: this);
_tabController.addListener(_handleTabControllerTick);
}
void _handleTabControllerTick() {
setState(() {
_currentIndex = _tabController.index;
});
}
_tabsContent() {
if (_currentIndex == 0) {
return Container( child: Text("Menu Tab"));
} else if (_currentIndex == 1) {
return Container( child: Text("About Tab"));
} else if (_currentIndex == 2) {
return Container( child: Text("Contact Tab"));
}
}
#override
Widget build(BuildContext context) {
return Material(
color: Theme.of(context).backgroundColor,
child: ListView(children: <Widget>[
Container( child: Text("Header")),
Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child:
Text("Name", style: Theme.of(context).textTheme.headline),
),
Padding(
padding: const EdgeInsets.fromLTRB(8, 4, 0, 6),
child:
Text("Subtitle", style: Theme.of(context).textTheme.body2),
),
],
),
),
Padding(
padding: const EdgeInsets.fromLTRB(3, 8, 3, 4),
child: Divider(),
),
TabBar(
indicatorColor: Theme.of(context).primaryColor,
labelColor: Theme.of(context).buttonColor,
labelStyle: Theme.of(context).textTheme.headline,
unselectedLabelColor: Theme.of(context).splashColor,
controller: _tabController,
tabs: tabs.map((e) => Tab(text: e)).toList(),
),
_tabsContent(),
]));
}
}

Related

How can I maintain the height of an animation widget even if it's contained in another widget?

This is what I mean:
As you can see the animation starts from the top to bottom, the problem begins when I integrate another file into it
Example:
Here, I wrapped with another widget and don't respect the height of the app bar
This is my code:
home_page_timer.dart
import 'dart:core';
import 'package:flutter/material.dart';
import 'package:google_nav_bar/google_nav_bar.dart';
import 'package:pomodoro/5.hourglass_animation/countdown_timer/pomodoro_animation.dart';
import 'dart:async';
class HomePageTimerUI extends StatefulWidget {
const HomePageTimerUI({Key? key}) : super(key: key);
#override
State<HomePageTimerUI> createState() => _HomePageTimerUIState();
}
class _HomePageTimerUIState extends State<HomePageTimerUI>
with TickerProviderStateMixin {
late TabController _tabController;
late Timer timer;
late AnimationController controller;
String get countText {
Duration count = controller.duration! * controller.value;
return controller.isDismissed
? '${controller.duration!.inHours.toString().padLeft(2, '0')}:${(controller.duration!.inMinutes % 60).toString().padLeft(2, '0')}:${(controller.duration!.inSeconds % 60).toString().padLeft(2, '0')}'
: '${count.inHours.toString().padLeft(2, '0')}:${(count.inMinutes % 60).toString().padLeft(2, '0')}:${(count.inSeconds % 60).toString().padLeft(2, '0')}';
}
#override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
void notify() {
if (countText == '00:00:00') {
_tabController.animateTo(1, duration: const Duration(milliseconds: 300));
}
}
#override
Widget build(BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height,
child: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.transparent,
bottom: PreferredSize(
preferredSize: const Size.fromHeight(35),
child: Container(
color: Colors.transparent,
child: SafeArea(
child: Column(
children: <Widget>[
TabBar(
controller: _tabController,
indicator: const UnderlineTabIndicator(
borderSide: BorderSide(
color: Color(0xff3B3B3B), width: 4.0),
insets:
EdgeInsets.fromLTRB(12.0, 12.0, 12.0, 11.0)),
indicatorWeight: 15,
indicatorSize: TabBarIndicatorSize.label,
labelColor: const Color(0xff3B3B3B),
labelStyle: const TextStyle(
fontSize: 12,
letterSpacing: 1.3,
fontWeight: FontWeight.w500),
unselectedLabelColor: const Color(0xffD7D7D7),
tabs: const [
Tab(
text: "POMODORO",
icon: Icon(Icons.work_history, size: 35),
),
Tab(
text: "SHORT BREAK",
icon: Icon(Icons.ramen_dining, size: 35),
),
Tab(
text: "LONG BREAK",
icon: Icon(Icons.battery_charging_full_rounded,
size: 35),
),
])
],
),
),
),
),
),
body: TabBarView(
controller: _tabController,
children: <Widget>[
Center(
child: Container(
height: MediaQuery.of(context).size.height,
child: StartPomodoro(end: DateTime.now())),
),
const Center(
child: Text('short break'),
),
const Center(
child: Text('long break '),
),
],
),
),
),
);
}
}
start_pomodoro.dart
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:pomodoro/5.hourglass_animation/countdown_timer/responsive.dart';
class StartPomodoro extends StatefulWidget {
const StartPomodoro({super.key, required this.end});
final DateTime end;
#override
State<StartPomodoro> createState() => _StartPomodoroState();
}
class _StartPomodoroState extends State<StartPomodoro>
with TickerProviderStateMixin {
final now = DateTime.now();
List<bool> isSelected = [true, false];
late Timer timer;
late AnimationController controller;
String get countText {
Duration count = controller.duration! * controller.value;
return controller.isDismissed
? '${controller.duration!.inHours.toString().padLeft(2, '0')}:${(controller.duration!.inMinutes % 60).toString().padLeft(2, '0')}:${(controller.duration!.inSeconds % 60).toString().padLeft(2, '0')}'
: '${count.inHours.toString().padLeft(2, '0')}:${(count.inMinutes % 60).toString().padLeft(2, '0')}:${(count.inSeconds % 60).toString().padLeft(2, '0')}';
}
double progress = 1.0;
bool LongBreak = true;
void notify() {
if (countText == '00:00:00') {}
}
#override
void initState() {
super.initState();
controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 0),
);
controller.addListener(() {
notify();
if (controller.isAnimating) {
setState(() {
progress = controller.value;
});
} else {
setState(() {
progress = 1.0;
LongBreak = true;
});
}
});
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor:
LongBreak ? const Color(0xffD94530) : const Color(0xff6351c5),
body: Stack(
children: [
GestureDetector(
onTap: () {
if (controller.isDismissed) {
showModalBottomSheet(
context: context,
builder: (context) => SizedBox(
height: 300,
child: CupertinoTimerPicker(
initialTimerDuration: controller.duration!,
onTimerDurationChanged: (time) {
setState(() {
controller.duration = time;
});
},
),
),
);
}
},
child: AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Stack(
children: <Widget>[
Align(
alignment: Alignment.bottomCenter,
child: Container(
color: const Color(0xffD94530),
height: controller.value *
MediaQuery.of(context).size.height,
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Responsive(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Align(
alignment: Alignment.bottomCenter,
child: Align(
alignment: FractionalOffset.bottomCenter,
child: Container(
width:
MediaQuery.of(context).size.width,
height: 210,
decoration: const BoxDecoration(
color: Color.fromARGB(
255, 245, 245, 245),
),
child: Container(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
const Text(
"Hyper-focused on... (+add task)",
style: TextStyle(
fontSize: 22.0,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 16),
Center(
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment:
MainAxisAlignment
.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Center(
child: Text(
countText,
style:
const TextStyle(
fontWeight:
FontWeight.w600,
letterSpacing: 4,
fontSize: 65.0,
color: Color(
0xff3B3B3B),
),
),
),
],
),
Row(
mainAxisAlignment:
MainAxisAlignment
.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: const [
Center(
child: Text(
' Hours Minutes Seconds ',
style: TextStyle(
fontWeight:
FontWeight.w500,
letterSpacing: 2,
fontSize: 20.0,
color: Color(
0xff3B3B3B),
),
),
),
],
),
],
),
),
],
),
),
),
),
),
//Spacer(),
Responsive(
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
crossAxisAlignment:
CrossAxisAlignment.stretch,
children: [
AnimatedBuilder(
animation: controller,
builder: (context, child) {
return const Padding(
padding: EdgeInsets.symmetric(
vertical: 2.0,
horizontal: 15.0),
);
}),
AnimatedBuilder(
animation: controller,
builder: (context, child) {
return Padding(
padding:
const EdgeInsets.symmetric(
vertical: 2.0,
horizontal: 15.0),
child: FloatingActionButton
.extended(
backgroundColor:
const Color(
0xffFAFAFA),
onPressed: () {
if (controller
.isAnimating) {
controller.stop();
setState(() {
LongBreak = false;
});
} else {
controller.reverse(
from: controller
.value ==
0
? 1.0
: controller
.value);
setState(() {
LongBreak = false;
});
}
},
icon: Icon(
controller.isAnimating
? Icons.pause
: Icons
.play_arrow,
color: const Color(
0xff3B3B3B),
),
label: Text(
controller.isAnimating
? "Pause"
: "Start",
style: const TextStyle(
color: Color(
0xff3B3B3B)),
)),
);
}),
],
),
),
],
),
),
),
],
);
}),
),
],
),
),
);
}
AnimationController _buildClockAnimation(TickerProvider tickerProvider) {
return AnimationController(
vsync: tickerProvider,
duration: const Duration(milliseconds: 750),
);
}
void _animateLeftDigit(
int prev,
int current,
AnimationController controller,
) {
final prevFirstDigit = (prev / 10).floor();
final currentFirstDigit = (current / 10).floor();
if (prevFirstDigit != currentFirstDigit) {
controller.forward();
}
}
}
How can I provide a height from the animation widget which respects the app bar widget and there is no lag when I started the timer?
What I mean is that I want to start the animation here:
Thank you for any help you can offer
As #Henrique Zanferrari suggested you are using height of the screen in
height: MediaQuery.of(context).size.height
Which is limiting the widgets to follow along with the appBar.
Try replacing this height with more general Widget like Expanded or Flexible like so.

Flutter Dynamic Tab bar with Firestore data

Initially getting this error, but it fixed after few seconds.
The following assertion was thrown building TabBarView(dirty, dependencies: [_TabControllerScope], state: _TabBarViewState#5d34a):
The controller's length property (0) does not match the number of tabs (3) present in TabBar's tabs property.
The relevant error-causing widget was
TabBarView
This is my code.
class HomeTopTabs extends StatefulWidget {
_HomeTopTabsState createState() => _HomeTopTabsState();
}
class _HomeTopTabsState extends State<HomeTopTabs>
with SingleTickerProviderStateMixin {
TabController _tabController;
List<Tab> tabs = [];
int tabCount = 0;
final CollectionReference categoryCollection =
Firestore.instance.collection('categories');
#override
void initState() {
super.initState();
_getCategoryTabs();
// print(tabs.length);
// print(tabCount);
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
void _handleTabSelection() {
setState(() {});
}
Future<void> _getCategoryTabs() async {
await categoryCollection.getDocuments().then((QuerySnapshot snapshot) {
if (snapshot.documents.isNotEmpty) {
List<Tab> _tabs = [];
for (int i = 0; i < snapshot.documents.length; i++) {
DocumentSnapshot snap = snapshot.documents[i];
_tabs.add(
Tab(
child: Text(
snap.documentID,
// style: TextStyle(color: Colors.white),
),
),
);
}
setState(() {
print(_tabs.length.toString() + "inner");
tabCount = _tabs.length;
tabs = _tabs;
});
_tabController = TabController(vsync: this, length: tabCount);
_tabController.addListener(_handleTabSelection);
}
});
}
Tab Bar
TabBar(
controller: _tabController,
isScrollable: true,
indicatorWeight: 0.1,
indicatorPadding: EdgeInsets.all(0.0),
indicatorColor: Colors.transparent,
unselectedLabelColor: Colors.white,
labelColor: Colors.orange,
tabs: tabs)
Whole code....
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
// ignore: must_be_immutable
class HomeTopTabs extends StatefulWidget {
_HomeTopTabsState createState() => _HomeTopTabsState();
}
class _HomeTopTabsState extends State<HomeTopTabs>
with SingleTickerProviderStateMixin {
TabController _tabController;
List<Tab> tabs = [];
int tabCount = 0;
// List<String> str = ["a", "b", "c"];
final CollectionReference categoryCollection =
Firestore.instance.collection('categories');
#override
void initState() {
super.initState();
_getCategoryTabs();
// print(tabs.length);
// print(tabCount);
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
void _handleTabSelection() {
setState(() {});
}
Future<void> _getCategoryTabs() async {
await categoryCollection.getDocuments().then((QuerySnapshot snapshot) {
if (snapshot.documents.isNotEmpty) {
List<Tab> _tabs = [];
for (int i = 0; i < snapshot.documents.length; i++) {
DocumentSnapshot snap = snapshot.documents[i];
// tabCount++;
_tabs.add(
Tab(
child: Text(
snap.documentID,
// style: TextStyle(color: Colors.white),
),
),
);
}
setState(() {
print(_tabs.length.toString() + "inner");
tabCount = _tabs.length;
tabs = _tabs;
_tabController = TabController(vsync: this, length: tabCount);
_tabController.addListener(_handleTabSelection);
});
}
});
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: tabCount,
child: Scaffold(
backgroundColor: Color(0xFF263238),
appBar: AppBar(
leading: null,
elevation: 0.0,
// titleSpacing: 0.0,
backgroundColor: Color(0xFF263238),
title: Text("Top Categories"),
bottom: TabBar(
controller: _tabController,
isScrollable: true,
indicatorWeight: 0.1,
indicatorPadding: EdgeInsets.all(0.0),
indicatorColor: Colors.transparent,
unselectedLabelColor: Colors.white,
labelColor: Colors.orange,
tabs: tabs
/*
[
Tab(
child: Text(
'Indian Recipe',
),
),
Tab(
child: Text(
'Thai Recipe',
),
),
Tab(
child: Text(
'Indian Recipe',
),
),
// Tab(
// child: Text(
// 'Indian Recipe',
// ),
// ),
// Tab(
// child: Text(
// 'Indian Recipe',
// ),
// ),
],*/
)),
body: TabBarView(
controller: _tabController,
children: <Widget>[
Container(
height: 150.0,
child: CustomList(),
),
Container(
height: 150.0,
child: CustomList(),
),
Container(
height: 150.0,
child: CustomList(),
),
// Container(
// height: 150.0,
// child: CustomList(),
// ),
// Container(
// height: 150.0,
// child: CustomList(),
// ),
// Container(
// height: 150.0,
// child: CustomList(),
// ),
],
),
),
);
}
}
class CustomList extends StatelessWidget {
const CustomList({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
final List<String> imgList = [
'https://images.unsplash.com/photo-1520342868574-5fa3804e551c?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=6ff92caffcdd63681a35134a6770ed3b&auto=format&fit=crop&w=1951&q=80',
'https://images.unsplash.com/photo-1522205408450-add114ad53fe?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=368f45b0888aeb0b7b08e3a1084d3ede&auto=format&fit=crop&w=1950&q=80',
'https://images.unsplash.com/photo-1519125323398-675f0ddb6308?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=94a1e718d89ca60a6337a6008341ca50&auto=format&fit=crop&w=1950&q=80',
'https://images.unsplash.com/photo-1523205771623-e0faa4d2813d?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=89719a0d55dd05e2deae4120227e6efc&auto=format&fit=crop&w=1953&q=80',
'https://images.unsplash.com/photo-1508704019882-f9cf40e475b4?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=8c6e5e3aba713b17aa1fe71ab4f0ae5b&auto=format&fit=crop&w=1352&q=80',
'https://images.unsplash.com/photo-1519985176271-adb1088fa94c?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=a0c8d632e977f94e5d312d9893258f59&auto=format&fit=crop&w=1355&q=80'
];
final List<Widget> imageSliders = imgList
.map((item) => Container(
child: Container(
width: 200,
margin: EdgeInsets.all(10.0),
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
child: Stack(
children: <Widget>[
Image.network(item, fit: BoxFit.cover, width: 1000.0),
Positioned(
bottom: 0.0,
left: 0.0,
right: 0.0,
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Color.fromARGB(200, 0, 0, 0),
Color.fromARGB(0, 0, 0, 0)
],
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
),
),
padding: EdgeInsets.symmetric(
vertical: 10.0, horizontal: 20.0),
child: Text(
'Recipe By Priyanka',
style: TextStyle(
color: Colors.white,
fontSize: 15.0,
fontWeight: FontWeight.normal,
),
),
),
),
],
),
),
),
))
.toList();
return ListView(
scrollDirection: Axis.horizontal,
children: imageSliders,
);
}
}
You should initiate any controller in initState
so try to make that your StatefullWidget
void initState() {
_tabController = TabController();
....
}

Flutter - How to make a custom TabBar

This is the output that I want. I am still new in flutter so can anyone let me know if there is already a widget for this kind of switch or how should I make one ??
Also, I want the data shown below this button to change if I choose the other button but I guess that's obvious.
Thanks in advance.
You can use the TabBar widget to achieve this. I added a full example demonstrating how you can create this using the TabBar widget:
CODE
class StackOver extends StatefulWidget {
#override
_StackOverState createState() => _StackOverState();
}
class _StackOverState extends State<StackOver>
with SingleTickerProviderStateMixin {
TabController _tabController;
#override
void initState() {
_tabController = TabController(length: 2, vsync: this);
super.initState();
}
#override
void dispose() {
super.dispose();
_tabController.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Tab bar',
),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
// give the tab bar a height [can change hheight to preferred height]
Container(
height: 45,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(
25.0,
),
),
child: TabBar(
controller: _tabController,
// give the indicator a decoration (color and border radius)
indicator: BoxDecoration(
borderRadius: BorderRadius.circular(
25.0,
),
color: Colors.green,
),
labelColor: Colors.white,
unselectedLabelColor: Colors.black,
tabs: [
// first tab [you can add an icon using the icon property]
Tab(
text: 'Place Bid',
),
// second tab [you can add an icon using the icon property]
Tab(
text: 'Buy Now',
),
],
),
),
// tab bar view here
Expanded(
child: TabBarView(
controller: _tabController,
children: [
// first tab bar view widget
Center(
child: Text(
'Place Bid',
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.w600,
),
),
),
// second tab bar view widget
Center(
child: Text(
'Buy Now',
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.w600,
),
),
),
],
),
),
],
),
),
);
}
}
OUTPUT
Try out this you have to change some colour and font:-
import 'package:flutter/material.dart';
typedef SwitchOnChange = Function(int);
class CustomSwitch extends StatefulWidget {
SwitchOnChange onChange;
CustomSwitch({this.onChange});
#override
State<StatefulWidget> createState() {
return CustomSwitchState();
}
}
class CustomSwitchState extends State<CustomSwitch>
with TickerProviderStateMixin {
AnimationController controller;
Animation animation;
GlobalKey key = GlobalKey();
#override
void initState() {
Future.delayed(Duration(milliseconds: 100)).then((v) {
controller = AnimationController(
vsync: this, duration: Duration(milliseconds: 300));
tabWidth = key.currentContext.size.width / 2;
// var width = (media.size.width - (2 * media.padding.left)) / 2;
animation = Tween<double>(begin: 0, end: tabWidth).animate(controller);
setState(() {});
controller.addListener(() {
setState(() {});
});
});
super.initState();
}
var selectedValue = 0;
double tabWidth = 0;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
selectedValue == 0 ? this.controller.forward() : controller.reverse();
selectedValue = selectedValue == 0 ? 1 : 0;
},
child: Container(
key: key,
height: 44,
decoration: BoxDecoration(
color: Colors.grey, borderRadius: BorderRadius.circular(22)),
child: Stack(
children: <Widget>[
Row(
children: <Widget>[
Transform.translate(
offset: Offset(animation?.value ?? 0, 0),
child: Container(
height: 44,
width: tabWidth,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(22),
boxShadow: [
BoxShadow(color: Colors.grey, blurRadius: 3),
]),
),
),
],
),
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
width: tabWidth,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.directions_walk),
SizedBox(width: 5),
Text("Place Bid")
],
),
),
Container(
width: tabWidth,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.directions_walk),
SizedBox(width: 5),
Text("Buy now")
],
),
)
],
),
),
],
),
),
);
}
}
The following is my workaround, which I believe to be the best method.
import 'package:flutter/material.dart';
class SettingsScreen extends StatelessWidget {
const SettingsScreen({
super.key,
});
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: const Text('Settings'),
bottom: PreferredSize(
preferredSize: Size.fromHeight(AppBar().preferredSize.height),
child: Container(
height: 50,
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 5,
),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(
10,
),
color: Colors.grey[200],
),
child: TabBar(
labelColor: Colors.white,
unselectedLabelColor: Colors.black,
indicator: BoxDecoration(
borderRadius: BorderRadius.circular(
10,
),
color: Colors.pink,
),
tabs: const [
Tab(
text: 'Basic',
),
Tab(
text: 'Advanced',
)
],
),
),
),
),
),
body: const TabBarView(
children: [
Center(
child: Text(
'Basic Settings',
style: TextStyle(
fontSize: 30,
),
),
),
Center(
child: Text(
'Advanced Settings',
style: TextStyle(
fontSize: 30,
),
),
),
],
),
),
);
}
}
You can use also PageView widget.
const double borderRadius = 25.0;
class CustomSwitchState extends StatefulWidget {
#override
_CustomSwitchStateState createState() => _CustomSwitchStateState();
}
class _CustomSwitchStateState extends State<CustomSwitchState> with SingleTickerProviderStateMixin {
PageController _pageController;
int activePageIndex = 0;
#override
void dispose() {
_pageController.dispose();
super.dispose();
}
#override
void initState() {
super.initState();
_pageController = PageController();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
physics: const ClampingScrollPhysics(),
child: GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 20.0),
child: _menuBar(context),
),
Expanded(
flex: 2,
child: PageView(
controller: _pageController,
physics: const ClampingScrollPhysics(),
onPageChanged: (int i) {
FocusScope.of(context).requestFocus(FocusNode());
setState(() {
activePageIndex = i;
});
},
children: <Widget>[
ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: Center(child: Text("Place Bid"),),
),
ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: Center(child: Text("Buy Now"),),
),
],
),
),
],
),
),
),
));
}
Widget _menuBar(BuildContext context) {
return Container(
width: 300.0,
height: 50.0,
decoration: const BoxDecoration(
color: Color(0XFFE0E0E0),
borderRadius: BorderRadius.all(Radius.circular(borderRadius)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Expanded(
child: InkWell(
borderRadius: BorderRadius.all(Radius.circular(borderRadius)),
onTap: _onPlaceBidButtonPress,
child: Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.symmetric(vertical: 15),
alignment: Alignment.center,
decoration: (activePageIndex == 0) ? const BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.all(Radius.circular(borderRadius)),
) : null,
child: Text(
"Place Bid",
style: (activePageIndex == 0) ? TextStyle(color: Colors.white) : TextStyle(color: Colors.black),
),
),
),
),
Expanded(
child: InkWell(
borderRadius: BorderRadius.all(Radius.circular(borderRadius)),
onTap: _onBuyNowButtonPress,
child: Container(
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.symmetric(vertical: 15),
alignment: Alignment.center,
decoration: (activePageIndex == 1) ? const BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.all(Radius.circular(borderRadius)),
) : null,
child: Text(
"Buy Now",
style: (activePageIndex == 1) ? TextStyle(color: Colors.white, fontWeight: FontWeight.bold) : TextStyle(color: Colors.black, fontWeight: FontWeight.bold),
),
),
),
),
],
),
);
}
void _onPlaceBidButtonPress() {
_pageController.animateToPage(0,
duration: const Duration(milliseconds: 500), curve: Curves.decelerate);
}
void _onBuyNowButtonPress() {
_pageController.animateToPage(1,
duration: const Duration(milliseconds: 500), curve: Curves.decelerate);
}
}
OUTPUT
If you want tab layout like this you can use this
Output:
import 'package:flutter/material.dart';
import 'package:icons_helper/icons_helper.dart';
class DetailScreen extends StatefulWidget {
var body;
String title = "";
DetailScreen(this.body, this.title);
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<DetailScreen> with TickerProviderStateMixin {
late int _startingTabCount;
List<Tab> _tabs = <Tab>[];
List<Widget> _generalWidgets = <Widget>[];
late TabController _tabController;
#override
void initState() {
_startingTabCount = widget.body["related_modules"].length;
_tabs = getTabs(_startingTabCount);
_tabController = getTabController();
super.initState();
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
bottom: TabBar(
isScrollable: true,
tabs: _tabs,
controller: _tabController,
),
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.grey,
Colors.blue,
],
stops: [0.3, 1.0],
),
),
),
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
color: Colors.white,
onPressed: () {
Navigator.of(context, rootNavigator: true).pop(context);
},
),
actions: <Widget>[
IconButton(
icon: Icon(Icons.skip_previous),
color: Colors.white,
onPressed: () {
goToPreviousPage();
},
),
Container(
margin: EdgeInsets.only(right: 15),
child: IconButton(
icon: Icon(Icons.skip_next),
color: Colors.white,
onPressed: () {
goToNextPage();
},
),
)
],
),
body: Column(
children: <Widget>[
Expanded(
child: TabBarView(
physics: NeverScrollableScrollPhysics(),
controller: _tabController,
children: getWidgets(),
),
),
],
),
);
}
TabController getTabController() {
return TabController(length: _tabs.length, vsync: this)
..addListener(_updatePage);
}
Tab getTab(int widgetNumber) {
return Tab(
icon: Column(
children: [
if (widget.body["related_modules"][widgetNumber]["icon"].toString() ==
"fa-comments-o") ...[
Icon(
Icons.comment_outlined,
),
] else if (widget.body["related_modules"][widgetNumber]["icon"]
.toString() ==
"fa-map-marker") ...[
Icon(
Icons.location_on_rounded,
),
] else if (widget.body["related_modules"][widgetNumber]["icon"]
.toString() ==
"fa-address-card") ...[
Icon(
Icons.contact_page_sharp,
),
] else ...[
Icon(
getIconUsingPrefix(
name: widget.body["related_modules"][widgetNumber]["icon"]
.toString()
.substring(3),
),
)
]
],
),
text: widget.body["related_modules"][widgetNumber]["label"].toString(),
);
}
Widget getWidget(int widgetNumber) {
return Center(
child: Text("Widget nr: $widgetNumber"),
);
}
List<Tab> getTabs(int count) {
_tabs.clear();
for (int i = 0; i < count; i++) {
_tabs.add(getTab(i));
}
return _tabs;
}
List<Widget> getWidgets() {
_generalWidgets.clear();
for (int i = 0; i < _tabs.length; i++) {
_generalWidgets.add(getWidget(i));
}
return _generalWidgets;
}
void _updatePage() {
setState(() {});
}
//Tab helpers
bool isFirstPage() {
return _tabController.index == 0;
}
bool isLastPage() {
return _tabController.index == _tabController.length - 1;
}
void goToPreviousPage() {
_tabController.animateTo(_tabController.index - 1);
}
void goToNextPage() {
isLastPage()
? showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text("End reached"),
content: Text("This is the last page.")))
: _tabController.animateTo(_tabController.index + 1);
}
}

No TabController for TabBarView flutter

I am trying add TabBar by using the below code:
TabBarView(
children: [
Icon(Icons.directions_car),
Icon(Icons.directions_transit),
Icon(Icons.directions_bike),
],
),
but I found the below error:
No TabController for TabBarView.
and this is whole code:
import '../providers/properties.dart';
import '../providers/cities.dart';
import '../providers/property.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../widgets/properties_grid.dart';
import '../app_theme.dart';
class MyHomePage extends StatefulWidget {
const MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
int currentTab = 0;
final PageStorageBucket bucket = PageStorageBucket();
var _showOnlyFavorites = false;
// List<HomeList> homeList = HomeList.homeList;
AnimationController animationController;
bool multiple = true;
#override
void initState() {
animationController = AnimationController(
duration: const Duration(milliseconds: 2000), vsync: this);
super.initState();
}
Future<bool> getData() async {
await Future<dynamic>.delayed(const Duration(milliseconds: 0));
return true;
}
#override
void dispose() {
animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
// final properties = Provider.of<Properties>(context, listen: false);
return Scaffold(
extendBody: true,
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {},
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
bottomNavigationBar: BottomAppBar(
elevation: 0,
shape: CircularNotchedRectangle(),
notchMargin: 10,
child: Container(
height: 60,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
MaterialButton(
padding: EdgeInsets.all(0),
minWidth: 155,
onPressed: () {
setState(() {
// currentScreen =
// Chat(); // if user taps on this dashboard tab will be active
currentTab = 1;
});
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.home,
color: currentTab == 1 ? Colors.blue : Colors.grey,
),
Text(
'Home',
style: TextStyle(
color: currentTab == 1 ? Colors.blue : Colors.grey,
),
),
],
),
)
],
),
// Right Tab bar icons
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
MaterialButton(
padding: EdgeInsets.all(0),
minWidth: 60,
onPressed: () {
setState(() {
// currentScreen =
// Settings(); // if user taps on this dashboard tab will be active
currentTab = 3;
});
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.view_list,
color: currentTab == 3 ? Colors.blue : Colors.grey,
),
Text(
'Property List',
style: TextStyle(
color: currentTab == 3 ? Colors.blue : Colors.grey,
),
),
],
),
),
MaterialButton(
padding: EdgeInsets.all(0),
minWidth: 77,
onPressed: () {
setState(() {
// currentScreen =
// Settings(); // if user taps on this dashboard tab will be active
currentTab = 4;
});
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(
Icons.location_searching,
color: currentTab == 4 ? Colors.blue : Colors.grey,
),
Text(
'Map',
style: TextStyle(
color: currentTab == 4 ? Colors.blue : Colors.grey,
),
),
],
),
),
],
)
],
),
),
),
backgroundColor: AppTheme.white,
body: Stack(
children: <Widget>[
FutureBuilder<bool>(
future: getData(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
if (!snapshot.hasData) {
return const SizedBox();
} else {
return Padding(
padding:
EdgeInsets.only(top: MediaQuery.of(context).padding.top),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
appBar(),
TabBarView(
children: [
Icon(Icons.directions_car),
Icon(Icons.directions_transit),
Icon(Icons.directions_bike),
],
),
Expanded(
child: FutureBuilder<bool>(
future: getData(),
builder: (BuildContext context,
AsyncSnapshot<bool> snapshot) {
if (!snapshot.hasData) {
return const SizedBox();
} else {
return ChangeNotifierProvider(
create: (context) => Properties(),
child: PropertiesGrid(_showOnlyFavorites),
);
}
},
),
),
],
),
);
}
},
),
],
),
);
}
Widget appBar() {
return SizedBox(
height: AppBar().preferredSize.height,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 8, left: 8),
child: Container(
width: AppBar().preferredSize.height - 8,
height: AppBar().preferredSize.height - 8,
),
),
Expanded(
child: Center(
child: Padding(
padding: const EdgeInsets.only(top: 4),
child:
Image.asset('assets/images/logo.png', fit: BoxFit.contain),
),
),
),
Padding(
padding: const EdgeInsets.only(top: 8, right: 8),
child: Container(
width: AppBar().preferredSize.height - 8,
height: AppBar().preferredSize.height - 8,
color: Colors.white,
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius:
BorderRadius.circular(AppBar().preferredSize.height),
child: Icon(
Icons.location_on,
color: AppTheme.dark_grey,
),
onTap: () {
setState(() {
multiple = !multiple;
});
},
),
),
),
),
],
),
);
}
So How Can I solve this problem...
How can TabBar get to know about the TabBarView? There should be a connection between them to change when tab press or if swap from view right?
So, to connect both two, you have to either wrap your parent widget using DefaultTabController or providing a TabController for TabBar and TabBarView to controll and configure Tabs.
Flutter cookbook example for DefaultTabController:
import 'package:flutter/material.dart';
void main() {
runApp(TabBarDemo());
}
class TabBarDemo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: [
Tab(icon: Icon(Icons.directions_car)),
Tab(icon: Icon(Icons.directions_transit)),
Tab(icon: Icon(Icons.directions_bike)),
],
),
title: Text('Tabs Demo'),
),
body: TabBarView(
children: [
Icon(Icons.directions_car),
Icon(Icons.directions_transit),
Icon(Icons.directions_bike),
],
),
),
),
);
}
}
Using TabController(Example from doc):
class MyTabbedPage extends StatefulWidget {
const MyTabbedPage({ Key key }) : super(key: key);
#override
_MyTabbedPageState createState() => _MyTabbedPageState();
}
class _MyTabbedPageState extends State<MyTabbedPage> with SingleTickerProviderStateMixin {
final List<Tab> myTabs = <Tab>[
Tab(text: 'LEFT'),
Tab(text: 'RIGHT'),
];
TabController _tabController;
#override
void initState() {
super.initState();
_tabController = TabController(vsync: this, length: myTabs.length);
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
bottom: TabBar(
controller: _tabController,
tabs: myTabs,
),
),
body: TabBarView(
controller: _tabController,
children: myTabs.map((Tab tab) {
final String label = tab.text.toLowerCase();
return Center(
child: Text(
'This is the $label tab',
style: const TextStyle(fontSize: 36),
),
);
}).toList(),
),
);
}
}

How to ignore touches for a certain GestureDetector widget and detect outside touches in Flutter?

I created a drop down widget but when I touch widgets like Text widgets or free space inside it drop down height jump to touched position. How to ignore this touches?
I used IgnorePointer widget but it also disabled Switch widgets.
Also, how to detect outside touches to close the drop down widget?
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:movie_god/MyApp.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget{
#override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp>{
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Flutter!'),
),
body: Stack(
children: <Widget>[
Container(
color: Colors.blueGrey[200],
child: Center(
child: Text('Widgets'),
),
),
BottomFilter()
],
),
),
);
}
}
class BottomFilter extends StatefulWidget{
#override
State<StatefulWidget> createState() => BottomFilterState();
}
class BottomFilterState extends State<BottomFilter> with SingleTickerProviderStateMixin{
double _minHeight = 20;
double _height;
double _maxHeight = 200;
double _transparentHeight = 30;
AnimationController _controller;
Animation _animation;
Map<String,dynamic> _switches = {
'switch1' : false,
'switch2' : false,
'switch3' : false,
'switch4' : false,
'option' : null
};
List<String> _options = <String>[];
#override
void initState() {
_controller = AnimationController(vsync: this,duration: Duration(milliseconds: 500));
_animation = Tween(begin: _minHeight+_transparentHeight, end: _maxHeight).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut));
super.initState();
}
#override
Widget build(BuildContext context) {
Size _size = MediaQuery.of(context).size;
return GestureDetector(
onVerticalDragUpdate: (drag){
setState(() {
_controller.reset();
double _postion = drag.globalPosition.dy-kToolbarHeight-_minHeight-_transparentHeight;
print(_postion.toString());
if(_postion<0){
_height=_minHeight+_transparentHeight;
} else if(_postion>_maxHeight){
double _newHeight = _maxHeight+_transparentHeight+_minHeight + ((_size.height-_postion)/_size.height)*((_postion-_maxHeight));
_height < _newHeight ? _height = _newHeight: null;
} else{
_height = _postion+_transparentHeight+_minHeight;
}
_animation = Tween(begin: _height, end: _maxHeight).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut));
});
},
onVerticalDragEnd: (drag){
if(_height>_maxHeight || _height>=_maxHeight/2){
_animation = Tween(begin: _height, end: _maxHeight).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut));
_controller.forward();
}else if(_height<_maxHeight/2){
_animation = Tween(begin: _height, end: _minHeight+_transparentHeight).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut));
_controller.forward();
}
},
child: AnimatedBuilder(
animation: _controller,
builder: (context,Widget child){
return Stack(
children: <Widget>[
Positioned(
bottom: _size.height - (kToolbarHeight + 20 + _animation.value),
child: child,
)
],
);
},
child: Container(
height: 400,
width: _size.width,
color: Colors.transparent,
child: Padding(
padding: EdgeInsets.only(bottom: _transparentHeight),
child: Container(
height: 300,
alignment: Alignment.bottomCenter,
width: _size.width,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(bottom: Radius.circular(20))
),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Text('switch1',style: TextStyle(color: Colors.blueGrey[800]),),
Switch.adaptive(
inactiveThumbColor: Colors.blue,
value: _switches['switch1'],
onChanged: (value){
setState(() {
_switches['switch1'] = value;
});
},
),
Text('switch2',style: TextStyle(color: Colors.blueGrey[800]),),
Switch.adaptive(
value: _switches['switch2'],
onChanged: (value){
setState(() {
_switches['switch2'] = value;
});
},
),
Text('switch3',style: TextStyle(color: Colors.blueGrey[800]),),
Switch.adaptive(
value: _switches['switch3'],
onChanged: (value){
setState(() {
_switches['switch3'] = value;
});
},
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Theme(
data: Theme.of(context).copyWith(
canvasColor: Colors.white,
brightness: Brightness.light
),
child: Row(
children: <Widget>[
DropdownButton<String>(
value: _switches['option'],
hint: Text('sample1',style: TextStyle(color: Colors.blueGrey[800]),),
style: TextStyle(
color: Colors.blueGrey[800]
),
onChanged: (String value){
if(value != null){
setState(() {
_switches['option'] = value;
print(_switches['option']);
});
}
},
items: <String>['option1','option2','option3','option4','option5','option6'].map<DropdownMenuItem<String>>((value){
return DropdownMenuItem<String>(
value: value,
child : Align(child: Text(value),alignment: Alignment(1, 0),)
);
}).toList(),
),
],
),
),
Theme(
data: Theme.of(context).copyWith(
canvasColor: Colors.white,
brightness: Brightness.light
),
child: Row(
children: <Widget>[
DropdownButton<String>(
hint: Text('sample2',style: TextStyle(color: Colors.blueGrey[800]),),
style: TextStyle(
color: Colors.blueGrey[800]
),
onChanged: (String value){
if(value != null){
_options.indexOf(value)<0?
setState(() {
_options.add(value);
}) : null;
}
},
items: <String>['option1','option2','option3','option4','option5','option6'].map<DropdownMenuItem<String>>((value){
return DropdownMenuItem<String>(
value: value,
child : Align(child: Text(value),alignment: Alignment(1, 0),)
);
}).toList(),
),
],
),
),
],
),
SizedBox(
height: 50,
child: ListView(
shrinkWrap: false,
scrollDirection: Axis.horizontal,
children: _genresGenerator(),
),
),
Align(
alignment: Alignment.bottomCenter,
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(horizontal: 40),
child: Divider(
color: Colors.blueGrey[500],
height: 10,
indent: 5,
),
),
Icon(FontAwesomeIcons.angleDoubleDown,size: 15,color: Colors.blueGrey[500],)
],
),
)
],
),
),
)
),
),
);
}
List<Widget> _genresGenerator() {
List<Widget> _optionsWidgets = _options.map<Widget>((String name) {
return InputChip(
key: ValueKey<String>(name),
label: Text(name),
onDeleted: () {
setState(() {
_removeTool(name);
});
});
}).toList();
return _optionsWidgets;
}
void _removeTool(String name) {
_options.remove(name);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
}
To collapse your drawer from, you can send a command to the child Widget from the parent Widget. Configure a Stream inside BottomFilter to listen for commands if the drawer should be retracted.
class BottomFilter extends StatefulWidget {
BottomFilter({Key? key, required Stream<bool> stream})
: stream = stream,
super(key: key);
final Stream<bool> stream;
#override
State<StatefulWidget> createState() => BottomFilterState();
}
Inside BottomFilterState, configure a function that does the collapse animation.
void close() {
setState(() {
_animation = Tween(begin: _height, end: 50)
.animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut));
});
}
Then setup the Stream listener inside initState()
#override
void initState() {
...
widget.stream.listen((bool isExpand) {
/// Collapse widget if [isExpand] is false
if(!isExpand) close();
});
super.initState();
}
In MyAppState, initialize your StreamController.
class MyAppState extends State<MyApp> {
var _expandStreamController = StreamController<bool>();
#override
void dispose() {
// Close the Stream when done
_expandStreamController.close();
super.dispose();
}
...
}
Add a GestureDetector on your screen to detect touches that'll prompt to collapse the widget.
Stack(
children: <Widget>[
GestureDetector(
onTap: () {
/// Collapse the widget
_expandStreamController.add(false);
},
child: Container(
color: Colors.blueGrey[200],
child: Center(
child: Text('Widgets'),
),
),
),
BottomFilter(
stream: _expandStreamController.stream,
),
],
),
Complete code, updated from the repro provided.
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
var _expandStreamController = StreamController<bool>();
#override
void dispose() {
_expandStreamController.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Flutter '),
),
body: Stack(
children: <Widget>[
GestureDetector(
onTap: () {
debugPrint('Close Drawer');
_expandStreamController.add(false);
},
child: Container(
color: Colors.blueGrey[200],
child: Center(
child: Text('Widgets'),
),
),
),
BottomFilter(
stream: _expandStreamController.stream,
)
],
),
),
);
}
}
class BottomFilter extends StatefulWidget {
BottomFilter({Key? key, required Stream<bool> stream})
: stream = stream,
super(key: key);
final Stream<bool> stream;
#override
State<StatefulWidget> createState() => BottomFilterState();
}
class BottomFilterState extends State<BottomFilter>
with SingleTickerProviderStateMixin {
double _minHeight = 20;
late double _height;
double _maxHeight = 200;
double _transparentHeight = 30;
late AnimationController _controller;
late Animation _animation;
Map<String, dynamic> _switches = {
'switch1': false,
'switch2': false,
'switch3': false,
'switch4': false,
'option': null
};
List<String> _options = <String>[];
void close() {
setState(() {
_animation = Tween(begin: _height, end: 50)
.animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut));
});
}
#override
void initState() {
_controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 500));
_animation = Tween(begin: _minHeight + _transparentHeight, end: _maxHeight)
.animate(CurvedAnimation(parent: _controller, curve: Curves.easeOut));
widget.stream.listen((bool isExpand) {
if(!isExpand) close();
});
super.initState();
}
#override
Widget build(BuildContext context) {
Size _size = MediaQuery.of(context).size;
return GestureDetector(
onTap: () {
debugPrint(
'onTap\nheight: $_height\nminHeight: $_minHeight\nmaxHeight: $_maxHeight');
close();
},
onVerticalDragUpdate: (drag) {
setState(() {
_controller.reset();
double _position = drag.globalPosition.dy -
kToolbarHeight -
_minHeight -
_transparentHeight;
print(_position.toString());
if (_position < 0) {
_height = _minHeight + _transparentHeight;
} else if (_position > _maxHeight) {
double _newHeight = _maxHeight +
_transparentHeight +
_minHeight +
((_size.height - _position) / _size.height) *
((_position - _maxHeight));
_height < _newHeight ? _height = _newHeight : null;
} else {
_height = _position + _transparentHeight + _minHeight;
}
_animation = Tween(begin: _height, end: _maxHeight).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeOut));
});
},
onVerticalDragEnd: (drag) {
if (_height > _maxHeight || _height >= _maxHeight / 2) {
_animation = Tween(begin: _height, end: _maxHeight).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeOut));
_controller.forward();
} else if (_height < _maxHeight / 2) {
_animation = Tween(
begin: _height, end: _minHeight + _transparentHeight)
.animate(
CurvedAnimation(parent: _controller, curve: Curves.easeOut));
_controller.forward();
}
},
child: AnimatedBuilder(
animation: _controller,
builder: (context, Widget? child) {
return Stack(
children: <Widget>[
Positioned(
bottom: _size.height - (kToolbarHeight + 20 + _animation.value),
child: child!,
)
],
);
},
child: Container(
height: 400,
width: _size.width,
color: Colors.transparent,
child: Padding(
padding: EdgeInsets.only(bottom: _transparentHeight),
child: Container(
height: 300,
alignment: Alignment.bottomCenter,
width: _size.width,
decoration: BoxDecoration(
color: Colors.white,
borderRadius:
BorderRadius.vertical(bottom: Radius.circular(20))),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Text(
'switch1',
style: TextStyle(color: Colors.blueGrey[800]),
),
Switch.adaptive(
inactiveThumbColor: Colors.blue,
value: _switches['switch1'],
onChanged: (value) {
setState(() {
_switches['switch1'] = value;
});
},
),
Text(
'switch2',
style: TextStyle(color: Colors.blueGrey[800]),
),
Switch.adaptive(
value: _switches['switch2'],
onChanged: (value) {
setState(() {
_switches['switch2'] = value;
});
},
),
Text(
'switch3',
style: TextStyle(color: Colors.blueGrey[800]),
),
Switch.adaptive(
value: _switches['switch3'],
onChanged: (value) {
setState(() {
_switches['switch3'] = value;
});
},
),
],
),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Theme(
data: Theme.of(context).copyWith(
canvasColor: Colors.white,
brightness: Brightness.light),
child: Row(
children: <Widget>[
DropdownButton<String>(
value: _switches['option'],
hint: Text(
'sample1',
style: TextStyle(color: Colors.blueGrey[800]),
),
style: TextStyle(color: Colors.blueGrey[800]),
onChanged: (String? value) {
if (value == null) {
setState(() {
_switches['option'] = value;
print(_switches['option']);
});
}
},
items: <String>[
'option1',
'option2',
'option3',
'option4',
'option5',
'option6'
].map<DropdownMenuItem<String>>((value) {
return DropdownMenuItem<String>(
value: value,
child: Align(
child: Text(value),
alignment: Alignment(1, 0),
));
}).toList(),
),
],
),
),
Theme(
data: Theme.of(context).copyWith(
canvasColor: Colors.white,
brightness: Brightness.light),
child: Row(
children: <Widget>[
DropdownButton<String>(
hint: Text(
'sample2',
style: TextStyle(color: Colors.blueGrey[800]),
),
style: TextStyle(color: Colors.blueGrey[800]),
// onChanged: (String? value) {
// if (value == null) {
// _options.indexOf(value) < 0
// ? setState(() {
// _options.add(value);
// })
// : null;
// }
// },
items: <String>[
'option1',
'option2',
'option3',
'option4',
'option5',
'option6'
].map<DropdownMenuItem<String>>((value) {
return DropdownMenuItem<String>(
value: value,
child: Align(
child: Text(value),
alignment: Alignment(1, 0),
));
}).toList(),
),
],
),
),
],
),
SizedBox(
height: 50,
child: ListView(
shrinkWrap: false,
scrollDirection: Axis.horizontal,
children: _genresGenerator(),
),
),
Align(
alignment: Alignment.bottomCenter,
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(horizontal: 40),
child: Divider(
color: Colors.blueGrey[500],
height: 10,
indent: 5,
),
),
Icon(
Icons.arrow_drop_down,
size: 15,
color: Colors.blueGrey[500],
)
],
),
)
],
),
),
)),
),
);
}
List<Widget> _genresGenerator() {
List<Widget> _optionsWidgets = _options.map<Widget>((String name) {
return InputChip(
key: ValueKey<String>(name),
label: Text(name),
onDeleted: () {
setState(() {
_removeTool(name);
});
});
}).toList();
return _optionsWidgets;
}
void _removeTool(String name) {
_options.remove(name);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
}