Want to highlight selected widget in flutter - flutter

I have made an demo app where I have created a custom widget and using this custom widget many times. now I want to highlight widget with different colour than others on tap..like BottomNavigationBarItem showing selected barite with differ colour than other
what should I implement to do it...specially any short way so that it can work with many same widgets..
here is my simple coding..
my custom widget
class MyContainer extends StatelessWidget {
final VoidCallback ontap;
MyContainer({required this.ontap});
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(10),
child: GestureDetector(
onTap: ontap,
child: Container(
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.circular(20),
//border:isselected==true? Border.all(width: 2,color: Colors.blue):null,
),
),
),
);
}
}
and here is home file
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: Row(
children: [
Expanded(child: MyContainer(
ontap: () {
setState(() {});
},
)),
Expanded(child: MyContainer(
ontap: () {
setState(() {});
},
))
],
)),
Expanded(child: MyContainer(
ontap: () {
setState(() {});
},
)),
],
),
);
}
}

You can use nullable int to hold selected index, and index can be considered as widget ID. Also pass the bool to show selected condition.
class MyContainer extends StatelessWidget {
final VoidCallback ontap;
bool isSelected;
MyContainer({
required this.ontap,
this.isSelected = false,
});
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(10),
child: GestureDetector(
onTap: ontap,
child: Container(
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.circular(20),
border: isSelected == true
? Border.all(width: 2, color: Colors.blue)
: null,
),
),
),
);
}
}
class HomeScreen extends StatefulWidget {
HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int? selectedIndex;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: Row(
children: [
Expanded(
child: MyContainer(
isSelected: selectedIndex == 1,
ontap: () {
selectedIndex = 1;
setState(() {});
},
),
),
Expanded(
child: MyContainer(
isSelected: selectedIndex == 2,
ontap: () {
selectedIndex = 2;
setState(() {});
},
),
)
],
)),
Expanded(
child: MyContainer(
isSelected: selectedIndex == 3,
ontap: () {
selectedIndex = 3;
setState(() {});
},
),
),
],
),
);
}
}

Related

Flutter Question: How to select/highlight widget based on widget key or unique key properties upon tap

Im trying to build dynamically generated widgets, each of them need to be selected/highlighted when I tap on them.
here is code borrowed by another solution on stackoverflow, reference code below is DartPad friendly to paste and play.
this is perfect solution for me except, I don't want hard coded integer to identify which widget is tapped on ( I don't have finite number of widget), instead need to check against key or unique key property of myContainer instance
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.light(),
debugShowCheckedModeBanner: false,
home: HomeScreen(),
);
}
}
class MyContainer extends StatelessWidget {
final VoidCallback ontap;
bool isSelected;
Key myKey = UniqueKey();
MyContainer({
required this.ontap,
this.isSelected = false,
});
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(10),
child: GestureDetector(
onTap: ontap,
child: Container(
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.circular(20),
border: isSelected == true
? Border.all(width: 2, color: Colors.blue)
: null,
),
),
),
);
}
}
class HomeScreen extends StatefulWidget {
HomeScreen({Key? key}) : super(key: key);
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int? selectedIndex;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: Row(
children: [
Expanded(
child: MyContainer(
isSelected: selectedIndex == 1,
ontap: () {
selectedIndex = 1;
setState(() {});
},
),
),
Expanded(
child: MyContainer(
isSelected: selectedIndex == 2,
ontap: () {
selectedIndex = 2;
setState(() {});
},
),
)
],
)),
Expanded(
child: MyContainer(
isSelected: selectedIndex == 3,
ontap: () {
selectedIndex = 3;
setState(() {});
},
),
),
],
),
);
}
} ```
you can use the spread operator ... to generate widgets dynamically based on your needs elements length, and use the index for each one :
inside the Column children:
Column(
children: <Widget>[
...List.generate(100, (index) =>
Expanded(
child: MyContainer(
isSelected: selectedIndex == index,
ontap: () {
selectedIndex = index;
setState(() {});
},
),
)
],
)),),],),
this will basically generate 100 widgets, each of them having an index from 0-99.
if you have an already List of your data, you can use its length instead of hardcoded 100, so it will be dynamic.

How to save and load two variables in the same screen in Flutter/Dart?

Here is my code:
import 'package:flutter/material.dart';
import 'package:csv/csv.dart';
import 'package:flutter/services.dart' show rootBundle;
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Example';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Container(
child: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatelessWidget(),
),
)
);
}
}
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const SizedBox(height: 30),
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: Stack(
children: <Widget>[
Positioned.fill(
child: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: <Color>[
Color(0xFF0D47A1),
Color(0xFF1976D2),
Color(0xFF42A5F5),
],
),
),
),
),
TextButton(
style: TextButton.styleFrom(
padding: const EdgeInsets.all(16.0),
primary: Colors.white,
textStyle: const TextStyle(fontSize: 20),
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const NewGameRoute()),
);
},
child: const Text('New Game'),
),
],
),
),
const SizedBox(height: 30),
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: Stack(
children: <Widget>[
Positioned.fill(
child: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: <Color>[
Color(0xFF0D47A1),
Color(0xFF1976D2),
Color(0xFF42A5F5),
],
),
),
),
),
TextButton(
style: TextButton.styleFrom(
padding: const EdgeInsets.all(16.0),
primary: Colors.white,
textStyle: const TextStyle(fontSize: 20),
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const NewGameRoute()),
);
},
child: const Text('Continue Game'),
),
],
),
),
],
),
);
}
}
// New Game route
class NewGameRoute extends StatelessWidget {
const NewGameRoute({key});
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
title: 'New Game',
home: ListFromCSV(),
);
}
}
class ListFromCSV extends StatefulWidget {
const ListFromCSV({Key? key}) : super(key: key);
#override
_ListFromCSVState createState() => _ListFromCSVState();
}
class _ListFromCSVState extends State<ListFromCSV> {
List<List<dynamic>> _listData = [
[""]
];
int _listCount = 0;
bool _isFirstLoad = true;
String assetPath = "files/main.jpg";
#override
void initState() {
_loadCSV();
}
// This function is only triggered at init, so we only load csv once
void _loadCSV() async {
String rawData = await rootBundle.loadString("files/Text.csv");
_listData = const CsvToListConverter().convert(rawData);
assetPath = _listData[_listCount][1] == ""
? "files/main.jpg"
: _listData[_listCount][1];
}
// This function is triggered when my button is pressed
void _nextCSV() {
setState(() {
_listData = _listData;
_listCount < _listData.length - 1
? _isFirstLoad
? _isFirstLoad = false
: _listCount++
: _listCount;
assetPath =
_listData[_listCount][1] == "" ? assetPath : _listData[_listCount][1];
});
}
// This function makes buttons visible/invisible
bool isVisible = true; //will be visible for the first frame
void _isVisible() {
setState(() {
isVisible = !isVisible;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('New Game'),
),
body: Container(
height: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(assetPath),
fit: BoxFit.cover)),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Visibility(
child: Column(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: Stack(
children: <Widget>[
Positioned.fill(
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('files/sheet.jpg'),
fit: BoxFit.cover)),
),
),
Text(_listData[_listCount][0]),
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ImageButton(label: 'OK', onButtonTap: _nextCSV),
ImageButton(label: 'Hide', onButtonTap: _isVisible),
],
),
],
),
visible: isVisible,
),
// your other widgets
Visibility(
child: ImageButton(label: 'Show', onButtonTap: _isVisible),
visible: !isVisible,
)
],
),
),
);
}
}
//Class for a cool button
class ImageButton extends StatelessWidget {
const ImageButton({Key? key, required this.label, required this.onButtonTap})
: super(key: key);
final String label;
final Function onButtonTap;
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () => onButtonTap(),
child: Container(
// customize you button shape and size and design
margin: const EdgeInsets.all(8),
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 32),
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(2)),
image: DecorationImage(
image: AssetImage("files/sheet.jpg"), // you can also pass the image dynamically with variable created for the widget.
fit: BoxFit.cover)),
child: Center(
child: Text(
label,
style: const TextStyle(
color: Colors
.black, // you can get dominant colour on image and change the text color accordingly or apply shadows to the text
fontWeight: FontWeight.w500,
fontSize: 16),
),
),
),
);
}
}
This experimental app consists of two screens:
Main screen:
Game screen:
I need to save 2 variables: _listCount and assetPath so that later on my users can continue the game. But at the same time, the New Game and Continue Game screens are almost identical. In fact, this is the same screen. The only difference is that when my user starts the game, the variables have the following values:
int_listCount = 0;
String assetPath = "files/main.jpg";
And when my user clicks on the "Continue game" button, they should get to the same screen, but the values of the variables should be different. What is the easiest way to do this?
I believe I should follow these steps:
Save the _listCount and assetPath variables when I click the OK button to the device's memory. To do this, I need to modify this code:
void _nextCSV() {
setState(() {
_listData = _listData;
_listCount < _listData.length - 1
? _isFirstLoad
? _isFirstLoad = false
: _listCount++
: _listCount;
assetPath =
_listData[_listCount][1] == "" ? assetPath : _listData[_listCount][1];
});
}
Load the values of the _listCount and assetPath variables from the device's memory when I click the Continue game button. To do this, I need to modify this code:
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const NewGameRoute()),
);
},
That is, the user will go to NewGameRoute, but with different values for these variables. Or would it be correct to create a ContinueGameRoute that is almost exactly the same as NewGameRoute (other than my values)? What is the easiest way to save and load these values from memory?
Edit 1. Using Sparsh Jain's code, I have the following:
Launching lib\main.dart on sdk gphone64 x86 64 in debug mode...
Running Gradle task 'assembleDebug'...
lib/main.dart:168:7: Error: Expected an identifier, but got ':'.
Try inserting an identifier before ':'.
: super(key: key);
^
lib/main.dart:168:24: Error: Expected '}' before this.
: super(key: key);
^
lib/main.dart:172:3: Error: Expected '{' before this.
final int _listCount;
^^^^^
lib/main.dart:75:23: Error: No named parameter with the name '_listCount'.
_listCount: 0, assetPath: "images/01.jpg" ///What I want to pass
^^^^^^^^^^
lib/main.dart:164:3: Context: Found this candidate, but the arguments don't match.
NewGameRoute(
^^^^^^^^^^^^
lib/main.dart:85:69: Error: Cannot invoke a non-'const' constructor where a const expression is expected.
Try using a constructor or factory that is 'const'.
MaterialPageRoute(builder: (context) => const NewGameRoute()),
^^^^^^^^^^^^
lib/main.dart:168:20: Error: Undefined name 'key'.
: super(key: key);
^^^
lib/main.dart:168:9: Error: Method invocation is not a constant expression.
: super(key: key);
^^^^
lib/main.dart:172:13: Error: Final field '_listCount' is not initialized.
Try to initialize the field in the declaration or in every constructor.
final int _listCount;
^^^^^^^^^^
lib/main.dart:174:16: Error: Final field 'assetPath' is not initialized.
Try to initialize the field in the declaration or in every constructor.
final String assetPath;
^^^^^^^^^
FAILURE: Build failed with an exception.
* Where:
Script 'D:\flutter\packages\flutter_tools\gradle\flutter.gradle' line: 1156
* What went wrong:
Execution failed for task ':app:compileFlutterBuildDebug'.
> Process 'command 'D:\flutter\bin\flutter.bat'' finished with non-zero exit value 1
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
* Get more help at https://help.gradle.org
BUILD FAILED in 9s
Exception: Gradle task assembleDebug failed with exit code 1
Edit 2. With the help of more experienced people I modified this code yesterday. Now it looks like this and has only one error. It still doesn't work, though.
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Example';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Container(
child: Scaffold(
appBar: AppBar(title: const Text(_title)),
// body: const MyStatelessWidget(),
body: const MainWidget(),
),
)
);
}
}
// class MyStatelessWidget extends StatelessWidget {
// const MyStatelessWidget({Key? key}) : super(key: key);
class MainWidget extends StatefulWidget {
const MainWidget({Key? key}) : super(key: key);
#override
State<MainWidget> createState() => _MainWidgetState();
}
class _MainWidgetState extends State<MainWidget> {
CheckUserConnection _checkUserConnection = CheckUserConnection();
InternetDialogHandler _internetDialogHandler = InternetDialogHandler();
bool? _internetAvailable;
#override
void initState(){
checkNet();
super.initState();
}
void checkNet() async{
_internetAvailable = await
_checkUserConnection.checkInternetAvailability();
setState((){});
}
#override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Column(
children: [
GradientButton(label: 'New Game', onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const NewGameRoute(
listCount: 0, assetPath: "images/01.jpg" ///What I want to pass
)),
);
}),
// GradientButton(label: 'Continue Game', onTap: () {
// return _internetAvailable == true ?
// {Navigator.push(
// context,
// MaterialPageRoute(builder: (context) => const NewGameRoute()),
// )}
// :
// _internetDialogHandler.showInternetDialog(context);
// }),
],
),
Column(
children: [
GradientButton(label: 'Back Button', onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const BackRoute()),
);
// print('Button 1');
}),
GradientButton(label: 'Button 2', onTap: () {print('Button 2');}),
GradientButton(label: 'Internet', onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const InternetRoute()),
);
}),
],
)
],
),
);
}
}
//Class for a gradient button
class GradientButton extends StatelessWidget {
const GradientButton({Key? key, required this.label, required this.onTap}) : super(key: key);
final String label;
final Function onTap;
#override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(vertical: 15),
child: GestureDetector(
onTap: () => onTap(),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(4)),
gradient: LinearGradient(
colors: <Color>[
Color(0xFF0D47A1),
Color(0xFF1976D2),
Color(0xFF42A5F5),
],
),
),
child: Text(label, style: const TextStyle(fontSize: 20, color: Colors.white, fontWeight: FontWeight.normal, decoration: TextDecoration.none),),
),
),
);
}
}
// New Game route
class NewGameRoute extends StatelessWidget {
const NewGameRoute({
Key? key,
required int listCount,
required this.assetPath,
}) : _listCount = listCount,
super(key: key);
final int _listCount;
final String assetPath;
#override
Widget build(BuildContext context) { // removed materialApp from here
return ListFromCSV();
}
}
class ListFromCSV extends StatefulWidget {
final int listCount;
const ListFromCSV({
Key? key,
required this.listCount,
}) : super(key: key);
#override
_ListFromCSVState createState() => _ListFromCSVState();
}
class _ListFromCSVState extends State<ListFromCSV> {
List<List<dynamic>> _listData = [
[""]
];
late int _listCount = widget.listCount;
// This function is only triggered at init, so we only load csv once
void _loadCSV() async {
String rawData = await rootBundle.loadString("files/Text.csv");
_listData = const CsvToListConverter().convert(rawData);
}
// This function is triggered when my button is pressed
void _nextCSV() {
setState(() {
_listData = _listData;
_listCount < _listData.length - 1
? _isFirstLoad
? _isFirstLoad = false
: _listCount++
: _listCount;
// assetPath =
// _listData[_listCount][1] == "" ? assetPath : _listData[_listCount][1];
_listData[_listCount][1] == "" ? null : _showAlertDialog();
});
}
// This function makes buttons visible/invisible
bool isVisible = true; //will be visible for the first frame
void _isVisible() {
setState(() {
isVisible = !isVisible;
});
}
//Alert Dialog about questions and answers
Widget _answer1TextButton(){
return TextButton(
child: Text(_listData[_listCount][3]),
onPressed: () {
setState(() {
assetPath = _listData[_listCount][6];
_listCount = _listData[_listCount][2]-1;
// _listData[_listCount][0];
// _nextCSV();
print('Answer 1');
print(_listCount);
// Navigator.of(globalContext).pop(); // Popping globalContext
});
},
);
}
Widget _answer2TextButton(){
return TextButton(
child: Text(_listData[_listCount][5]),
onPressed: () {
setState(() {
assetPath = _listData[_listCount][7];
_listCount = _listData[_listCount][4]-1;
print('Answer 2');
print(_listCount);
// Navigator.of(globalContext).pop(); // Popping globalContext
});
},
);
}
void _showAlertDialog() {
// set up the AlertDialog
AlertDialog alert = AlertDialog(
// title: Text(),
content: Text(_listData[_listCount][1]),
actions: [
_answer1TextButton(),
_answer2TextButton(),
],
);
// show the dialog
showDialog(
barrierDismissible: false, //use to dismiss any tap on the background of the dialog
context: context,
// useRootNavigator: false, //this property needs to be added
builder: (BuildContext context) {
return WillPopScope(
onWillPop: () async {
return true; // false to disable the back button
},
child: alert,
);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('New Game'),
),
body: Container(
height: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(assetPath),
fit: BoxFit.cover)),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Visibility(
child: Column(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: Stack(
children: <Widget>[
Positioned.fill(
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('files/sheet.jpg'),
fit: BoxFit.cover)),
),
),
Text(_listData[_listCount][0]),
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ImageButton(label: 'OK', onButtonTap: _nextCSV),
ImageButton(label: 'Hide', onButtonTap: _isVisible),
ImageButton(label: 'Test1', onButtonTap: _showAlertDialog),
],
),
],
),
visible: isVisible,
),
// your other widgets
Visibility(
child: ImageButton(label: 'Show', onButtonTap: _isVisible),
visible: !isVisible,
)
],
),
),
);
}
}
//Class for a cool button
class ImageButton extends StatelessWidget {
const ImageButton({Key? key, required this.label, required this.onButtonTap})
: super(key: key);
final String label;
final Function onButtonTap;
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () => onButtonTap(),
child: Container(
// customize you button shape and size and design
margin: const EdgeInsets.all(8),
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 32),
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(2)),
image: DecorationImage(
image: AssetImage("files/sheet.jpg"), // you can also pass the image dynamically with variable created for the widget.
fit: BoxFit.cover)),
child: Center(
child: Text(
label,
style: const TextStyle(
color: Colors
.black, // you can get dominant colour on image and change the text color accordingly or apply shadows to the text
fontWeight: FontWeight.w500,
fontSize: 16),
),
),
),
);
}
}
Now I only get one error.
The named parameter 'assetPath' is required, but there's no corresponding argument.
It leads to
#override
Widget build(BuildContext context) {
return ListFromCSV();
}
}
What could be the reason?
Edit 3. Here's what my code was missing:
#override
Widget build(BuildContext context) {
return ListFromCSV(listCount: _listCount, assetPath: assetPath);
}
Now it works perfect.
You can pass the value of variables from the base page to the NewGameRoute class.
can call it like, NewGameRoute(10, "abc.csv");
class NewGameRoute extends StatelessWidget {
NewGameRoute(
{Key? key,
required this. _listCount,
required this. assetPath,
: super(key: key);
final int _listCount;
final String assetPath;
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
title: 'New Game',
home: ListFromCSV(),
);
}
}

How to go back to previous screen by clicking on bottom navigation bar item in Flutter

I am using this library persistent_bottom_nav_bar to display bottom navigation bar even on navigating to new screen. Now there are two main pages Page1 and Page2, Page1 is using an icon of home where as Page2 is using an icon of search. In Page1 contain a button which navigate to new screen named as NewPage. What i wanted to achieve is if i navigate to NewPage from Page1 and if i decide to goback to previous screen which is Page1 by clicking on homeicon which is at bottom. So how can i click on bottom item and go back to previous screen? Hope you understand my question
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo',
home: HomeScaffold(),
);
}
}
class HomeScaffold extends StatefulWidget {
#override
_HomeScaffoldState createState() => _HomeScaffoldState();
}
class _HomeScaffoldState extends State<HomeScaffold> {
late PersistentTabController _controller;
#override
void initState() {
super.initState();
_controller = PersistentTabController(initialIndex: 0);
}
List<Widget> _buildScreens() {
return [
Page1(),
Page2(),
];
}
List<PersistentBottomNavBarItem> _navBarsItems() {
return [
_buildBottomNavBarItem('Page 1', Icons.home),
_buildBottomNavBarItem('Page 2', Icons.search),
];
}
#override
Widget build(BuildContext context) {
return PersistentTabView.custom(
context,
controller: _controller,
screens: _buildScreens(),
confineInSafeArea: true,
itemCount: 2,
handleAndroidBackButtonPress: true,
stateManagement: true,
screenTransitionAnimation: ScreenTransitionAnimation(
animateTabTransition: true,
curve: Curves.ease,
duration: Duration(milliseconds: 200),
),
customWidget: CustomNavBarWidget(
items: _navBarsItems(),
onItemSelected: (index) {
setState(() {
_controller.index = index; // go back to previous screen if i navigate to new screen
});
},
selectedIndex: _controller.index,
),
// ),
);
}
}
class CustomNavBarWidget extends StatelessWidget {
final int? selectedIndex;
final List<PersistentBottomNavBarItem> items;
final ValueChanged<int>? onItemSelected;
CustomNavBarWidget({
Key? key,
this.selectedIndex,
required this.items,
this.onItemSelected,
});
Widget _buildItem(PersistentBottomNavBarItem item, bool isSelected) {
return Container(
alignment: Alignment.center,
height: kBottomNavigationBarHeight,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Flexible(
child: IconTheme(
data: IconThemeData(
size: 26.0,
color: isSelected
? (item.activeColorSecondary == null
? item.activeColorPrimary
: item.activeColorSecondary)
: item.inactiveColorPrimary == null
? item.activeColorPrimary
: item.inactiveColorPrimary),
child: isSelected ? item.icon : item.inactiveIcon ?? item.icon,
),
),
Padding(
padding: const EdgeInsets.only(top: 5.0),
child: Material(
type: MaterialType.transparency,
child: FittedBox(
child: Text(
item.title!,
style: TextStyle(
color: isSelected
? (item.activeColorSecondary == null
? item.activeColorPrimary
: item.activeColorSecondary)
: item.inactiveColorPrimary,
fontWeight: FontWeight.w400,
fontSize: 12.0),
)),
),
)
],
),
);
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: Container(
width: double.infinity,
height: kBottomNavigationBarHeight,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: items.map((item) {
int index = items.indexOf(item);
return Flexible(
child: GestureDetector(
onTap: () {
this.onItemSelected!(index);
},
child: _buildItem(item, selectedIndex == index),
),
);
}).toList(),
),
),
);
}
}
PersistentBottomNavBarItem _buildBottomNavBarItem(String title, IconData icon) {
return PersistentBottomNavBarItem(
icon: Icon(icon),
title: title,
activeColorPrimary: Colors.indigo,
inactiveColorPrimary: Colors.grey,
);
}
class Page1 extends StatefulWidget {
const Page1({Key? key}) : super(key: key);
#override
_Page1State createState() => _Page1State();
}
class _Page1State extends State<Page1> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Container(
child: Center(
child: TextButton(
onPressed: () {
Navigator.push(
context, CupertinoPageRoute(builder: (context) => NewPage()));
},
child: Text('Click'),
),
),
),
);
}
}
class Page2 extends StatefulWidget {
const Page2({Key? key}) : super(key: key);
#override
_Page2State createState() => _Page2State();
}
class _Page2State extends State<Page2> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.red,
body: Container(),
);
}
}
class NewPage extends StatefulWidget {
const NewPage({Key? key}) : super(key: key);
#override
_NewPageState createState() => _NewPageState();
}
class _NewPageState extends State<NewPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(),
);
}
}
Have you tried simply using the built-in Navigator.of(context).pop() function as the onPressed callback?

Change color back after setState

I have column of InkWells. When a button is pressed, it changes color to red from white. The problem is, when another button is pressed, the previous button doesn't change back to white.
How can I solve this?
class NavigationButton extends StatefulWidget {
const NavigationButton({
Key key,
this.title,
}) : super(key: key);
final String title;
#override
_NavigationButtonState createState() => _NavigationButtonState();
}
class _NavigationButtonState extends State<NavigationButton> {
Color _color = Colors.white;
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
InkWell(
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
child: Container(
child: Text(
widget.title,
style: TextStyle(color: Colors.white, fontSize: 25)
)
),
onTap: () {
setState(() {
_color = Colors.red;
});
}
),
SizedBox(width: 10),
Container(
width: 10,
height: 10,
decoration: BoxDecoration(color: _color, shape: BoxShape.circle)
)
]
);
}
}
The menu wuth navigation buttons:
child: Container(
height: 205,
width: 210,
padding: EdgeInsets.only(right: 10),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
for (int index = 0; index < _sectionsName.length; index++)
NavigationButton(title: _sectionsName[index], index: index)
]
)
)
Thanks in advance...
can be solved by taking a boolean variable isSeleted;
For the current situation you will have to take bool in the widget where you have defined the NavigationButton List and wrap navigationButton with gesturedetector.
import 'package:flutter/material.dart';
class NavigationButton extends StatefulWidget {
final buttonColor;
final textColor;
final String title;
const NavigationButton({
Key key,
this.title,
this.buttonColor,
this.textColor,
}) : super(key: key);
#override
_NavigationButtonState createState() => _NavigationButtonState();
}
class _NavigationButtonState extends State<NavigationButton> {
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
SizedBox(width: 10),
Container(
decoration: BoxDecoration(
color: widget.buttonColor,
),
child: Container(
child: Text(
widget.title,
style: TextStyle(
color: widget.textColor,
fontSize: 25,
),
),
),
)
],
);
}
}
class MainScreen extends StatefulWidget {
const MainScreen({Key key}) : super(key: key);
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
// List isSelected = [];
Map _sectionsName = {"one ": false, "two": false};
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
height: 205,
width: 210,
padding: EdgeInsets.only(right: 10),
child: ListView(
children: _sectionsName.keys
.map<Widget>((item) => GestureDetector(
onTap: () {
_sectionsName.forEach((key, value) {
if (key == item) {
setState(() {
_sectionsName[key] = true;
});
}else{
_sectionsName[key] = false;
}
});
},
child: NavigationButton(
title: item,
buttonColor: _sectionsName[item] == true
? Colors.red
: Colors.white,
textColor: _sectionsName[item] == true
? Colors.white
: Colors.black,
),
))
.toList(),
),
),
);
}
}

animate show or hide widgets with flutter

i have something like this :
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _MyWidgetState();
}
}
class _MyWidgetState extends State<MyWidget> {
bool loading = true;
#override
Widget build(BuildContext context) {
if(loading) {
return Container(
color: Theme.of(context).scaffoldBackgroundColor,
child: Center(
child: SizedBox(
width: 24,
height: 24,
child: GestureDetector(
onTap: _toggle,
child: CircularProgressIndicator(),
),
),
),
);
} else {
return Container(
child: Center(
child: GestureDetector(
onTap: _toggle,
child: Text("WELCOME"),
),
),
);
}
}
_toggle() {
setState(() {
loading = !loading;
});
}
}
my big problem with flutter is animating between toggling widgets
i want when _toggle called, loading widget fadeOut and after animation completed remove from screen and then show normal widget with fadeIn effect
how can i achieved to this ?
thanks
Correct way is using AnimatedSwitcher widget:
class MyWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _MyWidgetState();
}
}
class _MyWidgetState extends State<MyWidget> {
bool loading = true;
#override
Widget build(BuildContext context) {
return Scaffold(
body: AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: loading ? Container(
key: Key("loading"),
color: Theme.of(context).scaffoldBackgroundColor,
child: Center(
child: SizedBox(
width: 24,
height: 24,
child: GestureDetector(
onTap: _toggle,
child: const CircularProgressIndicator(),
),
),
),
) : Container(
key: Key("normal"),
child: Center(
child: GestureDetector(
onTap: _toggle,
child: const Text("WELCOME"),
),
),
),
),
);
}
_toggle() {
setState(() {
loading = !loading;
});
}
}
note: you must give a key for children, in my example if you remove key animation not work
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _MyWidgetState();
}
}
class _MyWidgetState extends State<MyWidget> {
bool loading = true;
#override
Widget build(BuildContext context) {
return Container(
child: Stack(
children: <Widget>[
Center(
child: GestureDetector(
onTap: _toggle,
child: Text("WELCOME"),
),
),
IgnorePointer(
ignoring: !loading,
child: AnimatedOpacity(
opacity: loading ? 1 : 0,
duration: Duration(milliseconds: 500),
child: Container(
color: Theme.of(context).scaffoldBackgroundColor,
child: Center(
child: SizedBox(
width: 24,
height: 24,
child: GestureDetector(
onTap: _toggle,
child: CircularProgressIndicator(),
),
),
),
),
),
)
],
),
);
}
_toggle() {
setState(() {
loading = !loading;
});
}
}