I want to implement multiple animations with animation controllers by using getXController in flutter dart, but when I run the application it says animation controllers are paused and when I click on one of my TextFormFields animation controllers will work for just one second, and all of the data that I have entered in TextFormFields are removed.
This is my code LoginController code.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class LoginController extends GetxController with GetTickerProviderStateMixin //
{
late final Rx<AnimationController> _controller1;
late final Rx<AnimationController> _controller2;
late Rx<Animation<double>> _animation1;
late Rx<Animation<double>> _animation2;
late Rx<Animation<double>> _animation3;
late Rx<Animation<double>> _animation4;
get controller1 => _controller1.value;
set controller1(value) => _controller1.value = value;
get controller2 => _controller2.value;
set controller2(value) => _controller2.value = value;
get animation1 => _animation1.value;
set animation1(value) => _animation1.value = value;
get animation2 => _animation2.value;
set animation2(value) => _animation2.value = value;
get animation3 => _animation3.value;
set animation3(value) => _animation3.value = value;
get animation4 => _animation4.value;
set animation4(value) => _animation4.value = value;
#override
void onInit() {
super.onInit();
_controller1 = AnimationController(
vsync: this,
duration: const Duration(
seconds: 5,
),
).obs;
_animation1 = (Tween<double>(begin: .1, end: .15).animate(
CurvedAnimation(
parent: controller1,
curve: Curves.easeInOut,
),
)
..addListener(() => update())
..addStatusListener(
(status) {
if (status == AnimationStatus.completed) {
update([controller1.reverse()]);
} else if (status == AnimationStatus.dismissed) {
update([controller1.forward()]);
}
},
))
.obs;
_animation2 = (Tween<double>(begin: .02, end: .04).animate(
CurvedAnimation(
parent: controller1,
curve: Curves.easeInOut,
),
)..addListener(() => update()))
.obs;
_controller2 = AnimationController(
vsync: this,
duration: const Duration(
seconds: 5,
),
).obs;
_animation3 = (Tween<double>(begin: .41, end: .38).animate(
CurvedAnimation(
parent: controller2,
curve: Curves.easeInOut,
),
)
..addListener(() => update())
..addStatusListener(
(status) {
if (status == AnimationStatus.completed) {
update([controller2.reverse()]);
} else if (status == AnimationStatus.dismissed) {
update([controller2.forward()]);
}
},
))
.obs;
_animation4 = (Tween<double>(begin: 170, end: 190).animate(
CurvedAnimation(
parent: controller2,
curve: Curves.easeInOut,
),
)..addListener(() => update()))
.obs;
Timer(
const Duration(milliseconds: 2500),
() {
update([controller1.reverse()]);
},
);
update([controller1.forward()]);
}
#override
void onReady() {
super.onReady();
}
#override
void onClose() {
controller1.dispose();
controller2.dispose();
super.onClose();
}
}
and this is my GetViewPage.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(
const MaterialApp(
home: LoginPage(),
title: 'Login Page',
),
);
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.light,
),
);
}
class LoginPage extends GetView<LoginController> {
final String? action;
const LoginPage({Key? key, this.action}) : super(key: key);
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
final _userController = TextEditingController();
final _passwordController = TextEditingController();
return Scaffold(
backgroundColor: darkBackGround,
body: Form(
child: ScrollConfiguration(
behavior: Behavior(),
child: SingleChildScrollView(
child: SizedBox(
height: size.height,
child: Stack(
children: [
Positioned(
top: size.height * (controller.animation2.value + .58),
left: size.width * .21,
child: CustomPaint(
painter: CustomizedPainter(
radius: 50,
color1: darkLinearCustomPaintColor1,
color2: darkLinearCustomPaintColor2,
),
),
),
Positioned(
top: size.height * .98,
left: size.width * .1,
child: CustomPaint(
painter: CustomizedPainter(
radius: controller.animation4.value - 30,
color1: darkLinearCustomPaintColor1,
color2: darkLinearCustomPaintColor2,
),
),
),
Positioned(
top: size.height * .5,
left: size.width * (controller.animation2.value + .8),
child: CustomPaint(
painter: CustomizedPainter(
radius: 30,
color1: darkLinearCustomPaintColor1,
color2: darkLinearCustomPaintColor2,
),
),
),
Positioned(
top: size.height * controller.animation3.value,
left: size.width * (controller.animation1.value + .1),
child: CustomPaint(
painter: CustomizedPainter(
radius: 60,
color1: darkLinearCustomPaintColor1,
color2: darkLinearCustomPaintColor2,
),
),
),
Positioned(
top: size.height * .1,
left: size.width * .8,
child: CustomPaint(
painter: CustomizedPainter(
radius: controller.animation4.value,
color1: darkLinearCustomPaintColor1,
color2: darkLinearCustomPaintColor2,
),
),
),
Column(
children: [
Expanded(
flex: 5,
child: Padding(
padding: EdgeInsets.only(top: size.height * .1),
child: Text(
'',
style: TextStyle(
color: Colors.black.withOpacity(.7),
fontSize: 30,
fontWeight: FontWeight.bold,
letterSpacing: 1,
wordSpacing: 4,
fontFamily: 'pacific',
),
),
),
),
Expanded(
flex: 7,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
DarkThemeInput(
icon: Icons.account_circle_outlined,
hintText: 'User',
isPassword: false,
isEmail: false,
isPhone: false,
controller: _userController,
validator: (String? value) {
if (value!.isEmpty) {
return 'Enter your username or Email ';
}
return '';
},
),
DarkThemeInput(
icon: Icons.lock_outline,
hintText: 'Password...',
isPassword: true,
isEmail: false,
isPhone: false,
controller: _passwordController,
validator: (String? value) {
if (value!.isEmpty) {
return 'Password must be filled';
}
if (value.length < 6) {
return 'Enter more characters';
}
return '';
}),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
DarkThemeButton(
text: 'LOGIN', width: 2.58, onTap: () {}),
SizedBox(width: size.width / 20),
DarkThemeButton(
text: 'Forgotten password!',
width: 2.58,
onTap: () {},
),
],
),
],
),
),
Expanded(
flex: 6,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
DarkThemeButton(
text: 'Create a new Account',
width: 2,
onTap: () {
Get.toNamed('/signup');
},
),
SizedBox(height: size.height * .05),
],
),
),
],
),
],
),
),
),
),
),
);
}
}
I want to notice that DarkThemeInput is a customized TextFormField and when I press them animation controllers work just for one second.
Related
I am doing animated Appbar and I want to determine color of AppBar on theme of application but when I did this I received this error:
dependOnInheritedWidgetOfExactType<_InheritedTheme>() or dependOnInheritedElement() was called before _DetailsState.initState() completed
here is my initState code, I tried here to determine color depending on theme of application:
late AnimationController _ColorAnimationController;
late AnimationController _TextAnimationController;
late Animation _colorTween, _iconColorTween, _bgLeadingColor;
#override
void initState() {
_ColorAnimationController =
AnimationController(vsync: this, duration: Duration(seconds: 0));
_colorTween = ColorTween(begin: Colors.transparent, end: Theme.of(context).brightness == Brightness.dark ? Colors.black : Colors.white)
.animate(_ColorAnimationController);
_iconColorTween = ColorTween(begin: Colors.grey, end: Colors.white)
.animate(_ColorAnimationController);
_bgLeadingColor = ColorTween(begin: Colors.black26, end: Colors.transparent)
.animate(_ColorAnimationController);
_TextAnimationController =
AnimationController(vsync: this, duration: Duration(seconds: 0));
super.initState();
}
and
Widget build(BuildContext context) {
final size = widget.size;
return Scaffold(
body: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (scrollInfo.metrics.axis == Axis.vertical) {
_ColorAnimationController.animateTo(scrollInfo.metrics.pixels / 350);
_TextAnimationController.animateTo(
(scrollInfo.metrics.pixels - 350) / 50);
return true;
}
return true;
},
child: SafeArea(
child: Stack(
children: [
SingleChildScrollView(
controller: _scrollController,
child: Column(
children: [
SizedBox(
height: size.height * 0.49,
child: PhotoViewer(
/*
*/
)
),
SquareBoxMovableBuilder(
/*
*/
),
],
),
),
Container(
height: size.height * 0.075,
child: AnimatedBuilder(
animation: _ColorAnimationController,
builder: (context, child) => AppBar(
leading: GestureDetector(
onTap: () {
Navigator.of(context).pop();
},
child: Container(
decoration: BoxDecoration(
color: _bgLeadingColor.value,
borderRadius: BorderRadius.circular(10)
),
padding: EdgeInsets.all(size.width * 0.02),
margin: EdgeInsets.only(
top: size.height * 0.01,
left: size.height * 0.01,
right: size.height * 0.01,
bottom: size.height * 0.01,
),
child: Icon(
Icons.arrow_back,
color: Colors.white,
)
),
),
backgroundColor: _colorTween.value,
elevation: 0,
titleSpacing: 0.0,
iconTheme: IconThemeData(
color: _iconColorTween.value,
),
),
),
),
],
),
),
),
);
}
My question is how to avoid this error and still determine color of appbar depending on theme of application?
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 )
I have created a flip animation. The function is when i tap the container it flips. But what i want to have is, I just want to flip the container when onTap is called. But the animation starts while opening the app. What mistake i have mage her and please help me in fixing this. What mistake i have mage her and please help me in fixing this.
class _ChangeContainerState extends State<ChangeContainer>
with TickerProviderStateMixin {
Animation animation;
AnimationController animationController;
Naming selectedContainer = Naming.three;
#override
void initState() {
animationController =
AnimationController(duration: Duration(milliseconds: 500), vsync: this);
animation = Tween(begin: 0.0, end: 1.0).animate(animationController);
super.initState();
}
#override
Widget build(BuildContext context) {
animationController.forward();
return AnimatedBuilder(
animation: animationController,
builder: (BuildContext context, Widget child) {
return Scaffold(
body: Center(
child: selectedContainer == Naming.one
? GestureDetector(
onTap: () {
setState(() {
selectedContainer = Naming.two;
animationController.repeat();
});
},
child: Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.002)
..rotateX(pi + (pi * animation.value)),
alignment: FractionalOffset.center,
child: Container(
height: 200,
width: 200,
color: Colors.green,
child: Center(
child: Text(
'Container 2',
style: TextStyle(
fontSize: 30.0, color: Colors.white),
),
),
),
),
)
: selectedContainer == Naming.two
? GestureDetector(
onTap: () {
setState(() {
selectedContainer = Naming.three;
animationController.repeat();
});
},
child: Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.002)
..rotateX(pi + (pi * animation.value)),
alignment: FractionalOffset.center,
child: Container(
height: 200,
width: 200,
color: Colors.purpleAccent,
child: Center(
child: Text(
'Container 3',
style: TextStyle(
fontSize: 30.0, color: Colors.white),
),
),
),
),
)
: GestureDetector(
onTap: () {
setState(() {
selectedContainer = Naming.one;
animationController.repeat();
});
},
child: Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.002)
..rotateX(
pi + (pi * animation.value),
),
alignment: FractionalOffset.center,
child: Container(
height: 200,
width: 200,
color: Colors.yellow,
child: Center(
child: Text(
'Container 1',
style: TextStyle(fontSize: 30.0),
),
),
),
),
),
),
);
});
Whatever you add inside the init state will run as soon as the page opens. So you want the animation variable to be in the onTap method instead.
You will move your animationController and animation outside the initState method.
Full Code:
class _ChangeContainerState extends State<ChangeContainer>
with TickerProviderStateMixin {
Animation animation;
AnimationController animationController;
Naming selectedContainer = Naming.three;
#override
void initState() {
super.initState();
}
animationController =
AnimationController(duration: Duration(milliseconds: 500), vsync: this);
animation = Tween(begin: 0.0, end: 1.0).animate(animationController);
#override
Widget build(BuildContext context) {
animationController.forward();
return AnimatedBuilder(
animation: animationController,
builder: (BuildContext context, Widget child) {
return Scaffold(
body: Center(
child: selectedContainer == Naming.one
? GestureDetector(
onTap: () {
setState(() {
selectedContainer = Naming.two;
animationController.repeat();
});
},
child: Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.002)
..rotateX(pi + (pi * animation.value)),
alignment: FractionalOffset.center,
child: Container(
height: 200,
width: 200,
color: Colors.green,
child: Center(
child: Text(
'Container 2',
style: TextStyle(
fontSize: 30.0, color: Colors.white),
),
),
),
),
)
: selectedContainer == Naming.two
? GestureDetector(
onTap: () {
setState(() {
selectedContainer = Naming.three;
animationController.repeat();
});
},
child: Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.002)
..rotateX(pi + (pi * animation.value)),
alignment: FractionalOffset.center,
child: Container(
height: 200,
width: 200,
color: Colors.purpleAccent,
child: Center(
child: Text(
'Container 3',
style: TextStyle(
fontSize: 30.0, color: Colors.white),
),
),
),
),
)
: GestureDetector(
onTap: () {
setState(() {
selectedContainer = Naming.one;
animationController.repeat();
});
},
child: Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.002)
..rotateX(
pi + (pi * animation.value),
),
alignment: FractionalOffset.center,
child: Container(
height: 200,
width: 200,
color: Colors.yellow,
child: Center(
child: Text(
'Container 1',
style: TextStyle(fontSize: 30.0),
),
),
),
),
),
),
);
});
The issue is that progress icons and continue button are hidden in my stepper (cancel button is visible). So when i click on the place where "continue" button should exists (next to Cancel button) the "continue" event is happening and its correct.
The strange thing is that when i use the same code in a new test project, i can see both buttons and icons. Maybe its related to my sidebar but i am not sure.
import 'package:flutter/material.dart';
import 'package:taamin/bloc/navigation_bloc/navigation_bloc.dart';
class MyOrdersPage extends StatefulWidget with NavigationStates{
#override
_MyOrdersPageState createState() => _MyOrdersPageState();
}
class _MyOrdersPageState extends State<MyOrdersPage> {
List<Step> steps = [
Step(
title: const Text('New Account'),
isActive: true,
state: StepState.complete,
content: Column(
children: <Widget>[
TextFormField(
decoration: InputDecoration(labelText: 'Email Address'),
),
TextFormField(
decoration: InputDecoration(labelText: 'Password'),
),
],
),
),
Step(
title: const Text('Address'),
isActive: true,
state: StepState.editing,
content: Column(
children: <Widget>[
TextFormField(
decoration: InputDecoration(labelText: 'Home Address'),
),
TextFormField(
decoration: InputDecoration(labelText: 'Postcode'),
),
],
),
),
Step(
state: StepState.error,
title: const Text('Avatar'),
subtitle: const Text("Error!"),
content: Column(
children: <Widget>[
CircleAvatar(
backgroundColor: Colors.red,
)
],
),
),
];
StepperType stepperType = StepperType.vertical;
int currentStep = 0;
bool complete = false;
next() {
currentStep + 1 != steps.length
? goTo(currentStep + 1)
: setState(() => complete = true);
}
cancel() {
if (currentStep > 0) {
goTo(currentStep - 1);
}
}
goTo(int step) {
setState(() => currentStep = step);
}
switchStepType() {
setState(() => stepperType == StepperType.horizontal
? stepperType = StepperType.vertical
: stepperType = StepperType.horizontal);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text('Assurance VĂ©hicule'),
),
body: Column(children: <Widget>[
complete ? Expanded(
child: Center(
child: AlertDialog(
title: new Text("Profile Created"),
content: new Text(
"Tada!",
),
actions: <Widget>[
new FlatButton(
child: new Text("Close"),
onPressed: () {
setState(() => complete = false);
},
),
],
),
),
)
: Expanded(
child: Stepper(
steps: steps,
type: stepperType,
currentStep: currentStep,
onStepContinue: next,
onStepTapped: (step) => goTo(step),
onStepCancel: cancel,
),
),
]),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.list),
onPressed: switchStepType,
),
);
}
}
and here is the code of my sidebar menu:
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:rxdart/rxdart.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:taamin/bloc/navigation_bloc/navigation_bloc.dart';
import 'package:taamin/sidebar/menu_item.dart';
class SideBar extends StatefulWidget {
#override
_SideBarState createState() => _SideBarState();
}
class _SideBarState extends State<SideBar>
with SingleTickerProviderStateMixin<SideBar> {
AnimationController _animationController;
StreamController<bool> isSidebarOpenedStreamController;
Stream<bool> isSidebarOpenedStream;
StreamSink<bool> isSidebarOpenedSink;
final _animationDuration = const Duration(milliseconds: 500);
#override
void initState() {
super.initState();
_animationController =
AnimationController(vsync: this, duration: _animationDuration);
isSidebarOpenedStreamController = PublishSubject<bool>();
isSidebarOpenedStream = isSidebarOpenedStreamController.stream;
isSidebarOpenedSink = isSidebarOpenedStreamController.sink;
}
#override
void dispose() {
_animationController.dispose();
isSidebarOpenedStreamController.close();
isSidebarOpenedSink.close();
super.dispose();
}
void onIconPressed() {
final animationStatus = _animationController.status;
final isAnimationCompleted = animationStatus == AnimationStatus.completed;
//completed means sidebar is open
if (isAnimationCompleted) {
isSidebarOpenedSink.add(false);
_animationController.reverse();
} else {
isSidebarOpenedSink.add(true);
_animationController.forward();
}
}
#override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
return StreamBuilder<bool>(
initialData: false,
stream: isSidebarOpenedStream,
builder: (context, isSideBarOpenedAsync) {
return AnimatedPositioned(
duration: _animationDuration,
top: 0,
bottom: 0,
left: isSideBarOpenedAsync.data ? 0 : -screenWidth,
right: isSideBarOpenedAsync.data ? 0 : screenWidth - 30,
child: Row(
children: <Widget>[
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20),
color: const Color(0xFF262AAA),
child: Column(
children: <Widget>[
SizedBox(
height: 100,
),
ListTile(
title: Text(
"Yassine",
style: TextStyle(
color: Colors.white,
fontSize: 30,
fontWeight: FontWeight.w800),
),
subtitle: Text(
"firstname.name#gmail.com",
style: TextStyle(
color: Color(0xFF1BB5FD),
fontSize: 15,
),
),
leading: CircleAvatar(
child: Icon(
Icons.perm_identity,
color: Colors.white,
),
radius: 40,
),
),
Divider(
height: 64,
thickness: 0.5,
color: Colors.white.withOpacity(0.3),
indent: 32,
endIndent: 32,
),
MenuItem(
icon: Icons.home,
title: "Home",
onTap: () {
onIconPressed();
BlocProvider.of<NavigationBloc>(context).add(NavigationEvents.HomePageClickedEvent);
},
),
MenuItem(
icon: Icons.person,
title: "My Accounts",
onTap: () {
onIconPressed();
BlocProvider.of<NavigationBloc>(context).add(NavigationEvents.MyAccountClickedEvent);
},
),
MenuItem(
icon: Icons.directions_car,
title: "Assurance VĂ©hicule",
onTap: () {
onIconPressed();
BlocProvider.of<NavigationBloc>(context).add(NavigationEvents.MyOrdersClickedEvent);
},
),
MenuItem(
icon: Icons.card_giftcard,
title: "Wishlist",
),
Divider(
height: 64,
thickness: 0.5,
color: Colors.white.withOpacity(0.3),
indent: 32,
endIndent: 32,
),
MenuItem(
icon: Icons.settings,
title: "Settings",
),
MenuItem(
icon: Icons.exit_to_app,
title: "Logout",
onTap: () {
onIconPressed();
exit(0);
}
),
],
),
),
),
Align(
alignment: Alignment(0, -0.9),
child: GestureDetector(
onTap: () {
onIconPressed();
},
child: ClipPath(
clipper: CustomMenuClipper(),
child: Container(
width: 35,
height: 110,
color: Color(0xFF262AAA),
alignment: Alignment.centerLeft,
child: AnimatedIcon(
progress: _animationController.view,
icon: AnimatedIcons.menu_close,
color: Color(0xFF1BB5FD),
size: 25,
),
),
),
),
)
],
),
);
},
);
}
}
class CustomMenuClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
Paint paint = Paint();
paint.color = Colors.white;
final width = size.width;
final height = size.height;
Path path = Path();
path.moveTo(0, 0);
path.quadraticBezierTo(0, 8, 10, 16);
path.quadraticBezierTo(width - 1, height / 2 - 20, width, height / 2);
path.quadraticBezierTo(width + 1, height / 2 + 20, 10, height - 16);
path.quadraticBezierTo(0, height - 8, 0, height);
path.close();
return path;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) {
return false;
}
}
add controlsBuilder inside stepper
controlsBuilder: (BuildContext context,
{VoidCallback onStepContinue, VoidCallback onStepCancel}) {
return Row(
children: <Widget>[
FlatButton(
onPressed: onStepContinue,
child: const Text('Continue',
style: TextStyle(color: Colors.white)),
color: Colors.redAccent,
),
new Padding(
padding: new EdgeInsets.all(10),
),
FlatButton(
onPressed: onStepCancel,
child: const Text(
'Cancel',
style: TextStyle(color: Colors.white),
),
color: Colors.black,
),
],
);
},
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();
}
}