Flutter - Custom widget animation state reversed in Gridview - flutter

I have a gridview with a couple of custom cards that when selected will change color to indicate to the user that they have selected the card. When I scroll down and return to the previously selected card, the color changes like it was never selected.
At first, I thought it was a key issue but that didn't solve anything.
This is the code I have
// Code for the Gridview
GridView.builder(
padding: EdgeInsets.all(Insets.sm),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: Insets.sm,
crossAxisSpacing: Insets.sm,
),
itemCount: categories.length,
itemBuilder: (BuildContext context, int index) {
return CategoryCard(
key: UniqueKey(),
category: categories[index],
onTap: (isSelected) {
(isSelected)
? game.categories.add(categories[index])
: game.categories.remove(categories[index]);
},
);
},
),
// This is the code for the custom widget
class CategoryCard extends StatefulWidget {
const CategoryCard({
super.key,
required this.category,
required this.onTap,
});
final Category category;
final Function(bool) onTap;
#override
State<CategoryCard> createState() => _CategoryCardState();
}
class _CategoryCardState extends State<CategoryCard>
with TickerProviderStateMixin {
late AnimationController controller;
late Animation<Color?> containerBackground;
late Animation<Color?> imageBackground;
late Animation<Color?> textColor;
late CurvedAnimation curve;
bool isSelected = true;
#override
void initState() {
super.initState();
controller = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: this,
);
curve = CurvedAnimation(
parent: controller,
curve: Curves.easeInOut,
);
containerBackground =
ColorTween(begin: Colors.white, end: AppColors.blue200)
.animate(controller)
..addListener(() => setState(() {}));
imageBackground =
ColorTween(begin: AppColors.blue50, end: AppColors.blue100)
.animate(controller)
..addListener(() => setState(() {}));
textColor = ColorTween(begin: AppColors.blue300, end: Colors.white)
.animate(controller)
..addListener(() => setState(() {}));
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
void animateColor() {
if (isSelected) {
controller.forward();
} else {
controller.reverse();
}
widget.onTap(isSelected);
isSelected = !isSelected;
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => {animateColor()},
child: Container(
decoration: BoxDecoration(
color: containerBackground.value,
borderRadius: Corners.lgBorder,
),
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
Container(
padding: EdgeInsets.all(Insets.xs),
decoration: BoxDecoration(
color: imageBackground.value,
borderRadius: Corners.lgBorder,
),
clipBehavior: Clip.antiAlias,
child: Image.asset(
"assets/images/${widget.category.name.toLowerCase()}.png",
width: 78,
height: 78,
),
),
SizedBox(height: Insets.med),
Text(
widget.category.name,
style: TextStyles.body1.copyWith(color: textColor.value),
)
]),
),
);
}
}
Any help or explanation on why this is happening is appreciated.

Related

Persistent Navigation Bar only in some pages

Stackoverflowers!
I'm using an BottomAppBar inside the bottomNavigationBar section of the Scaffold. The problem is that it doesn't persists while I'm navigating. I used the persistent_bottom_nav_bar plugin, but it doesn't work with my custom navigation bar because it has a ripple animation in one button and a bottomSheet that is over the keyboard.
home_page.dart
This file has the CustomNavigationBar and the main pages for each item on it.
class HomePage extends StatefulWidget {
const HomePage({super.key});
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
NavigationProvider? navigationProvider;
AnimationController? rippleController;
AnimationController? scaleController;
Animation<double>? rippleAnimation;
Animation<double>? scaleAnimation;
#override
void initState() {
super.initState();
rippleController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 500));
scaleController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 500))
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
scaleController!.reverse();
Navigator.push(
context,
PageTransition(
type: PageTransitionType.bottomToTop,
child: pages.elementAt(2),
childCurrent: widget,
fullscreenDialog: true,
)).whenComplete(() => setState(() {
buttonColor = Colors.black;
}));
}
});
rippleAnimation =
Tween<double>(begin: 80.0, end: 90.0).animate(rippleController!)
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
rippleController!.reverse();
} else if (status == AnimationStatus.dismissed) {
rippleController!.forward();
}
});
scaleAnimation =
Tween<double>(begin: 1.0, end: 30.0).animate(scaleController!);
rippleController!.forward();
}
#override
void dispose() {
rippleController!.dispose();
scaleController!.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
navigationProvider = Provider.of<NavigationProvider>(context);
return Scaffold(
body:
pages.elementAt(navigationProvider!.bottomNavigationBarSelectedIndex),
bottomNavigationBar: CustomNavigationBar(
rippleController: rippleController,
scaleController: scaleController,
rippleAnimation: rippleAnimation,
scaleAnimation: scaleAnimation),
);
}
}
custom_navigation_bar.dart
This file contains the properties of the CustomNavigationBar.
class CustomNavigationBar extends StatefulWidget {
const CustomNavigationBar({
super.key,
this.rippleController,
this.scaleController,
this.rippleAnimation,
this.scaleAnimation,
});
final AnimationController? rippleController;
final AnimationController? scaleController;
final Animation<double>? rippleAnimation;
final Animation<double>? scaleAnimation;
#override
State<CustomNavigationBar> createState() => _CustomNavigationBarState();
}
class _CustomNavigationBarState extends State<CustomNavigationBar> {
#override
Widget build(BuildContext context) {
final navigationProvider = Provider.of<NavigationProvider>(context);
return BottomAppBar(
child: IconTheme(
data: const IconThemeData(color: Colors.black, size: 36),
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 5, 10, 5),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
direction: Axis.vertical,
children: [
IconButton(
icon: ...,
padding: ...,
constraints: ...,
onPressed: () {
//Here I change the selected index with Provider.
...
},
),
Text(
title,
style: ...,
),
],
),
const Spacer(),
Wrap(...),
const Spacer(),
InkWell(
onTap: () {
setState(
() {
//Executes the ripple animation.
widget.scaleController!.forward();
},
);
},
child: AnimatedBuilder(
animation: widget.scaleAnimation!,
builder: (context, child) => Transform.scale(
scale: widget.scaleAnimation!.value,
child: Container(
width: 50,
height: 50,
margin: const EdgeInsets.all(10),
decoration: const BoxDecoration(
shape: BoxShape.circle, color: Colors.blue),
child: Icon(Icons.add,
color: widget.scaleAnimation!.value == 1.0
? Colors.white
: Colors.blue),
),
),
),
),
const Spacer(),
Wrap(...),
const Spacer(),
Wrap(...),
],
),
),
),
);
}
}
As you can see, I use Provider to manage the state of the CustomNavigationBar when it changes the index.
Example of what I want:
This app is Splitwise and it has some pages with the navigation bar and others without it. That ripple animation is similar to mine. Also the bottom sheet has the same effect in my app.
I'll wait for all your suggestions, thanks!

How to create flutter Custom Curved Navigation Drawer

I am creating custom navigation drawer. I want add curved animation onItemSelected.
But I have no idea how to create curved animation effect. I have tried many plugins but could not find plugin which related with this design. Here is my example code
import 'package:flutter/material.dart';
import 'package:flutter_vector_icons/flutter_vector_icons.dart';
void main() {
runApp(
MaterialApp(
home: MyApp(),
),
);
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
List<bool> selected = [true, false, false, false, false];
class _MyAppState extends State<MyApp> {
List<IconData> icon = [
Feather.wind,
Feather.folder,
Feather.monitor,
Feather.lock,
Feather.mail,
];
void select(int n) {
for (int i = 0; i < 5; i++) {
if (i == n) {
selected[i] = true;
} else {
selected[i] = false;
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Container(
color: Colors.white,
),
Container(
margin: EdgeInsets.all(8.0),
height: MediaQuery.of(context).size.height,
width: 101.0,
decoration: BoxDecoration(
color: Color(0xff332A7C),
borderRadius: BorderRadius.circular(20.0),
),
child: Stack(
children: [
Positioned(
top: 110,
child: Column(
children: icon
.map(
(e) => NavBarItem(
icon: e,
selected: selected[icon.indexOf(e)],
onTap: () {
setState(() {
select(icon.indexOf(e));
});
},
),
)
.toList(),
),
),
],
),
),
],
),
);
}
}
class NavBarItem extends StatefulWidget {
final IconData icon;
final Function onTap;
final bool selected;
NavBarItem({
this.icon,
this.onTap,
this.selected,
});
#override
_NavBarItemState createState() => _NavBarItemState();
}
class _NavBarItemState extends State<NavBarItem> with TickerProviderStateMixin {
AnimationController _controller1;
AnimationController _controller2;
Animation<double> _anim1;
Animation<double> _anim2;
Animation<double> _anim3;
Animation<Color> _color;
bool hovered = false;
#override
void initState() {
super.initState();
_controller1 = AnimationController(
vsync: this,
duration: Duration(milliseconds: 250),
);
_controller2 = AnimationController(
vsync: this,
duration: Duration(milliseconds: 275),
);
_anim1 = Tween(begin: 101.0, end: 75.0).animate(_controller1);
_anim2 = Tween(begin: 101.0, end: 25.0).animate(_controller2);
_anim3 = Tween(begin: 101.0, end: 50.0).animate(_controller2);
_color = ColorTween(end: Color(0xff332a7c), begin: Colors.white)
.animate(_controller2);
_controller1.addListener(() {
setState(() {});
});
_controller2.addListener(() {
setState(() {});
});
}
#override
void didUpdateWidget(NavBarItem oldWidget) {
super.didUpdateWidget(oldWidget);
if (!widget.selected) {
Future.delayed(Duration(milliseconds: 10), () {
//_controller1.reverse();
});
_controller1.reverse();
_controller2.reverse();
} else {
_controller1.forward();
_controller2.forward();
Future.delayed(Duration(milliseconds: 10), () {
//_controller2.forward();
});
}
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
widget.onTap();
},
child: MouseRegion(
onEnter: (value) {
setState(() {
hovered = true;
});
},
onExit: (value) {
setState(() {
hovered = false;
});
},
child: Container(
width: 101.0,
color:
hovered && !widget.selected ? Colors.white12 : Colors.transparent,
child: Stack(
children: [
Container(
height: 80.0,
width: 101.0,
child: Center(
child: Icon(
widget.icon,
color: _color.value,
size: 18.0,
),
),
),
],
),
),
),
);
}
}
I want create Navigation Drawer like this image given in above example. How can I achieve this please help me for it Thank you in advance.

Adding textfield as a child in MarqueeWidget results in blank screen

Marquee Widget class
I want to create a textfield where you can enter about paragraph.
So, that's why this class was what is available.
Please check the error.
class MarqueeWidget extends StatefulWidget {
final Widget child;
final Axis direction;
final Duration animationDuration, backDuration, pauseDuration;
MarqueeWidget({
#required this.child,
this.direction: Axis.horizontal,
this.animationDuration: const Duration(milliseconds: 3000),
this.backDuration: const Duration(milliseconds: 800),
this.pauseDuration: const Duration(milliseconds: 800),
});
#override
_MarqueeWidgetState createState() => _MarqueeWidgetState();
}
class _MarqueeWidgetState extends State<MarqueeWidget> {
ScrollController scrollController;
#override
void initState() {
scrollController = ScrollController(initialScrollOffset: 50.0);
WidgetsBinding.instance.addPostFrameCallback(scroll);
super.initState();
}
#override
void dispose() {
scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: widget.child,
scrollDirection: widget.direction,
controller: scrollController,
);
}
void scroll(_) async {
while (scrollController.hasClients) {
await Future.delayed(widget.pauseDuration);
if (scrollController.hasClients)
await scrollController.animateTo(
scrollController.position.maxScrollExtent,
duration: widget.animationDuration,
curve: Curves.ease);
await Future.delayed(widget.pauseDuration);
if (scrollController.hasClients)
await scrollController.animateTo(0.0,
duration: widget.backDuration, curve: Curves.easeOut);
}
}
}
I want to create a textfield where you can enter about paragraph.
So, that's why this class was what is available.
Please check the error.
Usage of Marquee Widget
After, I run the app.
It displays nothing.
So, what to do now.
Padding(
padding: const EdgeInsets.all(8.0),
child: Expanded(
child: MarqueeWidget(
direction: Axis.horizontal,
child: TextField(
keyboardType: TextInputType.multiline,
maxLines: null,
onChanged: (value) {
about = value;
},
decoration: InputDecoration(
hintText: widget.currentUser.about,
),
style: TextStyle(color: Colors.black),
),
),
),
),

Flutter AnimatedList - Adding a CurvedAnimation to a SlideTransition

I've managed to get to a point where I have an AnimatedList which will slide in newly added items. A Dismissible widget that wraps my list items controls the remove animation. My question is how can I add some sort of animation curve to the SlideTransition so that I can control how the new list item appears when it slides in. Here's where I'm at so far:
#override
Widget build(BuildContext context) {
final checklists = Provider.of<Checklists>(context);
return AnimatedList(
key: listKey,
initialItemCount: checklists.lists.length,
itemBuilder: (ctx, i, animation) {
return SlideTransition(
position: CurvedAnimation(parent: ).drive(child) // <- this line needs changing
child: _buildListItem(checklists.lists[i], i),
);
},
);
}
I'm not sure what to do with the position argument. Previously for the standard SlideTransition I was simply using
animation.drive(Tween(begin: Offset(0.2, 0), end: Offset(0.0, 0))),
which works fine, but is lacking a curve ease. Any ideas?
Edit
Heres the complete .dart file for clarity as well as where I insert a new item from the parent widget:
GlobalKey<AnimatedListState> recentListsAnimationKey = GlobalKey();
class RecentLists extends StatefulWidget {
#override
_RecentListsState createState() => _RecentListsState();
}
class _RecentListsState extends State<RecentLists>
with TickerProviderStateMixin {
Widget _buildListItem(Checklist list, int listIndex) {
return Dismissible(
key: ObjectKey(list.id),
direction: DismissDirection.endToStart,
background: Container(
alignment: AlignmentDirectional.centerEnd,
color: Theme.of(context).accentColor,
child: Padding(
padding: EdgeInsets.fromLTRB(0.0, 0.0, 10.0, 0.0),
child: Icon(
Icons.delete,
color: Colors.white,
),
),
),
child: ListTile(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (ctx) => ListItemsScreen(list.id),
),
);
},
title: Text(list.name),
leading: Checkbox(
value: list.completed,
onChanged: (value) {
setState(() {
list.completed = value;
});
},
),
),
onDismissed: (direction) {
_onDeleteList(list, listIndex);
},
);
}
void _onDeleteList(Checklist list, int listIndex) {
Provider.of<Checklists>(context).deleteList(list.id);
recentListsAnimationKey.currentState
.removeItem(listIndex, (_, __) => Container());
Scaffold.of(context).showSnackBar(
SnackBar(
action: SnackBarAction(
label: 'UNDO',
onPressed: () {
Provider.of<Checklists>(context).undoDeleteList(list, listIndex);
recentListsAnimationKey.currentState
.insertItem(listIndex, duration: Duration(milliseconds: 100));
},
),
content: Text(
'List deleted',
style: TextStyle(color: Theme.of(context).accentColor),
),
),
);
}
AnimationController _controller;
Animation<Offset> _position;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: 200),
vsync: this,
);
_position = Tween(
begin: Offset(0.5, 0),
end: Offset(0.0, 0),
).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.decelerate,
),
);
_controller.forward();
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
#override
Widget build(BuildContext context) {
final checklists = Provider.of<Checklists>(context);
return AnimatedList(
key: recentListsAnimationKey,
initialItemCount: checklists.lists.length,
itemBuilder: (ctx, i, animation) {
return SlideTransition(
position: _position,
child: _buildListItem(checklists.lists[i], i),
);
},
);
}
}
Here is where I insert new items into the list. No slide animation is shown.
void createNewList(BuildContext context) {
if (nameController.text.isNotEmpty) {
Provider.of<Checklists>(context).addList(nameController.text);
recentListsAnimationKey.currentState.insertItem(0, duration: Duration(milliseconds: 100));
nameController.clear();
}
Navigator.of(context).pop();
}
Use the Animation.drive function with a CurveTween chained to your offset Tween. Your itemBuilder should look like this:
itemBuilder: (ctx, i, animation) {
return SlideTransition(
position: animation.drive(Tween(begin: Offset(2, 0.0), end: Offset(0.0, 0.0))
.chain(CurveTween(curve: Curves.elasticInOut))),
child: _buildListItem(checklists.lists[i], i),
);
},
In Flutter, Animation just a class which change data from start to end based on percent provided from AnimationController. You have to prepare position which instance of Animation<Offset>.
class DemoWidget extends StatefulWidget {
#override
_DemoWidgetState createState() => _DemoWidgetState();
}
class _DemoWidgetState extends State<DemoWidget>with TickerProviderStateMixin {
AnimationController _controller;
Animation<Offset> _position;
#override
Widget build(BuildContext context) {
final checklists = Provider.of<Checklists>(context);
return AnimatedList(
key: listKey,
initialItemCount: checklists.lists.length,
itemBuilder: (ctx, i, animation) {
return SlideTransition(
position: _position,
child: _buildListItem(checklists.lists[i], i),
);
},
);
}
#override
initState() {
super.initState();
_controller = AnimationController(duration: const Duration(milliseconds: 2000), vsync: this);
_position = Tween(begin: Offset(0.2, 0), end: Offset(0.0, 0)).animate(CurvedAnimation(parent: _controller, curve: Curves.decelerate));
_controller.forward();
}
#override
dispose() {
_controller.dispose();
super.dispose();
}
}

Smoothly animating Flutter TextField to Text

I'd like to be able to transition a TextField to (and from) just the Text, which means the input decorations fading out, and the text itself moving slightly to the left (as the input padding goes to 0).
I had some limited success with the following code, using the intrinsic animations of TextField. However, the text jumps left instead of moving there gradually, and the input decorations jump in/out as well as fading.
class ExampleScreen extends StatefulWidget {
#override
ExampleScreenState createState() => ExampleScreenState();
}
class ExampleScreenState extends State<ExampleScreen> {
TextEditingController text = TextEditingController();
bool editing = true;
#override
Widget build(BuildContext context) {
return Scaffold(
body: EditableText(controller: text, editing: editing),
floatingActionButton: FloatingActionButton(
onPressed: () => setState(() => editing = !editing),
child: Icon(Icons.ac_unit),
),
);
}
}
class EditableText extends StatelessWidget {
final TextEditingController controller;
final bool editing;
EditableText({this.controller, this.editing});
#override Widget build(BuildContext context) {
return TextField(
controller: controller,
enabled: editing,
decoration: InputDecoration().copyWith(
contentPadding: editing ? Theme.of(context).inputDecorationTheme.contentPadding : EdgeInsets.zero,
disabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.transparent))
),
);
}
}
Any clever ideas?
I wrote a working example, but there are still some flicker, you can try some [curve] to modify the animation.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>
with SingleTickerProviderStateMixin {
AnimationController controller;
#override
void initState() {
super.initState();
controller = AnimationController(
duration: Duration(seconds: 1),
vsync: this,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Page'),
),
body: Center(
child: AnimatedTextField(
controller: controller,
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.opacity),
onPressed: () {
var status = controller.status;
if (status == AnimationStatus.completed) {
controller.reverse();
} else if (status == AnimationStatus.dismissed) {
controller.forward();
}
},
),
);
}
}
class AnimatedTextField extends StatefulWidget {
final AnimationController controller;
final Animation<double> opacity;
final Animation<double> left;
AnimatedTextField({
Key key,
#required this.controller,
}) : opacity = Tween<double>(
begin: 1.0,
end: 0.0,
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(
0.0,
0.5,
curve: Curves.easeInOut,
),
),
),
left = Tween<double>(
begin: 20.0,
end: 0.0,
).animate(
CurvedAnimation(
parent: controller,
curve: Interval(
0.0,
1.0,
curve: Curves.easeIn,
),
),
),
super(key: key);
#override
_AnimatedTextFieldState createState() => _AnimatedTextFieldState();
}
class _AnimatedTextFieldState extends State<AnimatedTextField> {
TextEditingController _textEditingController =
TextEditingController(text: 'hello fluttr');
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: widget.controller,
builder: (context, child) {
var theme = Theme.of(context);
var o = widget.opacity.value;
var _child = o > 0.0
? TextField(
controller: _textEditingController,
style: theme.textTheme.body2,
decoration: InputDecoration(
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Color.fromRGBO(0, 0, 0, 1)),
),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(color: Color.fromRGBO(0, 0, 0, o)),
),
),
)
: Opacity(
opacity: (o - 1).abs(),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
_textEditingController.text,
style: theme.textTheme.body2,
),
),
);
return Padding(
padding: EdgeInsets.only(
left: widget.left.value,
top: 20,
right: 20,
bottom: 20,
),
child: _child,
);
},
);
}
}
Managed to work it out. Created an 'AnimatedDouble' widget which just makes a builder with the value of an animation as a parameter, triggered by a ChangeNotifier:
class AnimatedDouble extends StatefulWidget {
final Widget child;
final Function(BuildContext context, Widget child, double d) builder;
final Duration duration;
final ChangeNotifier listen;
AnimatedDouble({this.child, this.builder, this.duration, this.listen});
#override
_AnimatedDoubleState createState() => new _AnimatedDoubleState();
}
class _AnimatedDoubleState extends State<AnimatedDouble> with SingleTickerProviderStateMixin{
AnimationController controller;
#override
void initState() {
super.initState();
controller = AnimationController(value: 1.0, vsync: this, duration: widget.duration);
widget.listen.addListener(_go);
}
#override
void dispose() {
controller.dispose();
widget.listen.removeListener(_go);
super.dispose();
}
void _go() {
controller.forward(from: 0.0);
}
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: controller,
child: widget.child,
builder: (context, child) => widget.builder(context, child, controller.view.value),
);
}
}
Then was able to use it like so to get the effect I wanted:
AnimatedDouble(
duration: Duration(milliseconds: 200),
listen: something_to_trigger_animation,
builder: (context, child, d) => TextField(
controller: controller,
decoration: InputDecoration(
contentPadding: editing ?
EdgeInsets.symmetric(vertical: 16.0 * d, horizontal: 12.0 * d) :
EdgeInsets.symmetric(vertical: 16.0 * (1 - d), horizontal: 12.0 * (1 - d)),
disabledBorder: OutlineInputBorder(borderSide: BorderSide(color: Colors.transparent)),
),
enabled: editing,
),
),