Flutter - Remove Spacing Between headerBuilder and body in ExpansionPanelList - flutter

I'm using ExpansionPanelList.radio widget.
How do I remove or reduce the spacing between the headerBuilder and body in each ExpansionPanelRadio widget?
The code is just a modified version of the Flutter DartPad sample code.
Each ExpansionPanelRadio widget contains a ListView builder widget.
I can't use the Stack widget as it produces size is not finite/Viewport unbounded height error.
Placing Transform.translate(offset: const Offset(0.0, -32.0), child: Text('ABCD') in the body() does the work but leaves 32 unit emply space at the bottom of each ExpansionPanelRadio widget.
I'm ok to copy specific Flutter source files in my project and modify them. But I'm not getting the clue how the space is added between the headerBuilder() and the body.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
/// This is the main application widget.
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
),
);
}
}
// stores ExpansionPanel state information
class Item {
Item({
required this.id,
required this.expandedValue,
required this.headerValue,
});
int id;
String expandedValue;
String headerValue;
}
List<Item> generateItems(int numberOfItems) {
return List<Item>.generate(numberOfItems, (int index) {
return Item(
id: index,
headerValue: 'Panel $index',
expandedValue: 'Panel $index: The quick brown fox jumps over the lazy dog.',
);
});
}
/// This is the stateful widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
/// This is the private State class that goes with MyStatefulWidget.
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
final List<Item> _data = generateItems(8);
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Container(
child: _buildPanel(),
),
);
}
Widget _buildPanel() {
return ExpansionPanelList.radio(
initialOpenPanelValue: 2,
expandedHeaderPadding: const EdgeInsets.all(0.0),
children: _data.map<ExpansionPanelRadio>((Item item) {
return ExpansionPanelRadio(
value: item.id,
headerBuilder: (BuildContext context, bool isExpanded) {
return ListTile(
title: Text(item.headerValue),
);
},
body: SizedBox(
height: 20.0,
child: Text(item.expandedValue),
)
);
}).toList(),
);
}
}
Already tried solutions given in the SO link but it only removes space before/after the currently expanded ExpansionPanelRadio widget.
Is there a way to reduce the spacing between the headerBuilder and the body?
It appears taller on a real device.

You can use like that
Transform.translate(
offset: OffSet(0, -10), //you can change y value, like -10
child: SizedBox(
height: 20.0,
child: Text(item.expandedValue),
),
),

Related

Flutter desktop listview move by up/down key

I am creating a flutter windows app. One page has listview in a scaffold widget. There is an action button on app bar. When I move the up/down key on listview. The focus item jumps from item 2 to say item 7, instead of next item, item 3. This occurs when I use up key moves to app bar button, then down key into listview. This does not occur if I move up and down within listview. This is an example code snippet I created for illustration. I found that the Focus widget enclosing Scaffold widget causes this problem. Removing the Focus widget can solve the problem. If I replace the whole Shortcuts-Actions-Focus widget chain by FocusActionDetector, this problem also exists. As I am still confusing about flutter's focus system, this may be incorrect.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
final int nItems = 20;
late List<FocusNode> _fnList;
int idxFocus = 0;
#override
void initState() {
super.initState();
_fnList = List.generate(nItems, (i) => FocusNode());
}
#override
void dispose() {
super.dispose();
for (FocusNode fn in _fnList) {
fn.dispose();
}
}
#override
Widget build(BuildContext context) {
return Shortcuts(
shortcuts: <ShortcutActivator, Intent>{
LogicalKeySet(LogicalKeyboardKey.escape): const DismissIntent(),
},
child: Actions(
actions: <Type, Action<Intent>>{
DismissIntent: CallbackAction<DismissIntent>(
onInvoke: (DismissIntent intent) => debugPrint('escape pressed'),
),
},
child: Focus(
child: Scaffold(
appBar: AppBar(
title: const Text('ListView Focus Action Example'),
actions: [
IconButton(
icon: const Icon(Icons.done),
onPressed: (){},
),
]
),
body: Center(
child: ListView.builder(
itemCount: nItems,
itemBuilder: (BuildContext context, int index) {
return Focus(
focusNode: _fnList[index],
onFocusChange: (bool focused) {
debugPrint('Focus Change: $index - $focused');
},
debugLabel: 'index: $index',
child: Card(
child: ListTile(
title: Text('item $index'),
trailing: TextButton(
onPressed: () {},
child: const Text('OK')
),
),
)
);
},
),
),
),
),
),
// ),
);
}
}

Consumer not updating the state?

I am trying to create an Icon with a number indicator on top of it and the number indicator receives its data via a Consumer provider. The problem is that the state is not being updated by the consumer function and I don't understand why (if I update the state with a hot reload everything works just fine).
Here is the code for my main file:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => TestData())
// I use more providers but deleted them here for brevity
],
child: TestScreen3(),
),
);
}
}
The test screen 3 widget
class TestScreen3 extends StatefulWidget {
#override
_TestScreen3State createState() => _TestScreen3State();
}
class _TestScreen3State extends State<TestScreen3> {
#override
Widget build(BuildContext context) {
final testData = Provider.of<TestData>(context);
return Scaffold(
appBar: AppBar(
title: Text('Test app 3'),
actions: [
Consumer<TestData>(builder: (_, data, __) {
return IconButton(
icon: Badge(num: data.items.length.toString()),
onPressed: () => print(data.items.length));
})
],
),
body: Center(
child: ElevatedButton(
child: Text('Increase'),
onPressed: () {
testData.addItem();
},
),
),
);
}
}
The badge widget
class Badge extends StatelessWidget {
Badge({#required this.num});
final String num;
#override
Widget build(BuildContext context) {
return Stack(
children: [
Icon(Icons.assessment),
Positioned(
child: Container(
padding: EdgeInsets.all(2),
child: Text(
num,
style: TextStyle(fontSize: 8),
textAlign: TextAlign.center,
),
constraints: BoxConstraints(
minHeight: 12,
minWidth: 12,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.red,
),
),
),
],
);
}
}
and the data model I am using
class Item {
Item(this.id);
final String id;
}
class TestData with ChangeNotifier {
List<Item> _items = [];
List<Item> get items => [..._items];
void addItem() {
_items.add(Item(DateTime.now().toString()));
}
notifyListeners();
}
The imports work just fine, I left them out for brevity. I followed along a this tutorial: https://www.udemy.com/course/learn-flutter-dart-to-build-ios-android-apps/ and it uses a key argument for the badge that looks like this:
class Badge extends StatelessWidget {
const Badge({
Key key,
#required this.child,
#required this.value,
this.color,
}) : super(key: key);
final Widget child;
final String value;
final Color color;
However, the use of key or super is not explained in the tutorial and when I add these parameters to my code they don't seem to make a change.
Many thanks in advance, I probably missed something super obvious...
Add notifyListeners(); inside addItem() method
void addItem() {
_items.add(Item(DateTime.now().toString()));
notifyListeners();
}

Flutter - Best way to aggregate data from child widgets in an IndexedStack

I have an IndexedStack in a Scaffold that I use to manage my registration. The Registration widget itself is Stateful, but the widgets that compose it are Stateless. The parent widget looks like this:
class Registration extends StatefulWidget {
#override
_RegistrationState createState() => _RegistrationState();
}
class _RegistrationState extends State<Registration> {
int _index = 0;
void _nextPage() {
setState(() {
_index++;
});
}
void _prevPage() {
setState(() {
_index--;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: Colors.white,
appBar: new AppBar(
backgroundColor: Colors.white,
automaticallyImplyLeading: false,
leading: new IconButton(
icon: new Icon(Icons.arrow_back,
color: Theme.of(context).primaryColor),
onPressed: () {
if (_index == 0) {
Navigator.pop(context);
} else {
_prevPage();
}
}),
elevation: 0.0,
),
body: IndexedStack(
children: <Widget>[
RegistrationPhone(_nextPage),
RegistrationName(_nextPage),
RegistrationBirthday(_nextPage),],
index: _index,
),
);
}
}
What is the best way to take data from these child widgets?
Should I pass in a callback function and hold the data in the parent? Should I pass the information down the line from widget to widget until it's submitted? I don't know what the practices are for sharing data across multiple screens.
Use Provider
Add Dependency :
dependencies:
provider: ^4.3.3
here is the Example :
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
/// This is a reimplementation of the default Flutter application using provider + [ChangeNotifier].
void main() {
runApp(
/// Providers are above [MyApp] instead of inside it, so that tests
/// can use [MyApp] while mocking the providers
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Counter()),
],
child: const MyApp(),
),
);
}
/// Mix-in [DiagnosticableTreeMixin] to have access to [debugFillProperties] for the devtool
// ignore: prefer_mixin
class Counter with ChangeNotifier, DiagnosticableTreeMixin {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
/// Makes `Counter` readable inside the devtools by listing all of its properties
#override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(IntProperty('count', count));
}
}
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Example'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
Text('You have pushed the button this many times:'),
/// Extracted as a separate widget for performance optimization.
/// As a separate widget, it will rebuild independently from [MyHomePage].
///
/// This is totally optional (and rarely needed).
/// Similarly, we could also use [Consumer] or [Selector].
Count(),
],
),
),
floatingActionButton: FloatingActionButton(
key: const Key('increment_floatingActionButton'),
/// Calls `context.read` instead of `context.watch` so that it does not rebuild
/// when [Counter] changes.
onPressed: () => context.read<Counter>().increment(),
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
class Count extends StatelessWidget {
const Count({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Text(
/// Calls `context.watch` to make [Count] rebuild when [Counter] changes.
'${context.watch<Counter>().count}',
key: const Key('counterState'),
style: Theme.of(context).textTheme.headline4);
}
}

How to change CheckboxListTile height?

Shortly, I need the selectable area to be smaller in terms of height.
Text and Checkbox sizes are good, but the surrounding box is too big for the checklist I'm trying to create.
CheckBoxListTile current height vs. desired height
Tried wrapping it with Transform.scale but text becomes too small:
Transform.scale(
scale: 0.8,
child: CheckboxListTile(
title: const Text("Rinite alérgica"),
value: timeDilation != 1.0,
controlAffinity: ListTileControlAffinity.leading,
onChanged: (bool value) {
setState(() {
timeDilation = value ? 2.0 : 1.0;
});
},
),
),
Tried wrapping it with Container, but I get on-screen overflow warnings.
Does anyone have a better solution?
When trying to resize the CheckboxListFile it seems that Google actually recommends creating a custom Tile widget and making use of the Checkbox widget.
https://api.flutter.dev/flutter/material/CheckboxListTile-class.html#material.CheckboxListTile.3
Look at the LabeledCheckbox widget that's been created. You can very easily modify all components to fit your needs. For example, if you wished to make the Widget itself smaller, you can now wrap it in a Container
/// Flutter code sample for CheckboxListTile
// ![Custom checkbox list tile sample](https://flutter.github.io/assets-for-api-docs/assets/material/checkbox_list_tile_custom.png)
//
// Here is an example of a custom LabeledCheckbox widget, but you can easily
// make your own configurable widget.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
/// This is the main application widget.
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const Center(
child: MyStatefulWidget(),
),
),
);
}
}
class LabeledCheckbox extends StatelessWidget {
const LabeledCheckbox({
Key key,
#required this.label,
#required this.padding,
#required this.value,
#required this.onChanged,
}) : super(key: key);
final String label;
final EdgeInsets padding;
final bool value;
final Function onChanged;
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
onChanged(!value);
},
child: Container(
padding: padding,
child: Row(
children: <Widget>[
Expanded(child: Text(label)),
Checkbox(
value: value,
onChanged: (bool newValue) {
onChanged(newValue);
},
),
],
),
),
);
}
}
/// This is the stateful widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
/// This is the private State class that goes with MyStatefulWidget.
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
bool _isSelected = false;
#override
Widget build(BuildContext context) {
return LabeledCheckbox(
label: 'This is the label text',
padding: const EdgeInsets.symmetric(horizontal: 20.0),
value: _isSelected,
onChanged: (bool newValue) {
setState(() {
_isSelected = newValue;
});
},
);
}
}
Have you tried ?
SizedBox(
height: 50,
width: 50,
child: ......
)

Flutter: Detect rebuild of any widget which is not visible on screen but is in the widget tree

Summary:
As showing a page/route using the Navigator, a new branch is created from the nearest MaterialApp parent. Meaning both pages (Main & New) will be in memory and will rebuild if they are listening to the same ChangeNotifier.
I am having trouble finding out which widget is on-screen currently visible to the user.
I need this to handle a scenario to skip performing asynchronous or long processes with some side effects, from a widget that might be in the widget tree but currently not visible.
Note: The sample code given here represents the basic architecture of the app I am currently working on, but reproduces the exact problem.
I am having this problem with a very different and complex widget tree that I have in my app, executing the doLongProcess() from a widget that is not visible on the screen. Also doLongProcess() changes some common property in my app which causes an issue, as any background widget can modify the details which are visible on the other widget.
I am looking for a solution to this issue, if there's any other way to achieve the goal except finding which widget is on the screen then please let me know that as well.
My final goal is to allow the long process to be executed from only the visible widget(s).
Please run the app once, to understand the following details properly.
Note 2:
I have tried to use mounted property of the state to determine if it can be used or not but it shows true for both widgets (MainPage TextDisplay and NewPage TextDisplay)
Let me know in the comments if more details or I missed something which is required.
Use the following sample code with provider dependency included for reproducing the problem:
// add in pubspec.yaml: provider: ^4.3.2+1
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
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> {
#override
Widget build(BuildContext context) {
print('MainPage: build');
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextDisplay(
name: 'MainPage TextDisplay',
),
SizedBox(
height: 20,
),
RaisedButton(
child: Text('Open New Page'),
onPressed: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) => NewPage(),
)),
),
],
),
),
);
}
}
class TextDisplay extends StatefulWidget {
final String name;
const TextDisplay({Key key, #required this.name}) : super(key: key);
#override
_TextDisplayState createState() => _TextDisplayState();
}
class _TextDisplayState extends State<TextDisplay> {
#override
Widget build(BuildContext context) {
return Container(
child: ChangeNotifierProvider.value(
value: dataHolder,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(child: Text(widget.name)),
SizedBox(
height: 20,
),
Consumer<DataHolder>(
builder: (context, holder, child) {
// need to detect if this widget is on the screen,
// only then we should go ahead with this long process
// otherwise we should skip this long process
doLongProcess(widget.name);
return Text(holder.data);
},
),
RaisedButton(
child: Text('Randomize'),
onPressed: () => randomizeData(),
),
],
),
),
);
}
void doLongProcess(String name) {
print('$name: '
'Doing a long process using the new data, isMounted: $mounted');
}
}
class NewPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
print('NewPage: build');
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: true,
title: Text('New Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextDisplay(
name: 'NewPage TextDisplay',
),
],
),
),
);
}
}
/////////////////// Data Holder Class and methods ///////////////////
class DataHolder extends ChangeNotifier {
String _data;
String get data => _data ?? 'Nothing to show, Yet!';
setData(String newData) {
print('\n new data found: $newData');
_data = newData;
notifyListeners();
}
}
final dataHolder = DataHolder();
randomizeData() {
int mills = DateTime.now().millisecondsSinceEpoch;
dataHolder.setData(mills.toString());
}
Posting solution for others to refer.
Refer to this flutter plugin/package:
https://pub.dev/packages/visibility_detector
The solution code:
// add in pubspec.yaml: provider: ^4.3.2+1
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:visibility_detector/visibility_detector.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
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> {
#override
Widget build(BuildContext context) {
print('MainPage: build');
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextDisplay(
name: 'MainPage TextDisplay',
),
SizedBox(
height: 20,
),
RaisedButton(
child: Text('Open New Page'),
onPressed: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) => NewPage(),
)),
),
],
),
),
);
}
}
class TextDisplay extends StatefulWidget {
final String name;
const TextDisplay({Key key, #required this.name}) : super(key: key);
#override
_TextDisplayState createState() => _TextDisplayState();
}
class _TextDisplayState extends State<TextDisplay> {
/// this holds the latest known status of the widget's visibility
/// if [true] then the widget is fully visible, otherwise it is false.
///
/// Note: it is also [false] if the widget is partially visible since we are
/// only checking if the widget is fully visible or not
bool _isVisible = true;
#override
Widget build(BuildContext context) {
return Container(
child: ChangeNotifierProvider.value(
value: dataHolder,
/// This is the widget which identifies if the widget is visible or not
/// To my suprise this is an external plugin which is developed by Google devs
/// for the exact same purpose
child: VisibilityDetector(
key: ValueKey<String>(widget.name),
onVisibilityChanged: (info) {
// print('\n ------> Visibility info:'
// '\n name: ${widget.name}'
// '\n visibleBounds: ${info.visibleBounds}'
// '\n visibleFraction: ${info.visibleFraction}'
// '\n size: ${info.size}');
/// We use this fraction value to determine if the TextDisplay widget is
/// fully visible or not
/// range for fractional value is: 0 <= visibleFraction <= 1
///
/// Meaning we can also use fractional values like, 0.25, 0.3 or 0.5 to
/// find if the widget is 25%, 30% or 50% visible on screen
_isVisible = info.visibleFraction == 1;
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(child: Text(widget.name)),
SizedBox(
height: 20,
),
Consumer<DataHolder>(
builder: (context, holder, child) {
/// now that we have the status of the widget's visiblity
/// we can skip the long process when the widget is not visible.
if (_isVisible) {
doLongProcess(widget.name);
}
return Text(holder.data);
},
),
RaisedButton(
child: Text('Randomize'),
onPressed: () => randomizeData(),
),
],
),
),
),
);
}
void doLongProcess(String name) {
print('\n ============================ \n');
print('$name: '
'Doing a long process using the new data, isMounted: $mounted');
final element = widget.createElement();
print('\n name: ${widget.name}'
'\n element: $element'
'\n owner: ${element.state.context.owner}');
print('\n ============================ \n');
}
}
class NewPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
print('NewPage: build');
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: true,
title: Text('New Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextDisplay(
name: 'NewPage TextDisplay',
),
],
),
),
);
}
}
/////////////////// Data Holder Class and methods ///////////////////
class DataHolder extends ChangeNotifier {
String _data;
String get data => _data ?? 'Nothing to show, Yet!';
setData(String newData) {
print('\n new data found: $newData');
_data = newData;
notifyListeners();
}
}
final dataHolder = DataHolder();
randomizeData() {
int mills = DateTime.now().millisecondsSinceEpoch;
dataHolder.setData(mills.toString());
}