So I was trying to build a user id page for my flutter app where you tap on a container and the containers height is increased and a different set of data is shown. On expanded I also wanted to add a scrollable tabview and that's second part of the problem.
the expected ui looks like thishttps://i.stack.imgur.com/62sro.gif.
I have tried Expanded and expansion tile, Can't quite achieve the output
Is there any other method to achieve this?
Welcome #Anand Pillai,
First add this line to your pubspec.yaml expandable: ^5.0.1
try this code
import 'package:expandable/expandable.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late PageController _pageController;
final ExpandableController _controller = ExpandableController();
int activePage = 1;
int _counter = 0;
List<String> images = [
"https://images.pexels.com/photos/14686142/pexels-photo-14686142.jpeg",
"https://wallpaperaccess.com/full/2637581.jpg",
"https://uhdwallpapers.org/uploads/converted/20/01/14/the-mandalorian-5k-1920x1080_477555-mm-90.jpg"
];
List<Widget> indicators(imagesLength, currentIndex) {
return List<Widget>.generate(imagesLength, (index) {
return Container(
margin: const EdgeInsets.all(3),
width: 10,
height: 10,
decoration: BoxDecoration(
color: currentIndex == index ? Colors.white : Colors.blueGrey,
shape: BoxShape.circle),
);
});
}
AnimatedContainer slider(images, pagePosition, active) {
// double margin = active ? 10 : 20;
return AnimatedContainer(
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOutCubic,
// margin: EdgeInsets.all(margin),
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(images[pagePosition]),
fit: BoxFit.cover,
)),
);
}
#override
void initState() {
super.initState();
_pageController = PageController();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Stack(
alignment: Alignment.center,
children: [imageSlider(), expandedWidget(context)],
),
),
],
));
}
Positioned expandedWidget(BuildContext context) {
return Positioned.fill(
bottom: _controller.expanded ? 0 : 60,
left: 0,
right: 0,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_controller.expanded
? const SizedBox.shrink()
: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: indicators(images.length, activePage)),
ExpandableNotifier(
child: AnimatedContainer(
height: _controller.expanded ? 400 : 110.0,
width: double.infinity,
alignment: Alignment.bottomCenter,
padding: const EdgeInsets.all(15.0),
margin: _controller.expanded
? EdgeInsets.zero
: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: const Color.fromARGB(255, 255, 79, 77).withOpacity(0.8),
borderRadius: _controller.expanded
? const BorderRadius.only(
topRight: Radius.circular(15),
topLeft: Radius.circular(15),
)
: BorderRadius.circular(15.0),
),
duration: const Duration(milliseconds: 500),
child: Column(
children: <Widget>[
ScrollOnExpand(
scrollOnExpand: true,
scrollOnCollapse: false,
child: ExpandablePanel(
controller: _controller
..addListener(() {
setState(() {});
}),
theme: const ExpandableThemeData(
headerAlignment: ExpandablePanelHeaderAlignment.center,
tapBodyToCollapse: true,
iconColor: Colors.white,
),
header: Padding(
padding: const EdgeInsets.all(10),
child: Text(
"ExpandablePanel",
style: Theme.of(context)
.textTheme
.bodyText1!
.copyWith(color: Colors.white),
)),
collapsed: const Text(
"loremIpsum",
style: TextStyle(color: Colors.white),
softWrap: true,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
expanded: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
for (var _ in Iterable.generate(5))
const Padding(
padding: EdgeInsets.only(bottom: 10),
child: Text(
"loremIpsum",
style: TextStyle(color: Colors.white),
softWrap: true,
overflow: TextOverflow.fade,
)),
],
),
builder: (_, collapsed, expanded) {
return Padding(
padding: const EdgeInsets.only(
left: 10, right: 10, bottom: 10),
child: Expandable(
collapsed: collapsed,
expanded: expanded,
theme: const ExpandableThemeData(crossFadePoint: 0),
),
);
},
),
),
],
),
)),
],
));
}
PageView imageSlider() {
return PageView.builder(
itemCount: images.length,
physics: _controller.expanded
? const NeverScrollableScrollPhysics()
: ScrollPhysics(),
padEnds: false,
controller: _pageController,
onPageChanged: (page) {
setState(() {
activePage = page;
});
},
itemBuilder: (context, pagePosition) {
bool active = pagePosition == activePage;
return slider(images, pagePosition, active);
});
}
}
class _MyHomePageState extends State<MyHomePage> {
double _margin = 30, _height = 100, _width = 300;
final Text _widget1 = const Text('This is my Foo');
final Text _widget2 = const Text('This is Bar');
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: GestureDetector(
// When the child is tapped, set state is called.
onTap: () {
setState(() {
_margin = _margin == 30 ? 0 : 30;
_height = _height == 100 ? 300 : 100;
_width = _width == 300 ? MediaQuery.of(context).size.width : 300;
});
},
// The custom button
child: Align(
alignment: Alignment.bottomCenter,
child: AnimatedContainer(
width: _width,
height: _height,
curve: Curves.easeInExpo,
margin: EdgeInsets.fromLTRB(_margin, 0, _margin, _margin),
duration: Duration(milliseconds: 250),
padding: const EdgeInsets.all(0),
decoration: BoxDecoration(
color: Colors.lightBlue,
borderRadius: BorderRadius.circular(8.0),
),
child: _margin == 30 ? _widget1 : _widget2,
),
),
)),
);
}
}
Simple logic is to animate the container when tapped and change the widget in it. On tap it calls setsate that sets the height, width, margin and child of the container.
Related
The problem is described like this.
In a web environment, I have to build a horizontal list of images (like in Netflix) which should increase the size of the element when the user positions the mouse cursor over them. To achieve this, I'm using a Stack (with clipBehavior equals to Clip.none) to render each item in the list, when I detect the mouse-over event I add a new Container (larger than the size of the original item) to draw an AnimatedContainer inside which will grow to fill it.
The animation works great, but the container gets positioned down to the next right item on the list, however, I need it above the item.
Here is the code:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
final double zoomTargetHeight = 320;
final double zoomTargetWidth = 500;
final double zoomOriginalHeight = 225;
final double zoomOriginalWidth = 400;
double _zoomHeight = 225;
double _zoomWidth = 400;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: SingleChildScrollView(
child: Column(
children: [
Image.network("https://source.unsplash.com/random/1600x900?cars"),
Container(
color: Colors.black87,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 12,
),
const Text(
"List of items",
style: TextStyle(color: Colors.white),
),
const SizedBox(
height: 12,
),
SizedBox(
height: 235,
child: ListView.separated(
clipBehavior: Clip.none,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return buildCard(index);
},
separatorBuilder: (context, index) {
return const SizedBox(
width: 12,
);
},
itemCount: 10,
),
),
const SizedBox(
height: 200,
),
],
),
),
),
],
),
),
);
}
Map _showZoom = {};
Widget buildCard(int index) {
Stack stack = Stack(
clipBehavior: Clip.none,
children: [
MouseRegion(
onEnter: (event) {
setState(() {
_showZoom["$index"] = true;
});
},
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Stack(
children: [
Image.network(
"https://source.unsplash.com/random/400x225?sig=$index&cars"),
Container(
color: Colors.black.withAlpha(100),
height: zoomOriginalHeight,
width: zoomOriginalWidth,
),
],
),
),
),
if (_showZoom["$index"] != null && _showZoom["$index"]!)
Positioned(
left: (zoomOriginalWidth - zoomTargetWidth) / 2,
top: (zoomOriginalHeight - zoomTargetHeight) / 2,
child: MouseRegion(
onHover: (_) {
setState(() {
_zoomHeight = zoomTargetHeight;
_zoomWidth = zoomTargetWidth;
});
},
onExit: (event) {
setState(() {
_showZoom["$index"] = false;
_zoomHeight = zoomOriginalHeight;
_zoomWidth = zoomOriginalWidth;
});
},
child: SizedBox(
width: zoomTargetWidth,
height: zoomTargetHeight,
child: Center(
child: AnimatedContainer(
duration: const Duration(milliseconds: 400),
width: _zoomWidth,
height: _zoomHeight,
// color: Colors.green.withAlpha(100),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
image: DecorationImage(
image: NetworkImage(
"https://source.unsplash.com/random/400x225?sig=$index&cars"),
fit: BoxFit.cover,
),
),
),
),
),
),
),
],
);
return stack;
}
}
Remember flutter config --enable-web
I think this is precisely what you are looking for (Check also the live demo on DartPad):
The solution is:
Use an outer Stack that wraps the ListView;
Add another ListView in front of it in the Stack with the same number of items and same item sizes;
Then, ignore the pointer-events with IgnorePointer on this new ListView so the back one will receive the scroll/tap/click events;
Synchronize the scroll between the back ListView and the front one by listening to scroll events with NotificationListener<ScrollNotification>;
Here's the code
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
final double zoomTargetHeight = 320;
final double zoomTargetWidth = 500;
final double zoomOriginalHeight = 225;
final double zoomOriginalWidth = 400;
late final ScrollController _controllerBack;
late final ScrollController _controllerFront;
#override
void initState() {
super.initState();
_controllerBack = ScrollController();
_controllerFront = ScrollController();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
Image.network("https://source.unsplash.com/random/1600x900?cars"),
Container(
color: Colors.black87,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 12,
),
const Text(
"List of items",
style: TextStyle(color: Colors.white),
),
const SizedBox(
height: 12,
),
SizedBox(
height: 225,
child: NotificationListener<ScrollNotification>(
onNotification: (notification) {
_controllerFront.jumpTo(_controllerBack.offset);
return true;
},
child: Stack(
clipBehavior: Clip.none,
children: [
ListView.separated(
controller: _controllerBack,
clipBehavior: Clip.none,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return buildBackCard(index);
},
separatorBuilder: (context, index) {
return const SizedBox(
width: 12,
);
},
itemCount: 10,
),
IgnorePointer(
child: ListView.separated(
controller: _controllerFront,
clipBehavior: Clip.none,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return buildFrontCard(index);
},
separatorBuilder: (context, index) {
return const SizedBox(
width: 12,
);
},
itemCount: 10,
),
),
],
),
),
),
const SizedBox(
height: 200,
),
],
),
),
),
],
),
),
);
}
final Map _showZoom = {};
Widget buildBackCard(int index) {
return MouseRegion(
onEnter: (event) {
setState(() {
_showZoom["$index"] = true;
});
},
onExit: (event) {
setState(() {
_showZoom["$index"] = false;
});
},
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Stack(
children: [
Image.network(
"https://source.unsplash.com/random/400x225?sig=$index&cars",
),
Container(
color: Colors.black.withAlpha(100),
height: zoomOriginalHeight,
width: zoomOriginalWidth,
),
],
),
),
);
}
Widget buildFrontCard(int index) {
Widget child;
double scale;
if (_showZoom["$index"] == null || !_showZoom["$index"]!) {
scale = 1;
child = SizedBox(
height: zoomOriginalHeight,
width: zoomOriginalWidth,
);
} else {
scale = zoomTargetWidth / zoomOriginalWidth;
child = Stack(
clipBehavior: Clip.none,
children: [
Container(
height: zoomOriginalHeight,
width: zoomOriginalWidth,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
image: DecorationImage(
image: NetworkImage(
"https://source.unsplash.com/random/400x225?sig=$index&cars"),
fit: BoxFit.cover,
),
),
),
],
);
}
return AnimatedScale(
duration: const Duration(milliseconds: 400),
scale: scale,
child: child,
);
}
}
I'd do something different. Instead of Stacking the zoomed-out and zoomed-in images it could be just one image with a AnimatedScale to do the transitions.
Check the code below:
ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Stack(
children: [
AnimatedScale(
duration: const Duration(milliseconds: 400),
scale: _showZoom["$index"] == true
? zoomTargetWidth / zoomOriginalWidth
: 1,
child: Image.network(
"https://source.unsplash.com/random/400x225?sig=$index&cars"),
),
if (_showZoom["$index"] == null || _showZoom["$index"] == false)
Container(
color: Colors.black.withAlpha(100),
height: zoomOriginalHeight,
width: zoomOriginalWidth,
),
],
),
),
Check out the screenshot and the live demo on DartPad:
All source
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State createState() => _MyHomePageState();
}
class _MyHomePageState extends State {
final double zoomTargetHeight = 320;
final double zoomTargetWidth = 500;
final double zoomOriginalHeight = 225;
final double zoomOriginalWidth = 400;
double _zoomHeight = 225;
double _zoomWidth = 400;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
Image.network("https://source.unsplash.com/random/1600x900?cars"),
Container(
color: Colors.black87,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 12,
),
const Text(
"List of items",
style: TextStyle(color: Colors.white),
),
const SizedBox(
height: 12,
),
SizedBox(
height: 235,
child: ListView.separated(
clipBehavior: Clip.none,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return buildCard(index);
},
separatorBuilder: (context, index) {
return const SizedBox(
width: 12,
);
},
itemCount: 10,
),
),
const SizedBox(
height: 200,
),
],
),
),
),
],
),
),
);
}
Map _showZoom = {};
Widget buildCard(int index) {
Stack stack = Stack(
clipBehavior: Clip.none,
children: [
MouseRegion(
onEnter: (event) {
setState(() {
_showZoom["$index"] = true;
});
},
onExit: (event) {
setState(() {
_showZoom["$index"] = false;
});
},
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: Stack(
children: [
AnimatedScale(
duration: const Duration(milliseconds: 400),
scale: _showZoom["$index"] == true
? zoomTargetWidth / zoomOriginalWidth
: 1,
child: Image.network(
"https://source.unsplash.com/random/400x225?sig=$index&cars"),
),
if (_showZoom["$index"] == null || _showZoom["$index"] == false)
Container(
color: Colors.black.withAlpha(100),
height: zoomOriginalHeight,
width: zoomOriginalWidth,
),
],
),
),
),
],
);
return stack;
}
}
Is there a way to show a slider under the number stepper widget?
Depending upon the activeStep in the number stepper the slider should be placed under the activeStep.
Any suggestions?
I'm attaching an image of the desired result.
#override
Widget build(BuildContext context) {
screenWidth = MediaQuery.of(context).size.width;
questionsProgressBarWidth = screenWidth - 80.0;
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(
height: 20,
),
TutorialTestTopBar(screenWidth: screenWidth),
SizedBox(
height: 10,
),
Padding(
padding: const EdgeInsets.all(10.0),
child: quizQuestionWidget,
),
Spacer(),
],
),
),
);
}
Widget get quizQuestionWidget {
if (quizQuestion == null) {
return const Center(child: CircularProgressIndicator());
}
questions = quizQuestion.questions;
upperBound = questions.length;
for (int i = 1; i <= questions.length; i++) {
numbers.add(i);
}
return SizedBox(
height: MediaQuery.of(context).size.height * 0.85,
child: Column(
children: [
NumberStepper(
stepColor: Colors.white,
activeStepColor: Colors.green,
// activeStepBorderColor: Colors.green,
stepRadius: 15,
direction: Axis.horizontal,
lineColor: Colors.white,
numbers: numbers,
activeStep: activeStep,
onStepReached: (index) {
setState(() {
activeStep = index;
});
},
),
//NEED THE SLIDER HERE
Expanded(
child: PageView.builder(
controller: pageController,
onPageChanged: (value) {
setState(() {
pageChanged = value;
});
},
itemBuilder: (context, index) {
return buildContent(questions[index], index, upperBound);
},
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
previousButton(),
nextButton(),
],
),
],
),
);
}
You can define a custom border for each item, then update the Color property based on the current question being answered. Full example code:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
home: SomeScreen(),
);
}
}
class SomeScreen extends StatefulWidget {
#override
_SomeScreenState createState() => _SomeScreenState();
}
class _SomeScreenState extends State<SomeScreen> {
int _currentQuestion = 0;
List<int> _answered = [];
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
padding: EdgeInsets.all(20),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: List<Widget>.generate(
5,
(index) => _buildItem(index),
),
),
Container(
child: FlatButton(
color: Colors.orange,
onPressed: () {
setState(() {
if (!_answered.contains(_currentQuestion))
_answered.add(_currentQuestion);
if (_currentQuestion < 5) {
_currentQuestion += 1;
}
});
},
child: Text('Answer'),
),
)
],
),
),
),
);
}
Column _buildItem(int index) {
return Column(
children: [
Container(
child: CircleAvatar(
backgroundColor:
_answered.contains(index) ? Colors.green : Colors.transparent,
child: Text(
'${index + 1}',
style: TextStyle(color: Colors.black),
)),
),
Container(
height: 10,
width: 40,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: _currentQuestion == index
? Colors.orange
: Colors.transparent),
)
],
);
}
}
Result:
try this:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatelessWidget {
List<int> _steps = [1, 2, 3, 4, 5];
#override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width,
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: _numberStep(),
),
SizedBox(height: 10),
Stack(children: [
Container(
margin: EdgeInsets.symmetric(horizontal: 50),
width: MediaQuery.of(context).size.width,
height: 20,
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: const BorderRadius.all(Radius.circular(10.0)),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: _sliderStep(),
),
]),
],
),
);
}
List<Widget> _numberStep() {
List<Widget> _stepList = [];
_steps.forEach((step) {
_stepList.add(
Container(
alignment: Alignment.center,
width: 20,
height: 20,
decoration: BoxDecoration(
color: step < 3? Colors.green: Colors.transparent,
shape: BoxShape.circle,
),
child: Text(step.toString()),
),
);
});
return _stepList;
}
List<Widget> _sliderStep() {
List<Widget> _sliderList = [];
_steps.forEach((step) {
_sliderList.add(
Container(
width: 40,
height: 20,
decoration: BoxDecoration(
color: step == 3? Colors.orange: Colors.transparent,
borderRadius: const BorderRadius.all(Radius.circular(10.0)),
),
),
);
});
return _sliderList;
}
}
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);
}
}
I added padding for transparent outside. But fixed height. How to change it?
padding: EdgeInsets.fromLTRB(20, 50, 20, 50),
Is it possible to remove above this line and flexible(center)?
I am expected like this flexible height alert. click here
onPressed: () {
showGeneralDialog(
context: context,
barrierColor: Palette.black.withOpacity(.3),
barrierDismissible: true,
transitionDuration: Duration(milliseconds: 400),
pageBuilder: (_, __, ___) {
return ChangePropertyPage(
propertyModel: propertyModel);
},
);
},
change Property Page
class ChangePropertyPage extends StatelessWidget {
final List<PropertyModel> propertyModel;
const ChangePropertyPage({Key key, this.propertyModel}) : super(key: key);
#override
Widget build(BuildContext context) {
final double width = CustomMediaQuery.width(context);
return Padding(
padding: EdgeInsets.fromLTRB(20, 50, 20, 50),
child: Material(
borderRadius: BorderRadius.all(Radius.circular(10)),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
PropertyListTileWidget(
mainTitle: 'USER\'S Name', subTitle: 'USER\'S Email'),
VerticalSpacing(height: 10),
CustomLine(
height: 1,
width: (width - 40) - 20,
color: Palette.black.withOpacity(.2),
),
Expanded(
child: ListView.builder(
itemCount: propertyModel.length,//now length is 1
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: ()async{
},
child: PropertyListTileWidget(
mainTitle: '${propertyModel[index].propertyName}',
subTitle: '${propertyModel[index].ownerUId}'),
);
}),
)
],
),
),
),
);
}
}
if you are expecting this:
then
full code:
import 'package:flutter/material.dart';
class CustomDialogBox extends StatefulWidget {
#override
_CustomDialogBoxState createState() => _CustomDialogBoxState();
}
class _CustomDialogBoxState extends State<CustomDialogBox> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Custom Dialog Box"),
centerTitle: true,
),
body:Center(
child:FlatButton(
color: Colors.blue,
onPressed: (){
showDialog(
context: (context),
child: ShowCustomDialogBox()
);
},
child: Text("Show Dialog")
)
) ,
);
}
}
class ShowCustomDialogBox extends StatefulWidget {
#override
State<StatefulWidget> createState() => ShowCustomDialogBoxState();
}
class ShowCustomDialogBoxState extends State<ShowCustomDialogBox>with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> scaleAnimation;
#override
void initState() {
super.initState();
controller = AnimationController(vsync: this, duration: Duration(milliseconds: 450));
scaleAnimation =CurvedAnimation(parent: controller, curve: Curves.decelerate);
controller.addListener(() {
setState(() {});
});
controller.forward();
}
#override
Widget build(BuildContext context) {
return Center(
child: Material(
color: Colors.transparent,
child: ScaleTransition(
scale: scaleAnimation,
child: Container(
margin: EdgeInsets.all(20.0),
padding: EdgeInsets.all(8.0),
height: MediaQuery.of(context).size.height/2.5, //Change height of dialog box.
width: MediaQuery.of(context).size.width,
decoration: ShapeDecoration(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0))),
child: Column(
children: <Widget>[
Expanded(
flex: 4,
child: ListView.builder(
itemCount: 10,
itemBuilder: (context, index){
return Column(
// mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text((index+1).toString(),style: TextStyle(color:Colors.blue,fontSize:40),),
Divider()
],
);
}
)
),
Padding(
padding: const EdgeInsets.only(
left: 20.0, right: 10.0, top: 0.0,),
child: ButtonTheme(
height: 35.0,
minWidth: MediaQuery.of(context).size.width/3.5,
child: RaisedButton(
color: Colors.blue,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(5.0)),
splashColor: Colors.white.withAlpha(40),
child: Text(
'Next',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 13.0),
),
onPressed: () {
setState(() {
Navigator.pop(context);
});
},
)
)
),
],
)
),
),
),
);
}
}
I'm using an AnimatedContainer so that its height can adapt (with an animation) to the Tab's content present in it. I'm supposed to have access to the current index of the Tab with DefaultTabController.of(context).index and I'm transmitting this data to a function that will rebuild the widget depending on the current tab.
When I run the code, it displays me the error, but I don't understand : does it mean that DefaultTabController returns null ?
Here's the code :
import 'package:flutter/material.dart';
import 'package:travel_agent_app/loginForm.dart';
import 'package:travel_agent_app/registerForm.dart';
import 'package:travel_agent_app/bubble_tab_indicator.dart';
class Login extends StatefulWidget {
const Login({Key key}) : super(key: key);
_LoginState createState() => _LoginState();
}
class _LoginState extends State<Login> {
double _containerHeight = 300;
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
body: Container(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
children: <Widget>[
SizedBox(
height: 150,
),
Container(
padding: EdgeInsets.only(left: 20.0, right:20.0),
child: Column(
children: <Widget>[
Container(
height: 52.0,
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.all(Radius.circular(100.0))
),
child: TabBar(
indicatorSize: TabBarIndicatorSize.tab,
indicator: new BubbleTabIndicator(
indicatorHeight: 45.0,
indicatorColor: Colors.blueAccent,
tabBarIndicatorSize: TabBarIndicatorSize.tab),
labelColor: Colors.white,
unselectedLabelColor: Colors.black,
onTap: setAnimatedContainerHeight(DefaultTabController.of(context).index),
tabs: <Widget>[
Tab(text: 'Login'),
Tab(text: 'Register'),
],
),
),
SizedBox(
height: 20.0,
),
AnimatedContainer(
duration: Duration(seconds: 1),
padding: EdgeInsets.all(40.0),
width: double.infinity,
height: _containerHeight,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8.0),
boxShadow: [
BoxShadow(
color: Colors.black12,
offset: Offset(0.0, 15.0),
blurRadius: 15.0),
]),
child: TabBarView(
children: <Widget>[
Container(
width: 500.0,
child: LoginForm(),
),
RegisterForm(),
],
)
),
],
),
)
],
),
)
)
)
);
}
setAnimatedContainerHeight(int index){
if(index == 0){
setState(() {
_containerHeight = 300;
});
}
else{
setState(() {
_containerHeight = 450;
});
}
}
}
The issue here is the DefaultTabController context is the same as in which DefaultTabController.of(context).index is defined.
To solve the error - you need to define DefaultTabController in the parent context.
Your code working:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: DefaultTabController( // Add here
child: Login(),
length: 2,
),
);
}
}
class Login extends StatefulWidget {
const Login({Key key}) : super(key: key);
_LoginState createState() => _LoginState();
}
class _LoginState extends State<Login> {
double _containerHeight = 300;
#override
Widget build(BuildContext context) {
return Scaffold( // Remove from here
body: Container(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
children: <Widget>[
SizedBox(
height: 150,
),
Container(
padding: EdgeInsets.only(left: 20.0, right: 20.0),
child: Column(
children: <Widget>[
Container(
height: 52.0,
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.all(Radius.circular(100.0))),
child: TabBar(
indicatorSize: TabBarIndicatorSize.tab,
indicator: new BubbleTabIndicator(
indicatorHeight: 45.0,
indicatorColor: Colors.blueAccent,
tabBarIndicatorSize: TabBarIndicatorSize.tab),
labelColor: Colors.white,
unselectedLabelColor: Colors.black,
onTap: setAnimatedContainerHeight(
DefaultTabController.of(context).index),
tabs: <Widget>[
Tab(text: 'Login'),
Tab(text: 'Register'),
],
),
),
SizedBox(
height: 20.0,
),
AnimatedContainer(
duration: Duration(seconds: 1),
padding: EdgeInsets.all(40.0),
width: double.infinity,
height: _containerHeight,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8.0),
boxShadow: [
BoxShadow(
color: Colors.black12,
offset: Offset(0.0, 15.0),
blurRadius: 15.0),
]),
child: TabBarView(
children: <Widget>[
Container(
width: 500.0,
child: LoginForm(),
),
RegisterForm(),
],
)),
],
),
)
],
),
)));
}
setAnimatedContainerHeight(int index) {
if (index == 0) {
setState(() {
_containerHeight = 300;
});
} else {
setState(() {
_containerHeight = 450;
});
}
}
}
Or using FutureBuilder:
import 'package:flutter/material.dart';
import 'package:travel_agent_app/loginForm.dart';
import 'package:travel_agent_app/registerForm.dart';
import 'package:travel_agent_app/bubble_tab_indicator.dart';
class Login extends StatefulWidget {
const Login({Key key}) : super(key: key);
_LoginState createState() => _LoginState();
}
class _LoginState extends State<Login> {
double _containerHeight = 300;
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: FutureBuilder(
future: Future.delayed(Duration.zero),
builder: (BuildContext context, AsyncSnapshot snapshot) {
return Scaffold(
// ...
// The context within here is from FutureBuilder, which include DefaultTabController
// ...
);
},
),
);
}
setAnimatedContainerHeight(int index) {
if (index == 0) {
setState(() {
_containerHeight = 300;
});
} else {
setState(() {
_containerHeight = 450;
});
}
}
}