Making a Common Button Widget in Flutter - flutter

Im making a common Button widget that have three states Active / Disable / Loading.
im managing the status from the place where I use the ButtonWidget.
I'll post what I did so far.
when I try to run the button widget it gets an error. namely
The following assertion was thrown while finalizing the widget tree:
_ButtonWidgetState#ab5a5(ticker active) was disposed with an active Ticker.
_ButtonWidgetState created a Ticker via its SingleTickerProviderStateMixin, but at the time dispose() was called on the mixin, that Ticker was still active. The Ticker must be disposed before calling super.dispose().
I imagine this is due the to animation I already have in my button widget.
can anyone help me to overcome this.?
would be really helpful.
ButtonWidget
import 'package:flutter/material.dart';
import 'dart:async';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
const TextStyle tButtonMedium = TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
);
enum eButtonType {
bText,
bIcon,
bIconText,
}
enum eButtonState {
bActive,
bDisable,
bLoading,
}
void main() {
runApp(MyApp());
}
class CustomColors {
static const Color grey100 = Color(0xFFF0F0F0);
static const Color grey600 = Color(0xFFAFAFAF);
static const Color mWhite = Color(0xFFFFFFFF);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
var _isLoading = false;
eButtonState? btnState;
#override
void initState() {
_makeActive();
super.initState();
}
void _makeActive() {
Future.delayed(
const Duration(seconds: 5),
() => setState(() {
btnState = eButtonState.bActive;
}),
);
}
void _onSubmit() {
setState(() {
btnState = eButtonState.bLoading;
});
Future.delayed(
const Duration(seconds: 10),
() => setState(() {
btnState = eButtonState.bActive;
}));
}
#override
Widget build(BuildContext context) {
return ButtonWidget(
key: UniqueKey(),
onPressed: () {
_onSubmit();
},
btnColor: Colors.white,
borderColor: Colors.blue,
textColor: Colors.blue,
text: "Save",
eButtonState: btnState,
eButtonType: eButtonType.bIconText,
iconData: Icons.save_alt,
);
}
}
////////////////////////////////////////////////////////////////////////
class ButtonWidget extends StatefulWidget {
//
final String? text;
final Color? btnColor;
final Color? borderColor;
final Color? textColor;
final Function? onPressed;
// final bool? isActive;
final eButtonType;
final eButtonState;
final IconData? iconData;
const ButtonWidget({
Key? key,
this.text,
this.btnColor,
this.borderColor,
this.textColor,
this.eButtonType,
this.iconData,
#required this.eButtonState,
#required this.onPressed,
// #required this.isActive,
}) : super(key: key);
#override
_ButtonWidgetState createState() => _ButtonWidgetState();
}
class _ButtonWidgetState extends State<ButtonWidget>
with SingleTickerProviderStateMixin {
late double _scale;
late AnimationController _controller;
#override
void initState() {
_controller = AnimationController(
vsync: this,
duration: const Duration(
milliseconds: 100,
),
upperBound: 0.1,
)..addListener(() {
setState(() {});
});
super.initState();
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
void buttonHandler() {
_controller.forward();
Timer(const Duration(milliseconds: 50), () {
_controller.reverse();
});
Timer(const Duration(milliseconds: 50), () {
widget.onPressed!();
});
}
#override
Widget build(BuildContext context) {
_scale = 1 - _controller.value;
return Transform.scale(
scale: _scale,
child: ElevatedButton(
style: TextButton.styleFrom(
elevation: widget.eButtonState == eButtonState.bActive ? 2.0 : 0.0,
primary: widget.eButtonState == eButtonState.bActive
? widget.btnColor!
: CustomColors.grey600,
backgroundColor: widget.eButtonState == eButtonState.bActive
? widget.btnColor!
: CustomColors.grey100,
minimumSize: const Size(80.0, 50.0),
maximumSize: const Size(double.infinity, 50.0),
shape: RoundedRectangleBorder(
side: BorderSide(
color: widget.eButtonState == eButtonState.bActive
? widget.borderColor!
: CustomColors.grey100,
width: 2,
),
borderRadius: BorderRadius.circular(16.0),
),
splashFactory: NoSplash.splashFactory,
),
onPressed: () => widget.eButtonState == eButtonState.bActive
? buttonHandler()
: null,
child: widget.eButtonState == eButtonState.bLoading
? const CircularProgressIndicator(
color: CustomColors.grey600,
backgroundColor: Colors.transparent,
)
: (widget.eButtonType == eButtonType.bText
? Text(
widget.text!,
style: tButtonMedium.copyWith(
color: widget.eButtonState == eButtonState.bActive
? widget.textColor!
: CustomColors.grey600,
),
textAlign: TextAlign.center,
textScaleFactor: 1.0,
)
: (widget.eButtonType == eButtonType.bIconText)
? Row(
// mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.min,
children: [
Icon(
widget.iconData,
color: widget.eButtonState == eButtonState.bActive
? widget.textColor
: CustomColors.grey600,
),
Spacer(),
Text(
widget.text!,
style: tButtonMedium.copyWith(
color: widget.eButtonState == eButtonState.bActive
? widget.textColor!
: CustomColors.grey600,
),
textAlign: TextAlign.center,
textScaleFactor: 1.0,
),
Spacer(),
],
)
: Icon(
widget.iconData,
color: widget.eButtonState == eButtonState.bActive
? CustomColors.mWhite
: CustomColors.grey600,
)),
),
);
}
}

I've managed to get to fixed by simply doing
void dispose() {
_controller.dispose();
super.dispose();
}
If anyone wish to have a common button widget. please feel free to use this code. and I appreciate if veterans can guide to improve this widget. cheers

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

onTap with Flutter plugin floating_frosted_bottom_bar

I am using the Flutter plugin floating_frosted_bottom_bar, and I want to display a page when the second item is clicked. I don't know how to do this. The code I have so far is this:
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:floating_frosted_bottom_bar/floating_frosted_bottom_bar.dart';
import 'logo_list.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Frosted bottom bar',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Frosted bottom bar'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
late int currentPage;
late TabController tabController;
final List<Color> colors = [
Colors.blue,
Colors.blue,
Colors.blue,
Colors.blue,
Colors.blue
];
#override
void initState() {
currentPage = 0;
tabController = TabController(length: 5, vsync: this);
tabController.animation!.addListener(
() {
final value = tabController.animation!.value.round();
if (value != currentPage && mounted) {
changePage(value);
}
},
);
super.initState();
}
void changePage(int newPage) {
setState(() {
currentPage = newPage;
});
}
#override
void dispose() {
tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: FrostedBottomBar(
opacity: 0.6,
sigmaX: 5,
sigmaY: 5,
child: TabBar(
indicatorPadding: const EdgeInsets.fromLTRB(6, 0, 6, 0),
controller: tabController,
indicator: const UnderlineTabIndicator(
borderSide: BorderSide(color: Colors.blue, width: 4),
insets: EdgeInsets.fromLTRB(16, 0, 16, 8),
),
tabs: [
TabsIcon(
icons: Icons.home,
color: currentPage == 0 ? colors[0] : Colors.white),
TabsIcon(
icons: Icons.search,
color: currentPage == 1 ? colors[1] : Colors.white),
TabsIcon(
icons: Icons.queue_play_next,
color: currentPage == 2 ? colors[2] : Colors.white),
TabsIcon(
icons: Icons.file_download,
color: currentPage == 3 ? colors[3] : Colors.white),
TabsIcon(
icons: Icons.menu,
color: currentPage == 4 ? colors[4] : Colors.white),
],
),
borderRadius: BorderRadius.circular(500),
duration: const Duration(milliseconds: 800),
hideOnScroll: false,
body: (context, controller) => TabBarView(
controller: tabController,
dragStartBehavior: DragStartBehavior.down,
physics: const BouncingScrollPhysics(),
children: colors
.map(
(e) => ListView.builder(
controller: controller,
itemBuilder: (context, index) {
return const Card(child: FittedBox(child: FlutterLogo()));
},
),
)
.toList(),
),
),
);
}
}
class TabsIcon extends StatelessWidget {
final Color color;
final double height;
final double width;
final IconData icons;
const TabsIcon(
{Key? key,
this.color = Colors.white,
this.height = 60,
this.width = 50,
required this.icons})
: super(key: key);
#override
Widget build(BuildContext context) {
return SizedBox(
height: height,
width: width,
child: Center(
child: Icon(
icons,
color: color,
),
),
);
}
}
The output I got: https://imgur.com/a/MKkHcEH
I am a beginner in Flutter so if you have a solution, please make it simple. Thanks in advance!
I updated your code, Put your screens in List[Screens] only instead off ffff() , hope it helps.
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:floating_frosted_bottom_bar/floating_frosted_bottom_bar.dart';
import 'ffff.dart';
import 'hhkllj.dart';
// import 'logo_list.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Frosted bottom bar',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Frosted bottom bar'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
int currentPage = 0;
late TabController tabController;
final List<Color> colors = [
Colors.blue,
Colors.blue,
Colors.blue,
Colors.blue,
Colors.blue
];
List<Widget> Screens = [
fff(),
hhhhh(),
fff(),
hhhhh(),
fff(),
];
#override
void initState() {
tabController = TabController(length: 5, vsync: this);
tabController.animation!.addListener(
() {
final value = tabController.animation!.value.round();
if (value != currentPage && mounted) {
changePage(value);
}
},
);
super.initState();
}
void changePage(int newPage) {
setState(() {
currentPage = newPage;
});
}
#override
void dispose() {
tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: FrostedBottomBar(
opacity: 0.6,
sigmaX: 5,
sigmaY: 5,
child: TabBar(
indicatorPadding: const EdgeInsets.fromLTRB(6, 0, 6, 0),
controller: tabController,
indicator: const UnderlineTabIndicator(
borderSide: BorderSide(color: Colors.blue, width: 4),
insets: EdgeInsets.fromLTRB(16, 0, 16, 8),
),
tabs: [
TabsIcon(
icons: Icons.home,
color: currentPage == 0 ? colors[0] : Colors.white),
TabsIcon(
icons: Icons.search,
color: currentPage == 1 ? colors[1] : Colors.white),
TabsIcon(
icons: Icons.queue_play_next,
color: currentPage == 2 ? colors[2] : Colors.white),
TabsIcon(
icons: Icons.file_download,
color: currentPage == 3 ? colors[3] : Colors.white),
TabsIcon(
icons: Icons.menu,
color: currentPage == 4 ? colors[4] : Colors.white),
],
),
borderRadius: BorderRadius.circular(500),
duration: const Duration(milliseconds: 800),
hideOnScroll: false,
body: (context, controller) => Screens[currentPage]
// (context, controller) => TabBarView(
// controller: tabController,
// dragStartBehavior: DragStartBehavior.down,
// physics: const BouncingScrollPhysics(),
// children: colors.map(
// (e) => ListView.builder(
// controller: controller,
// itemBuilder: (context, index) {
// return const Card(child: FittedBox(child: FlutterLogo()));
// },
// ),
// )
// .toList(),
// ),
),
);
}
}
class TabsIcon extends StatelessWidget {
final Color color;
final double height;
final double width;
final IconData icons;
const TabsIcon(
{Key? key,
this.color = Colors.white,
this.height = 60,
this.width = 50,
required this.icons})
: super(key: key);
#override
Widget build(BuildContext context) {
return SizedBox(
height: height,
width: width,
child: Center(
child: Icon(
icons,
color: color,
),
),
);
}
}

Flutter - How to keep the page alive when changing it with PageView or BottomNavigationBar

i'm making audio app with PageView and BottomNavigationBar, it should run the audio when isSelected
is true and it's working but when I change pages it stop working and isSelected become false again, how to prevent that from happening? i'm also using AudioPlayers pagckage.
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int selectedIndex = 0;
final PageController pageController = PageController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: PageView(
controller: pageController,
children: <Widget>[
TimerPage(),
TodoPage(),
CalenderPage(),
MusicPage(),
SettingsPage(),
],
onPageChanged: (pageIndex) {
setState(() {
selectedIndex = pageIndex;
});
},
),
),
bottomNavigationBar: SizedBox(
height: 70,
child: ClipRRect(
borderRadius: const BorderRadius.only(
topRight: Radius.circular(25),
topLeft: Radius.circular(25),
),
child: BottomNavigationBar(
onTap: (selectedIndex) {
setState(() {
pageController
..animateToPage(selectedIndex,
duration: Duration(milliseconds: 500),
curve: Curves.ease);
});
},
backgroundColor: MyColors.lightgray,
selectedItemColor: MyColors.accentRed,
unselectedItemColor: MyColors.disabledGrey,
selectedFontSize: 15,
unselectedFontSize: 15,
type: BottomNavigationBarType.fixed,
currentIndex: selectedIndex,
showSelectedLabels: false,
showUnselectedLabels: false,
items: [
BottomNavigationBarItem(
icon: const Icon(FontAwesomeIcons.clock),
label: "",
),
BottomNavigationBarItem(
icon: const FaIcon(FontAwesomeIcons.check),
label: "",
),
BottomNavigationBarItem(
icon: const FaIcon(FontAwesomeIcons.calendarAlt),
label: "",
),
BottomNavigationBarItem(
icon: const FaIcon(FontAwesomeIcons.music),
label: "",
),
BottomNavigationBarItem(
icon: const FaIcon(FontAwesomeIcons.ellipsisH)
label: "",
),
],
),
),
),
);
}
}
the play button:
class SoundChip extends StatefulWidget {
final String title;
final String image;
final String audioName;
final VoidCallback onPress;
SoundChip({Key key, this.title, this.image, this.onPress, this.audioName})
: super(key: key);
#override
_SoundChipState createState() => _SoundChipState();
}
class _SoundChipState extends State<SoundChip> {
bool isSelected = false;
AudioPlayer audioPlayer = AudioPlayer();
PlayerState audioPlayerState = PlayerState.PAUSED;
AudioCache audioCache;
play() async {
await audioCache.loop(widget.audioName,
stayAwake: true, mode: PlayerMode.LOW_LATENCY);
}
pause() async {
await audioPlayer.pause();
}
#override
void initState() {
super.initState();
audioCache = AudioCache(fixedPlayer: audioPlayer);
audioPlayer.onPlayerStateChanged.listen((PlayerState state) {
setState(() {
audioPlayerState = state;
});
});
}
#override
void dispose() {
super.dispose();
audioPlayer.release();
audioPlayer.dispose();
audioCache.clearAll();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
if (audioPlayerState == PlayerState.PLAYING) {
pause();
isSelected = false;
} else {
play();
isSelected = true;
}
widget.onPress();
},
child: AnimatedOpacity(
opacity: isSelected ? 1 : 0.5,
duration: Duration(milliseconds: 100),
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: AnimatedContainer(
duration: Duration(seconds: 1),
width: 160,
height: 100,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage(widget.image),
fit: BoxFit.cover,
),
),
child: Center(
child: Text(
widget.title,
style: TextStyle(
fontSize: 30,
shadows: [
Shadow(
color: Colors.black,
blurRadius: 20,
),
],
),
)),
),
),
),
);
}
}
Add AutomaticKeepAliveClientMixin to your page that you want to keep alive even if it is not in focus in the PageView.
How to add AutomaticKeepAliveClientMixin?
Add with AutomaticKeepAliveClientMixin to your widget's state class.
class _MyWidgetState extends State<MyWidget> with AutomaticKeepAliveClientMixin {
...
}
Add wantKeepAlive getter to your widget's state.
class _MyWidgetState extends State<MyWidget> with AutomaticKeepAliveClientMixin {
bool get wantKeepAlive => true;
...
}
Add super.build(context) to the build method of your widget's state.
class _MyWidgetState extends State<MyWidget> with AutomaticKeepAliveClientMixin {
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
return ...
}
}

Flutter - How to Make floating action button animation like gmail?

I am able to make quite a similar floating action button animation like Gmail app, but I am getting a little bit of margin when I isExpanded is false. Any solution?
Here is my code
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool isExpanded = false;
Widget build(context) {
return Scaffold(
floatingActionButton: AnimatedContainer(
width: isExpanded ? 150 : 56,
height: 56,
duration: Duration(milliseconds: 300),
child: FloatingActionButton.extended(
onPressed: () {},
icon: Icon(Icons.ac_unit),
label: isExpanded ? Text("Start chat") : SizedBox(),
),
),
appBar: AppBar(),
body: FlatButton(
onPressed: () {
setState(() {
isExpanded = !isExpanded;
});
},
child: Text('Press here to change FAB')));
}
}
Looks like FloatingActionButton has some hardcoded padding set for an icon. To fix that, you could do the following:
FloatingActionButton.extended(
onPressed: () {},
icon: isExpanded ? Icon(Icons.ac_unit) : null,
label: isExpanded ? Text("Start chat") : Icon(Icons.ac_unit),
)
If you want an animation then you have to write your own custom fab:
class ScrollingExpandableFab extends StatefulWidget {
const ScrollingExpandableFab({
Key? key,
this.controller,
required this.label,
required this.icon,
this.onPressed,
this.scrollOffset = 50.0,
this.animDuration = const Duration(milliseconds: 500),
}) : super(key: key);
final ScrollController? controller;
final String label;
final Widget icon;
final VoidCallback? onPressed;
final double scrollOffset;
final Duration animDuration;
#override
State<ScrollingExpandableFab> createState() => _ScrollingExpandableFabState();
}
class _ScrollingExpandableFabState extends State<ScrollingExpandableFab>
with TickerProviderStateMixin {
late final AnimationController _controller = AnimationController(
duration: widget.animDuration,
vsync: this,
);
late final Animation<double> _anim = Tween<double>(begin: 0.0, end: 1.0)
.animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
Color get _backgroundColor =>
Theme.of(context).floatingActionButtonTheme.backgroundColor ??
Theme.of(context).colorScheme.secondary;
ScrollController? get _scrollController => widget.controller;
_scrollListener() {
final position = _scrollController!.position;
if (position.pixels > widget.scrollOffset &&
position.userScrollDirection == ScrollDirection.reverse) {
_controller.forward();
} else if (position.pixels <= widget.scrollOffset &&
position.userScrollDirection == ScrollDirection.forward) {
_controller.reverse();
}
}
#override
void initState() {
super.initState();
_scrollController?.addListener(_scrollListener);
}
#override
void dispose() {
_scrollController?.removeListener(_scrollListener);
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: const BoxConstraints.tightFor(height: 48.0),
child: AnimatedBuilder(
animation: _anim,
builder: (context, child) => Material(
elevation: 4.0,
type: MaterialType.button,
color: _backgroundColor,
shape: const CircleBorder(),
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: widget.onPressed,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
widget.icon,
ClipRect(
child: Align(
alignment: AlignmentDirectional.centerStart,
widthFactor: 1 - _anim.value,
child: Opacity(
opacity: 1 - _anim.value,
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: SimpleclubText.button(widget.label),
),
),
),
),
],
),
),
),
),
),
);
}
}
Please follow example below:
FloatingActionButton.extended(
onPressed: () {
setState(() {
expanded = !expanded;
});
},
backgroundColor: context.theme.colorScheme.secondary,
label: expanded ? Text(t.addPlan) : Icon(Icons.add),
icon: expanded ? Icon(Icons.add) : null,
shape: expanded ? null : CircleBorder(),
),
);
It's important that you don't set isExtended on the fab and you need to set CircleBorder when the fab is not expanded to make sure the fab still has a circular shape.
I hope this helps!

How can I change ColorTween begin or end color with setState?

This is a widget for my quiz app. I am currently working on the quiz option button.
I want it to blink green or red whether the answer is true or not.
But it's not working. I tried different things but I couldn't succeed.
import 'package:flutter/material.dart';
class TestButton extends StatefulWidget {
TestButton({this.text, this.color, this.onPressed});
final Function onPressed;
final Color color;
final String text;
#override
_TestButtonState createState() => _TestButtonState();
}
class _TestButtonState extends State<TestButton> with TickerProviderStateMixin {
AnimationController _animationController;
Animation _animation;
void initState() {
super.initState();
_animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 400));
_animation = ColorTween(begin: Colors.transparent, end: widget.color)
.animate(CurvedAnimation(curve: Curves.decelerate, parent: _animationController))
..addListener(() {
setState(() {
});
});
_animationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_animationController.reverse();
} else if (status == AnimationStatus.dismissed) {
_animationController.forward();
}
});
_animationController.forward();
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
return SizedBox(
width: width / 1.3,
child: InkWell(
borderRadius: BorderRadius.circular(30),
onTap: widget.onPressed,
child: Container(
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(width / 20),
decoration: BoxDecoration(
color: _animation.value,
border: Border.all(color: Colors.white38),
borderRadius: BorderRadius.circular(20)),
child: Text(
widget.text,
style: TextStyle(fontSize: 16, fontFamily: "Fondamento"),
),
),
),
),
);
}
}
And this is the usage of the button. I am applying a setState function but it doesn't make sense. The problem is that the animation works but the setState function doesn't change the color value in the ColorTween
TestButton(
text: question.optionA,
color: colorA,//Colors.transparent
onPressed: () {
if (canTap) {
canTap = false;
if (question.answer == 1) {
setState(() {
colorA = Colors.green;
});
} else {
setState(() {
colorA = Colors.red;
});
}
}
},
),
Remove this part:
..addListener(() {
setState(() {
});
});
And use AnimatedBuilder like this:
return SizedBox(
width: width / 1.3,
child: InkWell(
borderRadius: BorderRadius.circular(30),
onTap: widget.onPressed,
child: Container(
child: AnimatedBuilder(
animation: _animationController,
builder: (_, __) => Container(
alignment: Alignment.center,
padding: EdgeInsets.all(width / 20),
decoration: BoxDecoration(
color: _animationController.value,
border: Border.all(color: Colors.white38),
borderRadius: BorderRadius.circular(20)),
child: Text(
widget.text,
style: TextStyle(fontSize: 16, fontFamily: "Fondamento"),
),
)
),
),
),
);
You can copy paste run full code below
You can use didUpdateWidget to reset _animation
In working demo, when click button, color changes from red to green
#override
void didUpdateWidget(covariant TestButton oldWidget) {
if (oldWidget.color != widget.color) {
_animation = ColorTween(begin: Colors.transparent, end: widget.color)
.animate(CurvedAnimation(
curve: Curves.decelerate, parent: _animationController))
..addListener(() {
setState(() {});
});
}
super.didUpdateWidget(oldWidget);
}
working demo
full code
import 'package:flutter/material.dart';
class TestButton extends StatefulWidget {
TestButton({this.text, this.color, this.onPressed});
final Function onPressed;
final Color color;
final String text;
#override
_TestButtonState createState() => _TestButtonState();
}
class _TestButtonState extends State<TestButton> with TickerProviderStateMixin {
AnimationController _animationController;
Animation _animation;
void initState() {
super.initState();
_animationController =
AnimationController(vsync: this, duration: Duration(milliseconds: 400));
_animation = ColorTween(begin: Colors.transparent, end: widget.color)
.animate(CurvedAnimation(
curve: Curves.decelerate, parent: _animationController))
..addListener(() {
setState(() {});
});
_animationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_animationController.reverse();
} else if (status == AnimationStatus.dismissed) {
_animationController.forward();
}
});
_animationController.forward();
}
#override
void didUpdateWidget(covariant TestButton oldWidget) {
if (oldWidget.color != widget.color) {
_animation = ColorTween(begin: Colors.transparent, end: widget.color)
.animate(CurvedAnimation(
curve: Curves.decelerate, parent: _animationController))
..addListener(() {
setState(() {});
});
}
super.didUpdateWidget(oldWidget);
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
return SizedBox(
width: width / 1.3,
child: InkWell(
borderRadius: BorderRadius.circular(30),
onTap: widget.onPressed,
child: Container(
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(width / 20),
decoration: BoxDecoration(
color: _animation.value,
border: Border.all(color: Colors.white38),
borderRadius: BorderRadius.circular(20)),
child: Text(
widget.text,
style: TextStyle(fontSize: 16, fontFamily: "Fondamento"),
),
),
),
),
);
}
}
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> {
Color colorA = Colors.red;
bool canTap = true;
int answer = 1;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TestButton(
text: "question.optionA",
color: colorA, //Colors.transparent
onPressed: () {
if (canTap) {
canTap = false;
if (answer == 1) {
setState(() {
colorA = Colors.green;
});
} else {
setState(() {
colorA = Colors.red;
});
}
}
},
),
],
),
),
);
}
}