How to design a screen resolution compatible UI in Flutter? - user-interface

The design of an application I developed with Flutter is broken on devices with different screen sizes (tablet vs. phone). Cards and containers overlap and vary in size. What is your suggestion?

I really suggest you to give a look at the LayoutBuilder class that's been created exactly to solve your problem. Give a look at the doc for every info; here's a simple usage:
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < YOUR_SIZE)
return Column();
else
return GridView();
},
),
Very intuitively: if the width of the device is lower than YOUR_SIZE, the screen is not so "wide" and a column fits well. Otherwise you could place your widgets in a grid with N columns probably.
Official video about LayoutBuilder on YouTube.
Use widgets composition instead of functions that return Widget. Functions are NOT optimized, you can't const-construct the widget and they get rebuilt every time!

To make the size of any widget proportional with any device, you need to get the device width and height before setting widget size.
For instance, to create a container of size that is responsive to any device height and width and also scale when rotated, you need to use the MediaQuery to get the device height and width to scale you widget size as follows:
....
#override
Widget build(BuildContext context){
double _height = MediaQuery.of(context).size.height;
double _width = MediaQuery.of(context).size.width;
return Container(
height: _width > _height ? _width / 10 : _height / 10,
width: _width > _height? ? _height / 10 : _widht / 10,
child: AnyWidget(),
);
}
...

Related

With canvas, ctx.lineTo draws a longer "Y" line than instructed (160 instead of 120) [duplicate]

I have 2 canvases, one uses HTML attributes width and height to size it, the other uses CSS:
<canvas id="compteur1" width="300" height="300" onmousedown="compteurClick(this.id);"></canvas>
<canvas id="compteur2" style="width: 300px; height: 300px;" onmousedown="compteurClick(this.id);"></canvas>
Compteur1 displays like it should, but not compteur2. The content is drawn using JavaScript on a 300x300 canvas.
Why is there a display difference?
It seems that the width and height attributes determine the width or height of the canvas’s coordinate system, whereas the CSS properties just determine the size of the box in which it will be shown.
This is explained in the HTML specification:
The canvas element has two attributes to control the size of the element’s bitmap: width and height. These attributes, when specified, must have values that are valid non-negative integers. The rules for parsing non-negative integers must be used to obtain their numeric values. If an attribute is missing, or if parsing its value returns an error, then the default value must be used instead. The width attribute defaults to 300, and the height attribute defaults to 150.
To set the width and height on a canvas, you may use:
canvasObject.setAttribute('width', '150');
canvasObject.setAttribute('height', '300');
For <canvas> elements, the CSS rules for width and height set the actual size of the canvas element that will be drawn to the page. On the other hand, the HTML attributes of width and height set the size of the coordinate system or 'grid' that the canvas API will use.
For example, consider this (jsfiddle):
var ctx = document.getElementById('canvas1').getContext('2d');
ctx.fillStyle = "red";
ctx.fillRect(10, 10, 30, 30);
var ctx2 = document.getElementById('canvas2').getContext('2d');
ctx2.fillStyle = "red";
ctx2.fillRect(10, 10, 30, 30);
canvas {
border: 1px solid black;
}
<canvas id="canvas1" style="width: 50px; height: 100px;" height="50" width="100"></canvas>
<canvas id="canvas2" style="width: 100px; height: 100px;" height="50" width="100"></canvas>
Both have had the same thing drawn on them relative to the internal coordinates of the canvas element. But in the second canvas, the red rectangle will be twice as wide because the canvas as a whole is being stretched across a bigger area by the CSS rules.
Note: If the CSS rules for width and/or height aren't specified then the browser will use the HTML attributes to size the element such that 1 unit of these values equals 1px on the page. If these attributes aren't specified then they will default to a width of 300 and a height of 150.
The canvas will be stretched if you set the width and height in your CSS. If you want to dynamically manipulate the dimension of the canvas you have to use JavaScript like so:
canvas = document.getElementById('canv');
canvas.setAttribute('width', '438');
canvas.setAttribute('height', '462');
The browser uses the css width and height, but the canvas element scales based on the canvas width and height. In javascript, read the css width and height and set the canvas width and height to that.
var myCanvas = $('#TheMainCanvas');
myCanvas[0].width = myCanvas.width();
myCanvas[0].height = myCanvas.height();
Shannimal correction
var el = $('#mycanvas');
el.attr('width', parseInt(el.css('width')))
el.attr('height', parseInt(el.css('height')))
Canvas renders image by buffer, so when you specify the width and height HTML attributes the buffer size and length changes, but when you use CSS, the buffer's size is unchanged. Making the image stretched.
Using HTML sizing.
Size of canvas is changed -> buffer size is changed -> rendered
Using CSS sizing
Size of canvas is changed -> rendered
Since the buffer length is kept unchanged, when the context renders the image,
the image is displayed in resized canvas (but rendered in unchanged buffer).
CSS sets the width and height of the canvas element so it affects the coordinate space leaving everything drawn skewed
Here's my way on how to set the width and height with Vanilla JavaScript
canvas.width = numberForWidth
canvas.height = numberForHeight
I believe CSS has much better machinery for specifying the size of the canvas and CSS must decide styling, not JavaScript or HTML. Having said that, setting width and height in HTML is important for working around the issue with canvas.
CSS has !important rule that allows to override other styling rules for the property, including those in HTML. Usually, its usage is frowned upon but here the use is a legitimate hack.
In Rust module for WebAssembly you can do the following:
fn update_buffer(canvas: &HtmlCanvasElement) {
canvas.set_width(canvas.client_width() as u32);
canvas.set_height(canvas.client_height() as u32);
}
//..
#[wasm_bindgen(start)]
pub fn start() -> Result<(), JsValue> {
// ...
let canvas: Rc<_> = document
.query_selector("canvas")
.unwrap()
.unwrap()
.dyn_into::<HtmlCanvasElement>()
.unwrap()
.into();
update_buffer(&canvas);
// ...
// create resizing handler for window
{
let on_resize = Closure::<dyn FnMut(_)>::new(move |_event: Event| {
let canvas = canvas.clone();
// ...
update_buffer(&canvas);
// ...
window.add_event_listener_with_callback("resize", on_resize.as_ref().unchecked_ref())?;
on_resize.forget();
}
}
There we update the canvas buffer once the WASM module is loaded and then whenever the window is resized. We do it by manually specifying width and height of canvas as values of clientWidth and clientHeight. Maybe there are better ways to update the buffer but I believe this solution is better than those suggested by #SamB, #CoderNaveed, #Anthony Gedeon, #Bluerain, #Ben Jackson, #Manolo, #XaviGuardia, #Russel Harkins, and #fermar because
The element is styled by CSS, not HTML.
Unlike elem.style.width & elem.style.height trick used by #Manolo or its JQuery equivalent used by #XaviGuardia, it will work for canvas whose size is specified by usage as flex or grid item.
Unlike the solution by #Russel Harkings, this also handles resizing. Though I like his answer because it is really clean and easy.
WASM is the future! Haha :D
P.S. there's a ton of .unwrap() because Rust explicitly handles possible failures.
P.P.S.
{
let on_resize = Closure::<dyn FnMut(_)>::new(move |_event: Event| {
let canvas = canvas.clone();
// ...
update_buffer(&canvas);
// ...
window.add_event_listener_with_callback("resize", on_resize.as_ref().unchecked_ref())?;
on_resize.forget();
}
can be done much cleaner with better libraries. E.g.
add_resize_handler(&window, move |e: ResizeEvent| {
let canvas = canvas.clone();
// ...
update_buffer(&canvas);
})
If you want a dynamic behaviour based on, e.g. CSS media queries, don't use canvas width and height attributes. Use CSS rules and then, before getting the canvas rendering context, assign to width and height attributes the CSS width and height styles:
var elem = document.getElementById("mycanvas");
elem.width = elem.style.width;
elem.height = elem.style.height;
var ctx1 = elem.getContext("2d");
...

Make a card big enough to fit the child text

I'm creating a simple card that contains a long description.
I want to set the normal height of the card to 50px and when the user clicks on it I want to expand the card to be able to fit all the content inside.
So far, to obtain my needs I wrote this:
String aLotOfText = "When Gradle resolves the compile classpath, it first resolves the runtime classpath and uses the result to determine what versions of dependencies should be added to the compile classpath. In other words, the runtime classpath determines the required version numbers for identical dependencies on downstream classpaths. Your app's runtime classpath also determines the version numbers that Gradle requires for matching dependencies in the runtime classpath for the app's test APK. The hierarchy of classpaths is described in figure 1.";
bool cardExpanded = false;
InkWell(
onTap: () {
expandCard();
},
child: AnimatedContainer(
height: cardExpanded ? null : 50,
duration: Duration(milliseconds: 200),
child: Card(
child: Text(aLotOfText),
),
)
)
expandCard() {
setState(() {
cardExpanded = !cardExpanded;
});
}
As you can see on the line height: cardExpanded ? null : 50, when cardExpanded is true I didn't specify the height, so in this way I'm able to expand the card to the right height to contain the text.
But with this little hack I've completely lost the animation and as long as this card would go in a listview with 20 or more other cards, the opening and closing makes the list to jump up and down.
I'm sure that there is a way to expand the card to the right height keeping also the animation.
I would also like to specify that the one above is just an example, in my card, there will be images and buttons as well.
In addition, when the card is opened with a button, I could add some text, so the card has to expand as well when it is opened.
That's why I cannot specify a height.
EDIT
With height: cardExpanded ? null : 50 I get what I want, so, the card grows big enough to fit the content, but as you can see there is no animation there
Instead if I provide a value like height: cardExpanded ? 100 : 50, I get the animation, but obviously the card grows until it gets to 100 pixels and it dosen't show all the content.
The result that I would like to obtain is what there is in the first example plus the animation.
Thankyou
You could set two variables:
height = MediaQuery.of(context).size.height * 0.8; //the percentage you want the card to grow
width = MediaQuery.of(context).size.width * 0.8;
So when expanded is true, the container grows.
height: cardExpanded ? height : 50,
width: cardExpanded ? width : 50,
Also, to specify how the animation will perform, you have a property that can make the animation look smoother.
curve: Curves.bounceInOut,
or you could try using a widget like expanded to adjust.

Flutter - more efficient pan and zoom for CustomPaint

I'm rendering a collection of grids of tiles, where each tile is pulled from an image. To render this, I'm rendering everything inside my own implementation of CustomPainter (because the grids can get pretty large). To support pan and zoom functionality, I opted to perform the offsetting and scaling as part of canvas painting.
Here is a portion of my custom painting implementation.
#override
void paint(Canvas canvas, Size size) {
// With the new canvas size, we may have new constraints on min/max offset/scale.
zoom.adjust(
containerSize: size,
contentSize: Size(
(cellWidth * columnCount).toDouble(),
(cellHeight * rowCount).toDouble(),
),
);
canvas.save();
canvas.translate(zoom.offset.dx, zoom.offset.dy);
canvas.scale(zoom.scale);
// Now, draw the background image and grids.
While this is functional, performance can start to breakdown after enough cells are rendered (for example, a grid of 100x100 causes some lag on each GestureDetector callback that updates the zoom values). And, because the offsetting and scaling is done in the CustomPaint, I basically can't return false for bool shouldRepaint(MyPainter old) because it needs to repaint to render its new offset and scale.
So, my question is: What is a more performant way of approaching this problem?
I've tried one other approach:
var separateRenderTree = RepaintBoundary(
child: OverflowBox(
child: CustomPaint(
painter: MyPainter(),
),
),
);
return Transform(
transform: Matrix4.translationValues(_zoom.offset.dx, _zoom.offset.dy, 0)..scale(_zoom.scale),
child: separateRenderTree,
);
This also works, but can also get laggy when scaling (translating is buttery smooth).
So, again, what is the right approach to this problem?
Thank you.
Here's where I ended up.
I size my custom painter to be as large as it needs, and then I position it inside a Transform widget (that is top-left aligned with an offset of zero).
On top of this widget I overlay an invisible widget that manages touch inputs. Using a GestureDetector, it will respond to events and notify the Transform widget to update.
With the pan/zoom officially moved out of the painter, I then implemented the "shouldRepaint" function to be more strict.
This has allowed me to render very, very large grids at good-enough speeds.

how to handle different screen sizes in react native?

I am developing an application on react-native. I have made a UI which works fine on iPhone 6 but not working fine on iPhone 5 or lower versions.
How should I fix this ?
You need to think about proportion when building your UI.
1, Use percentage(%) for width and aspectRatio for height, or vice versa.
container: {
width: "100%",
aspectRatio: 10 / 3, //height will be "30%" of your width
}
2, Use flex for the jobs percentage can't do. For example, if you have arbitrary size of items in a list and you want them to share equal sizes. Assign each of them with flex: 1
3, Use rem from EStyleSheet instead of pixels. rem is a scale fator. For example, if your rem is 2 and your “11rem” will become “11*2” = “22”. If we make rem proportion to the screen sizes, your UI will scale with any screen sizes.
//we define rem equals to the entireScreenWidth / 380
const entireScreenWidth = Dimensions.get('window').width;
EStyleSheet.build({$rem: entireScreenWidth / 380});
//how to use rem
container: {
width: "100%",
aspectRatio: 10 / 3, //height will be "30%"
padding: "8rem", //it'll scale depend on the screen sizes.
}
4, Use scrollView for contents that could potentially scale out of the boxes. For example, a TextView
5, Every time you think about using pixels, consider use rem in method 3.
For a detailed explanation, you can read the article here. 7 Tips to Develop React Native UIs For All Screen Sizes
Have you designed the app using fixed widths and heights? You should definitely use the capabilities of flexbox and try to avoid settings fixed sizes as much as possible. The flex property can be used to define how much space a <View /> should use releative to others, and the other properties on that page can be used to lay out elements in a flexible way that should give the desired results on a range of different screen sizes.
Sometimes, you may also need a <ScrollView />.
When you do need fixed sizes, you could use Dimensions.get('window').
You need to calculate sizes dynamically, relying on screen size.
import { Dimensions, StyleSheet } from 'react-native'
[...]
const { width, height } = Dimensions.get('window')
[...]
const styles = StyleSheet.create({
container: {
flex: 1.
flexDirection: 'column',
},
myView: {
width: width * 0.8, // 80% of screen's width
height: height * 0.2 // 20% of screen's height
}
})
If you are using TabbarIOS, remember that Dimensions.get('window') gives you the whole screen's height, this means that you'll have to take in account that the tabbar has fixed-height of 56.
So for example, when using TabbarIOS:
const WIDTH = Dimensions.get('window').width,
HEIGHT = Dimensions.get('window').height - 56
Then use WIDTH and HEIGHT as above.

reorganize layout on orientation change in titanium

I have a layout that has sum specifications when it is in portrait. But when I change the screen orientation the layout needs to reorganize to fit the entire screen. The problem is that the layout keeps he's dimensions. So if the layout starts portrait the I change to landscape the layout keeps the portrait configuration. How can I make my layout so it auto reorganizes on screen orientation changes?
How I create my views:
var deviceHeight = Ti.Platform.displayCaps.platformHeight,
deviceWidth = Ti.Platform.displayCaps.platformWidth,
platform = Ti.Platform.osname;
if (platform == 'android') {
deviceHeight = Math.round(Ti.Platform.displayCaps.platformHeight / Ti.Platform.displayCaps.logicalDensityFactor);
deviceWidth = Math.round(Ti.Platform.displayCaps.platformWidth / Ti.Platform.displayCaps.logicalDensityFactor);
}
var View = Ti.UI.createView({
height : deviceHeight,
width : deviceWidth,
backgroundColor : 'white',
layout : 'vertical'
});
Very important: Don't specify width in points/pixels, but in percentages. Or use relative width. For example, if you want a view that is full width, minus 10 left and 10 right, specify that:
Ti.UI.createView(){
left: 10,
right: 10
}
Treat Apps different as websites! Make everything relative. There are so many resolutions it is impossible to make a layout PER resolution. Make a single solution for mobile, and possible re-arrange some stuff for tablets.
If you really want to redraw manually, use this event and redefine all your views after again:
Ti.Gesture.addEventListener('orientationchange',function(e) {
});
For this to work, you need to keep a reference to all your views and adjust where you like.

Resources