I want to slide out my first widget from right and slide in second from left of screen.
I'm trying to use AnimatedSwitcher with SlideTransition
my current code bug is that first widget doesn't slide out and just vanishes
here is my complete code snippet.
Any help would be appriciated
class LoginPage extends StatefulWidget {
LoginPage({Key? key}) : super(key: key);
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage>
with SingleTickerProviderStateMixin {
static const int PIN_CODE_LENGTH = 4;
final TextEditingController _mobileController = TextEditingController();
final TextEditingController _pinController = TextEditingController();
final UniqueKey _mobileKey = UniqueKey();
final UniqueKey _pinKey = UniqueKey();
bool _submittable = false;
bool _isLoginStepOne = true;
String _buttonText = Strings.next;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Directionality(
textDirection: TextDirection.rtl,
child: SingleChildScrollView(
child: SizedBox(
height: SizePercentConfig.screenHeight,
child: Column(
children: [
_buildHeader(),
Expanded(
child: _buildForm(),
),
],
),
),
),
),
);
}
Widget _buildHeader() {
return Container(
height: SizePercentConfig.safeBlockVertical * 60,
child: Stack(
children: [
Positioned(
bottom: 0,
right: SizePercentConfig.blockSizeHorizontal * 30,
left: SizePercentConfig.blockSizeHorizontal * 30,
child: Image.asset(
Assets.logo,
fit: BoxFit.fitWidth,
),
),
Container(
height: SizePercentConfig.safeBlockVertical * 50,
child: Stack(
children: [
Positioned(
bottom: 0,
child: Image.asset(
Assets.loginHeader,
width: SizePercentConfig.screenWidth,
fit: BoxFit.fitWidth,
),
),
],
),
),
],
),
);
}
Widget _buildForm() {
return Form(
onChanged: _validate,
child: Padding(
padding: const EdgeInsets.all(Dimens.unitX2),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
AnimatedSwitcher(
duration: const Duration(seconds: 1),
transitionBuilder: (Widget child, Animation<double> animation) {
final inAnimation = Tween<Offset>(
begin: Offset(1.0, 0.0), end: Offset(0.0, 0.0))
.animate(animation);
final outAnimation = Tween<Offset>(
begin: Offset(-1.0, 0.0), end: Offset(0.0, 0.0))
.animate(animation);
print('** child key: ${child.key}');
print('** mobile key: $_mobileKey');
print('** pin key: $_pinKey');
if (child.key == _mobileKey) {
// in animation
print('>>>>>>> first statement');
return ClipRect(
child: SlideTransition(
position: inAnimation,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: child,
),
),
);
} else {
// out animation
print('>>>>>>> second statement');
return ClipRect(
child: SlideTransition(
position: outAnimation,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: child,
),
),
);
}
},
layoutBuilder:
(Widget? currentChild, List<Widget> previousChildren) {
return currentChild!;
},
child: _isLoginStepOne
? AppTextField(
key: _mobileKey,
controller: _mobileController,
hint: Strings.mobileNumber,
textInputType: TextInputType.phone,
)
: _buildPinCode()),
SizedBox(height: Dimens.unitX2),
AppSolidButton(
onPressed: _buttonAction,
text: _buttonText,
width: SizePercentConfig.screenWidth,
enabled: _submittable,
),
SizedBox(height: Dimens.unitX2),
],
),
),
);
}
void _validate() {
if (_isLoginStepOne) {
if (Regex.mobileRegex.hasMatch(_mobileController.value.text) !=
_submittable)
setState(() {
print('--> setState called in _validate');
_submittable = !_submittable;
});
} else {
if ((_pinController.value.text.length == 4) != _submittable)
setState(() {
print('--> setState called in _validate');
_submittable = !_submittable;
});
}
}
void _buttonAction() {
if (_submittable) {
setState(() {
print('--> setState called in _buttonPressed');
_isLoginStepOne = false;
_submittable = false;
_buttonText = Strings.login;
});
} else {}
}
Widget _buildPinCode() {
return Directionality(
textDirection: TextDirection.ltr,
child: PinCodeTextField(
key: _pinKey,
controller: _pinController,
appContext: context,
length: PIN_CODE_LENGTH,
onChanged: (_) {},
enablePinAutofill: true,
enableActiveFill: true,
textStyle: TextStyle(color: Palette.scorpion),
pinTheme: PinTheme(
shape: PinCodeFieldShape.circle,
fieldHeight: SizePercentConfig.safeBlockHorizontal * 20,
fieldWidth: SizePercentConfig.safeBlockHorizontal * 20,
activeFillColor: Palette.concrete,
inactiveFillColor: Palette.concrete,
selectedFillColor: Palette.roseBud,
activeColor: Palette.concrete,
disabledColor: Palette.concrete,
inactiveColor: Palette.concrete,
selectedColor: Palette.roseBud,
),
cursorColor: Palette.transparent,
keyboardType: TextInputType.number,
),
);
}
}
Give your ClipRect widgets unique keys:
If the "new" child is the same widget type and key as the "old" child, but with different parameters, then AnimatedSwitcher will not do a transition between them, since as far as the framework is concerned, they are the same widget and the existing widget can be updated with the new parameters. To force the transition to occur, set a Key on each child widget that you wish to be considered unique (typically a ValueKey on the widget data that distinguishes this child from the others).
Related
I'm trying to add a custom dropdown menu whose items are just links to other pages
I tried using DropdownButton
But I failed to make its elements as a link and it requires a value, and I do not have a value to pass to it
thank you
You can use OverlayEntry for this case. Below is a simple working example of a dropdown using OverlayEntry:
class TestDropdownWidget extends StatefulWidget {
TestDropdownWidget({Key? key}) : super(key: key);
#override
_TestDropdownWidgetState createState() => _TestDropdownWidgetState();
}
class _TestDropdownWidgetState extends State<TestDropdownWidget>
with TickerProviderStateMixin {
final LayerLink _layerLink = LayerLink();
late OverlayEntry _overlayEntry;
bool _isOpen = false;
//Controller Animation
late AnimationController _animationController;
late Animation<double> _expandAnimation;
#override
void dispose() {
super.dispose();
_animationController.dispose();
}
#override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 200),
);
_expandAnimation = CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
);
}
#override
Widget build(BuildContext context) {
return CompositedTransformTarget(
link: _layerLink,
child: InkWell(
onTap: _toggleDropdown,
child: Text('Click Me'), //Define your child here
),
);
}
OverlayEntry _createOverlayEntry() {
return OverlayEntry(
builder: (context) => GestureDetector(
onTap: () => _toggleDropdown(close: true),
behavior: HitTestBehavior.translucent,
// full screen container to register taps anywhere and close drop down
child: SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Stack(
children: [
Positioned(
left: 100,
top: 100.0,
width: 250,
child: CompositedTransformFollower(
//use offset to control where your dropdown appears
offset: Offset(0, 20),
link: _layerLink,
showWhenUnlinked: false,
child: Material(
elevation: 2,
borderRadius: BorderRadius.circular(6),
borderOnForeground: true,
color: Colors.white,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
border: Border.all(color: Colors.grey),
),
child: SizeTransition(
axisAlignment: 1,
sizeFactor: _expandAnimation,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
//These are the options that appear in the dropdown
Text('Option 1'),
Text('Option 2'),
Text('Option 3'),
Text('Option 4'),
Text('Option 5'),
],
),
),
),
),
),
),
],
),
),
),
);
}
void _toggleDropdown({
bool close = false,
}) async {
if (_isOpen || close) {
_animationController.reverse().then((value) {
_overlayEntry.remove();
if (mounted) {
setState(() {
_isOpen = false;
});
}
});
} else {
_overlayEntry = _createOverlayEntry();
Overlay.of(context)!.insert(_overlayEntry);
setState(() => _isOpen = true);
_animationController.forward();
}
}
}
Here's a gif to show the ui:
I am trying to build a list view with cards with different animation behaviors depending on user interactions like hovering. The hovered card needs to be scaled up and the remaining cards in the list need to be scaled down and made less opaque.
Like:
Expected smoothness
View code:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:simple_animations/multi_tween/multi_tween.dart';
import 'package:simple_animations/simple_animations.dart';
import 'package:simple_animations/stateless_animation/custom_animation.dart';
import '../controllers/work_controller.dart';
class WorkView extends GetView<WorkController> {
#override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
flex: 1,
child: Container(color: Colors.blueGrey),
),
Expanded(
flex: 2,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
10,
(index) {
return MouseRegion(
onExit: (event) => controller.hoverIndex.value = -1,
onEnter: ((event) {
print("Setting index: $index");
controller.hoverIndex.value = index;
}),
child: AnimatedWorkCard(
index: index,
));
},
),
),
))
],
);
}
}
class AnimatedWorkCard extends StatefulWidget {
final int index;
const AnimatedWorkCard({
Key? key,
required this.index,
}) : super(key: key);
#override
State<AnimatedWorkCard> createState() => _AnimatedWorkCardState();
}
enum CardAnimationProps {
isNotHoveredOpacity,
isHoveredImgScale,
isNotHoveredTranslateX,
isHoveredTextTranslateY,
isHoveredTextOpacity,
}
class _AnimatedWorkCardState extends State<AnimatedWorkCard> {
final WorkController controller = Get.find();
var animationControl = CustomAnimationControl.play;
#override
Widget build(BuildContext context) {
return Obx(() {
var isHovered = controller.hoverIndex.value == widget.index;
if (!isHovered) {
return CustomAnimation<TimelineValue<CardAnimationProps>>(
control: animationControl,
tween: createNotHoveredTween(),
builder: (context, child, value) {
return Transform.translate(
offset: Offset(
value.get(CardAnimationProps.isNotHoveredTranslateX), 0),
child: child,
);
},
child: Card(
color: Colors.amber,
child: Container(
width: Get.width * 0.2,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Work ${widget.index}',
style: TextStyle(
fontSize: Get.width * 0.03,
),
),
ElevatedButton(
child: Text('Hovering: ${controller.hoverIndex.value}'),
onPressed: () {
Get.toNamed('/work/${widget.index}');
},
),
],
),
),
),
);
} else {
return CustomAnimation<TimelineValue<CardAnimationProps>>(
control: animationControl,
tween: createTween(controller.hoverIndex.value, widget.index),
builder: (context, child, value) {
return Transform.scale(
scale: value.get(CardAnimationProps.isHoveredImgScale),
child: child ?? Container());
},
child: Card(
color: Colors.amber,
child: Container(
width: Get.width * 0.2,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Work ${widget.index}',
style: TextStyle(
fontSize: Get.width * 0.03,
),
),
ElevatedButton(
child: Text('Hovering: ${controller.hoverIndex.value}'),
onPressed: () {
Get.toNamed('/work/${widget.index}');
},
),
],
),
),
),
);
}
});
}
createTween(int hoverIndex, int index) {
TimelineTween timelineTween = TimelineTween<CardAnimationProps>();
var scene =
timelineTween.addScene(begin: 0.seconds, end: 2000.milliseconds);
scene.animate(
CardAnimationProps.isHoveredImgScale,
tween: Tween<double>(begin: 1, end: 1.3),
);
scene.animate(
CardAnimationProps.isHoveredTextOpacity,
tween: Tween<double>(begin: 0, end: 1),
);
scene.animate(
CardAnimationProps.isHoveredTextTranslateY,
tween: Tween<double>(begin: 20, end: 0),
);
return timelineTween;
}
createNotHoveredTween() {
TimelineTween timelineTween = TimelineTween<CardAnimationProps>();
var scene =
timelineTween.addScene(begin: 0.seconds, end: 2000.milliseconds);
scene.animate(
CardAnimationProps.isNotHoveredOpacity,
tween: Tween<double>(begin: 1, end: 0.2),
);
scene.animate(
CardAnimationProps.isNotHoveredTranslateX,
tween: Tween<double>(
begin: 0,
end: 20,
),
);
return timelineTween;
}
}
Controller code:
import 'package:get/get.dart';
class WorkController extends GetxController {
RxInt hoverIndex = (-1).obs;
#override
void onReady() {
super.onReady();
}
#override
void onClose() {}
}
But the animation is not smooth and it's just jumping from the states.
Any idea how this can made smoother or any other way this can be thought of implementing?
Animation demo
I am looking to create a DropDownMenu on Flutter for a form.
This DropDown menu is not pasted/collapse to the value selector.
So I implemented an Expand List View that comes just below the Row.
I want my SizedTransition widget to go over the parent widget and not shift the display. Is this possible? How do I do this?
I have :
Column [
InkWell,
SizeTransition -> ListView
]
I would like my SizeTransition should display over parent widget (not expand it).
There is my widget code app_drop_down_form.dart :
This widget take a list to item to display in expanded list and a default title when nothing is selected.
class AppDropDownForm extends StatefulWidget {
const AppDropDownForm({
required this.defaultTitle,
required this.dropList,
Key? key,
}) : super(key: key);
final String defaultTitle;
final List<AppDropDownItem> dropList;
#override
_AppDropDownFormState createState() => _AppDropDownFormState();
}
class _AppDropDownFormState extends State<AppDropDownForm>
with SingleTickerProviderStateMixin {
bool enableList = false;
int? _selectedIndex;
late AnimationController _expandController;
late Animation<double> _expandAnimation;
#override
void initState() {
_expandController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 300));
_expandAnimation =
CurvedAnimation(parent: _expandController, curve: Curves.easeInCubic);
super.initState();
}
#override
void dispose() {
_expandController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
Container selectedItemWidget = _buildHeaderForm();
return Column(children: <Widget>[
InkWell(
onTap: _performExpand,
child: Container(
decoration: BoxDecoration(
border: Border.all(color: AppTheme.lightGrey, width: 1),
borderRadius: enableList
? const BorderRadius.vertical(top: Radius.circular(10))
: const BorderRadius.all(Radius.circular(10)),
color: Colors.white),
padding: const EdgeInsets.symmetric(horizontal: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.min,
children: [
Expanded(child: selectedItemWidget),
const Icon(Icons.expand_more,
size: 24.0, color: AppTheme.thirdColor)
]))),
SizeTransition(
sizeFactor: _expandAnimation, child: _buildExpandableSelectableList())
]);
}
Container _buildHeaderForm() {
if (_selectedIndex == null) {
return Container(
padding: const EdgeInsets.symmetric(vertical: 15),
child:
Text(widget.defaultTitle, style: AppTheme.dropDownHintTextStyle));
} else {
var selectItem = widget.dropList
.where((element) => element.index == _selectedIndex)
.first;
return Container(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(children: [
SizedBox(
height: 25,
width: 25,
child: SvgPicture.asset(selectItem.iconPath,
color: AppTheme.black)),
const SizedBox(width: 20),
Text(selectItem.name, style: AppTheme.dropDownSelectedTextStyle)
]));
}
}
Widget _buildExpandableSelectableList() {
return Container(
decoration: BoxDecoration(
border: Border.all(color: AppTheme.lightGrey, width: 1),
borderRadius:
const BorderRadius.vertical(bottom: Radius.circular(10)),
color: AppTheme.thirdColor),
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.vertical,
physics: const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics()),
itemCount: widget.dropList.length,
itemBuilder: (context, position) {
return _buildExpandableItem(position);
}));
}
InkWell _buildExpandableItem(int position) {
var item =
widget.dropList.where((element) => element.index == position).first;
return InkWell(
onTap: () {
_onChanged(position);
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(children: [
SizedBox(
height: 25,
width: 25,
child:
SvgPicture.asset(item.iconPath, color: AppTheme.white)),
const SizedBox(width: 20),
Text(item.name, style: AppTheme.dropDownLightTextStyle)
])));
}
void _onChanged(int position) {
setState(() {
_selectedIndex = position;
_performExpand();
});
}
void _performExpand() {
enableList = !enableList;
if (enableList) {
_expandController.forward();
} else {
_expandController.reverse();
}
}
}
````
I want that as I move my slider button towards right, the opacity of text decreases and arrow icon rotates exactly oppposite, i.e. it strts rotating and at last last it should point backwards. I want to use opacity and Transform.rotate widgets, but how do I keep updating the value of dx ,so I can divide it with total width of container and use the fraction for calculation.
If there is another way, please do tell me.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:passenger_flutter_app/utils/colors.dart';
import 'package:passenger_flutter_app/widgets/custom_sliding_button.dart';
class CommonSwipeButton extends StatelessWidget {
final String? buttonText1;
final String buttonText2;
final VoidCallback buttonCallBack2;
final bool isInfo;
final VoidCallback? buttonCallBack1;
final Widget itemWidget;
CommonSwipeButton(
{this.buttonCallBack1,
required this.buttonCallBack2,
this.isInfo = false,
this.buttonText1,
required this.buttonText2,
required this.itemWidget});
#override
Widget build(BuildContext context) {
return Container(
child: Column(
//crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Padding(padding: const EdgeInsets.only(left: 16.0, right: 16.0, bottom: 16.0, top: 16), child: itemWidget),
Padding(
padding:
const EdgeInsets.only(bottom: 16.0, left: 16.0, right: 16.0),
child: Align(
alignment: Alignment.center,
child: SizedBox(
width: MediaQuery.of(context).size.width,
height: 44,
child: CustomSlidingButton(
//text: buttonText2,
),
),
),
)
],
),
);
}
}
/*
class SwipeButton extends StatefulWidget {
final ValueChanged<double>? valueChanged;
final String? text;
final Function? callBack;
SwipeButton({this.valueChanged, this.text, this.callBack});
#override
SwipeButtonState createState() {
return new SwipeButtonState();
}
}
class SwipeButtonState extends State<SwipeButton> {
ValueNotifier<double> valueListener = ValueNotifier(.0);
GlobalKey swipeKey = GlobalKey();
ValueNotifier<double> x=ValueNotifier<double>(0);
ValueNotifier<bool> isVisible = ValueNotifier<bool>(true);
#override
void initState() {
valueListener.addListener(notifyParent);
super.initState();
}
void notifyParent() {
if (widget.valueChanged != null) {
widget.valueChanged!(valueListener.value);
}
}
void getPos(double totalSize) {
RenderBox box = swipeKey.currentContext?.findRenderObject() as RenderBox;
Offset position = box.localToGlobal(Offset.zero); //this is global position
x.value = position.dx;
print(x);
if(x.value>355) {
print("Reached");
isVisible.value=false;
}
}
#override
Widget build(BuildContext context) {
return Container(
color: colorPrimary,
height: 40.0,
padding: EdgeInsets.symmetric(horizontal: 10.0),
child: Stack(
children: [
Center(
child: Padding(
padding: const EdgeInsets.only(left: 10.0),
child: Text(
"${widget.text}",
style: TextStyle(
color: Colors.white,
fontSize: 17,
),
),
),
),
Builder(
builder: (context) {
final handle = GestureDetector(
onHorizontalDragUpdate: (details) {
valueListener.value = (valueListener.value +
details.delta.dx / context.size!.width)
.clamp(.0, 1.0);
getPos(context.size!.width-5);
print(context.size?.width);
},
child: ValueListenableBuilder(
valueListenable: isVisible,
builder: (BuildContext context, bool val, Widget? child) {
return Container(
key: swipeKey,
height: 25.0,
width: 25.0,
color: val ? Colors.white : colorPrimary,
child: Center(
child: ValueListenableBuilder(
valueListenable: x,
builder: (BuildContext context, double d, Widget? child) {
return Transform.rotate(
angle: -pi*(d/350),
child: Icon(
Icons.arrow_forward,
color: Colors.orange,
size: 12,
),
);
},
),
),
);
},
),
);
return AnimatedBuilder(
animation: valueListener,
builder: (context, child) {
return Align(
alignment: Alignment(valueListener.value * 2 - 1, 0),
child: child,
);
},
child: handle,
);
},
),
],
),
);
}
}*/
You can use Slider widget from Flutter framework and update a local variable in the onChange function:
Slider(
value: _currentSliderValue,
max: 100, //or any max value you need
onChanged: (double value) {
setState(() {
_value = value;
});
},
);
And the _value variable you will use in Opacity and Transform widgets.
In my simple part of mobile application i used Hero without any problem and that works fine, now when i try to add a class as Widget which named AnimatedFab in part of this class i get this error:
There are multiple heroes that share the same tag within a subtree.
i don't use any Hero in this class and i'm wondering why i get the error
i used Hero in Stack and implementation code is:
Positioned(
top: 259.0,
left: 6.0,
child: SizedBox(
key: _imageKey,
width: 43.0,
height: 43.0,
child: InkWell(onTap: () {
//...
},child: MyHero(hiveFeed: widget.hiveFeeds)),
)),
and in parent of Stack which above code is one child of that, i have this code:
Positioned(top: 140.0, right: -40.0, child: const AnimatedFab().pl(8.0)),
full Stack children:
return Stack(
children: [
Card(
child: Stack(
children: [
Positioned(top: 140.0, right: -40.0, child: const AnimatedFab().pl(8.0)),
],
),
),
Positioned(
top: 259.0,
left: 6.0,
child: SizedBox(
key: _imageKey,
width: 43.0,
height: 43.0,
child: InkWell(onTap: () {
//...
},child: MyHero(hiveFeed: widget.hiveFeeds)),
)),
],
);
UPDATED
i consider heroTag as a value into below class:
AnimatedFab class which i have problem with that is below code:
Positioned(top: 140.0, right: -40.0, child: AnimatedFab(key:_imageKey).pl(8.0)),
class AnimatedFab extends StatefulWidget {
final VoidCallback onPressed;
final Key _key;
const AnimatedFab({Key key, this.onPressed}) : _key = key;
#override
_AnimatedFabState createState() => _AnimatedFabState();
}
class _AnimatedFabState extends State<AnimatedFab> with SingleTickerProviderStateMixin {
AnimationController _animationController;
Animation<Color> _colorAnimation;
final double expandedSize = 160.0;
final double hiddenSize = 50.0;
#override
void initState() {
super.initState();
_animationController = AnimationController(vsync: this, duration: const Duration(milliseconds: 200));
_colorAnimation = ColorTween(begin: Colors.transparent, end: Colors.pink[800]).animate(_animationController);
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SizedBox(
width: expandedSize,
height: expandedSize,
child: AnimatedBuilder(
animation: _animationController,
builder: (BuildContext context, Widget child) {
return Stack(
alignment: Alignment.center,
children: <Widget>[
_buildFabCore(widget.key),
],
);
},
),
);
}
Widget _buildOption(IconData icon, double angle) {
if (_animationController.isDismissed) {
return Container();
}
double iconSize = 0.0;
if (_animationController.value > 0.8) {
iconSize = 26.0 * (_animationController.value - 0.8) * 5;
}
return Transform.rotate(
angle: angle,
child: Align(
alignment: Alignment.topCenter,
child: Padding(
padding: const EdgeInsets.only(top: 8.0),
child: IconButton(
onPressed: _onIconClick,
icon: Transform.rotate(
angle: -angle,
child: Icon(
icon,
color: Colors.black54,
),
),
iconSize: iconSize,
alignment: Alignment.center,
padding: const EdgeInsets.all(0.0),
),
),
),
);
}
Widget _buildExpandedBackground() {
final double size = hiddenSize + (expandedSize - hiddenSize) * _animationController.value;
return AnimatedOpacity(
opacity: _animationController.value,
duration: const Duration(milliseconds: 300),
child: Card(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(100.0)),
elevation: 4.0,
child: Container(
height: size,
width: size,
),
),
);
}
Widget _buildFabCore(Key key) {
final double scaleFactor = 2 * (_animationController.value - 0.5).abs();
return FloatingActionButton(
key: key,
elevation: 0.0,
mini: true,
onPressed: _onFabTap,
backgroundColor: _colorAnimation.value,
child: Transform(
alignment: Alignment.center,
transform: Matrix4.identity()..scale(1.0, scaleFactor),
child: Icon(
_animationController.value > 0.5 ? Icons.close : Icons.filter_list,
color: _animationController.value > 0.5 ? Colors.white:Colors.black54,
size: 26.0,
),
),
);
}
void open() {
if (_animationController.isDismissed) {
_animationController.forward();
}
}
void close() {
if (_animationController.isCompleted) {
_animationController.reverse();
}
}
void _onFabTap() {
if (_animationController.isDismissed) {
open();
} else {
close();
}
}
void _onIconClick() {
widget.onPressed();
close();
}
}
how can i solve this issue? i think main problem is in _buildFabCore(),, method which i have this in this class.
thanks in advance
Consider passing a value to heroTag for the FloatingActionButton inside _buildFabCore or simply pass null.
This may happen if you have another FloatingActionButton is used within the app so if you didn't pass different heroTag for each one of them you will get this error.