Wrap widget doesn't prevent overflow when using accessibility options - flutter

I have a very particular question. I'm coding a cryptogram app and I'm trying to figure out how to accommodate for visibility as best I can. I have the Wrap widget functioning fine when the font is the standard size (as set in the system) but if I crank the font size way up (like a friend of mine sometimes has to do) then the widgets overflow. I currently have each cryptogram broken down into an array of strings, and each string is further broken down into an array of character display widgets, so the children of the wrap are a list of word widgets that are, in turn, a list of these character widgets. I'll post code below. But what I am trying to achieve is the same kind of wrapping as is happening now, because there are 260 quotes so programming a layout for each one is out of the question, but also allow for side to side scrolling when the font is much larger than normal. I've also attached screenshots to illustrate what is happening.
Expanded(
flex: 5,
child: Scrollbar(
child: SingleChildScrollView(
child: Container(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: _createWordWidgets(),
),
),
),
),
),
),
List<Widget> _createWordWidgets() {
List<Widget> list = List();
for (int i = 0; i < gameManager.getWords().length; i++) {
list.add(WordWidget(
word: gameManager.getWords()[i],
selection: selection,
));
}
return list;
}
List<String> getWords() {
return _cipherHelper.getWordList();
}
void _makeWordList() {
_words = _cryptogram.split(' ').toList();
}
List<String> getWordList() {
return _words;
}
class WordWidget extends StatefulWidget {
WordWidget({this.word, this.selection});
final String word;
final String selection;
#override
_WordWidgetState createState() => _WordWidgetState();
}
class _WordWidgetState extends State<WordWidget> {
List<String> characters;
List<LetterWidget> letterWidgets = List();
CipherHelper brain = CipherHelper.getInstance();
#override
Widget build(BuildContext context) {
characters = widget.word.split('');
return Padding(
padding: EdgeInsets.symmetric(
horizontal: 4.0,
),
child: Container(
child: Row(
mainAxisSize: MainAxisSize.min,
children: _createLetters(widget.selection),
),
),
);
}
class LetterWidget extends StatelessWidget {
final String playerGuess;
final String codeLetter;
final String selectedLetter;
LetterWidget({this.codeLetter, this.playerGuess, this.selectedLetter});
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 2.0),
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: 50, minWidth: 24),
child: Container(
color: setContainerColor(context),
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text(
setText(),
style: kGuessText,
textAlign: TextAlign.end,
),
Container(
color: setDividerColor(context),
height: 2,
width: 24,
// indent: 20,
// endIndent: 20,
),
Text(
codeLetter,
style: kCipherText,
),
],
),
),
),
),
);
}
standard size font
accessibility increased font

Related

how to set width of container based on text length in flutter

I have created list of cateogories using list listview.builder
here I want to highlight selected category by underlining category text with container and I want to apply width based on text length...same way like we do underline for text,
I know they are inbuilt some packages but I don't want to use it as I want to implement my logic.
here is my code
I have set comment where I want to dynamic width
class CatogoryList extends StatefulWidget {
#override
State<CatogoryList> createState() => _CatogoryListState();
}
class _CatogoryListState extends State<CatogoryList> {
List<String> categories=['HandBag','Jwellery','FootWear','Dresses','Pens','Jeans','Trousers'];
int selectedindex=2;
#override
Widget build(BuildContext context) {
return SizedBox(
height: 30,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: categories.length,
itemBuilder: (context,index){
return buildCategory(index);
}),
);
}
Widget buildCategory(int index)
{
return GestureDetector(
onTap: (){
setState(() {
selectedindex=index;
});
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(categories[index],style: TextStyle(fontSize: 20,color: selectedindex==index?Colors.blue:Colors.grey),),
if(selectedindex==index)
Container(
// here I want to set widget of container based on text length
height: 3,width: 30,color: Colors.blue,),
],),
),
);
}
}
One way is to wrap the Column in an IntrinsicWidth and leave out the width from the Container. Like
Widget buildCategory(int index)
{
return GestureDetector(
onTap: (){
setState(() {
selectedindex=index;
});
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: IntrinsicWidth(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(categories[index],style: TextStyle(fontSize: 20,color: selectedindex==index?Colors.blue:Colors.grey),),
if(selectedindex==index)
Container(height: 3,color: Colors.blue,),
],),
),
),
);
}
I think the proper approach would be to wrap the selected Text widget with Container and apply the decoration there.
I'm answering your question as well:
final TextPainter textPainter = TextPainter(
text: TextSpan(text: text, style: yourTextStyle), /// apply your style here
textScaleFactor: MediaQuery.of(context).textScaleFactor,
textDirection: TextDirection.ltr,
)..layout();
final double width = textPainter.size.width;

Make Text widget shrink to longest line when text is wrapped to multiple lines

Is it possible to shrink a Text widget to the width of the longest line when the text is wrapped because it becomes too long to fit on a single line?
Basically I have this custom widget:
class MyWidget extends StatelessWidget {
final String text1;
final String text2;
const MyWidget({Key? key, this.text1='', this.text2=''}) : super(key: key);
#override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Container(
color: Colors.lightBlue,
child: Center(child: Text(text1))
)
),
Expanded(
child: Container(
color: Colors.lightGreen,
alignment: Alignment.centerRight,
child: Text(text2, textAlign: TextAlign.start)
)
)
]
);
}
}
Here's what it looks like with one short and one long text:
But what I want is this:
Note that I do not want the text to be right-aligned - the wrapped lines should be aligned to the left, but the entire Text widget should shrink to the longest line.
Just found the answer myself when investigating the more exotic properties of the Text widget. Apparently, textWidthBasis: TextWidthBasis.longestLine does exactly what I want.
class MyWidget extends StatelessWidget {
final String text1;
final String text2;
const MyWidget({Key? key, this.text1='', this.text2=''}) : super(key: key);
#override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Container(
color: Colors.lightBlue,
child: Center(child: Text(text1))
)
),
Expanded(
child: Container(
color: Colors.lightGreen,
alignment: Alignment.centerRight,
child: Text(text2, textWidthBasis: TextWidthBasis.longestLine)
)
)
]
);
}
}
you can wrap Text with FittedBox
FittedBox(
fit: BoxFit.fitWidth,
child: Text(
"some text"
),),
This answered for me. You can replace the following part in your code:
Expanded(
child: Container(
color: Colors.lightGreen,
alignment: Alignment.centerRight,
child: LayoutBuilder(builder: (context, size) {
final span = TextSpan(text: text2);
final tp = TextPainter(
text: span,
maxLines: 1,
textDirection: Directionality.of(context));
tp.layout(maxWidth: size.maxWidth);
if (tp.didExceedMaxLines) {
return Padding(
padding: const EdgeInsetsDirectional.only(start: 16.0),
child: Text(text2),
);
} else {
return Text(text2);
}
})))

fl_chart custom Legend gets pushed down when section is empty

I'm using the PieChart of fl_chart to display the distribution of locally saved documents. The percentages displayed in the chart are the result of the length of the two document type lists (See image below).
But when one List is empty I have a weird bug were my custom Legend gets pushed downwards. The PieChart and the Legend are positioned inside of a Row with flex factors on each children (2 for the PieChart and 4 for the Legend).
I really don't understand what pushes the Legend downwards because my Expanded widgets are always positioned inside of Rows so that the PieChart and Legend only take up the available, horizontal space and not the vertical space which happens in the bug (image 2).
PieChart widget:
class PersonalFilesCircularGraph extends StatefulWidget {
const PersonalFilesCircularGraph();
#override
_PersonalFilesCircularGraphState createState() =>
_PersonalFilesCircularGraphState();
}
class _PersonalFilesCircularGraphState
extends State<PersonalFilesCircularGraph> {
late List<FileTypeData> data;
List<PieChartSectionData> getSections() => data
.asMap()
.map<int, PieChartSectionData>((index, data) {
final value = PieChartSectionData(
color: data.color,
value: data.percent,
showTitle: false,
radius: 3,
);
return MapEntry(index, value);
})
.values
.toList();
#override
void initState() {
/* Example getFileTypeData result
[
FileTypeData(
"Patient Questionnaire",
patientQuestionnaires.length /
(patientQuestionnaires.length +
receivedPatientQuestionnaires.length) *
100,
const Color(0xFF3861FB),
),
FileTypeData(
"Received Patient Questionnaire",
receivedPatientQuestionnaires.length /
(receivedPatientQuestionnaires.length +
patientQuestionnaires.length) *
100,
Colors.teal.shade400,
),
];
*/
data = context.read<SessionBloc>().state.getFileTypeData;
super.initState();
}
#override
Widget build(BuildContext context) {
return BlocConsumer<SessionBloc, SessionState>(
listenWhen: (previous, current) {
final bool listenWhen = previous.patientQuestionnaires.length !=
current.patientQuestionnaires.length ||
previous.receivedPatientQuestionnaires.length !=
current.receivedPatientQuestionnaires.length;
return !listenWhen;
},
listener: (context, state) {
data = context.read<SessionBloc>().state.getFileTypeData;
},
builder: (context, state) {
return Row(
children: [
Expanded(
flex: 2,
child: Container(
constraints: const BoxConstraints(
maxWidth: 60,
maxHeight: 60,
),
child: PieChart(
PieChartData(
sections: getSections(),
),
),
),
),
const SizedBox(
width: kMediumPadding,
),
Expanded(
flex: 4,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: data
.map(
(data) => Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: buildLegend(
percent: data.percent,
text: data.fileName == "Patient Questionnaire"
? L.of(context).patientQuestionnaires
: L.of(context).receivedPatientQuestionnaire,
color: data.color,
),
),
)
.toList(),
),
),
],
);
},
);
}
Widget buildLegend({
required double percent,
required String text,
required Color color,
}) =>
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Row(
children: [
Container(
width: 10,
height: 10,
color: color,
),
const SizedBox(
width: kSmallPadding,
),
Expanded(
child: Text(
text,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
Text(
"${percent.toStringAsFixed(0)}%",
overflow: TextOverflow.ellipsis,
)
],
);
}
I display the chart widget inside a CustomScrollView, wrapped with a SliverToBoxAdapter inside of my home screen:
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
return CustomScrollView(
physics: const BouncingScrollPhysics(),
slivers: <Widget>[
SliverAppBar(
elevation: 0.0,
floating: true,
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
title: Text(
"Home",
style: Theme.of(context).textTheme.headline5,
),
centerTitle: true,
),
const SliverPadding(
padding: EdgeInsets.symmetric(
vertical: kSmallPadding,
horizontal: kMediumPadding,
),
sliver: SliverToBoxAdapter(
child: PersonalFilesCircularGraph(),
),
)
],
);
}
}
EDIT:
I just did some more investigation on this bug and placed a colored Container in my CustomScrollView, below the SliverPadding of the CircularGraph to check if the Column of labels expands downwards. But as you can see the colored Container is not effected. It just looks like the Legend is inside a Stack and positioned without effecting other widgets above and below.
const SliverPadding(
padding: EdgeInsets.symmetric(
vertical: kSmallPadding,
horizontal: kMediumPadding,
),
sliver: SliverToBoxAdapter(
child: PersonalFilesCircularGraph(),
),
),
SliverToBoxAdapter(
child: Container(
width: double.infinity,
height: 60,
color: Colors.green,
),
)

Best way to define a bespoke Card in Flutter

I've been attempting to define a bespoke Card in Flutter using row and column and cannot seem to get a fixed format layout similar to the image above (the red lines denote the areas of the card and are just there to show the areas).
e.g.
return Card(child: Column(
children: [
Row(
children: [
Column( children: [
Text('Riverside cafe...'),
Ratingbar(),
],),
ImageWidget(),
],
),
Container(child: Text('Pubs & restaurants'), color : Colors.purple)
],
The resulting cards are to be displayed in a listview and using rows and columns above results in the areas being different sized depending on the data.
It seems to me that using row and column may not be the best way to achieve this. Is there a better way?
As for the best, I suppose that's for you and your client to decide.
For as long as I've been working with Flutter, I haven't come across anything like CSS grid which is great for situations like this. The closest comparison is StaggeredGrid (https://pub.dev/packages/flutter_staggered_grid_view) but that doesn't offer as much control as CSS grid and doesn't seem to quite fit your use case.
Rows, Columns (and other layout widgets) can get the job done:
Here's the main.dart that produced the above example. Code quality isn't perfect, but hopefully you can follow it well enough and it helps you get done what you need to get done.
import 'package:auto_size_text/auto_size_text.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
MyApp({Key key}) : super(key: key);
static const String _title = 'Bespoke card example';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Bespoke card example')),
body: Center(
child: Wrap(runSpacing: 10.0, children: [
BespokeCard(title: 'Short name', width: 350),
BespokeCard(
title: 'Riverside Cafe with a really long name', width: 350)
]),
),
);
}
}
class BespokeCard extends StatelessWidget {
final String title;
final double width;
BespokeCard({this.title, this.width});
#override
Widget build(BuildContext context) {
Widget _restaurantNameContainer = Container(
constraints: BoxConstraints(
minHeight: 0,
maxHeight: 120,
maxWidth: (500.0 - 40 - 175 + 1),
minWidth: (500.0 - 40 - 175 + 1),
),
child: AutoSizeText(
title,
style: TextStyle(fontSize: 60),
maxLines: 2,
minFontSize: 10,
stepGranularity: 0.1,
overflow: TextOverflow.ellipsis,
),
);
Widget _rightSideSection = Container(
width: 175,
height: Size.infinite.height,
child: Center(
child: Icon(
Icons.umbrella,
size: 70,
),
),
);
Widget _topSection = Flexible(
flex: 1,
child: Row(
children: [
Flexible(
fit: FlexFit.tight,
flex: 3,
child: Padding(
padding: EdgeInsets.only(left: 40.0, top: 25.0),
child: Column(
children: [
Flexible(child: Container(), flex: 1),
_restaurantNameContainer,
Text('* * * * *', style: TextStyle(fontSize: 70)),
],
),
),
),
_rightSideSection
],
),
);
Widget _bottomSection = Container(
height: 70,
width: Size.infinite.width,
child: Center(
child: Text('Pubs & Restaurants',
style: TextStyle(color: Colors.white, fontSize: 40)),
),
color: Colors.purple);
Widget unfittedCard = Card(
child: SizedBox(
width: 500,
height: 300,
child: Column(
mainAxisSize: MainAxisSize.max,
children: [_topSection, _bottomSection],
),
));
return Container(
width: this.width,
child: FittedBox(fit: BoxFit.fitWidth, child: unfittedCard));
}
}
NOTES:
Be aware of flexFit (tight or loose) property: https://api.flutter.dev/flutter/widgets/Flexible/fit.html
You can either define fixed ratios with all flexibles, or you can mix Flexibles with Containers / SizedBoxes what have you
The package auto_size_text is great for situations like this. (Add auto_size_text: ^2.1.0 to your dependencies)
Be aware of box constraints. I needed them to make the title autosizing text be able to grow tall without also sitting in a large container.
Fitted box is really handy and makes scaling very easy in flutter.

Does Flutter support negative margin?

Negative margin is generally not needed but there are situations where it’s really useful. For example: why use negative margins?
For now, when I set margin for a container to a negative value, I got the following error:
I/flutter ( 3173): 'package:flutter/src/widgets/container.dart': Failed assertion: line 251: 'margin == null ||
I/flutter ( 3173): margin.isNonNegative': is not true.
The container has a useful transform property.
child: Container(
color: Theme.of(context).accentColor,
transform: Matrix4.translationValues(0.0, -50.0, 0.0),
),
I'm gonna give an answer for this, mostly because I had to find a way to do this.
I would like to say that it is not ideal and could likely be accomplished in a better way, but it does give the desired effect.
As you can see, the text can be pulled negatively outside its parent using a stack:
Container(
constraints: BoxConstraints.loose(Size.fromHeight(60.0)),
decoration: BoxDecoration(color: Colors.black),
child:
Stack(
alignment: Alignment.topCenter,
overflow: Overflow.visible,
children: [
Positioned(
top: 10.0,
left: -15.0,
right: -15.0,
child: Text("OUTSIDE CONTAINER", style: TextStyle(color: Colors.red, fontSize: 24.0),)
)
]
)
)
To answer this question you first have to define what "negative margins", or really "margins" in general, really are. In CSS, margins have various meanings in the various layout models, most commonly, they are one of several values that contribute to computing the offset that the block layout model uses to place subsequent children; a negative total margin in this case merely means the next child is placed above the bottom of the previous child instead of after it.
In Flutter, as in CSS, there are several layout models; however, there is currently no widget that is equivalent to the CSS block layout model (which supports margin collapsing, negative margins, skipping floats, etc). Such a layout model could certainly be implemented, it just hasn't been implemented yet, at least not in the framework itself.
To implement such a layout model, you would create a RenderBox descendant similar to RenderFlex or RenderListBody, probably providing a way to set the margins of each child using a ParentDataWidget in the same way that Flex children can have their flex configured using the Expanded widget.
Probably the most complicated part of designing a new layout model like this would be deciding how to handle overflow or underflow, when the children are too big or too small to fit the constraints passed to this new layout render object. The RenderFlex render object has a way to distribute the space if the children underflow, and considers it an error if they overflow (in debug mode, this is shown by a yellow-and-black striped warning area and a message logged to the console); the RenderListBody render object on the other hand takes the view that the constraints must be unbounded in the main axis, which means you can basically only use this layout model inside a list (hence the name).
If writing a new layout model is not attractive, you could use one of the existing layout widgets that allow overlapping children. Stack is the obvious choice, where you set the explicit positions of each child and they can overlap arbitrarily (this is vaguely similar to the CSS absolute position layout model). Another option is the CustomMultiChildLayout widget, which lets you layout and position each child in turn. With this, you could position each child one after the other, simulating negative margins by setting the position of the subsequent child to a value that's derived from the size and position of the previous child, but such that the subsequent child's top is above the previous child's bottom.
If there's interest in a block-like layout model, we could certainly implement it (please file a bug and describe the model you'd like implemented, or, implement it yourself and send a pull request for review). So far, though, we've not found that it has been that useful in practice, at least not useful enough to justify the complexity.
To extend the accepted answer, you can wrap any widget with Transform.translate. It takes a simple Offset as parameter.
I find it is easier to use than the translation Matrix.
Transform.translate(
// e.g: vertical negative margin
offset: const Offset(-10, 0),
child: ...
),
The short answer is "No, it doesn't".
To give few more details, Flutter has a sophisticated but effective algorithm for rendering its widgets. Margins and Paddings are analyzed at runtime, and the final size and position of the widget is determined. When you try to issue a negative margine you are purposefully creating a not valide layout where a widget is somehow dropping out of the space it is supposed to occupy.
Consider reading the doc here.
Anyhow I believe you should formulate better the question in another thread and really ask a solution for the behavior you are trying to achieve with those negative margins. I am sure you'll get much more that way.
Cheers
No, Flutter does not allow negative margins but just in case you still want your widgets to overlap each other, you can use a Stack with Positioned which will allow you to generate the layout which you can do with negative margins.
Here is an example :
import 'package:flutter/material.dart';
class MyHomePage extends StatefulWidget {
MyHomePageState createState() => new MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Container(
padding: const EdgeInsets.all(8.0),
height: 500.0,
width: 500.0,
child: new Stack(
overflow: Overflow.visible,
children: <Widget>[
new Icon(Icons.pages, size: 36.0, color: Colors.red),
new Positioned(
left: 20.0,
child: new Icon(Icons.pages, size: 36.0, color: Colors.green),
),
],
),
),
)
);
}
}
void main() {
runApp(new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.deepPurple,
),
home: new MyHomePage(),
));
}
This will result in :
NOTE: You can also give negative values in Positioned Widget.
You can use OverflowBox to disregard certain constraints.
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
color: Colors.blue.shade300,
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
Expanded(
child: Container(
color: Colors.white,
child: Center(
child: Text('Padding on this one.'),
),
),
),
SizedBox(height: 20),
Expanded(
child: OverflowBox(
maxWidth: MediaQuery.of(context).size.width,
child: Container(
color: Colors.red.shade300,
child: Center(
child: Text('No padding on this one!'),
),
),
),
),
SizedBox(height: 20),
Expanded(
child: Container(
color: Colors.yellow.shade300,
child: Center(
child: Text('Look, padding is back!'),
),
),
),
],
),
),
),
),
);
}
Result:
A hack if you really want this (for example, me) and need performance:
Disadvantage: The hit testing has problem on those edges. But if you only want to display the widget without wanting to click it, it is completely fine.
How to use it: As if you are using Padding widget, except that now your padding can be negative and no errors will happen.
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class AllowNegativePadding extends SingleChildRenderObjectWidget {
const AllowNegativePadding({
Key key,
#required this.padding,
Widget child,
}) : assert(padding != null),
super(key: key, child: child);
/// The amount of space by which to inset the child.
final EdgeInsetsGeometry padding;
#override
RenderAllowNegativePadding createRenderObject(BuildContext context) {
return RenderAllowNegativePadding(
padding: padding,
textDirection: Directionality.of(context),
);
}
#override
void updateRenderObject(BuildContext context, RenderAllowNegativePadding renderObject) {
renderObject
..padding = padding
..textDirection = Directionality.of(context);
}
#override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
}
}
class RenderAllowNegativePadding extends RenderShiftedBox {
RenderAllowNegativePadding({
EdgeInsetsGeometry padding,
TextDirection textDirection,
RenderBox child,
}) : assert(padding != null),
// assert(padding.isNonNegative),
_textDirection = textDirection,
_padding = padding,
super(child);
EdgeInsets _resolvedPadding;
void _resolve() {
if (_resolvedPadding != null) return;
_resolvedPadding = padding.resolve(textDirection);
// assert(_resolvedPadding.isNonNegative);
}
void _markNeedResolution() {
_resolvedPadding = null;
markNeedsLayout();
}
/// The amount to pad the child in each dimension.
///
/// If this is set to an [EdgeInsetsDirectional] object, then [textDirection]
/// must not be null.
EdgeInsetsGeometry get padding => _padding;
EdgeInsetsGeometry _padding;
set padding(EdgeInsetsGeometry value) {
assert(value != null);
// assert(value.isNonNegative);
if (_padding == value) return;
_padding = value;
_markNeedResolution();
}
/// The text direction with which to resolve [padding].
///
/// This may be changed to null, but only after the [padding] has been changed
/// to a value that does not depend on the direction.
TextDirection get textDirection => _textDirection;
TextDirection _textDirection;
set textDirection(TextDirection value) {
if (_textDirection == value) return;
_textDirection = value;
_markNeedResolution();
}
#override
double computeMinIntrinsicWidth(double height) {
_resolve();
final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
if (child != null) // next line relies on double.infinity absorption
return child.getMinIntrinsicWidth(math.max(0.0, height - totalVerticalPadding)) + totalHorizontalPadding;
return totalHorizontalPadding;
}
#override
double computeMaxIntrinsicWidth(double height) {
_resolve();
final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
if (child != null) // next line relies on double.infinity absorption
return child.getMaxIntrinsicWidth(math.max(0.0, height - totalVerticalPadding)) + totalHorizontalPadding;
return totalHorizontalPadding;
}
#override
double computeMinIntrinsicHeight(double width) {
_resolve();
final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
if (child != null) // next line relies on double.infinity absorption
return child.getMinIntrinsicHeight(math.max(0.0, width - totalHorizontalPadding)) + totalVerticalPadding;
return totalVerticalPadding;
}
#override
double computeMaxIntrinsicHeight(double width) {
_resolve();
final double totalHorizontalPadding = _resolvedPadding.left + _resolvedPadding.right;
final double totalVerticalPadding = _resolvedPadding.top + _resolvedPadding.bottom;
if (child != null) // next line relies on double.infinity absorption
return child.getMaxIntrinsicHeight(math.max(0.0, width - totalHorizontalPadding)) + totalVerticalPadding;
return totalVerticalPadding;
}
#override
void performLayout() {
final BoxConstraints constraints = this.constraints;
_resolve();
assert(_resolvedPadding != null);
if (child == null) {
size = constraints.constrain(Size(
_resolvedPadding.left + _resolvedPadding.right,
_resolvedPadding.top + _resolvedPadding.bottom,
));
return;
}
final BoxConstraints innerConstraints = constraints.deflate(_resolvedPadding);
child.layout(innerConstraints, parentUsesSize: true);
final BoxParentData childParentData = child.parentData as BoxParentData;
childParentData.offset = Offset(_resolvedPadding.left, _resolvedPadding.top);
size = constraints.constrain(Size(
_resolvedPadding.left + child.size.width + _resolvedPadding.right,
_resolvedPadding.top + child.size.height + _resolvedPadding.bottom,
));
}
#override
void debugPaintSize(PaintingContext context, Offset offset) {
super.debugPaintSize(context, offset);
assert(() {
final Rect outerRect = offset & size;
debugPaintPadding(context.canvas, outerRect, child != null ? _resolvedPadding.deflateRect(outerRect) : null);
return true;
}());
}
#override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding));
properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
}
}
To overcome some horizontal padding you can create such a Widget:
Usage (will take out 8pt from the padding left and right.
const ExpandWidth(
child: MyWidget(),
width: 8,
)
Implementation:
class ExpandWidth extends StatelessWidget {
final double width;
final Widget child;
const ExpandWidth({
super.key,
required this.child,
this.width = 0,
});
#override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return IntrinsicHeight(
child: OverflowBox(
maxWidth: constraints.maxWidth + width * 2,
child: child,
),
);
},
);
}
}
EDIT:
Margin Widget
I played a little around and wanted to share this here:
It's far from perfect, but at least anything to start with.
You can modify horizontal, vertical, left and top.
The interesting part is the Margin widget.
In this example all the grey container have a padding of 16.
Result
Code example of the screenshot
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 360,
height: 300,
color: Colors.black12,
padding: const EdgeInsets.all(16),
child: Container(
color: Colors.black38,
padding: const EdgeInsets.all(16),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Margin(
horizontal: -24,
top: -8,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
color: Colors.greenAccent.withOpacity(0.8),
child: const Center(child: Text('Horizontal: -24 & Top: -8')),
),
),
// const SizedBox(height: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
color: Colors.greenAccent.withOpacity(0.8),
child: const Center(child: Text('No modification')),
),
const SizedBox(height: 8),
Margin(
vertical: -16,
top: -16,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
color: Colors.greenAccent.withOpacity(0.8),
child: const Center(child: Text('Vertical: -24 & Top: -16')),
),
),
],
),
Margin(
vertical: -16,
top: 32,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
color: Colors.greenAccent.withOpacity(0.8),
child: const Center(child: Text('Third')),
),
),
],
),
),
),
const SizedBox(height: 16),
Container(
width: 360,
height: 300,
color: Colors.black12,
padding: const EdgeInsets.all(16),
child: Container(
color: Colors.black38,
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Row(
children: [
Flexible(
child: Margin(
vertical: -24,
// horizontal: 8,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
color: Colors.greenAccent.withOpacity(0.8),
child: const Center(child: Text('V -24')),
),
),
),
const SizedBox(width: 16),
Flexible(
child: Margin(
vertical: 0,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
color: Colors.greenAccent.withOpacity(0.8),
child: const Center(child: Text('Nothing')),
),
),
),
],
),
),
const SizedBox(width: 16),
Margin(
vertical: -16,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
color: Colors.greenAccent.withOpacity(0.8),
child: const Center(
child: Text(
'V\n-16',
textAlign: TextAlign.center,
)),
),
),
],
),
),
),
],
),
);
margin.dart
import 'package:flutter/material.dart';
class SizeProviderWidget extends StatefulWidget {
final Widget child;
final Function(Size) onChildSize;
const SizeProviderWidget({
super.key,
required this.onChildSize,
required this.child,
});
#override
_SizeProviderWidgetState createState() => _SizeProviderWidgetState();
}
class _SizeProviderWidgetState extends State<SizeProviderWidget> {
#override
void initState() {
_onResize();
super.initState();
}
void _onResize() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (context.size is Size) {
widget.onChildSize(context.size!);
}
});
}
#override
Widget build(BuildContext context) {
///add size listener for every build uncomment the fallowing
///_onResize();
return widget.child;
}
}
class Margin extends StatefulWidget {
const Margin({
super.key,
required this.child,
this.horizontal = 0,
this.vertical = 0,
this.left = 0,
this.top = 0,
});
final Widget child;
final double horizontal;
final double vertical;
final double top;
final double left;
#override
State<Margin> createState() => _MarginState();
}
class _MarginState extends State<Margin> {
Size childSize = Size.zero;
#override
Widget build(BuildContext context) {
final horizontalMargin = widget.horizontal * 2 * -1;
final verticalMargin = widget.vertical * 2 * -1;
final newWidth = childSize.width + horizontalMargin;
final newHeight = childSize.height + verticalMargin;
if (childSize != Size.zero) {
return LimitedBox(
maxWidth: newWidth,
maxHeight: newHeight,
child: OverflowBox(
maxWidth: newWidth,
maxHeight: newHeight,
child: Transform.translate(
offset: Offset(widget.left, widget.top),
child: SizedBox(
width: newWidth,
height: newHeight,
child: widget.child,
),
),
),
);
}
return SizeProviderWidget(
child: widget.child,
onChildSize: (size) {
setState(() => childSize = size);
},
);
}
}
You can try something like this:
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
home: MyApp(),
));
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('text'),
),
body: Container(
child: Center(
child: Column(
children: <Widget>[
Container(
height: 300.0,
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(
"https://images.unsplash.com/photo-1539450780284-0f39d744d390?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=d30c5801b9fff3d4a5b7f1522901db9f&auto=format&fit=crop&w=1051&q=80"),
fit: BoxFit.cover)),
child: Stack(
alignment: Alignment.topCenter,
overflow: Overflow.visible,
children: [
Positioned(
top: 200.0,
child: Card(
child: Text("Why not?"),
))
]))
],
),
),
),
);
}
}