Flutter - only show image if enough space available - flutter

I have an Expanded Widget with an Image.asset(...) as it's child.
On small phones there is not enough space and the image is scaled down massively. Since it is only for eye-candy anyways, I'd like to hide / not show the Image if there isn't at least a height >= 100.0 available.
How do I accomplish this in Flutter? Is there a way to get the parent widgets size?

You can use LayoutBuilder to obtain the input constraints of a widget.
Then based on these constraints decide to display or not an image
LayoutBuilder(
builder: (context, constraint) {
if (constraint.maxHeight < 100.0) {
// too small
return Container();
} else {
// ok
return Column(
children: <Widget>[
Image.asset("foo"),
Container()
],
);
}
},
)

Awesome Remi's solution, and You can use the MediaQuery and MediaQueryData too, has some properties and methods that can help your in situations like that.
Some properties are orientation, size, padding, devicePixelRatio, etc.
Example:
MediaQuery.of(context).orientation
https://docs.flutter.io/flutter/widgets/MediaQuery-class.html
https://docs.flutter.io/flutter/widgets/MediaQueryData-class.html

Related

Converting multistate toggle into dropdown if available width is shrunk in Flutter

I'm new to flutter, I have created a multistate toggle button but I want to convert it into dropdown in case the container doesn't have enough space to show the widget. Earlier I was using the LayoutBuilder context width but it is taking the whole width of the page not of the container containing the widget. Can you please help me with this ?
You are using the right approach. LayoutBuilder would work for you, but you should factor the entire widget into its own StateLess widget and use that context to do your measurements.
LayoutBuilder provides a BoxConstraints object with minWidth, maxWidth, minHeight, maxHeight properties that you can use in your code to decide which widget to embed in the tree. The dimension of that BoxConstraints object are calculated from your Widget's immediate ancestor.
Supposed you create MyMultistateToggleBoxOrDropdownWidget() and implement its build method like this:
#override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
// If constraints.maxWidth isn't wide enough,
// return a dropdown Widget,
// otherwise return the multistate toggle box.
}
}
Now that you have a Widget that returns what you want based on width, you need to now wire it into the correct context.
What you're probably missing is that you need to wrap your custom widget inside of something that controls the screen space that you want to present your Widget inside. A SizedBox() will do for this example.
Your solution took the whole page width because that was the width of your Widget's immediate ancestor in the Widget tree.
SizedBox(
width: 100.0,
height: 100.0,
child: MyMultistateToggleBoxOrDropdownWidget()
)

How to get size of a child Widget

I have a special requirement to allow for a header widget, usually containing static content to appear at the top of a scroll view. The scroll view should overlap the header widget so that a clip shape can be used for effect. I've achieved this effect by using a stack view with the header widget as the first item in the stack, the scroll view as the top element. The scroll view contains a column with the first child being an empty container of the desired height (the height of the header widget minus the amount of overlap). This achieves the desired effect when passing in a known height as a hard-coded parameter. (NOTE: I tried to accomplish this using a Sliver List, but wasn't able to achieve the desired overlap to meet product requirements.)
The header widget contains an image which is loaded via an API response. The height of the images can vary so I need to determine this at runtime and adjust the UI accordingly. I didn't think this would be a difficult task but so far, I've not found a way to do this. The following two images show the desired effect, the image on the right shows the correct behavior on scrolling up. Note that the scroll view must overlap the header image by the same amount as the radius of the clip.
This generates the list. _getComponents provides child widgets for a column contained in the SingleChildScrollView:
List<Widget> _getComponents(List<Section> sections, BuildContext context, double? height) {
List<Widget> widgetList = [
Container(height: 225) // need to obtain correct height here
];
for (int i = 1; i < sections.length; i++) {
Section section = sections[i];
if (section.model != null) {
switch (section.code) {
case mediaTypeCode:
if (section.model.runtimeType == MediaModel) {
widgetList.add(HeaderComponent(section: section));
}
break;
case articleTypeCode:
if (section.model.runtimeType == ArticleSectionModel) {
widgetList.add(TitleContentHeader(
model: section.model as ArticleSectionModel));
}
break;
}
}
}
return widgetList;
}
Then in my view widget, the stack is built as follows:
return Container(
color: Colors.white,
child: Stack(
children: [
_getHeader(sections),
SingleChildScrollView(
child: Column(
children: _getComponents(sections!, context, null),
),
),
],
),
);
I need to be able to get the height of the header returned in _getHeader(sections) and pass it to the _getComponents function so that I can determine the correct starting position for the scroll view.
Can anyone offer any insight into this?
Or, can you suggest a plugin that would allow the behavior show in the images above? As mentioned above, Sliver List did not produce the desired effect.
Thanks!
You can get size of a widget using RenderBox :
import 'package:flutter/material.dart';
class WidgetPosition {
getSizes(GlobalKey key) {
final RenderBox renderBoxRed = key.currentContext.findRenderObject();
final sizeRed = renderBoxRed.size;
// print("SIZE: $sizeRed");
return [sizeRed.width, sizeRed.height];
}
getPositions(GlobalKey key) {
final RenderBox renderBoxRed = key.currentContext.findRenderObject();
final positionRed = renderBoxRed.localToGlobal(Offset.zero);
// print("POSITION: $positionRed ");
return [positionRed.dx, positionRed.dy];
}
}

Why does ListView ignore constraints

Why does a ListView completely ignore imposed constraints?
#override
Widget build(BuildContext context) {
return Padding(
child: Container(
constraints: BoxConstraints(maxWidth: 500),
child: ListView(
children: buildList(data),
),
),
);
}
The displayed ListView does not seem to be bothered at all with the maxWidth:500 of the parent container (same if replaced with a ConstrainedBox) and all elements (which are Rows) go full screen width.
Why?
The solution is to work with width of children instead, but still I'd much rather constrain the entire listView, and also if there's a constraint, shouldn't everything inside be forced not to exceed it?
EDIT: After hours of trying various things it finally works. Turns out the list view needed to a child of a Center, otherwise it would not pay any attention to SizedBoxes, ConstrainedBoxes, Containers etc. you'd put on it or its children and stretch full screen no matter what.
What are the parent widgets of the Padding(child: Container(... list view ))? I mean are you sure that a parent is not imposing its constraints on its children? Try do replace your ListView with Container(color: Colors.red, height: 100) and see if it respects the constraints. If it does not, there probably is a parent widget with infinite width constraint or something like that!
A solution can be wrap your Container with an UnconstrainedBox but I would recommend first checking my first point :D

Flutter Markdown "Show more" button

I use MarkdownBody from flutter_markdown inside a LimitedBox. When pressing a "Show more" button the maxHeight is set to double.infinity and the full text is shown:
LimitedBox(
maxHeight:
showMoreCommentsIds
.contains(
commentId)
? double.infinity
: 100,
child: Wrap(
direction:
Axis.horizontal,
children: <Widget>[
MarkdownBody(
data: showList[index]
.comment
.comment,
)
],
),
),
But how can I find out the height of the text and only display the "Show more" button, if it is necessary?
You can find the length of the text using text.length and based on that information determine if the "Show more" button is needed. For example:
if(text.length > 60) {
_showButton();
}
You may need to do a little testing with the length of text in order to find out which length you want as the threshold. I just chose 60 as an arbitrary number.
I struggled a little bit with a similar problem because I had to know the size of a widget to apply some logic.
I learned that you can prerender the widget in an Offstage widget, determine it's size with the widget key once it is mounted.
You should wait for the rebuild, so you can force it and then you get the size with a function like this:
Future.delayed(Duration(milliseconds:100)).then(
final widgetSize = getWidgetSize(widgetKey);
// You can make logic here to remove the Offstage, variables, and free its space.
// Todo: apply the size in your code after this
)
...
Size getWidgetSize(GlobalKey key) {
final RenderBox renderBox = key.currentContext.findRenderObject();
return renderBox.size;
}
In my case, I needed it after tapping somewhere, so I used it inside a function, but maybe you will apply it directly on the initState(), inside a post frame callback.
initState(){
super.initState();
ServicesBinding.instance.addPostFrameCallback((_) async {
// here
});
}

Get ErrorWidget size for rebuild

To get the size/position of a widget on screen, I can use GlobalKey to get its BuildContext and then find the RenderBox.
But for ErrorWidget(Red Screen) when build() error happened I want to calculate the error area's size, and then decide whether to destroy the page or replace with other widget e.g. Container().
I have already used ErrorWidget.builder create custom ErrorWidget, but need to be more precise, different sizes of ErrorWidget are treated differently. How to get ErrorWidget size for rebuild?
ErrorWidget do not evade the widgets rules.
A widget cannot depend on the size of anything else.
You can, however, use LayoutBuilder to calculate the available size.
LayoutBuilder Widget can help us know how much space is available for the child widget, before finally build it. It's builder function has parameters BuildContext context, BoxConstraints constraints.
BoxConstraints constraints provides us with the opportunity to execute custom logic.
ErrorWidget.builder = (FlutterErrorDetails details) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
Size screenSize = MediaQuery.of(context).size;
double screenRatio = (constraints.maxWidth * constraints.maxHeight) /
(screenSize.width * screenSize.height);
if (screenRatio < ACCEPTABLE_SCREEN_RATIO) {
return Container();
}
return ErrorWidget(details.exception);
},
);
};