Flutter How to make 3 buttons only radio selectable? - flutter

I have created a row of 3 buttons and each of them will change the color independently when clicked, each button works independently which means you could make all buttons clicked, but what i want to achieve is that i can only make one button clicked at one time, which means if i click button 2 after clicking button1, button2 will change the state to clicked, and button1 will change the state to unclicked, just like radio buttons. How can this can be done?
Below is the code of the buttons
class _MyHomePageBodyState extends State<MyHomePageBody> {
#override
Widget build(BuildContext context) {
return Container(
child: Row(
children: [
Dis(),
SizedBox(width:5),
Price(),
SizedBox(width:5),
New(),
],
),
);
}
}
class Gcolor {
static Color background1 = Colors.white;
static Color font1 = Colors.blue;
static Color background2 = Colors.white;
static Color font2 = Colors.blue;
static Color background3 = Colors.white;
static Color font3 = Colors.blue;
static int times2=0;
static int times3=0;
static var pressed =true;
}
class Dis extends StatefulWidget {
const Dis({Key? key,}) : super(key: key);
#override
State<Dis> createState() => _DisState();
}
class _DisState extends State<Dis> {
var clicked=false;
#override
Widget build(BuildContext context) {
return Expanded(
child: ElevatedButton(
style: ButtonStyle(
side:MaterialStateProperty.all(BorderSide(color: Colors.blue),),
backgroundColor: MaterialStateProperty.all(Gcolor.background1),
foregroundColor: MaterialStateProperty.all(Gcolor.font1),
),
onPressed: (){
setState((){
clicked=!clicked;
if(clicked){
Gcolor.background1=Colors.blue;
Gcolor.font1=Colors.white;
}
else{
Gcolor.background1=Colors.white;
Gcolor.font1=Colors.blue;
}
});
},
child: Row(
children: [
Text("距離 "),
Icon(Icons.add_location_outlined,
size: 20,),
],
)
),
);
}
}
class Price extends StatefulWidget {
const Price({Key? key}) : super(key: key);
#override
State<Price> createState() => _PriceState();
}
class _PriceState extends State<Price> {
#override
Widget build(BuildContext context) {
return Expanded(
child: ElevatedButton(
style: ButtonStyle(
side:MaterialStateProperty.all(BorderSide(color: Colors.blue),),
backgroundColor: MaterialStateProperty.all(Gcolor.background2),
foregroundColor: MaterialStateProperty.all(Gcolor.font2),
),
onPressed: (){
setState((){
if(Gcolor.pressed){
Gcolor.times2++;
if(Gcolor.times2%3==1){
Gcolor.background2=Colors.blue;
Gcolor.font2=Colors.white;
}
else if(Gcolor.times2%3==2){
Gcolor.background2=Colors.blue;
Gcolor.font2=Colors.white;
}
else if(Gcolor.times2%3==0){
Gcolor.background2=Colors.white;
Gcolor.font2=Colors.blue;
}
}
});
},
child: Row(
children: [
Text("價格"),
Icon(Icons.attach_money_outlined,
size: 15,),
if(Gcolor.times2%3==1 )
Icon(Icons.arrow_downward,size: 15,color: Colors.yellow,),
if(Gcolor.times2%3==2 )
Icon(Icons.arrow_upward,size: 15,color: Colors.yellow,)
],
)
),
);
}
}
class New extends StatefulWidget {
const New({Key? key}) : super(key: key);
#override
State<New> createState() => _NewState();
}
class _NewState extends State<New> {
#override
Widget build(BuildContext context) {
return Expanded(
child: ElevatedButton(
style: ButtonStyle(
side:MaterialStateProperty.all(BorderSide(color: Colors.blue),),
backgroundColor: MaterialStateProperty.all(Gcolor.background3),
foregroundColor: MaterialStateProperty.all(Gcolor.font3),
),
onPressed: (){
setState((){
Gcolor.times3++;
if(Gcolor.times3%3==1){
Gcolor.background3=Colors.blue;
Gcolor.font3=Colors.white;
}
else if(Gcolor.times3%3==2){
Gcolor.background3=Colors.blue;
Gcolor.font3=Colors.white;
}
else if(Gcolor.times3%3==0){
Gcolor.background3=Colors.white;
Gcolor.font3=Colors.blue;
}
});
},
child: Row(
children: [
Text("最新"),
Icon(Icons.access_time,
size:15 ,),
if(Gcolor.times3%3==1 )
Icon(Icons.arrow_downward,size: 15,color: Colors.yellow,),
if(Gcolor.times3%3==2 )
Icon(Icons.arrow_upward,size: 15,color: Colors.yellow,)
],
)
),
);
}
}

I have updated your code to manage it to radio button. For price and new classes, I don't find any proper selection there. So just put it in initstate and you can change color based on selection.
Button classes:
class Dis extends StatefulWidget {
Dis({required this.isSelected, required this.onClicked, Key? key,}) : super(key: key);
Function() onClicked;
bool isSelected;
#override
State<Dis> createState() => _DisState();
}
class _DisState extends State<Dis> {
#override
Widget build(BuildContext context) {
return Expanded(
child: ElevatedButton(
style: ButtonStyle(
side:MaterialStateProperty.all(BorderSide(color: Colors.blue),),
backgroundColor: MaterialStateProperty.all(widget.isSelected ? Colors.blue : Colors.white),
foregroundColor: MaterialStateProperty.all(widget.isSelected ? Colors.white : Colors.blue),
),
onPressed: (){
widget.onClicked();
},
child: Row(
children: [
Text("距離 "),
Icon(Icons.add_location_outlined,
size: 20,),
],
)
),
);
}
}
class Price extends StatefulWidget {
Price({required this.isSelected, required this.onClicked,Key? key}) : super(key: key);
Function() onClicked;
bool isSelected;
#override
State<Price> createState() => _PriceState();
}
class _PriceState extends State<Price> {
#override
void initState() {
super.initState();
if(widget.isSelected){
Gcolor.times2++;
if(Gcolor.times2%3==1){
Gcolor.background2=Colors.blue;
Gcolor.font2=Colors.white;
}
else if(Gcolor.times2%3==2){
Gcolor.background2=Colors.blue;
Gcolor.font2=Colors.white;
}
else if(Gcolor.times2%3==0){
Gcolor.background2=Colors.white;
Gcolor.font2=Colors.blue;
}
}
}
#override
Widget build(BuildContext context) {
return Expanded(
child: ElevatedButton(
style: ButtonStyle(
side:MaterialStateProperty.all(BorderSide(color: Colors.blue),),
backgroundColor: MaterialStateProperty.all(Gcolor.background2),
foregroundColor: MaterialStateProperty.all(Gcolor.font2),
),
onPressed: (){
widget.onClicked();
},
child: Row(
children: [
Text("價格"),
Icon(Icons.attach_money_outlined,
size: 15,),
if(Gcolor.times2%3==1 )
Icon(Icons.arrow_downward,size: 15,color: Colors.yellow,),
if(Gcolor.times2%3==2 )
Icon(Icons.arrow_upward,size: 15,color: Colors.yellow,)
],
)
),
);
}
}
class New extends StatefulWidget {
New({required this.isSelected, required this.onClicked,Key? key}) : super(key: key);
Function() onClicked;
bool isSelected;
#override
State<New> createState() => _NewState();
}
class _NewState extends State<New> {
#override
void initState() {
super.initState();
Gcolor.times3++;
if(Gcolor.times3%3==1){
Gcolor.background3=Colors.blue;
Gcolor.font3=Colors.white;
}
else if(Gcolor.times3%3==2){
Gcolor.background3=Colors.blue;
Gcolor.font3=Colors.white;
}
else if(Gcolor.times3%3==0){
Gcolor.background3=Colors.white;
Gcolor.font3=Colors.blue;
}
}
#override
Widget build(BuildContext context) {
return Expanded(
child: ElevatedButton(
style: ButtonStyle(
side:MaterialStateProperty.all(BorderSide(color: Colors.blue),),
backgroundColor: MaterialStateProperty.all(Gcolor.background3),
foregroundColor: MaterialStateProperty.all(Gcolor.font3),
),
onPressed: (){
widget.onClicked();
},
child: Row(
children: [
Text("最新"),
Icon(Icons.access_time,
size:15 ,),
if(Gcolor.times3%3==1 )
Icon(Icons.arrow_downward,size: 15,color: Colors.yellow,),
if(Gcolor.times3%3==2 )
Icon(Icons.arrow_upward,size: 15,color: Colors.yellow,)
],
)
),
);
}
}
class Gcolor {
static Color background1 = Colors.white;
static Color font1 = Colors.blue;
static Color background2 = Colors.white;
static Color font2 = Colors.blue;
static Color background3 = Colors.white;
static Color font3 = Colors.blue;
static int times2=0;
static int times3=0;
static var pressed =true;
}
Row:
Row(
children: [
Dis(isSelected: _selectedIndex == 0, onClicked: (){
setState((){
_selectedIndex = 0;
});
}),
SizedBox(width:5),
Price(isSelected: _selectedIndex == 1, onClicked: (){
setState((){
_selectedIndex = 1;
});
}),
SizedBox(width:5),
New(isSelected: _selectedIndex == 2, onClicked: (){
setState((){
_selectedIndex = 2;
});
}),
],
)
Please let me know if this don't work for you.

Related

Flutter Textbutton doesn't reset instant

I was trying to code a TikTakToe game in FLutter but failed at the point where I tried to make a button which resets the fields.
class TikTakToe extends StatefulWidget {
const TikTakToe({Key? key}) : super(key: key);
#override
State<TikTakToe> createState() => _TikTakToeState();
}
class _TikTakToeState extends State<TikTakToe> {
int currentindex = 0;
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
backgroundColor: Colors.teal[100],
appBar: AppBar(
title: const Text("Tik Tak Toe"),
backgroundColor: Colors.teal[400],
),
...
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
ElevatedButton(
onPressed: () {
setState(() {
gameOver = true;
});
}, child: const Icon(Icons.refresh))
],
),
],
),
),
),
);
}
}
bool gameOver = false;
class Field extends StatefulWidget {
const Field({Key? key, required this.fieldnumber}) : super(key: key);
final int fieldnumber;
#override
State<Field> createState() => _FieldState();
}
class _FieldState extends State<Field> {
String playersign = "";
#override
Widget build(BuildContext context) {
return TextButton(
child: Text(
gameOver ? "" : signlist[widget.fieldnumber],
style: const TextStyle(fontSize: 60),
),
style: TextButton.styleFrom(
shape: const RoundedRectangleBorder(
side: BorderSide(color: Color.fromARGB(255, 88, 221, 208), width: 2),
borderRadius: BorderRadius.all(Radius.zero),
),
enableFeedback: false,
primary: Colors.amber[800],
fixedSize: const Size(120.0, 120.0),
backgroundColor: Colors.white,
),
onPressed: () {
setState(() {
... Winner evaluation
}
}
}
);
},
);
}
}
Now I have the problem that the buttons (Fields) are actually resetting but only after clicking on them and not instant after clicking the reset button In the TikTakToe class.
The only thing that worked was adding
(context as Element).reassamble(); to the onPressed of the Elevated button
setState(() {
gameOver = true;
(context as Element).reassemble();
});
but I got this warning:
The member 'reassemble' can only be used within instance members of subclasses of 'package:flutter/src/widgets/framework.dart'.
Thanks!
Change gameover variable place into _FieldState class.
class Field extends StatefulWidget {
const Field({Key? key, required this.fieldnumber}) : super(key: key);
final int fieldnumber;
#override
State<Field> createState() => _FieldState();
}
class _FieldState extends State<Field> {
String playersign = "";
bool gameover = false;
#override
Widget build(BuildContext context) {
return TextButton(
child: Text(
gameover ? "" : signlist[widget.fieldnumber],
style: const TextStyle(fontSize: 60),
),: const TextStyle(fontSize: 60),
),
You need to put gameOver variable inside of Field widget and dont forget to use camelCase in naming variables, on the other hand you have to pass onPressed to TextButton if you dont want to be clickable just pass onPressed: null
class Field extends StatefulWidget {
const Field({Key key}) : super(key: key);
#override
State<Field> createState() => _FieldState();
}
class _FieldState extends State<Field> {
String playerSign = "";
bool gameOver = false; // put var inside
#override
Widget build(BuildContext context) {
return Column(
children: [
TextButton(
onPressed: null,
child: Text(
gameOver ? "" : signlist[widget.fieldnumber],
style: const TextStyle(fontSize: 60),
),
),
TextButton(
onPressed: () {
setState(() {
setState(() {
gameOver = true;
});
});
},
child: const Text(
"reset",
style: TextStyle(fontSize: 60),
),
),
],
);
}
}
suggest to read this doc

Open / close filter menu

I have a code that is responsible for building a menu filter. It allows you to filter data by category and then by subcategory.
Initially, subcategories are in a closed state, but when you click on the arrow, they can be opened. Take a look
But my problem is that if I click on the arrow for any category (Country in my case), then all subcategories open at once. Take a look
It's my code
class _FilterDialogUserState extends State<FilterDialogUser> {
Map<String, List<String>?> filters = {};
bool needRefresh = false;
bool isClickedCountry = false;
#override
void initState() {
super.initState();
filters = widget.initialState;
}
List<FilterItem> children = [
FilterItem('Georgia', subitems: [
FilterItem('Tbilisi'),
FilterItem('Batumi'),
]),
FilterItem('Poland', subitems: [
FilterItem('Warsaw'),
FilterItem('Krakow'),
FilterItem('Wroclaw'),
]),
FilterItem('Armenia', subitems: [
FilterItem('Erevan'),
FilterItem('Gyumri'),
]),
];
// Building a dialog box with filters.
#override
Widget build(BuildContext context) {
return SimpleDialog(
title: const Text('Filters',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 25,
fontFamily: 'SuisseIntl',
)),
contentPadding: const EdgeInsets.all(16),
// Defining parameters for filtering.
children: [
Column(
children: children.map(
(e) {
return Column(
children: [
InkWell(
onTap: () async {
setState(() {
isClickedCountry = !isClickedCountry;
});
},
child: Row(
children: [
Checkbox(
value: e.selected,
onChanged: (value) => setState(() {
e.subitems.forEach((element) =>
element.selected = value as bool);
e.selected = value as bool;
}),
),
Text(e.text),
const Spacer(),
isClickedCountry
? const Icon(Icons.arrow_circle_up)
: const Icon(Icons.arrow_circle_down)
],
),
),
if (e.subitems.isNotEmpty)
!isClickedCountry
? Container()
: Padding(
padding: const EdgeInsets.fromLTRB(30, 0, 0, 0),
child: Column(
children: e.subitems.map((e) {
return Row(children: [
Checkbox(
value: e.selected,
onChanged: (value) => setState(() {
e.selected = value as bool;
}),
),
Text(e.text),
]);
}).toList(),
),
)
],
);
},
).toList(),
),
]);
}
}
class FilterItem {
final String text;
bool selected;
List<FilterItem> subitems;
FilterItem(
this.text, {
this.selected = false,
this.subitems = const [],
});
}
Tell me, is it possible to change my code so that not all subcategories are opened, but only the one that the user clicks on?
The each main filter item must be controlled one by one.
Define List isClickedCountry variable
Save and load state from List isClickedCountry variable
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
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> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: _buildBody(),
floatingActionButton: FloatingActionButton(
onPressed: () {},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
Widget _buildBody() {
return FilterDialogUser();
}
}
class FilterDialogUser extends StatefulWidget {
FilterDialogUser({Key key}) : super(key: key);
#override
State<FilterDialogUser> createState() => _FilterDialogUserState();
}
class _FilterDialogUserState extends State<FilterDialogUser> {
Map<String, List<String>> filters = {};
bool needRefresh = false;
List<bool> isClickedCountry = List.filled(3, false);
#override
void initState() {
super.initState();
// filters = widget.initialState;
}
List<FilterItem> children = [
FilterItem('Georgia', subitems: [
FilterItem('Tbilisi'),
FilterItem('Batumi'),
]),
FilterItem('Poland', subitems: [
FilterItem('Warsaw'),
FilterItem('Krakow'),
FilterItem('Wroclaw'),
]),
FilterItem('Armenia', subitems: [
FilterItem('Erevan'),
FilterItem('Gyumri'),
]),
];
// Building a dialog box with filters.
#override
Widget build(BuildContext context) {
return SimpleDialog(
title: const Text('Filters',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 25,
fontFamily: 'SuisseIntl',
)),
contentPadding: const EdgeInsets.all(16),
// Defining parameters for filtering.
children: [
Column(
children: children.map(
(e) {
final int index = children.indexOf(e);
return Column(
children: [
InkWell(
onTap: () async {
setState(() {
isClickedCountry[index] = !isClickedCountry[index];
});
},
child: Row(
children: [
Checkbox(
value: e.selected,
onChanged: (value) => setState(() {
e.subitems.forEach((element) =>
element.selected = value as bool);
e.selected = value as bool;
}),
),
Text(e.text),
const Spacer(),
isClickedCountry[index]
? const Icon(Icons.arrow_circle_up)
: const Icon(Icons.arrow_circle_down)
],
),
),
if (e.subitems.isNotEmpty)
!isClickedCountry[index]
? Container()
: Padding(
padding: const EdgeInsets.fromLTRB(30, 0, 0, 0),
child: Column(
children: e.subitems.map((e) {
return Row(children: [
Checkbox(
value: e.selected,
onChanged: (value) => setState(() {
e.selected = value as bool;
}),
),
Text(e.text),
]);
}).toList(),
),
)
],
);
},
).toList(),
),
]);
}
}
class FilterItem {
final String text;
bool selected;
List<FilterItem> subitems;
FilterItem(
this.text, {
this.selected = false,
this.subitems = const [],
});
}

Unable to call a method in another dart file

I have a dart file with IndexedStack and the following function in the same file to change the stacks.
The file with method as follows-
class RootApp extends StatefulWidget with selectedTab {
#override
_RootAppState createState() => _RootAppState();
}
class _RootAppState extends State<RootApp> {
int pageIndex = 0;
List<Widget> pages = [
......
];
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
....
return AnimatedBottomNavigationBar(
.......
onTap: (index) {
selectedTab(index);
},
);
}
selectedTab(index) {
setState(() {
pageIndex = index;
});
}
}
There is this other dart file from which i would like to call selectedTab method with value of 0. The other file is as follows---
class CreatBudgetPage extends StatefulWidget {
#override
_CreatBudgetPageState createState() => _CreatBudgetPageState();
}
class _CreatBudgetPageState extends State<CreatBudgetPage> {
.......
FirebaseFirestore.instance
.collection('expenses/' + userId + '/' + todayDate)
.add({
....
}).then((_) {
print("collection created");
void rootApp() => selectedTab(0);
}).catchError((error) {
print(error);
});
}
How can i call this method from the other dart file?
P.S: I am a Newbie
You can pass the function callback as parameters from the source class to the intended class and invoke the function as you want.
here is a simple example.
class Example extends StatefulWidget {
const Example({Key? key}) : super(key: key);
#override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
int pageIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('BottomNavigationBar Sample'),
),
body: IndexedStack(
index: pageIndex,
children: [
Tab1Page(),
Tab2Page(
/// solution 1
onPressed1: () {
selectedTab(0);
},
/// solution 2
// onPressed2: selectedTab,
),
],
),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'TAB 1',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'TAB 2',
),
],
currentIndex: pageIndex,
selectedItemColor: Colors.amber[800],
onTap: selectedTab,
),
);
}
selectedTab(index) {
setState(() {
pageIndex = index;
});
}
}
class Tab1Page extends StatelessWidget {
const Tab1Page({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
child: Center(
child: Text(
"Tab1",
style: TextStyle(fontSize: 20),
),
),
);
}
}
class Tab2Page extends StatelessWidget {
final VoidCallback? onPressed1;
// final Function(int)? onPressed2;
const Tab2Page({
Key? key
/// solution 1
,
this.onPressed1,
/// solution 2
// this.onPressed2
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
color: Colors.red,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"Tab2",
style: TextStyle(fontSize: 20),
),
ElevatedButton(
onPressed: () {
/// solution 1
if (onPressed1 != null) {
onPressed1!();
}
/// solution 2
// if(onPressed2!=null){
// onPressed2!(0);
//
// }
},
child: Text("click to navigate to another tab",
style: TextStyle(fontSize: 20)),
),
],
),
),
);
}
}

How to call a method from one statefulWidget in another Widget (Flutter)

I am trying to run method doAnimation from another widget by clicking on a FloatingActionButton.
Please tell me how to do this with this simple example. I know how to do this using the Provider package, but the code is cumbersome. How can I do this using Flutter's native methods?
Or, most likely, it can be done nicely with the Provider, but I don't know how.
A similar question has already been asked, but the second version of flutter and dart has already been released.
State management difficulties are probably the biggest newbies problem.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: MyAnimation(),
floatingActionButton: FloatingActionButton(
onPressed: () {
//TODO! error is here
_MyAnimationState().doAnimation();
//MyAnimation().createState().doAnimation(); // ?
},
child: Icon(Icons.play_arrow),
),
);
}
}
class MyAnimation extends StatefulWidget {
const MyAnimation({Key? key}) : super(key: key);
#override
_MyAnimationState createState() => _MyAnimationState();
}
class _MyAnimationState extends State<MyAnimation> {
double _height = 250;
bool _isOpen = true;
void doAnimation() {
_isOpen = !_isOpen;
setState(() {
if (_isOpen) {
_height = 250;
} else {
_height = 0;
}
});
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: AnimatedContainer(
padding: EdgeInsets.all(20),
duration: Duration(milliseconds: 250),
width: 250,
height: _height,
color: Colors.lightBlueAccent,
child: Center(
child: Text(
'My Test String',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 35, fontWeight: FontWeight.bold),
),
),
),
),
ElevatedButton(
onPressed: () {
doAnimation(); // works as it should
},
child: (_isOpen)? Text('Close Widget') : Text('Open Widget'))
],
);
}
}
This error occurs when I try to use the class methods in the usual way.
This happens when you call setState() on a State object for a widget that hasn't been inserted into the widget tree yet. It is not necessary to call setState() in the constructor, since the state is already assumed to be dirty when it is initially created.
This is actually quite simple and doesn't require any kind of package. You can do this with the help of global keys. First create a global key like this GlobalKey<_MyAnimationState> _key = GlobalKey<_MyAnimationState>();. Then pass this key while using MyAnimation class like this MyAnimation(key: _key). Now use this key in the onPressed function to call the doAnimation method like this _key.currentState!.doAnimation();
Here is the complete implementation.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
GlobalKey<_MyAnimationState> _key = GlobalKey<_MyAnimationState>(); // declaration of the key
#override
Widget build(BuildContext context) {
return Scaffold(
body: MyAnimation(key: _key), // passing the key
floatingActionButton: FloatingActionButton(
onPressed: () {
_key.currentState!.doAnimation(); // calling the method from child widget
},
child: Icon(Icons.play_arrow),
),
);
}
}
class MyAnimation extends StatefulWidget {
const MyAnimation({Key? key}) : super(key: key);
#override
_MyAnimationState createState() => _MyAnimationState();
}
class _MyAnimationState extends State<MyAnimation> {
double _height = 250;
bool _isOpen = true;
void doAnimation() {
_isOpen = !_isOpen;
setState(() {
if (_isOpen) {
_height = 250;
} else {
_height = 0;
}
});
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: AnimatedContainer(
padding: EdgeInsets.all(20),
duration: Duration(milliseconds: 250),
width: 250,
height: _height,
color: Colors.lightBlueAccent,
child: Center(
child: Text(
'My Test String',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 35, fontWeight: FontWeight.bold),
),
),
),
),
ElevatedButton(
onPressed: () {
doAnimation(); // works as it should
},
child: (_isOpen)? Text('Close Widget') : Text('Open Widget'))
],
);
}
}
You can try to write the "doAnimation()" in the initState() instead the use of the floatingActionButton in order to trigger this action when the widget be initialized.
double _height;
bool _isOpen;
#override
void initState() {
super.initState();
bool _isOpen = true;
if (_isOpen) {
_height = 250;
} else {
_height = 0;
}
}
Then use the ElevatedButton as the setter of the bool and setState to re-rendered the widgets:
ElevatedButton(
onPressed: () {
setState(() {
_isOpen = !_isOpen;
if (_isOpen) {
_height = 250;
} else {
_height = 0;
}
});
},
child: (_isOpen)? Text('Close Widget') : Text('Open Widget'))
],
);
Your code will look like this:
class MyAnimation extends StatefulWidget {
#override
_MyAnimationState createState() => _MyAnimationState();
}
class _MyAnimationState extends State<MyAnimation> {
double _height;
bool _isOpen;
#override
void initState() {
super.initState();
bool _isOpen = true;
if (_isOpen) {
_height = 250;
} else {
_height = 0;
}
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: AnimatedContainer(
padding: EdgeInsets.all(20),
duration: Duration(milliseconds: 250),
width: 250,
height: _height,
color: Colors.lightBlueAccent,
child: Center(
child: Text(
'My Test String',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 35, fontWeight: FontWeight.bold),
),
),
),
),
ElevatedButton(
onPressed: () {
setState(() {
_isOpen = !_isOpen;
if (_isOpen) {
_height = 250;
} else {
_height = 0;
}
});
},
child: (_isOpen)? Text('Close Widget') : Text('Open Widget'))
],
);
}
}
You can also use the Provider to set the _isOpen value with setter function and avoid all these validations on the widget buttons.

Flutter: Update String in other statefull widget

I'm using a statefull widget to handle the length of my text. (show more, show less)
class DescriptionTextWidget extends StatefulWidget {
final String text;
DescriptionTextWidget({#required this.text});
#override
_DescriptionTextWidgetState createState() =>
new _DescriptionTextWidgetState();
}
class _DescriptionTextWidgetState extends State<DescriptionTextWidget> {
String firstHalf;
String secondHalf;
bool flag = true;
#override
void initState() {
super.initState();
if (widget.text.length > 400) {
firstHalf = widget.text.substring(0, 400);
secondHalf = widget.text.substring(400, widget.text.length);
} else {
firstHalf = widget.text;
secondHalf = "";
}
}
#override
Widget build(BuildContext context) {
return new Container(
padding: new EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
child: secondHalf.isEmpty
? new Text(firstHalf, style: TextStyle(color: Colors.white))
: new Column(
children: <Widget>[
new Text(
flag ? (firstHalf + "...") : (firstHalf + secondHalf),
style: TextStyle(color: Colors.white),
),
new InkWell(
splashColor: Colors.transparent,
child: new Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
new Text(
flag ? "show more" : "show less",
style:
new TextStyle(color: Colors.white.withOpacity(0.8)),
),
],
),
onTap: () {
setState(() {
flag = !flag;
});
},
),
],
),
);
}
}
In my main class: I 'give' the text to that stfull widget like this:
GestureDetector(
onTap: () async {
},
child: DescriptionTextWidget(
text: myString,
),
If I update myString in my main statefull widget, the String doesn't get updated in the statefull widget 'DescriptionTextWidget'.
What's the best way to update the String in the class DescriptionTextWidget?
Thanks in advance!
Sample on DartPad
class DescriptionTextWidget extends StatefulWidget {
final ValueNotifier<String> text;
}
class _DescriptionTextWidgetState extends State<DescriptionTextWidget> {
#override
void initState() {
super.initState();
widget.text.addListener(() => setState(initText));
initText();
}
initText() {
if (widget.text.value.length > 400) {
firstHalf = widget.text.value.substring(0, 400);
secondHalf = widget.text.value.substring(400, widget.text.value.length);
} else {
firstHalf = widget.text.value;
secondHalf = "";
}
}
}
main class:
ValueNotifier<String> myString;
updateString(String value){
myString.value = value;
}
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#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> {
int _counter = 0;
List<String> myString = ["test"];
void _replace() {
setState(() {
myString[0] = "tapped";
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: DescriptionTextWidget(
text: myString,
),
),
floatingActionButton: FloatingActionButton(
onPressed: _replace,
child: Icon(Icons.autorenew),
),
);
}
}
class DescriptionTextWidget extends StatefulWidget {
final List<String> text;
DescriptionTextWidget({#required this.text});
#override
_DescriptionTextWidgetState createState() =>
new _DescriptionTextWidgetState();
}
class _DescriptionTextWidgetState extends State<DescriptionTextWidget> {
bool flag = true;
#override
Widget build(BuildContext context) {
String firstHalf;
String secondHalf;
if (widget.text[0].length > 400) {
firstHalf = widget.text[0].substring(0, 400);
secondHalf = widget.text[0].substring(400, widget.text[0].length);
} else {
firstHalf = widget.text[0];
secondHalf = "";
}
return new Container(
padding: new EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
child: secondHalf.isEmpty
? new Text(firstHalf, style: TextStyle(color: Colors.black))
: new Column(
children: <Widget>[
new Text(
flag ? (firstHalf + "...") : (firstHalf + secondHalf),
style: TextStyle(color: Colors.black),
),
new InkWell(
splashColor: Colors.transparent,
child: new Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
new Text(
flag ? "show more" : "show less",
style:
new TextStyle(color: Colors.black.withOpacity(0.8)),
),
],
),
onTap: () {
setState(() {
flag = !flag;
});
},
),
],
),
);
}
}