Hi i'm trying to change the background color of my TextField Widget in flutter when the user focus on it. But it kinda seems there is no way to do it. If anybody has any idea please let me know ^^.
Here's the Widget itself:
TextField(
controller: emailController,
style: TextStyle(
color: Colors.white, fontSize: 18, height: 0.8),
textAlign: TextAlign.center,
decoration: InputDecoration(
focusColor: AppColors.inputBackgroundDarkFocus,
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide(color: Color(color)),
),
filled: true,
fillColor: Color(0xff25282C),
hintText: 'Email',
hintStyle:
TextStyle(fontSize: 18, color: Colors.white),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
),
),
),
Thks <3
Hover & focus are two different things, so this might not answer your question, but the below can change field color "on focus" (the cursor is in the field).
If you're implementing purely for Flutter web and you want to handle "hover" you could do the below with a MouseRegion wrapper instead of Focus. Info on MouseRegion.
By wrapping a form field in a Focus widget, you can listen/capture focus changes on that field using the onFocusChange constructor argument (of Focus).
It takes a Function(bool).
Here's an example of an onFocusChange handler function:
onFocusChange: (hasFocus) {
if (hasFocus) {
print('Name GAINED focus');
setState(() {
bgColor = Colors.purple.withOpacity(.2);
});
}
else {
print('Name LOST focus');
setState(() {
bgColor = Colors.white;
});
}
},
If you wrap your field in a Container, you can give the Container a color and change that color using a Focus widget described above.
Here's a full page example (stolen from another answer of mine on a similar topic):
import 'package:flutter/material.dart';
class TextFieldFocusPage extends StatefulWidget {
#override
_TextFieldFocusPageState createState() => _TextFieldFocusPageState();
}
class _TextFieldFocusPageState extends State<TextFieldFocusPage> {
Color bgColor = Colors.white;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 20),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// ↓ Add this wrapper
Focus(
child: Container(
color: bgColor,
child: TextField(
autofocus: true,
decoration: InputDecoration(
labelText: 'Name'
),
textInputAction: TextInputAction.next,
// ↓ Handle focus change via Software keyboard "next" button
onEditingComplete: () {
print('Name editing complete');
FocusScope.of(context).nextFocus();
},
),
),
canRequestFocus: false,
// ↓ Focus widget handler, change color here
onFocusChange: (hasFocus) {
if (hasFocus) {
print('Name GAINED focus');
setState(() {
bgColor = Colors.purple.withOpacity(.2);
});
}
else {
print('Name LOST focus');
setState(() {
bgColor = Colors.white;
});
}
},
),
TextField(
decoration: InputDecoration(
labelText: 'Password'
),
),
],
),
),
),
);
}
}
I think you can repeat this for each field you need colored.
Hovering can be managed using the hoverColor property of InputDecoration:
Full source code:
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Hover TextField Demo',
home: HomePage(),
),
);
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(16.0),
alignment: Alignment.center,
child: TextField(
decoration: InputDecoration(
filled: true,
hoverColor: Colors.indigo.shade200,
hintText: 'Email',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
),
),
),
),
);
}
}
Almost every style property can be changed by decoration property, which requires an InputDecoration object.
To handle the hover style, this object provides only the hoverColor property, which overwrite the filledColor when the mouse is hovering the widget. The filled property needs to be true for this to work.
InputDecoration(
filled: true,
hoverColor: Colors.red,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0),
),
),
The problem is when you want to change others style properties like the border for example.
In that case you need to make your widget Statefull and add a state boolean like isHover.
Then you could wrap your widget inside MouseRegion and handle the hovering like in this example:
return MouseRegion(
onEnter: (_)=>setState(()=>isHover = true),
onExit: (_)=>setState(()=>isHover = false),
child: YourWidget(),
);
Once isHover is available you can use wherever you want to change the UI according to the hover state.
Example with border:
TextField(
decoration: InputDecoration(
filled: true,
hoverColor: Colors.red,
border: isHover? OutlineInputBorder(
borderSide: BorderSide(
color: Colors.green,
width: 1.5,
),
borderRadius: BorderRadius.circular(8.0),
) : null,
//By leaving null, the component will use the default value
),
),
I think the limitation is due to the fact that the hover behaviour is more web/desktop related. I hope flutter is going to implement better properties to handle this inside InputDecoration.
Related
I'am trying to do a mobile app which is about crypto currencies.
I want to make two TextFields like USDT and BTC, And they are supposed to work like:
Let me say that BTC is equal to 15$,
And USDT is equal to 1$,
Now those text fields should be editable. so if I write 1 on BTC textfield, USDT textfield should me edited as 15.
Also, when I write 30 on USDT textfield, BTC field should become 2. Moreover, while in this position, if I delete 0 from the usdt field, BTC should updated with "0.something" directly.
How can I do that?
Thanks for the replies !
I managed to do something like USDT is input, and BTC is output. However, I want to make them both input and output. Below are my classes, widgets and codes.
import 'package:cryptx/Constants/app_colors.dart';
import 'package:cryptx/Providers/basic_providers.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class USDTInput extends ConsumerWidget {
const USDTInput({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: TextField(
decoration: InputDecoration(
icon: SizedBox(
height: 30,
child: Image.network(
"https://assets.coingecko.com/coins/images/325/small/Tether.png?1668148663")),
hintText: "USDT",
border: InputBorder.none,
),
onChanged: (value) {
ref
.read(usdProvider.notifier)
.update((state) => value != "" ? num.parse(value) : 0);
},
autocorrect: false,
keyboardType: const TextInputType.numberWithOptions(decimal: true),
),
);
}
}
import 'package:cryptx/Objects/coin.dart';
import 'package:cryptx/Providers/basic_providers.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class CoinOutput extends ConsumerWidget {
const CoinOutput({super.key});
#override
Widget build(BuildContext context, WidgetRef ref) {
Coin coin = ref.watch(coinDetailProvider) as Coin;
num usd = ref.watch(usdProvider);
num amount = usd != 0 ? usd / coin.current_price : 0;
//return Text(amount.toString());
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: TextField(
decoration: InputDecoration(
icon: SizedBox(height: 30, child: Image.network(coin.image)),
hintText: "Coin",
border: InputBorder.none,
),
controller:
TextEditingController(text: "$amount ${coin.symbol.toUpperCase()}"),
readOnly: true,
autocorrect: false,
keyboardType: const TextInputType.numberWithOptions(decimal: true),
onChanged: (value) {
ref.watch(coin_usdProvider.notifier).update((state) =>
value != "" ? num.parse(value) / coin.current_price : 0);
},
),
);
}
}
I think the easiest solution would be to create an outside function which can relate/update these two values.
for example:
void updateValues(float BTC, var USDT)
And then use a FocusNode (see also stackoverflow_question) to detect which of the TextFields is/was selected. That was you know which value is the new one and which one to change.
In this solution you should call this function in an onChanged property of the widgets.
FocusNode can be used either to call something when its focus is changed or to check if something has a focus. So using this class you can solve it in a couple different ways.
Try this example out:
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final btcTextController = TextEditingController();
final usdtTextController = TextEditingController();
final btcFocusNode = FocusNode();
final usdtFocusNode = FocusNode();
double btcValue = 15;
double usdTValue = 1;
String curSelection = "";
#override
void initState() {
btcTextController.addListener(calcBTC);
usdtTextController.addListener(calcUSDT);
super.initState();
}
void calcBTC() {
if (btcFocusNode.hasFocus) {
usdtTextController.clear();
if (btcTextController.text.isNotEmpty &&
double.tryParse(btcTextController.text) != null) {
setState(() {
usdtTextController.text =
(double.parse(btcTextController.text) * (btcValue / usdTValue))
.toString();
});
}
}
}
void calcUSDT() {
if (usdtFocusNode.hasFocus) {
btcTextController.clear();
if (usdtTextController.text.isNotEmpty &&
double.tryParse(usdtTextController.text) != null) {
setState(() {
btcTextController.text =
(double.parse(usdtTextController.text) * (usdTValue / btcValue))
.toString();
});
}
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: Card(
color: Colors.white,
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
elevation: 4,
child: Container(
margin: const EdgeInsets.all(10),
width: 300,
height: 300,
child: Column(
children: [
Expanded(
child: TextField(
style: const TextStyle(color: Colors.black),
controller: btcTextController,
focusNode: btcFocusNode,
decoration: InputDecoration(
filled: true,
fillColor: Colors.blue.shade100,
labelText: 'BTC',
labelStyle: const TextStyle(color: Colors.pink),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none,
)),
),
),
Expanded(
child: TextField(
style: const TextStyle(color: Colors.black),
controller: usdtTextController,
focusNode: usdtFocusNode,
decoration: InputDecoration(
filled: true,
fillColor: Colors.blue.shade100,
labelText: 'USDT',
labelStyle: const TextStyle(color: Colors.pink),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none,
)),
)),
],
),
),
),
),
),
);
}
}
You will deffo need to add more validation etc. Also you can possibly use one single function to do calculations and use the focusNodes to decide which side needs to be calculated against which.
See for the invoice page I have BlocBuilder wrapped in a scaffold of stateful page, inside that body under several widgets is a call to future void in separate file call to create a dialog widget. And inside the dialog method is a call to create an invoice form which is in a separate file and is stateful class displayed to be displayed on the dialog screen. In this form the user will be able to add and delete UI elements from a list view what I need to do is rebuild the widget either dialog screen/form or the list view/ to reflect the changes made by the user
import 'package:flutter/material.dart';
import 'dart:developer' as dev;
import 'package:track/src/features/invoices/application/bloc.dart';
import 'package:track/src/features/invoices/application/events.dart';
import 'package:track/src/features/invoices/application/pdf_invoice_api.dart';
class InvoiceForm extends StatefulWidget {
final InvoiceBlocController blocController;
const InvoiceForm(this.blocController, {Key? key}) : super(key: key);
#override
State<InvoiceForm> createState() => _InvoiceFormState();
}
class _InvoiceFormState extends State<InvoiceForm> {
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
controller: TextEditingController()
..text = widget.blocController.invoice.client,
validator: (value) {
value!.isEmpty ? 'Enter a value for client' : null;
},
style: Theme.of(context).textTheme.labelMedium,
decoration: InputDecoration(
focusedBorder: const UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
),
),
enabledBorder: const UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
),
),
labelText: 'Client:',
labelStyle: Theme.of(context).textTheme.labelMedium),
),
TextFormField(
controller: TextEditingController()
..text =
'${widget.blocController.invoice.projectNumber}-${widget.blocController.invoice.invoiceNumber}',
validator: (value) {
value!.isEmpty ? 'Enter a valid project number' : null;
},
style: Theme.of(context).textTheme.labelMedium,
decoration: InputDecoration(
focusedBorder: const UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
),
),
enabledBorder: const UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
),
),
labelText: 'Client:',
labelStyle: Theme.of(context).textTheme.labelMedium),
),
ListView.builder(
shrinkWrap: true,
itemCount: widget.blocController.invoice.items.length,
itemBuilder: (context, index) {
final item = widget.blocController.invoice.items[index];
return ListTile(
contentPadding: EdgeInsets.zero,
trailing: IconButton(
onPressed: () {
widget.blocController.add(DeleteItemFromInvoice(index));
},
icon: Icon(Icons.delete)),
title: Column(
children: [
Row(
children: [
itemTextFormField(
initialValue: item.name ?? '',
labelText: 'name',
index: index),
SizedBox(width: 20),
itemTextFormField(
initialValue: item.description ?? '',
labelText: 'description',
index: index),
],
),
Row(
children: [
itemTextFormField(
initialValue: item.quantity.toString(),
labelText: 'quantity',
index: index),
SizedBox(width: 20),
itemTextFormField(
initialValue: item.costBeforeVAT.toString(),
labelText: 'Cost Before VAT',
index: index),
],
),
SizedBox(height: 30),
Divider(
thickness: 2,
color: Colors.black,
)
],
),
);
},
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
onPressed: () {
dev.log('button clicked to add new item');
widget.blocController.add(AddNewItemToInvoice());
},
icon: Icon(Icons.add)),
IconButton(
onPressed: () async {
_formKey.currentState!.save();
Navigator.pop(context);
await PdfInvoiceApi.generate(widget.blocController.invoice);
},
icon: Icon(Icons.send))
],
)
],
),
);
}
Expanded itemTextFormField({
required String initialValue,
required String labelText,
required int index,
}) {
return Expanded(
child: TextFormField(
controller: TextEditingController()..text = initialValue,
onSaved: (newValue) {
widget.blocController.add(UpdateInvoiceDetails(index));
},
style: Theme.of(context).textTheme.labelMedium,
decoration: InputDecoration(
focusedBorder: const UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
),
),
enabledBorder: const UnderlineInputBorder(
borderSide: BorderSide(
color: Colors.white,
),
),
labelText: labelText,
labelStyle: Theme.of(context).textTheme.labelMedium,
),
),
);
}
}
InvoiceDialog Source code: https://pastebin.com/PCjmCWsk
InvoiceDialog Source code: https://pastebin.com/VS5CG22D
Edit 2: Made the follwoing changes to bloc per Mostafa answer as best I could, getting pressed against a deadline here so really need some help:
These changes were to main page calling the show dialog passing bloc.
showDialog(
context: context,
builder: (context) => BlocProvider.value(
value: blocController,
child: InvoiceDetailsDialog(
screenWidth: screenWidth,
screenHeight: screenHeight),
),
);
This file was the original place where showdialog was called and was custom Future return showDialog.
Results: showDialog takes enitre screen. Rendering Invoice form reulsts in error being displayed in place of the form:
No Material widget found.
Edit 3: fixed previous error but back where i started bloc is still being called succesfully but no changes to the ui:
Widget build(BuildContext context) {
final blocController = BlocProvider.of<InvoiceBlocController>(context);
return Center(
child: Material(color: Colors.red,
borderRadius: BorderRadius.circular(50),
child: SizedBox(
width: screenWidth / 2, height: screenHeight / 2,
child: Padding(padding: const EdgeInsets.all(20),
child: Column(children: [
Expanded(child: ListView(children: [
Text('Invoices',
style: Theme.of(context)
.textTheme.bodyLarge?.copyWith(color: Colors.white)),
InvoiceForm()
]))])))));
}
As form nothing changed except instead of passing the blocController through a method I am now calling it like:
class _InvoiceFormState extends State<InvoiceForm> {
final _formKey = GlobalKey<FormState>();
late final InvoiceBlocController blocController;
#override
void initState() {
blocController = BlocProvider.of<InvoiceBlocController>(context);
super.initState();
}
Still nothing changes.
Edit 4: Set state does work and leaving in bloc code was executing and if I clicked add two items would be added or delete would remove two items. But with setstate commented out it went back to not rebuilding. Using setstate for now but not preferred.
Edit 5: Don't if this is still being paid attention to hopefully is. Can I keep add add events like: add(NewItem), add(deleteItem),add(GeneratePDF). Without changing state. currently I have done that once so far. Is this bad practice
You can pass the main bloc to the dialog widget and call the bloc function that you want and it will reflect on the main screen
How can you do this? by injecting the MainBloc value to DialogWidget with BlocProvider.value
MainWidget
class MainWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => MainBloc(),
child: BlocConsumer<MainBloc, MainStates>(
listener: (BuildContext context, MainStates state) {},
builder: (BuildContext context, MainStates state) {
final bloc = MainBloc.get(context);
return GestureDetector(
onTap: () {
showDialog(
context,
builder: (context) => BlocProvider.value(
value: bloc,
child: WidgetTwoDialog(),
),
);
},
child: Item(),
);
},
),
);
}
}
DialogWidget
class DialogWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
final bloc = MainBloc.get(context);
return GestureDetector(
onTap: () {
bloc.addToList();
},
child: Text('Remove form the main screen'),
);
}
}
Also, this answer might help you to get my point well here
you can wrap your dialog within the stateful builder and then you will get the method to set your dialog state.
I want to recreate automatically my page and clear all my TextFields on button press.Is there a method to do it?
Edited :
Here is an example of how to achieve this :
class _MyWidgetState extends State<MyWidget> {
final myAController = TextEditingController();
final myBController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: <Widget>[
FlatButton(
onPressed: () {
setState(() {
myAController.clear();
myBController.clear();
});
},
child: Text(
"Clear text fields",
),
),
TextField(
controller: myAController,
decoration: InputDecoration(
border: InputBorder.none, hintText: 'Enter value A'),
),
TextField(
controller: myBController,
decoration: InputDecoration(
border: InputBorder.none, hintText: 'Enter Value B'),
),
],
),
),
);
}
}
As you can see every time i press my flatbutton the widget reloads with the updated counter value.
Here is an updated working demo.
I think it's
setState((){
// If there is anything you want to change put it here
});
This will rebuild the widget.
I am trying to create custom textformfield so I can easily style only in one place. But currently I am stuck on how to pass validation and save process. Can someone give me a working example of custom widget textformfield that I can use? I have been searching it for whole day and cannot find one. Thank you for help.
Example here is on raised button:
import 'package:flutter/material.dart';
import 'package:wkndr/resources/constants.dart';
class CustomButton extends StatelessWidget {
CustomButton({#required this.text, #required this.onPressed});
final String text;
final GestureTapCallback onPressed;
#override
Widget build(BuildContext context) {
return RaisedButton(
color: colorPrimary,
child: Text(text, style: TextStyle(fontSize: 17.0, color: colorWhite)),
onPressed: onPressed,
shape: new RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(30.0)),
);
}
}
Calling custom raised button:
final _signUpButton = Container(
child: CustomButton(
text: sign_up,
onPressed: () {
_signUp();
}),
);
Instead of making custom textformfield you can make common InputDecoration for styling
class CommonStyle{
static InputDecoration textFieldStyle({String labelTextStr="",String hintTextStr=""}) {return InputDecoration(
contentPadding: EdgeInsets.all(12),
labelText: labelTextStr,
hintText:hintTextStr,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
);}
}
Example:-
TextFormField(
controller: usernameController,
keyboardType: TextInputType.text,
textInputAction: TextInputAction.next,
focusNode: userFocus,
onFieldSubmitted: (_) {
FocusScope.of(context).requestFocus(passFocus);
},
validator: (value) => emptyValidation(value),
decoration: CommonStyle.textFieldStyle(labelTextStr:"Username",hintTextStr:"Enter Username"),
)
You can define InputDecorationTheme in your app theme to set global style for text fields.
MaterialApp(
title: title,
theme: ThemeData(
brightness: Brightness.dark,
...
inputDecorationTheme: InputDecorationTheme(
fillColor: Colors.blue,
filled: true,
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.white)),
border: OutlineInputBorder(
borderSide: BorderSide(color: Colors.blue)),
hintStyle: TextStyle(color: Colors.white.withAlpha(80)),
),
)
);
You can also change theme properties for a particular widget using Theme widget:
Theme(
data: Theme.of(context).copyWith(inputDecorationTheme: /*new decoration theme here*/),
child: Scaffold(
body: ...,
),
);
See more information about themes in Flutter docs.
You can try custom TextFormField.
You can make easily common TextFormField for customizing TextFormField.
You can try like this.
Step 1: First create one dart class i.e EditTextUtils
Step 2: Create a function or method i.e getCustomEditTextArea
class EditTextUtils {
TextFormField getCustomEditTextArea(
{String labelValue = "",
String hintValue = "",
bool validation,
TextEditingController controller,
TextInputType keyboardType = TextInputType.text,
TextStyle textStyle,
String validationErrorMsg}) {
TextFormField textFormField = TextFormField(
keyboardType: keyboardType,
style: textStyle,
controller: controller,
validator: (String value) {
if (validation) {
if (value.isEmpty) {
return validationErrorMsg;
}
}
},
decoration: InputDecoration(
labelText: labelValue,
hintText: hintValue,
labelStyle: textStyle,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(5.0))),
);
return textFormField;
}
}
Example: You can try like this
EditTextUtils().getCustomEditTextArea(
labelValue: 'label',
hintValue: 'hint',
validation: true,
controller: controller_name,
keyboardType: TextInputType.number,
textStyle: textStyle,
validationErrorMsg: 'error_msg')
I have a widget that adding some decoration to out of the widget as the code below.I also wanted to change decoration of the widgets like removing underline in TextFormField widget.
class FormRowWidget extends StatelessWidget {
final Widget inputWidget;
FormRowWidget({this.title, this.inputWidget});
#override
Widget build(BuildContext context) {
var isTextField = inputWidget is TextFormField;
return Container(
padding: EdgeInsets.symmetric(horizontal: 8.0,vertical: 8.0),
decoration: BoxDecoration(
shape: BoxShape.rectangle,
color: Colors.white,
boxShadow: [
new BoxShadow(
color: Colors.black,
blurRadius: 10,
)
],
),
child: inputWidget// I want to change input widgets' properties.
])
}
}
I am using this widget like the code below.
children: <Widget>[
FormRowWidget(
inputWidget: TextFormField(
decoration: InputDecoration(border: InputBorder.none),
controller: usernameController,
validator: (value) {
if (value.length < 4) return "Username err!";
},
onSaved: (value) {
print(value);
},
),
),
FormRowWidget(
inputWidget: TextFormField(
decoration: InputDecoration(border: InputBorder.none),
controller: receiverFirmController,
),
)]
The thing I wonder is what is the best way of overriding inputWidget in my FormRowWidget.
After all, I want to access and change the settings of the widget like the below.Is it possible to handle it?
var customTextWidget = inputWidget as TextFormField;
customTextWidget.decoration