Related
I am creating a simple web portfolio to have hand-on practice in flutter. I was wondering how other website work when we click on home/about/contact button the web scroll to that specific widget.
I tried to do this but didn't get it how to implement this functionality
Code example will be helpful and will be appreciated as upvote.
you can give global key to the widget you want to scroll to
e.g
final dataKey = new GlobalKey();
assign it to the widget
Container(key:dataKey);
and then on the button onpressed write this code
onPressed: (){
Scrollable.ensureVisible(dataKey.currentContext);
}
you could use this package scroll_to_id
and here is an exemple made by github#yusukeinouehatchout
import 'package:flutter/material.dart';
import 'package:scroll_to_id/scroll_to_id.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
ScrollToId? scrollToId;
final ScrollController scrollController = ScrollController();
List<Color> _colorList = [
Colors.green,
Colors.red,
Colors.yellow,
Colors.blue
];
void _scrollListener() {
print(scrollToId!.idPosition());
}
#override
void initState() {
super.initState();
/// Create ScrollToId instance
scrollToId = ScrollToId(scrollController: scrollController);
scrollController.addListener(_scrollListener);
}
/// Generate 10 Container
/// Case [Axis.horizontal] set buildStackHorizontal() to body
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Scroll to id',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: const Text('Scroll to id'),
),
body: buildStackVertical(),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.navigate_next),
onPressed: () {
scrollToId!.animateToNext(
duration: Duration(milliseconds: 500), curve: Curves.ease);
},
),
),
);
}
/// [Axis.vertical]
/// https://raw.githubusercontent.com/wiki/yusukeinouehatchout/scroll_to_id/gif/scroll_to_id_vertical.gif
Widget buildStackVertical() {
return Stack(
alignment: Alignment.topRight,
children: [
InteractiveScrollViewer(
scrollToId: scrollToId,
children: List.generate(10, (index) {
return ScrollContent(
id: '$index',
child: Container(
alignment: Alignment.center,
width: double.infinity,
height: 200,
child: Text(
'$index',
style: TextStyle(color: Colors.white, fontSize: 50),
),
color: _colorList[index % _colorList.length],
),
);
}),
),
Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.white, width: 3),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: List.generate(10, (index) {
return GestureDetector(
child: Container(
width: 100,
alignment: Alignment.center,
height: 50,
child: Text(
'$index',
style: TextStyle(color: Colors.white),
),
color: _colorList[index % _colorList.length],
),
onTap: () {
/// scroll with animation
scrollToId!.animateTo('$index',
duration: Duration(milliseconds: 500),
curve: Curves.ease);
/// scroll with jump
// scrollToId.jumpTo('$index');
},
);
}),
),
)
],
);
}
/// [Axis.horizontal]
/// https://raw.githubusercontent.com/wiki/yusukeinouehatchout/scroll_to_id/gif/scroll_to_id_horizontal.gif
Widget buildStackHorizontal() {
return Column(
children: [
Container(
width: double.infinity,
decoration: BoxDecoration(
border: Border.all(color: Colors.white, width: 3),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(10, (index) {
return Expanded(
child: GestureDetector(
child: Container(
alignment: Alignment.center,
height: 80,
child: Text(
'$index',
style: TextStyle(color: Colors.white),
),
color: _colorList[index % _colorList.length],
),
onTap: () {
/// scroll with animation
scrollToId!.animateTo('$index',
duration: Duration(milliseconds: 500),
curve: Curves.ease);
/// scroll with jump
// scrollToId.jumpTo('$index');
},
),
);
}),
),
),
Expanded(
child: InteractiveScrollViewer(
scrollToId: scrollToId,
scrollDirection: Axis.horizontal,
children: List.generate(10, (index) {
return ScrollContent(
id: '$index',
child: Container(
alignment: Alignment.center,
width: 200,
child: Text(
'$index',
style: TextStyle(color: Colors.white, fontSize: 50),
),
color: _colorList[index % _colorList.length],
),
);
}),
),
),
],
);
}
}
I faced the same problem but I managed to solve it with this package it's helpful and easy scroll_to_index
define a listview controller
ListView( controller: controller, )
then you just need to wrap the elements in your screen with AutoScrollTag like
AutoScrollTag(
key: ValueKey(index),
controller: controller,
index: index,
child: YOUR_CHILD_HERE
)
finally scroll to our element like this
controller.scrollToIndex(index, preferPosition: AutoScrollPosition.begin)
How do you achieve the smooth transition when the checkmark is added?
Clicking the element will setState update the pressAttention variable, and therefore add the checkmark widget to the list of children of the row.
For now it just instantly rebuilds the row, and adds the checkmark, but I would really like it to smoothly do as in the GIF.
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
widget.amount,
style: const TextStyle(
color: Colors.white, fontWeight: FontWeight.w700),
),
if (pressAttention)
Padding(
padding: const EdgeInsets.only(left: 10),
child: Container(
width: 23,
height: 23,
decoration: BoxDecoration(
color: Theme.of(context).highlightColor,
shape: BoxShape.circle,
),
child: Padding(
padding: const EdgeInsets.all(4),
child: SvgPicture.asset(
MyIcons.checkmarkThick,
),
),
),
)
],
),
try this:
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
late Animation<double> _animation;
late Animation<double> _opacityAnimation;
late Animation _colorAnimation;
late AnimationController _controller;
var pressAttention = false;
#override
void initState() {
super.initState();
_controller =
AnimationController(duration: Duration(milliseconds: 500), vsync: this)
..addListener(() => setState(() {}));
_animation = Tween(begin: 15.0, end: 0.0).animate(_controller);
_opacityAnimation = Tween(begin: 0.0, end: 1.0).animate(_controller);
_colorAnimation =
ColorTween(begin: Colors.grey, end: Colors.purple).animate(_controller);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Center(
child: Center(
child: Container(
child: InkWell(
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
onTap: () {
if (pressAttention) {
_controller.forward();
} else {
_controller.reverse();
}
setState(() {
pressAttention = !pressAttention;
});
},
child: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Container(
width: 100,
decoration: BoxDecoration(
border: Border.all(color: _colorAnimation.value),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Transform.translate(
offset: Offset(_animation.value, 0),
child: Text(
'1000',
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.w700),
),
),
Opacity(
opacity: _opacityAnimation.value,
child: Padding(
padding: EdgeInsets.only(left: 10),
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).highlightColor,
shape: BoxShape.circle,
),
child: const Padding(
padding: EdgeInsets.all(4),
child: Icon(
Icons.check,
size: 13,
),
),
),
),
)
],
),
);
}),
),
),
),
),
);
}
}
You can achieve this with a combination of Animated Widgets. Set their behaviours and durations and you should be good to go.
I want to build a progress indicator for each page of the pageview builder. Pageview changes after 10 seconds and the progress bar shifts to the next bar. I have successfully implemented this, but encounter an issue of 4 bars loading at the same time, while going back and forth to the page view screen or when I minimize and reopen the app.
This is what I already have achieved with the following code:
This is my code:
class WelcomeScreen extends StatefulWidget {
const WelcomeScreen({Key? key}) : super(key: key);
#override
State<WelcomeScreen> createState() => _WelcomeScreenState();
}
class _WelcomeScreenState extends State<WelcomeScreen>
with TickerProviderStateMixin {
final WelcomeController controller = Get.put(WelcomeController());
late AnimationController animationControllerOne;
late AnimationController animationControllerTwo;
late AnimationController animationControllerThree;
late AnimationController animationControllerFour;
late Animation<double> animation;
#override
void initState() {
super.initState();
animationControllerOne =
AnimationController(duration: const Duration(seconds: 10), vsync: this);
animation = Tween(begin: 0.0, end: 1.0).animate(animationControllerOne)
..addListener(() {
setState(() {
});
});
animationControllerOne.forward();
animationControllerTwo =
AnimationController(duration: const Duration(seconds: 10), vsync: this);
animation = Tween(begin: 1.0, end: 2.0).animate(animationControllerTwo)
..addListener(() {
setState(() {
});
});
Timer(const Duration(seconds: 10), () {
animationControllerTwo.forward();
});
animationControllerThree =
AnimationController(duration: const Duration(seconds: 10), vsync: this);
animation = Tween(begin: 2.0, end: 3.0).animate(animationControllerThree)
..addListener(() {
setState(() {});
});
Timer(const Duration(seconds: 20), () {
animationControllerThree.forward();
});
animationControllerFour =
AnimationController(duration: const Duration(seconds: 10), vsync: this);
animation = Tween(begin: 3.0, end: 4.0).animate(animationControllerFour)
..addListener(() {
setState(() {});
});
Timer(const Duration(seconds: 30), () {
animationControllerFour.forward();
});
animationControllerFour.addStatusListener((status) {
if (status == AnimationStatus.completed) {
animationControllerOne.reset();
animationControllerTwo.reset();
animationControllerThree.reset();
animationControllerFour.reset();
animationControllerOne.forward();
}
animationControllerOne.addStatusListener((status) {
if (status == AnimationStatus.completed) {
animationControllerTwo.reset();
animationControllerTwo.forward();
}
});
animationControllerTwo.addStatusListener((status) {
if (status == AnimationStatus.completed) {
animationControllerThree.reset();
animationControllerThree.forward();
}
});
animationControllerThree.addStatusListener((status) {
if (status == AnimationStatus.completed) {
animationControllerFour.reset();
animationControllerFour.forward();
}
});
});
}
#override
void dispose() {
animationControllerOne.dispose();
animationControllerTwo.dispose();
animationControllerThree.dispose();
animationControllerFour.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: context.theme.scaffoldBackgroundColor,
appBar: AppBar(
systemOverlayStyle: SystemUiOverlayStyle(
statusBarColor: Get.isDarkMode ? darkBackground : lightBackground,
statusBarIconBrightness:
Get.isDarkMode ? Brightness.light : Brightness.dark,
),
title: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
key: const Key('loading bar'),
children: [
Expanded(
child: LinearProgressIndicator(
color: primaryBlue,
backgroundColor: const Color(0xFFD9D9D9),
minHeight: 8,
value: animationControllerOne.value,
),
),
const SizedBox(
width: 10,
),
Expanded(
child: LinearProgressIndicator(
color: primaryBlue,
backgroundColor: const Color(0xFFD9D9D9),
minHeight: 8,
value: animationControllerTwo.value,
),
),
const SizedBox(
width: 10,
),
Expanded(
child: LinearProgressIndicator(
color: primaryBlue,
backgroundColor: const Color(0xFFD9D9D9),
minHeight: 8,
value: animationControllerThree.value,
),
),
const SizedBox(
width: 10,
),
Expanded(
child: LinearProgressIndicator(
backgroundColor: const Color(0xFFD9D9D9),
color: primaryBlue,
minHeight: 8,
value: animationControllerFour.value,
),
),
],
),
),
elevation: 0,
backgroundColor: Get.isDarkMode ? darkBackground : lightBackground,
centerTitle: true,
),
body: Padding(
padding: const EdgeInsets.symmetric(vertical: 15),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Welcome to Demonstration Bank!'.tr,
key: const Key('message'),
textAlign: TextAlign.center,
style: TextStyle(
color: Get.isDarkMode ? lightBackground : darkBackground,
fontSize: Get.height * 0.027,
fontWeight: FontWeight.w600,
),
),
SizedBox(
height: Get.height * 0.05,
),
SizedBox(
height: Get.height * 0.4,
width: Get.width * 0.8,
child: PageView.builder(
controller: controller.gifsController,
physics: const NeverScrollableScrollPhysics(),
itemCount: controller.images.length,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
return Image.asset(
controller.images[index],
fit: BoxFit.fitWidth,
);
},
),
),
SizedBox(
height: Get.height * 0.15,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CustomButton(
key: const Key('sign in button'),
height: Get.height * 0.069,
width: Get.width * 0.335,
fontSize: Get.height * 0.021,
color: Get.isDarkMode ? lightBackground : darkBackground,
textColor: Get.isDarkMode ? darkBackground : lightBackground,
text: 'Sign in'.tr,
onPressed: () => Get.to(() => const SigninScreen()),
),
const SizedBox(
width: 19,
),
CustomButton(
key: const Key('sign up button'),
height: Get.height * 0.069,
width: Get.width * 0.335,
fontSize: Get.height * 0.021,
color: primaryBlue,
textColor: Colors.white,
text: 'Sign Up'.tr,
onPressed: () => Get.to(() => CreateAccountScreen()),
),
],
),
const SizedBox(
height: 24,
),
],
),
),
);
}
}
Unexpected result: ( Note: this problem does not happen always )
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);
}
}
By this simple code I can show dialog on bottom of screen like with this screenshot:
But I have three simple issue:
set margin on bottom of dialog such as 20.0 on showing dialog
using controller.reverse() on dismiss dialog
dismiss dialog on click on outside of dialog
Full source code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
void showPopup() {
showDialog(
context: context,
builder: (_) => PopUp(),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: showPopup,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class PopUp extends StatefulWidget {
#override
State<StatefulWidget> createState() => PopUpState();
}
class PopUpState extends State<PopUp> with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> opacityAnimation;
Tween<double> opacityTween = Tween<double>(begin: 0.0, end: 1.0);
Tween<double> marginTopTween = Tween<double>(begin: 300, end: 280);
Animation<double> marginTopAnimation;
#override
void initState() {
super.initState();
controller = AnimationController(duration: const Duration(milliseconds: 300), vsync: this);
marginTopAnimation = marginTopTween.animate(controller)
..addListener(() {
setState(() {});
});
controller.forward();
}
#override
Widget build(BuildContext context) {
return FadeTransition(
opacity: opacityTween.animate(controller),
child: Material(
color: Colors.transparent,
child: Container(
margin: EdgeInsets.only(
top: marginTopAnimation.value,
left:20.0,
right:20.0,
),
color: Colors.red,
child: Text("Container"),
),
),
);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
}
Screenshot:
Code:
floatingActionButton: FloatingActionButton(
onPressed: () {
showGeneralDialog(
barrierLabel: "Label",
barrierDismissible: true,
barrierColor: Colors.black.withOpacity(0.5),
transitionDuration: Duration(milliseconds: 700),
context: context,
pageBuilder: (context, anim1, anim2) {
return Align(
alignment: Alignment.bottomCenter,
child: Container(
height: 300,
child: SizedBox.expand(child: FlutterLogo()),
margin: EdgeInsets.only(bottom: 50, left: 12, right: 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(40),
),
),
);
},
transitionBuilder: (context, anim1, anim2, child) {
return SlideTransition(
position: Tween(begin: Offset(0, 1), end: Offset(0, 0)).animate(anim1),
child: child,
);
},
);
},
)
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(home: CityPage()));
}
class CityPage extends StatelessWidget {
const CityPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: TextButton(
child: const Text('Press me'),
onPressed: () => BottomDialog().showBottomDialog(context),
),
),
],
),
);
}
}
class BottomDialog {
void showBottomDialog(BuildContext context) {
showGeneralDialog(
barrierLabel: "showGeneralDialog",
barrierDismissible: true,
barrierColor: Colors.black.withOpacity(0.6),
transitionDuration: const Duration(milliseconds: 400),
context: context,
pageBuilder: (context, _, __) {
return Align(
alignment: Alignment.bottomCenter,
child: _buildDialogContent(),
);
},
transitionBuilder: (_, animation1, __, child) {
return SlideTransition(
position: Tween(
begin: const Offset(0, 1),
end: const Offset(0, 0),
).animate(animation1),
child: child,
);
},
);
}
Widget _buildDialogContent() {
return IntrinsicHeight(
child: Container(
width: double.maxFinite,
clipBehavior: Clip.antiAlias,
padding: const EdgeInsets.all(16),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
child: Material(
child: Column(
children: [
const SizedBox(height: 16),
_buildImage(),
const SizedBox(height: 8),
_buildContinueText(),
const SizedBox(height: 16),
_buildEmapleText(),
const SizedBox(height: 16),
_buildTextField(),
const SizedBox(height: 16),
_buildContinueButton(),
],
),
),
),
);
}
Widget _buildImage() {
const image =
'https://user-images.githubusercontent.com/47568606/134579553-da578a80-b842-4ab9-ab0b-41f945fbc2a7.png';
return SizedBox(
height: 88,
child: Image.network(image, fit: BoxFit.cover),
);
}
Widget _buildContinueText() {
return const Text(
'Continue with account',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.w500,
),
);
}
Widget _buildEmapleText() {
return const Text(
'example.com',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
);
}
Widget _buildTextField() {
const iconSize = 40.0;
return Container(
height: 60,
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(width: 1, color: Colors.grey.withOpacity(0.4)),
borderRadius: const BorderRadius.all(Radius.circular(8)),
),
child: Row(
children: [
Container(
width: iconSize,
height: iconSize,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey[200],
),
child: const Center(
child: Text('Е'),
),
),
const SizedBox(height: 16),
Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: const [
Text(
'elisa.g.beckett#gmail.com',
style: TextStyle(
fontWeight: FontWeight.w600,
),
),
Text('**********'),
],
)
],
),
);
}
Widget _buildContinueButton() {
return Container(
height: 40,
width: double.maxFinite,
decoration: const BoxDecoration(
color: Color(0xFF3375e0),
borderRadius: BorderRadius.all(Radius.circular(8)),
),
child: RawMaterialButton(
onPressed: () {
Navigator.of(context, rootNavigator: true).pop();
},
child: const Center(
child: Text(
'Continue',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
),
),
);
}
}
From top to bottom you can use
bool _fromTop = true;
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
showGeneralDialog(
barrierLabel: "Label",
barrierDismissible: true,
barrierColor: Colors.black.withOpacity(0.5),
transitionDuration: Duration(milliseconds: 700),
context: context,
pageBuilder: (context, anim1, anim2) {
return Align(
alignment: _fromTop ? Alignment.topCenter : Alignment.bottomCenter,
child: Container(
height: 300,
child: SizedBox.expand(child: FlutterLogo()),
margin: EdgeInsets.only(top: 50, left: 12, right: 12, bottom: 50),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(40),
),
),
);
},
transitionBuilder: (context, anim1, anim2, child) {
return SlideTransition(
position: Tween(begin: Offset(0, _fromTop ? -1 : 1), end: Offset(0, 0)).animate(anim1),
child: child,
);
},
);
},
),
);
}
Output:
Not sure if I got your question clearly, if this is what you are looking for, replace your PopUp class with mine.
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
void showPopup() {
showDialog(
context: context,
builder: (_) => PopUp(),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: showPopup,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class PopUp extends StatefulWidget {
#override
State<StatefulWidget> createState() => PopUpState();
}
class PopUpState extends State<PopUp> with TickerProviderStateMixin {
AnimationController controller;
double _bottom = 0, _fromTop = 300, _screenHeight, _containerHeight = 300;
#override
void initState() {
super.initState();
controller = AnimationController(duration: const Duration(milliseconds: 300), vsync: this)
..addListener(() {
Timer.periodic(Duration(milliseconds: 15), (timer) {
if (_bottom < _screenHeight - _fromTop - _containerHeight) {
_bottom += 1;
setState(() {});
}
});
});
controller.forward();
}
#override
Widget build(BuildContext context) {
_screenHeight = MediaQuery.of(context).size.height;
return SizedBox(
width: double.infinity,
height: double.infinity,
child: Stack(
children: <Widget>[
Positioned(
bottom: _bottom,
left: 0,
right: 0,
child: Container(height: _containerHeight, color: Colors.green),
),
],
),
);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
}
I think showModalBottomSheet function does this out of the box.