Hello, issue with GestureDetector, Pan and Scale issue - android

I've a code that allows me to move text with a finger over an image, and also scale the text using both fingers over the text, but there are two problems and not much about it at google, hope someone can help me, first problem is:
If I uncomment the commented code, I get this error:
*The following assertion was thrown building HomePage(dirty, state: _HomePageState#8b5a9):
Incorrect GestureDetector arguments.
Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan.
Just use the scale gesture recognizer.
*
If I only use scale, delta(details.delta.dx) is not available in scale, so I get an error.
And the other issue is:
When I set textScaleFactor: _scaleFactor, inside my TEXT widget, the text desappears , how can I fix this ? Thanks a lot guys.
import 'package:flutter/material.dart';
void main() {
runApp(const TextOverImage());
}
class TextOverImage extends StatelessWidget {
const TextOverImage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
// Size size = MediaQuery.of(context).size;
return MaterialApp(
home: Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text('Text Over Image Image Example'),
),
body: Center(
child: Container(
height: 300,
width: 300,
child: Stack(
children: <Widget>[
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: Colors.blue,
image: const DecorationImage(
image: NetworkImage(
"https://thumbs.dreamstime.com/b/funny-face-baby-27701492.jpg"),
fit: BoxFit.fill)),
),
const HomePage()
],
),
),
),
),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
Offset offset = Offset.zero;
Offset offset2 = Offset.zero;
double scale = 0.0;
double _scaleFactor = 1.0;
double _baseScaleFactor = 1.0;
double _savedVal = 1.0;
#override
Widget build(BuildContext context) {
return Stack(
children: [
Positioned(
left: offset.dx,
top: offset.dy,
child: Row(
children: [
GestureDetector(
onPanUpdate: (details) {
setState(() {
offset = Offset(offset.dx + details.delta.dx,
offset.dy + details.delta.dy);
});
},
// behavior: HitTestBehavior.translucent,
//
// onScaleStart: (details) {
// _baseScaleFactor = _scaleFactor;
//
// },
//
// onScaleUpdate: (details) {
// setState(() {
// _scaleFactor = _baseScaleFactor * details.scale;
// });
// },
child: SizedBox(
width: 300,
height: 300,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Text("You Think You Are Funny But You Are Not",
// here if I remove _scaleFactor the text is GONE
textScaleFactor: _scaleFactor,
textAlign: TextAlign.center,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18.0,
color: Colors.red)),
),
),
),
),
],
),
),
Positioned(
left: offset2.dx,
top: offset2.dy,
child: Row(
children: [
GestureDetector(
onPanUpdate: (details) {
setState(() {
offset2 = Offset(offset2.dx + details.delta.dx,
offset2.dy + details.delta.dy);
});
},
child: const SizedBox(
width: 300,
height: 300,
child: Padding(
padding: EdgeInsets.all(8.0),
child: Center(
child: Text("xx xxxx x xx x x xxxxxx",
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18.0,
color: Colors.red)),
),
),
),
),
],
),
),
],
);
}
}

You can achieve the functionality using MatrixGestureDetector from matrix_gesture_detector package.
Straightforward implementation will be as follows:
class FloatingWidget extends StatefulWidget {
final Widget child;
const FloatingWidget({Key? key, required this.child}) : super(key: key);
#override
State<FloatingWidget> createState() => _FloatingWidgetState();
}
class _FloatingWidgetState extends State<FloatingWidget> {
Matrix4 _transform = Matrix4.identity();
#override
Widget build(BuildContext context) => Transform(
transform: _transform,
child: MatrixGestureDetector(
onMatrixUpdate: (matrix, translationDeltaMatrix, scaleDeltaMatrix, rotationDeltaMatrix) {
setState(() {
_transform = matrix;
});
},
child: widget.child,
),
);
}
In your case,
import 'package:flutter/material.dart';
import 'package:matrix_gesture_detector/matrix_gesture_detector.dart';
void main() {
runApp(const TextOverImage());
}
class TextOverImage extends StatelessWidget {
const TextOverImage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
// Size size = MediaQuery.of(context).size;
return MaterialApp(
home: Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text('Text Over Image Image Example'),
),
body: Center(
child: SizedBox.fromSize(
size: const Size(300, 300),
child: Stack(
children: <Widget>[
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: Colors.blue,
image: const DecorationImage(
image: NetworkImage(
"https://thumbs.dreamstime.com/b/funny-face-baby-27701492.jpg"),
fit: BoxFit.fill)),
),
const HomePage()
],
),
),
),
),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Stack(
children: [
FloatingWidget(
child: Row(
children: [
SizedBox.fromSize(
size: const Size(300, 300),
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Center(
child: Text("You Think You Are Funny But You Are Not",
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18.0,
color: Colors.red,
)
),
),
),
),
],
),
),
FloatingWidget(
child: Row(
children: [
SizedBox.fromSize(
size: const Size(300, 300),
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Center(
child: Text("xx xxxx x xx x x xxxxxx",
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18.0,
color: Colors.red,
),
),
),
),
),
],
),
),
],
);
}
}
Well, scale gesture is a superset of pan gesture and you can get its offset delta as
scaleUpdateDetails.focalPointDelta
I have no idea about the text scale factor problem at this point but it will be irrelevant if you are using Transform widget.
To learn more about Transform widget, I suggest this article from Medium.

Related

Flutter execute a form validation with Button with seperate class

I have one login page .dart that contains 3 seperate object class dart such as : InputEmail , Password and ButtonLogin which its split each other but it's called together in login page
My problem is how can i validate form input email and password when i submit the button login when field is empty and email not valid
I tried to create Globalkey FormState inside login page and call it on button login class dart though
Onpressed event but nothing give me error message.
class LoginPage extends StatefulWidget {
const LoginPage({Key? key}) : super(key: key);
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: [Colors.redAccent, Colors.lightBlueAccent]),
),
key: _formKey,
child: ListView(
children: <Widget>[
Column(
children: <Widget>[
Row(children: const <Widget>[
VerticalText(),
TextLogin(),
]),
const InputEmail(),
const PasswordInput(),
const ButtonLogin(),
const FirstTime(),
],
),
],
),
),
);
}
}
class ButtonLogin extends StatefulWidget {
const ButtonLogin({Key? key}) : super(key: key);
#override
_ButtonLoginState createState() => _ButtonLoginState();
}
class _ButtonLoginState extends State<ButtonLogin> {
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 40, right: 50, left: 200),
child: Container(
alignment: Alignment.bottomRight,
height: 50,
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
boxShadow: const [
BoxShadow(
color: Colors.blue,
blurRadius: 10.0, // has the effect of softening the shadow
spreadRadius: 1.0, // has the effect of extending the shadow
offset: Offset(
5.0, // horizontal, move right 10
5.0, // vertical, move down 10
),
),
],
color: Colors.white,
borderRadius: BorderRadius.circular(30),
),
child: FlatButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// If the form is valid, display a snackbar. In the real world,
// you'd often call a server or save the information in a database.
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Processing Data')),
);
}
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
Text(
'OK',
style: TextStyle(
color: Colors.lightBlueAccent,
fontSize: 14,
fontWeight: FontWeight.w700,
),
),
Icon(
Icons.arrow_forward,
color: Colors.lightBlueAccent,
),
],
),
),
),
);
}
}
You can pass the callback of onPressed function of button to Login Page and validate it there.
class ButtonLogin extends StatefulWidget {
const ButtonLogin({Key? key, required this.onPressed}) : super(key: key);
final VoidCallBack onPressed ;
#override
_ButtonLoginState createState() => _ButtonLoginState();
}
class _ButtonLoginState extends State<ButtonLogin> {
#override
Widget build(BuildContext context) {
return Padding(
child: Container(
....
child: FlatButton(
onPressed: widget.onPressed,
child: Row(
...
);
}
}
and change add this onPressed parameter inside ButtonLogin widget on Login page
const InputEmail(),
const PasswordInput(),
const ButtonLogin(onPressed: () {
if (_formKey.currentState!.validate()) {
// If the form is valid, display a snackbar. In the real world,
// you'd often call a server or save the information in a database.
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Processing Data')),
);
}
},),
const FirstTime(),
The easy way is to create a single StateFulWidget and within the StateFulWidget, you brake your form Widgets (InputEmail, Password and ButtonLogin) into separate Widget methods(functions). Afterward Wrap them with a Form widget using Row, Column or any Widget that accept list.
Next you add the _formKey to the Form widget. I will advice the use of controller for your inputs. Below is an example using your code. By the way change FlatButton -> TextButton
import 'package:flutter/material.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
#override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: [Colors.redAccent, Colors.lightBlueAccent]),
),
child: Form(
key: _formKey,
child: ListView(
children: [
Column(
children: [
// inputEmail(),
// passwordInput(),
buttonLogin(),
],
),
],
),
),
),
);
}
Widget buttonLogin() {
return Padding(
padding: const EdgeInsets.only(top: 40, right: 50, left: 200),
child: Container(
alignment: Alignment.bottomRight,
height: 50,
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(30),
boxShadow: const [
BoxShadow(
color: Colors.blue,
blurRadius: 10.0,
spreadRadius: 1.0,
offset: Offset(5.0, 5.0),
),
],
),
child: TextButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Processing Data')),
);
}
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
Text(
'OK',
style: TextStyle(
color: Colors.lightBlueAccent,
fontSize: 14,
fontWeight: FontWeight.w700,
),
),
Icon(
Icons.arrow_forward,
color: Colors.lightBlueAccent,
),
],
),
),
),
);
}
}

Updating State of StatefulWidget from other StatefulWidget in Flutter?

i am trying to update the state of the animated switcher in the middle area. i am trying to do this using a setstate in the lower area. but it does not work.
the first thing i did is to create a variable with a boolean data type in the home class.
then i passed the variable to both the middle area and the lower area
the idea was that if the same variable is passed to the class whose state i am trying to update, and the class with the set state, it would work. but it seems i am wrong. i would appreciate some assistance.
the boolean variable i am trying to make work is the _rep variable
This is the Home widget
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
late AnimationController _animationController;
late AnimationController _controller;
late Animation<Offset> _animation;
late Animation<Offset> _anime;
bool _rep = true;
#override
void initState() {
_animationController = AnimationController(
vsync: this,
duration: Duration(seconds: 2)
);
_animation = Tween<Offset>(
begin:Offset (0.0, 0.0),
end: Offset (0.0,3.0),
).animate(CurvedAnimation(
parent: _animationController,
curve: Curves.easeIn));
_anime = Tween<Offset>(
begin:Offset (0.0, 0.0),
end: Offset (0.0,-0.55),
).animate(CurvedAnimation(
parent: _animationController,
curve: Curves.easeIn));
super.initState();
}
#override
void dispose() {
_animationController.dispose();
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
scrollDirection: Axis.vertical,
physics: const NeverScrollableScrollPhysics(),
child: Padding(
padding: EdgeInsets.only(top: 3.h),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TopIcon(icons: Icons.arrow_back, color:Colors.grey.shade300 ,),
SizedBox(
height: 13.h,
width: 13.w,
child: Image.asset('assets/images/download.png')
),
TopIcon(icons: Icons.shopping_bag_outlined, color: Colors.grey.shade300,),
],
),
SizedBox(
height: 3.h,
),
Text('Frappuccino',
style: TextStyle(
fontSize: 27.sp,
fontWeight: FontWeight.bold
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text('White Chocolate',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.grey.shade400
),
),
),
MiddleArea(
controller: _animationController,
animation: _animation,
rep: _rep,
),
LowerArea(controller: _animationController, anime: _anime, rep = _rep),
],
),
),
),
);
}
}
This is the middle area
class MiddleArea extends StatefulWidget {
MiddleArea({Key? key, required this.controller, required this.animation, required this.rep}) : super(key: key);
AnimationController controller;
Animation<Offset> animation;
final bool rep;
#override
State<MiddleArea> createState() => _MiddleAreaState();
}
class _MiddleAreaState extends State<MiddleArea> {
bool _flag = true;
bool _favourite = true;
#override
Widget build(BuildContext context) {
print(widget.rep);
return SizedBox(
height: 52.h,
child: Stack(
children: [
Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 135.0),
child: Text('STARBUCKS',
style: TextStyle(
fontFamily: 'Typette',
color: Colors.brown.shade200,
fontSize: 30.sp,
fontWeight: FontWeight.w400
),
),
),
Text('STARBUCKS',
style: TextStyle(
fontFamily: 'Typette',
color: Colors.brown.shade100,
fontSize: 30.sp,
fontWeight: FontWeight.w400
),
),
Text('STARBUCKS',
style: TextStyle(
fontFamily: 'Typette',
color: Colors.brown.shade50,
fontSize: 30.sp,
fontWeight: FontWeight.w400
),
),
],
),
Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
SizeAndFave(text: 'Preference'),
SizeAndFave(text: 'Fave!')
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: (){
setState(() {
_flag = !_flag;
});
},
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
transitionBuilder: (Widget child, Animation<double> animation){
return FadeTransition(opacity: animation, child: child,);
},
child: widget.rep == true?Padding(
padding: const EdgeInsets.all(14.0),
key: const Key('1'),
child: Container(
height: 40,
width: 40,
decoration: BoxDecoration(
border: Border.all(
color: Colors.brown.shade300,
width: 3
),
borderRadius: BorderRadius.circular(10)
),
child: const Center(
child: Icon(
Icons.coffee_outlined,
size: 20,
),
)
),
):null,
)
),
GestureDetector(
onTap: (){
setState(() {
_favourite = !_favourite;
});
},
child: _favourite? TopIcon(icons: Icons.favorite_border, color: Colors.brown.shade300)
:TopIcon(
icons: Icons.favorite, color: Colors.brown.shade300)
)
],
)
],
),
AnimatedSwitcher(
duration: Duration(seconds: 1),
transitionBuilder: (Widget child, Animation<double> animation) {
return FadeTransition( opacity: animation,
child: child);
},
child: _flag == true ? Center(
key: const Key('1'),
child: SlideTransition(
position: widget.animation,
child: SizedBox(
height: 80.h,
width: 80.w,
child: Image.asset('assets/images/starcup.png'),
),
),
):Center(
key: const Key('2'),
child: SlideTransition(
position: widget.animation,
child: SizedBox(
height: 80.h,
width: 80.w,
child: Image.asset('assets/images/greeen.png'),
),
),
),
),
Positioned(
child:
Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: EdgeInsets.only(top: 30.h),
child: TopIcon(
icons: Icons.car_crash_outlined, color: Colors.brown.shade300),
),
)),
const Positioned(
child:
Align(
alignment: Alignment.bottomLeft,
child: Padding(
padding: EdgeInsets.only(top: 330.0, left: 14),
child: Text('\$ 5.99',
style: TextStyle(
fontSize: 27,
fontWeight: FontWeight.bold
),
),
),
))
],
),
);
}
}
and lastly, the lower area
class LowerArea extends StatefulWidget {
final AnimationController controller;
final Animation<Offset> anime;
bool rep;
LowerArea({Key? key, required this.controller, required this.anime, required this.rep}) : super(key: key);
#override
State<LowerArea> createState() => _LowerAreaState();
}
class _LowerAreaState extends State<LowerArea> {
bool _bigger = true;
bool _fade = true;
void move(){
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: EdgeInsets.all(1.h),
child: const Text('Tall Frappuccino',
style: TextStyle(
fontWeight: FontWeight.w500
),
),
),
Padding(
padding: EdgeInsets.only(right: 5.h),
child: const Text('Swipe Down',
style: TextStyle(
fontWeight: FontWeight.w500
),
),
),
Padding(
padding: EdgeInsets.all(2.h),
child: const Text('Pickup',
style: TextStyle(
fontWeight: FontWeight.w500
),
),
)
],
),
),
SlideTransition(
position: widget.anime,
child: AnimatedContainer(
// height: 11.h,
width: _bigger ? 35.h: 80.h,
duration: const Duration(milliseconds: 500),
child: Stack(
fit: StackFit.loose,
children: [
Center(child: Image.asset('assets/images/baggie.png')),
Center(
child: Padding(
padding: EdgeInsets.only(bottom: 4.h),
child: GestureDetector(
onTap: (){
widget.controller.forward();
setState(() {
_bigger = !_bigger;
_fade = !_fade;
widget.rep = !widget.rep;
print('this is fade $_fade ');
});
},
child: AnimatedSwitcher(
duration: Duration(milliseconds: 300),
transitionBuilder: (Widget child, Animation<double> animation){
return FadeTransition(opacity: animation, child: child,);
},
child: _fade? Container(
key: Key('1'),
height: 8.h,
width: 7.w,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(15)
),
child: Column(
children: [
Padding(
padding: EdgeInsets.all(0.3.h),
child: Icon(
Icons.lock_outline,
color: Colors.white54,
size: 2.5.h,
),
),
Icon(
Icons.arrow_drop_down,
color: Colors.white12,
size: 3.h,
),
],
),
):null,
),
),
),
)
],
),
),
)
],
);
}
use provider pacakges https://pub.dev/packages/provider
Create a class that inherits the functions of ChangeNotifyer to create a flag to control and create a setter.
provider class
class StateController extends ChangeNotifier{
bool _req = false;
bool get req => _req; //getter
setReqValue(){
_req = !_req;
notifyListener();
}
}
Declare the provider class in the main function. You can change the location of the declaration according to Wiget tree, but first declare it in main
Change main.dart
void main(){
runApp(
Multiprovider(
providers: [
ChangeNotifierProvider(create: (_) => StateController()),
],
child: HomePage(),
)
);
}
The UI is updated by notifyListener().
change UI
child: context.watch<StateController>().req == true ? Padding(
padding: const EdgeInsets.all(14.0),
key: const Key('1'),
child: Container(
height: 40,
width: 40,
decoration: BoxDecoration(
border: Border.all(
color: Colors.brown.shade300,
width: 3
),
borderRadius: BorderRadius.circular(10)
),
child: const Center(
child: Icon(
Icons.coffee_outlined,
size: 20,
),
)
),
):null,
Change State
onTap: (){
widget.controller.forward();
setState(() {
_bigger = !_bigger;
_fade = !_fade;
context.read<StateController>().setReqValue();
print('this is fade $_fade ');
});
},
I am too lazy to try understand your code. But if you want to update state of the page after you pop from Navigation to it.
In page you want to update
Navigation.push(context, /* Page that will change something */)
// Future will trigger then you return to this page.
.then((_) => setState(() {}))

Flutter - How to change the visibility of a single widget in a widget list?

I pull some variables in Firebase and I create a widget list with these variables. I want to control widget visibility when I click a widget. When I use the Visibility widget and set "visible: widgetVisibility" value, all widgets are changed at the same time. I only want the widget I clicked to change. How can I do that?
body: StreamBuilder<QuerySnapshot>(
stream: _keyService.getKeys(),
builder: (context, snapshot) {
return !snapshot.hasData
? const Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index) {
DocumentSnapshot mypost = snapshot.data!.docs[index];
return Padding(
padding: EdgeInsets.all(size * 0.3),
child: InkWell(
onTap: () {
valueVisible = !valueVisible;
},
child: Container(
decoration: BoxDecoration(
color: ColorItems.mainColor,
border: Border.all(width: 5, color: Colors.grey),
borderRadius: BorderRadius.all(Radius.circular(20))),
child: Padding(
padding: EdgeInsets.all(size),
child: Container(
child: Row(
children: [
Expanded(
child: Text(
"${mypost['key']}",
style: const TextStyle(
color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
),
),
const Text(
": ",
style:
TextStyle(color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
),
const SizedBox(
width: 20,
),
Expanded(
child: Visibility(
visible: valueVisible,
child: Text(
"${mypost['value']}",
style: const TextStyle(
color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold),
),
))
],
),
),
),
),
),
);
},
);
})
Additionally, screenshots is here..
This might not be the optimal solution, but I always create a List for that purpose:
instead of one valueVisible bool I create a List and add a bool for each item in the list.
...itemBuilder: (context, index) { valueVisibleList.add(true)...
and in the button I then use the current item index to change only the corresponding bool
onTap: () { setState(({
valueVisibleList[index] = !valueVisibleList[index];
})
},
Just use a Map<String, bool> where the keys are the post's key and the value its visibility. The visibility should default to true if the key is not present. And the state should only change inside a setState function.
It would be something like the following (Check out the live demo on DartPad):
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 const MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
final posts = [
for (var i = 0; i < 100; i++) {'key': 'key$i', 'value': '$i'}
];
class _MyHomePageState extends State<MyHomePage> {
final valueVisible = <String, bool>{};
#override
Widget build(BuildContext context) {
const size = 16.0;
return Scaffold(
body: ListView.builder(
itemCount: posts.length,
itemBuilder: (context, index) {
final mypost = posts[index];
return Padding(
padding: const EdgeInsets.all(size * 0.3),
child: InkWell(
onTap: () {
setState(() {
valueVisible[mypost['key']!] =
!(valueVisible[mypost['key']!] ?? true);
});
},
child: Container(
decoration: BoxDecoration(
color: const Color(0xffff9400),
border: Border.all(width: 5, color: Colors.grey),
borderRadius: const BorderRadius.all(Radius.circular(20))),
child: Padding(
padding: const EdgeInsets.all(size),
child: Row(
children: [
Expanded(
child: Text(
"${mypost['key']}",
style: const TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold),
),
),
const Text(
": ",
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold),
),
const SizedBox(
width: 20,
),
Expanded(
child: Visibility(
visible: valueVisible[mypost['key']!] ?? true,
child: Text(
"${mypost['value']}",
style: const TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold),
),
),
)
],
),
),
),
),
);
},
),
);
}
}

How to disable a button when there is no checkbox got checked? - Flutter

Here I made a ListView.builder with a length from merchants list. The NEXT button is presented here in this dart file.
import 'package:flutter/material.dart';
import 'package:untitled/constants.dart';
import 'package:untitled/text_checkbox.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text(
"Test",
style: TextStyle(color: Colors.white),
),
backgroundColor: Colors.black,
),
body: Container(
padding: const EdgeInsets.only(
top: 20,
),
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 35),
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height * 0.3,
child: ListView.builder(
itemCount: Merchants.merchants.length,
itemBuilder: (context, index) {
return const TextCheckbox();
},
),
),
ElevatedButton(
onPressed: () {
print ('NEXT');
},
child: const Text('NEXT'),
)
],
),
),
),
);
}
}
and here's the code from the TextCheckbox
import 'package:untitled/constants.dart';
class TextCheckbox extends StatefulWidget {
const TextCheckbox({Key? key}) : super(key: key);
#override
_TextCheckboxState createState() => _TextCheckboxState();
}
class _TextCheckboxState extends State<TextCheckbox> {
bool isChecked = false;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: Row(
children: [
InkWell(
onTap: () {
setState(() {
isChecked = !isChecked;
});
},
child: Container(
width: 15,
height: 15,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(3),
border: Border.all(
width: isChecked == false ? 1 : 0,
color: isChecked == false ? const Color(0xff7D7D7D) : const Color(0xFF31708F)),
color: isChecked == false ? const Color(0xffffffff) : const Color(0xFF31708F),
shape: BoxShape.rectangle,
),
child: isChecked == false
? const Text("")
: const Icon(
Icons.check,
size: 15,
color: Color(0xffffffff),
),
),
),
const Spacer(),
Text(Merchants.randomMerchant),
],
),
);
}
}
the merchants list is called from another dart file called constants.
class Merchants {
static List<String> merchants = ['Bang Ongot', 'Bang Kotan', 'Bang Bangtut', 'Bang BCA'];
static var randomMerchant = (merchants.toList()..shuffle()).last;
}
what I wanna ask is, how to disable the NEXT button if there is no checkbox got checked?
Try this:
Have your TextCheckbox Like so
class TextCheckbox extends StatefulWidget {
final ValueChanged<bool> onChanged;
const TextCheckbox({Key? key}) : super(key: key);
#override
_TextCheckboxState createState() => _TextCheckboxState();
}
class _TextCheckboxState extends State<TextCheckbox> {
bool isChecked = false;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 5),
child: Row(
children: [
InkWell(
onTap: () {
setState(() {
isChecked = !isChecked;
});
widget.onChanged(isChecked);
},
child: Container(
width: 15,
height: 15,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(3),
border: Border.all(
width: isChecked == false ? 1 : 0,
color: isChecked == false ? const Color(0xff7D7D7D) : const Color(0xFF31708F)),
color: isChecked == false ? const Color(0xffffffff) : const Color(0xFF31708F),
shape: BoxShape.rectangle,
),
child: isChecked == false
? const Text("")
: const Icon(
Icons.check,
size: 15,
color: Color(0xffffffff),
),
),
),
const Spacer(),
Text(Merchants.randomMerchant),
],
),
);
}
}
And your HomeScreen like so:
class HomeScreen extends StatefulWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int numberOfChecks = 0;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text(
"Test",
style: TextStyle(color: Colors.white),
),
backgroundColor: Colors.black,
),
body: Container(
padding: const EdgeInsets.only(
top: 20,
),
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 35),
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height * 0.3,
child: ListView.builder(
itemCount: Merchants.merchants.length,
itemBuilder: (context, index) {
return TextCheckbox(
onChanged: (value){
setState((){
value ? numberOfChecks++ : numberOfChecks--;
});
}
);
},
),
),
ElevatedButton(
onPressed: numberOfChecks == 0 ? null : () {
print ('NEXT');
},
child: const Text('NEXT'),
)
],
),
),
),
);
}
}
set ElevatedButton enabled value like this:
ElevatedButton(
enabled: isChecked
onPressed: () {
print ('NEXT');
},
child: const Text('NEXT'),
)
You should do something like this:
int totalNumberOfCheckedTextBoxes = 0;
ElevatedButton(
onPressed: totalNumberOfCheckedTextBoxes > 0 ? () {
print ('NEXT');
} : null,
child: const Text('NEXT'),
)
increment / decrement the value of totalNumberOfCheckedTextBoxes when a checkbox is checked or unchecked

How to Pass Widget and Functions of That Widget To main.dart When Completing an Animation?

I'm kinda new to Flutter. In main.dart file I have a logo and when I run the application, the logo fades into the screen and go to the top of the screen. I have used two animation controllers for that.
In welcome.dart file there is a code for two buttons (login and Signup) one animation controller for fade in animation to that buttons.
I need to show that when logo completes the animations, show the buttons on the screen with fade in animation.
What I have tried is put adListener to the logo animation and when logo animation completes, start the button animations. But it's not working.
Here's my code -
main.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'welcome.dart';
void main() {
runApp(MyApp());
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarBrightness: Brightness.light),
);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: ('SplashScreeen'),
home: MySplashScreen(title: 'SplashScreen'),
);
}
}
class MySplashScreen extends StatefulWidget {
MySplashScreen({Key key, this.title}) : super(key: key);
final String title;
#override
_MySplashScreenState createState() => _MySplashScreenState();
}
class _MySplashScreenState extends State<MySplashScreen>
with TickerProviderStateMixin {
AnimationController fadeAnimationLogoController;
AnimationController moveUpAnimationLogoController;
Animation<double> fadeAnimationLogo;
Animation<Offset> moveUpAnimationLogo;
initState(){
super.initState();
fadeAnimationLogoController = AnimationController(duration: Duration(milliseconds: 1500),vsync: this);
moveUpAnimationLogoController = AnimationController(duration: Duration(milliseconds: 1000),vsync: this,);
fadeAnimationLogo =CurvedAnimation(parent: fadeAnimationLogoController, curve: Curves.easeIn);
moveUpAnimationLogo = Tween<Offset>(begin: Offset(0,0),end: Offset(0, -0.2),).animate(moveUpAnimationLogoController);
fadeAnimationLogoController.forward();
fadeAnimationLogoController.addListener((){
if(fadeAnimationLogo.status == AnimationStatus.completed){
moveUpAnimationLogoController.forward();
}
});
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: FadeTransition (
opacity: fadeAnimationLogo,
child: SlideTransition(
position: moveUpAnimationLogo,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image(
image: AssetImage('assets/images/csrhuntlogo.png'),
height: 300,
width: 300,
),
Text(
('C S R H U N T'),
style: TextStyle(
fontFamily: 'League Spartan',
height: 1,
fontSize: 34,
color: Colors.black,
decoration: TextDecoration.none,
),
),
Text(
('FIND PLAY EARN'),
style: TextStyle(
fontFamily: 'Montserrat',
height: 1,
fontSize: 15,
color: Colors.black,
decoration: TextDecoration.none,
),
),
],
),
),
),
);
}
}
welcome.dart
import 'package:flutter/material.dart';
class Welcome extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: ('WelcomeScreen'),
home: WelcomeScreen(title: 'WelcomeScreen'),
);
}
}
class WelcomeScreen extends StatefulWidget {
WelcomeScreen({Key key, this.title}) : super(key: key);
final String title;
#override
_WelcomeScreenState createState() => _WelcomeScreenState();
}
class _WelcomeScreenState extends State<WelcomeScreen>
with SingleTickerProviderStateMixin {
AnimationController fadeAnimationWelcomeController;
Animation<double> fadeAnimationWelcome;
#override
void initState() {
fadeAnimationWelcomeController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 2000),
);
fadeAnimationWelcome = CurvedAnimation(
parent: fadeAnimationWelcomeController, curve: Curves.easeIn);
super.initState();
fadeAnimationWelcomeController.forward();
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: FadeTransition(
opacity: fadeAnimationWelcome,
child: Stack(
children: <Widget>[
Positioned(
top: 590,
left: 20,
child: SizedBox(
width: 350.0,
height: 50.0,
child: RaisedButton(
color: new Color.fromRGBO(255, 213, 0, 1.0),
textColor: Colors.black,
onPressed: () {},
child: Text(
'log in',
style: TextStyle(
height: 1,
fontSize: 25,
fontFamily: 'League Spartan',
),
),
shape: RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(18.0),
side: BorderSide(color: Colors.transparent),
),
),
),
),
Positioned(
bottom: 50,
left: 20,
child: SizedBox(
width: 350.0,
height: 50.0,
child: RaisedButton(
color: new Color.fromRGBO(255, 213, 0, 1.0),
textColor: Colors.black,
onPressed: () {},
child: Text(
'Sign up',
style: TextStyle(
height: 1,
fontSize: 25,
fontFamily: 'League Spartan',
),
),
shape: RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(18.0),
side: BorderSide(color: Colors.transparent),
),
),
),
),
],
),
),
);
}
}
You can copy paste run full code below
For demo purpose, I slow down animation duration to 5 seconds
You can set a bool showWelcome to control when to show SingUp button
When Move Up Logo animation complete show SignUp button with setState
code snippet
moveUpAnimationLogoController.addListener(() {
if (moveUpAnimationLogo.status == AnimationStatus.completed) {
//moveUpAnimationLogoController.forward();
setState(() {
showWelcome = true;
});
}
});
showWelcome
? Expanded(
child: WelcomeScreen(
title: "test",
),
)
: Container(),
working demo
full code
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(MyApp());
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarBrightness: Brightness.light),
);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: ('SplashScreeen'),
home: MySplashScreen(title: 'SplashScreen'),
);
}
}
class MySplashScreen extends StatefulWidget {
MySplashScreen({Key key, this.title}) : super(key: key);
final String title;
#override
_MySplashScreenState createState() => _MySplashScreenState();
}
class _MySplashScreenState extends State<MySplashScreen>
with TickerProviderStateMixin {
AnimationController fadeAnimationLogoController;
AnimationController moveUpAnimationLogoController;
Animation<double> fadeAnimationLogo;
Animation<Offset> moveUpAnimationLogo;
bool showWelcome = false;
initState() {
super.initState();
fadeAnimationLogoController =
AnimationController(duration: Duration(seconds: 5), vsync: this);
moveUpAnimationLogoController = AnimationController(
duration: Duration(seconds: 5),
vsync: this,
);
fadeAnimationLogo = CurvedAnimation(
parent: fadeAnimationLogoController, curve: Curves.easeIn);
moveUpAnimationLogo = Tween<Offset>(
begin: Offset(0, 0),
end: Offset(0, -0.2),
).animate(moveUpAnimationLogoController);
fadeAnimationLogoController.forward();
fadeAnimationLogoController.addListener(() {
if (fadeAnimationLogo.status == AnimationStatus.completed) {
moveUpAnimationLogoController.forward();
}
});
moveUpAnimationLogoController.addListener(() {
if (moveUpAnimationLogo.status == AnimationStatus.completed) {
//moveUpAnimationLogoController.forward();
setState(() {
showWelcome = true;
});
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Container(
color: Colors.white,
child: FadeTransition(
opacity: fadeAnimationLogo,
child: SlideTransition(
position: moveUpAnimationLogo,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image(
image: AssetImage('assets/images/csrhuntlogo.png'),
height: 300,
width: 300,
),
Text(
('C S R H U N T'),
style: TextStyle(
fontFamily: 'League Spartan',
height: 1,
fontSize: 34,
color: Colors.black,
decoration: TextDecoration.none,
),
),
Text(
('FIND PLAY EARN'),
style: TextStyle(
fontFamily: 'Montserrat',
height: 1,
fontSize: 15,
color: Colors.black,
decoration: TextDecoration.none,
),
),
],
),
),
),
),
showWelcome
? Expanded(
child: WelcomeScreen(
title: "test",
),
)
: Container(),
],
),
);
}
}
class WelcomeScreen extends StatefulWidget {
WelcomeScreen({Key key, this.title}) : super(key: key);
final String title;
#override
_WelcomeScreenState createState() => _WelcomeScreenState();
}
class _WelcomeScreenState extends State<WelcomeScreen>
with SingleTickerProviderStateMixin {
AnimationController fadeAnimationWelcomeController;
Animation<double> fadeAnimationWelcome;
#override
void initState() {
fadeAnimationWelcomeController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 2000),
);
fadeAnimationWelcome = CurvedAnimation(
parent: fadeAnimationWelcomeController, curve: Curves.easeIn);
super.initState();
fadeAnimationWelcomeController.forward();
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: FadeTransition(
opacity: fadeAnimationWelcome,
child: Stack(
children: <Widget>[
Positioned(
top: 590,
left: 20,
child: SizedBox(
width: 350.0,
height: 50.0,
child: RaisedButton(
color: new Color.fromRGBO(255, 213, 0, 1.0),
textColor: Colors.black,
onPressed: () {},
child: Text(
'log in',
style: TextStyle(
height: 1,
fontSize: 25,
fontFamily: 'League Spartan',
),
),
shape: RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(18.0),
side: BorderSide(color: Colors.transparent),
),
),
),
),
Positioned(
bottom: 50,
left: 20,
child: SizedBox(
width: 350.0,
height: 50.0,
child: RaisedButton(
color: new Color.fromRGBO(255, 213, 0, 1.0),
textColor: Colors.black,
onPressed: () {},
child: Text(
'Sign up',
style: TextStyle(
height: 1,
fontSize: 25,
fontFamily: 'League Spartan',
),
),
shape: RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(18.0),
side: BorderSide(color: Colors.transparent),
),
),
),
),
],
),
),
);
}
}

Categories

Resources