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: ......
)
Related
I created a horizontal popup menu but i cant increase the width of the popupMenuEntry because the default width is too short for my use case. I tried to set the child of my popupMenuEntry to double.infinity and MediaQuery.of(context).sized.width but nothing is happening..
It looks like ok in the emulator but when i tested out in the actual device, its too small thats why i need to resize it to at least 90-95% of the screen width.
here is my implementation of my popupMenu.
class ReactionPopupMenu extends StatefulWidget {
const ReactionPopupMenu(
{Key? key, required this.onSelect, required this.child, this.onTap})
: super(key: key);
final void Function(String) onSelect;
final Widget child;
final VoidCallback? onTap;
#override
State<ReactionPopupMenu> createState() => _ReactionPopupMenuState();
}
class _ReactionPopupMenuState extends State<ReactionPopupMenu>
with CustomPopupMenu {
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: widget.onTap,
onTapDown: storePosition,
onLongPress: () => this.showMenu(
context: context,
shape: const StadiumBorder(),
items: [
CustomedPopupEntry(
child: SizedBox(
width: MediaQuery.of(context).size.width,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: [
for (var i = 0; i < kEmojies.length; i++)
ReactiveEmoji(
emoji: kEmojies[i],
onTap: () =>
Navigator.pop(context, kValue[i]))
]),
),
)
]).then(
(value) => value == null ? null : widget.onSelect(value)),
child: widget.child);
}
}
mixin CustomPopupMenu<T extends StatefulWidget> on State<T> {
Offset? _tapPosition;
/// Pass this method to an onTapDown parameter to record the tap position.
void storePosition(TapDownDetails details) =>
_tapPosition = details.globalPosition;
/// Use this method to show the menu.
// ignore: avoid_shadowing_type_parameters
Future<T?> showMenu<T>({
required BuildContext context,
required List<PopupMenuEntry<T>> items,
T? initialValue,
double? elevation,
String? semanticLabel,
ShapeBorder? shape,
Color? color,
bool captureInheritedThemes = true,
bool useRootNavigator = false,
}) {
// final RenderObject? overlay =
// Overlay.of(context)!.context.findRenderObject();
return material.showMenu<T>(
context: context,
position: RelativeRect.fromLTRB(
_tapPosition!.dx,
_tapPosition!.dy,
_tapPosition!.dx,
_tapPosition!.dy,
),
items: items,
initialValue: initialValue,
elevation: elevation,
semanticLabel: semanticLabel,
shape: shape,
color: color,
useRootNavigator: useRootNavigator,
);
}
}
class CustomedPopupEntry<T> extends PopupMenuEntry<T> {
const CustomedPopupEntry({Key? key, required this.child}) : super(key: key);
final Widget child;
#override
State<StatefulWidget> createState() => _CustomedPopupEntryState();
#override
double get height => 100;
#override
bool represents(T? value) => false;
}
class _CustomedPopupEntryState extends State<CustomedPopupEntry> {
#override
Widget build(BuildContext context) => widget.child;
}
class ReactiveEmoji extends StatelessWidget {
const ReactiveEmoji({Key? key, required this.emoji, required this.onTap})
: super(key: key);
final String emoji;
final VoidCallback onTap;
#override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
child: CircleAvatar(
backgroundColor: Colors.transparent,
radius: MediaQuery.of(context).size.width * 0.046,
backgroundImage: AssetImage(emoji),
),
);
}
}
Your solution is not with adding more width to popUpMenu. you should make your emoji list scrollable, so when use it in small device it could be scrolled. So wrap your emoji row with SingleChildScrollView and set its scrollDirection to horizontal.
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),
),
),
I have a ReorderableListView, and I'm using proxyDecorator to detect when an item starts being dragged and then adjust its height, and the height of the underlying Widget which is actually present in ListView (as I assume the widget returned by proxyDecorator is just used to show what is being dragged.) The issue is that the 'space' in the list where the dragged item was, or is being moved to, remains its original size, rather than reflecting the new size of the widget. Once the dragged item is placed, if I disable the code to return it to it's original size, the rest of the listview bunches up to reflect the new size. But I want this to happen as soon as the drag begins. Is this possible? I have tried using a setState call in the proxyDecorator, but this causes an error with triggering a rebuild during a rebuild.
I'm not sure what you meant by proxy decorator, but if you just want to resize an item when it's being dragged, it can be done.
Obviously this is just a quick demo, lots of edge cases are not tested. But if you are interested, here's the full source code used in the above demo:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final colors = [
Colors.red,
Colors.blue,
Colors.green,
Colors.orange,
Colors.purple,
Colors.pink,
Colors.cyan
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ReorderableListView'),
),
body: ReorderableListView(
children: colors
.map((color) => Box(
key: ValueKey(color),
color: color,
))
.toList(),
onReorder: (int oldIndex, int newIndex) {
if (newIndex > oldIndex) newIndex--;
setState(() {
final color = colors.removeAt(oldIndex);
colors.insert(newIndex, color);
});
},
),
);
}
}
class Box extends StatefulWidget {
final Color color;
const Box({Key? key, required this.color}) : super(key: key);
#override
_BoxState createState() => _BoxState();
}
class _BoxState extends State<Box> {
bool _big = false;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: (_) => setState(() => _big = true),
onTapCancel: () => setState(() => _big = false),
child: AnimatedContainer(
duration: Duration(milliseconds: 200),
height: _big ? 100 : 50,
margin: EdgeInsets.all(4.0),
color: widget.color,
),
);
}
}
By using ListView i came into a problem i don't understand what is going on. I got one ListView with a list of Task and a second with a list of TaskTile (stateless widget with ListTile in build method).
With the first List i map the tasks into TaskTile, the rebuild works as expected but in the second it works only if i scroll the list and not when the state changes.
Here is the code for the two lists and a gif representing the problem:
import 'package:flutter/material.dart';
import 'package:todo/screens/task/task_screen.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const TaskScreen(),
);
}
}
class Task {
String title;
bool completed;
Task({required this.title, this.completed = false});
void toggleCompleted() {
completed = !completed;
}
}
class TaskScreen extends StatefulWidget {
const TaskScreen({Key? key}) : super(key: key);
#override
State<TaskScreen> createState() => _TaskScreenState();
}
class _TaskScreenState extends State<TaskScreen> {
List<Task> tasks = [
Task(title: "task1"),
Task(title: "task2"),
Task(title: "task3"),
];
List<TaskTile> taskTiles = [
TaskTile(task: Task(title: "tile1")),
TaskTile(task: Task(title: "tile2")),
TaskTile(task: Task(title: "tile3")),
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Tasks"),
),
body: SafeArea(
child: Column(
children: [
Expanded(
child: TasksList(
tasks: tasks,
)),
const Divider(
thickness: 10,
color: Colors.blue,
),
Expanded(
child: TaskTilesList(
tasks: taskTiles,
)),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () {
setState(() {
tasks.add(Task(title: "Task"));
});
},
child: const Text("Add Task")),
ElevatedButton(
onPressed: () {
setState(() {
taskTiles.add(TaskTile(task: Task(title: "Tile")));
});
},
child: const Text("Add Task Tile")),
],
),
],
),
),
);
}
}
class TasksList extends StatelessWidget {
const TasksList({Key? key, required this.tasks}) : super(key: key);
final List<Task> tasks;
#override
Widget build(BuildContext context) {
//************* List of Task mapped to TaskTile *******************
return ListView(children: tasks.map((e) => TaskTile(task: e)).toList());
}
}
class TaskTilesList extends StatelessWidget {
const TaskTilesList({Key? key, required this.tasks}) : super(key: key);
final List<TaskTile> tasks;
#override
Widget build(BuildContext context) {
//************ List of TaskTile *************
return ListView(
children: tasks,
);
}
}
class TaskTile extends StatefulWidget {
const TaskTile({Key? key, required this.task}) : super(key: key);
final Task task;
#override
State<TaskTile> createState() => _TaskTileState();
}
class _TaskTileState extends State<TaskTile> {
bool checkBoxValue = false;
#override
Widget build(BuildContext context) {
print("Task: ${widget.task.title}");
return ListTile(
title: Text(
widget.task.title,
style: TextStyle(color: checkBoxValue ? Colors.black : Colors.black),
),
trailing: TileCheckbox(
checkBoxValue: checkBoxValue,
callback: (val) {
setState(() {
checkBoxValue = val;
});
}),
onTap: () {
print("tile tapped");
setState(() {});
},
);
}
}
class TileCheckbox extends StatelessWidget {
const TileCheckbox(
{Key? key, required this.checkBoxValue, required this.callback})
: super(key: key);
final bool checkBoxValue;
final Function callback;
#override
Widget build(BuildContext context) {
return Checkbox(
value: checkBoxValue,
onChanged: (bool? value) {
callback(value);
},
);
}
}
When i replace ListView with ListView.builder in the problematic list it works but i don't understand why it does not work without the builder constructor. I tried to understand it with prints when i add the new TaskTile, in console the two lists are rebuild i can even see the newly added TaskTile but it does not appear in UI without scrolling the list. Is it supposed to work like this? What is the difference when passing a list of TaskTile instead of a mapped list of TaskTile?
By definition ListView requires you to create all items at once, while the builder constructor creates a lazy list which loads the itens on demand
from the oficial docs:
ListView: Creates a scrollable, linear array of widgets from an
explicit List. This constructor is appropriate for list views with a
small number of children because constructing the List requires doing
work for every child that could possibly be displayed in the list view
instead of just those children that are actually visible.
ListView.builder Creates a scrollable, linear array of widgets that
are created on demand. This constructor is appropriate for list views
with a large (or infinite) number of children because the builder is
called only for those children that are actually visible.
Where does the FocusScope widget create in the tree and we pass every context in it and it can request to any focus nodes. When we pass context to FocusScope it will start looking above the context and we never used the FocusScope widget in the code in the hierarchy where does it create and how does it resolves in the case of scaffold if we pass context that is above in the tree then it throws an exception then we use builder type of thing but in FocusScope why it doesn't throw an error?
Here is the example for the FocusScope
import 'package:flutter/material.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(),
);
}
}
/// A demonstration pane.
///
/// This is just a separate widget to simplify the example.
class Pane extends StatelessWidget {
const Pane({
Key? key,
required this.focusNode,
this.onPressed,
required this.backgroundColor,
required this.icon,
this.child,
}) : super(key: key);
final FocusNode focusNode;
final VoidCallback? onPressed;
final Color backgroundColor;
final Widget icon;
final Widget? child;
#override
Widget build(BuildContext context) {
return Material(
color: backgroundColor,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
Center(
child: child,
),
Align(
alignment: Alignment.topLeft,
child: IconButton(
autofocus: true,
focusNode: focusNode,
onPressed: onPressed,
icon: icon,
),
),
],
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
bool backdropIsVisible = false;
FocusNode backdropNode = FocusNode(debugLabel: 'Close Backdrop Button');
FocusNode foregroundNode = FocusNode(debugLabel: 'Option Button');
#override
void dispose() {
super.dispose();
backdropNode.dispose();
foregroundNode.dispose();
}
Widget _buildStack(BuildContext context, BoxConstraints constraints) {
final Size stackSize = constraints.biggest;
return Stack(
fit: StackFit.expand,
// The backdrop is behind the front widget in the Stack, but the widgets
// would still be active and traversable without the FocusScope.
children: <Widget>[
// TRY THIS: Try removing this FocusScope entirely to see how it affects
// the behavior. Without this FocusScope, the "ANOTHER BUTTON TO FOCUS"
// button, and the IconButton in the backdrop Pane would be focusable
// even when the backdrop wasn't visible.
FocusScope(
// TRY THIS: Try commenting out this line. Notice that the focus
// starts on the backdrop and is stuck there? It seems like the app is
// non-responsive, but it actually isn't. This line makes sure that
// this focus scope and its children can't be focused when they're not
// visible. It might help to make the background color of the
// foreground pane semi-transparent to see it clearly.
canRequestFocus: backdropIsVisible,
child: Pane(
icon: const Icon(Icons.close),
focusNode: backdropNode,
backgroundColor: Colors.lightBlue,
onPressed: () => setState(() => backdropIsVisible = false),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// This button would be not visible, but still focusable from
// the foreground pane without the FocusScope.
ElevatedButton(
onPressed: () => debugPrint('You pressed the other button!'),
child: const Text('ANOTHER BUTTON TO FOCUS'),
),
DefaultTextStyle(
style: Theme.of(context).textTheme.headline2!,
child: const Text('BACKDROP')),
],
),
),
),
AnimatedPositioned(
curve: Curves.easeInOut,
duration: const Duration(milliseconds: 300),
top: backdropIsVisible ? stackSize.height * 0.9 : 0.0,
width: stackSize.width,
height: stackSize.height,
onEnd: () {
if (backdropIsVisible) {
backdropNode.requestFocus();
} else {
foregroundNode.requestFocus();
}
},
child: Pane(
icon: const Icon(Icons.menu),
focusNode: foregroundNode,
// TRY THIS: Try changing this to Colors.green.withOpacity(0.8) to see for
// yourself that the hidden components do/don't get focus.
backgroundColor: Colors.green,
onPressed: backdropIsVisible
? null
: () => setState(() => backdropIsVisible = true),
child: DefaultTextStyle(
style: Theme.of(context).textTheme.headline2!,
child: const Text('FOREGROUND')),
),
),
],
);
}
#override
Widget build(BuildContext context) {
// Use a LayoutBuilder so that we can base the size of the stack on the size
// of its parent.
return LayoutBuilder(builder: _buildStack);
}
}