I am working on a calculator type app that has a lot of buttons on the screen. When I turn on dev tools to check repainting, I noticed that whenever one button is clicked, every button on the screen repaints. Instead of pasting tons of code in here, I created a demo app that shows what it is that I am doing. This is what the screen looks like:
Whenever any of the buttons are pressed, every button on the screen redraws.
This is the code that I'm using:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Button Testing'),
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(
buttonPressed,
textAlign: TextAlign.center,
),
SizedBox(
height: 10.0,
),
RaisedButton(
child: Text('Button One'),
onPressed: () {
setState(() {
buttonPressed = 'Button One Was Pressed';
});
},
),
RaisedButton(
child: Text('Button Two'),
onPressed: () {
setState(() {
buttonPressed = 'Button Two Was Pressed';
});
},
),
RaisedButton(
child: Text('Button Three'),
onPressed: () {
setState(() {
buttonPressed = 'Button Three Was Pressed';
});
},
),
RaisedButton(
child: Text('Button Four'),
onPressed: () {
setState(() {
buttonPressed = 'Button Four Was Pressed';
});
},
),
],
),
),
),
);
}
I've tried breaking the button area out into a separate widget and nothing I've done matters. My question is, is there something else that I should be doing? Is this expected and acceptable behavior? Does it even matter that everything is redrawing whenever any buttons are tapped? I thought that in Flutter, only the things that needed to be redrawn were redrawn.
Whenever you call setState then it rebuild whole build method, so it is obvious that it rebuild RaisedButton.
You can avoid by using provider, Bloc and so on which will not rebuild whole build method. It will only change data.
It is totally depends on you that you want to accept it or not. If widget is not that much big than you can keep it according to me.
Redrawing widget every time is really not good practice but if it is not rebuilding very frequently or your application is not that much big then it is fine.
setState will rebuild all the screen ,
So you can avoid that by using Bloc it is a state controller which won't rebuild everything only what you need to rebuild you can use it from scratch or use the plugin bloc 1.0.0
Related
I have a page containing many widgets which is opened in view only mode. i used AbsorbPointer for ignoring user inputs. but in some cases i have to get onTap event on one button which is inside AbsorbPointer. how can i achieve that ?
I tried using GestureDetector but its not giving onTap event.
any idea??
AbsorbPointer(
absorbing:true,
child: Column(children: [
Text("HELLO"),
Text("HELLO 2"),
GestureDetector(
onTap: () {
showToast("I tried this");
},
child: IconButton(
icon: Icon(
Icons.local_offer,
color: Colors.red,
),
onPressed: () {
print("I need this event");
},
),
)
]),
);
use absorbing: property for your cases... set it to false in the case you want to allow the GestureDetector inside
I a working with ModalBottomSheet and I love the package. However I am encountering an issue when trying to navigate with in a ModalBottomSheet.
Here is a ScreenVideo for a better understanding.
As you can see the View is presented as a ModalBottomSheet. But when popping it, it is not simply dismissing to the bottom but instead it is popping to some empty screen with a MaterialPage Pop animation.
I changed my code so I can push with a MaterialPageRoute-Animation inside that ModalBottomSheet. Before that everything was working as expected.
Here is my Page:
class _AboutScreenState extends State<AboutScreen> {
#override
Widget build(BuildContext context) {
return Material(
color: AppColors.primary,
child: Navigator(
onGenerateRoute: (settings) => MaterialPageRoute(
builder: (context2) => Builder(
builder: (context) => CupertinoPageScaffold(
backgroundColor: AppColors.secondary,
resizeToAvoidBottomInset: false,
child: SafeArea(
child: Padding(
padding: EdgeInsets.all(sidePadding),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButtonWithExpandedTouchTarget(
onTapped: () {
Navigator.pop(context);
},
svgPath: 'assets/icons/down.svg',
),
],
),
Text(
'About',
style: AppTextStyles.montserratH2SemiBold,
),
...
RoundedCornersTextButton(
title: 'Impressum',
onTap: () {
Navigator.pop(context);
},
textColor: AppColors.primary,
backgroundColor: AppColors.secondary,
borderColor: AppColors.primary,
),
],
),
),
)),
),
),
),
);
}
}
I was simply following this example. What am I missing here? With the code above I can push inside the ModalSheet as expected with a MaterialPageRoute Animation but popping the first screen brings up the issue...
Let me know if you need any more info! Any help is appreciated :)
I think you are popping with the wrong context. The example is popping with rootContext, which is from the top-most widget in the hierarchy. You are popping from with context, defined at your lowest builder in the hierarchy.
I believe you are using the incorrect context. rootContext, which is from the top-most widget in the hierarchy, is what makes the sample pop. You're popping from your lowest builder in the hierarchy, which is dictated by context.
I have a TextField inside of an InkWell with an onLongPress() callback. The problem is, despite the fact that even when long pressing on the TextField, I see the ripple effect on InkWell, but the onLongPress() does not run after the long press time passes. It only gets me into editing Text. When pressing on the bottom side of the Card, everything runs fine.
In short: On tap I want to get into TextField editing. On long press I want to trigger the onLongPress(), not the TextField, even if I am pressing on it.
How do I do this? Thank you.
InkWell(
onLongPress: () {
// do stuff
}
child: ListTile(
title: TextField(),
),
),
You can use the AbsorbPointer widget to ignore the TextField gesture recognizer:
InkWell(
onLongPress: () {
print('onLongPress');
},
child: AbsorbPointer(
child: ListTile(
title: TextField(),
),
),
)
To still enabling the editing of TextField when single tapping on it, you can use FocusNode like this:
InkWell(
onLongPress: () {
print('onLongPress');
},
onTap: () => node.requestFocus(),
child: AbsorbPointer(
child: ListTile(
title: TextField(
focusNode: node,
controller: textController,
),
),
),
)
#Bach 's answer helped me to find a solution. Thank you!
InkWell(
onLongPress: () {
// do stuff
},
child: ListTile(
title: GestureDetector(
onTap: () => FocusScope.of(context).requestFocus(_focusNode),
child: AbsorbPointer(
child: TextField(
focusNode: _focusNode,
),
),
),
),
The only problem is now that I started messing with focusNode, multiple input fiels are focusing at the same time. But that is a whole other story ;)
UPD: Just realised, that I can't move text cursor this way. So not useful.
It seems that IntrinsicWidth widget can find the right balance between long press and text editing.
The rationale behind is that IntrinsicWidth will let the TextField shrink to its minimum width, therefore avoiding a gesture collision with the InkWell
So your solution can be like this:
InkWell(
onLongPress: () {
// do stuff
}
child: ListTile(
child: IntrinsicWidth(
title: TextField(
//remember to make some hints here
//because with intrinsicwidth if your textfield is empty it might disappear
),
),
),
),
So I'm searching for a Menu often found in a Settings Screen. It's used to select a Setting.
It opens when you press on a ListTile and then it fills the mayority of the Screen, while the borders are transparent. In the menu you have to select one of the options.
An example usecase of that would be to select a Language(onTap of the ListTile opens a Menu where you can select between all the avaliable languages)
It is similar to that of a PopupMenuButton, however as already mentioned it fills most of the screen, and the individual selectable items have a radiobutton in front of the text(probably RadioListTiles)
I hope someone gets what I'm talking about, because for the past 3 hours I'm searching for this widget but just find articles about the PopupMenuButton over and over.
Edit: I finally found an app that uses the feature I'm looking for
: https://imgur.com/a/A9k71io
By clicking on the first ListTile in the first Picture, the dialog in the second picture opens up.
Hopefully this custom widget is something roughly what you want, try to copy and paste it in your project and play with it.
this is made using the Dialog widget, to exit the Dialog you can tap out of it/ press the device back arrow and I added a small back icon on the top left corner.
tell me if it helped :)
class TestPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final deviceSize = MediaQuery.of(context).size;// the device size
return Scaffold(
body: Center(
child: ListTile(
tileColor: Colors.grey[300],
title: Text(
'Language',
style: TextStyle(fontSize: 25),
),
onTap: () => showDialog(
context: context,
builder: (context) => Dialog(
insetPadding: EdgeInsets.all(20),
child: Container(
color: Colors.white,
height: deviceSize.height,
width: deviceSize.width,
child: Column(
children: [
Row(
children: [
IconButton(
color: Colors.red,
icon: Icon(Icons.arrow_back),
onPressed: () => Navigator.of(context).pop(),
),
],
),
Spacer(),
Text('Pick A Language'),
Spacer(),
],
),
),
),
),
),
),
);
}
}
I'm trying to hide keyboard when tapped everywhere outside of textField. So I wrapped Scaffold with GestureDetector and set onTap with unfocused(). That works well however when a button is pressed then the keyboard is still active
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Scaffold(
appBar: AppBar(
actions: <Widget>[FlatButton(child: Text('Done'), onPressed: () {})],
),
body: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
FlatButton(
child: Text('something'),
onPressed: () {},
),
TextField(),
],
),
),
);
}
Is there any way to remove the focus without adding that unfocused in onTap of all buttons.. Reason is I've got many buttons there and some has even onLogTap set so there would be a lot of duplicate codes
You need to also add code for hide keyboard inside onPressed() method of FlatButton
FlatButton(
child: Text('something'),
onPressed: () {
FocusScope.of(context).unfocus();
},
),
was hoping for some solution where I wouldn't need so many duplicate codes to do one thing.
AFAIK that is not possible because the click event of GestureDetector widget and click event of FlatButton both are different,
You are registering different/separate click event of FlatButton that's why your keyboard is not hiding when you click on FlatButton
Now the reason why your keyboard not hiding when pressed on buttons
Because the click event of your GestureDetector widget if overridden by the click event of your FlatButton
SOLUTION
You can do one thing, create a common method to hide the keyboard, and call that method to from button click
By thinking a little outside of the box I have managed to hide keyboard on all taps by modifying GestureDetector..
Widget build(BuildContext context) {
return GestureDetector(
onPanDown: (pd) {FocusScope.of(context).unfocus();}, //<- replaced
child: Scaffold(
appBar: AppBar(
actions: <Widget>[FlatButton(child: Text('Done'), onPressed: () {})],
),
body: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
FlatButton(
child: Text('something'),
onPressed: () {},
),
TextField(),
],
),
),
);
}
Now the keyboard will hide on taping everywhere outside of the TextField even on Buttons click.. No need to hide it in each button click. Please if you know about better solution then let me know
UPDATE:
This solution will create exception when tap on already focused TextField