How to create dotted border around circular avatar in flutter - flutter

I'd like to display Instagram-like stories on my Flutter app and want to show the number of stories a user has uploaded by using borders around the user's avatar.
Say a user has uploaded 3 stories, i'll show 3 rounded border lines around the avatar image separated by equal number of spaces & if a user uploads 80 stories, i'll show 80 tiny round border lines separated by equal number of spaces.
I tried using plugins from pub.dev for this, like
dotted_border 2.0.0+2
fdottedline 1.0.1
just to name a few, but i can't seem to get an accurate count of spaces & dashes to fulfill the requirements above.
Below is an example:
FDottedLine(
color: Colors.black,
strokeWidth: 2.0,
dottedLength: 30,
space: 4,
corner: FDottedLineCorner.all(100.0),
child: Padding(
padding: const EdgeInsets.all(3.0),
child: SizedBox.square(
dimension: 0.055.h,
child: ClipRRect(
borderRadius: BorderRadius.circular(100),
child: ImageBox.network(
photo: user.photo.getOrEmpty,
elevation: 2,
replacement: Image.asset(AppAssets.defaultUserImage(user.gender.getOrNull)),
borderRadius: BorderRadius.circular(100),
),
),
),
),
),
No matter how i tweak the dottedLength & space params, i can't get equal number of spaces nor dashes.
I also tried using Path(), CustomPainter() but i barely know much about how to use it.
Any idea how i can achieve this using either CustomPainter() or a plugin?

Thanks for posting all your tries as it made me jump to CustomPaint directly to try
the approach that (could) work (but not tested well) is drawArc
the logic is simply to draw an arc based on the number of stories and start the next arc after leaving some space
the below code is looping for the number of stories to draw every story arc and start the next story arc (if stories > 1) after adding some value (the space between the stories) to the start of the next arc location (on the circle).
for(int i =0;i<numberOfStories;i++){
canvas.drawArc(
rect,
inRads(startOfArcInDegree),
inRads(arcLength),
false,
Paint()
..color = i==0||i==1?Colors.grey:Colors.teal
..strokeWidth =14.0
..style = PaintingStyle.stroke
);
startOfArcInDegree += arcLength + spaceLength;
}
full code with detailed explanation:
import 'dart:math';
import 'package:flutter/material.dart';
class DottedBorder extends CustomPainter {
//number of stories
final int numberOfStories;
//length of the space arc (empty one)
final int spaceLength;
//start of the arc painting in degree(0-360)
double startOfArcInDegree = 0;
DottedBorder({required this.numberOfStories, this.spaceLength = 10});
//drawArc deals with rads, easier for me to use degrees
//so this takes a degree and change it to rad
double inRads(double degree){
return (degree * pi)/180;
}
#override
bool shouldRepaint(DottedBorder oldDelegate) {
return true;
}
#override
void paint(Canvas canvas, Size size) {
//circle angle is 360, remove all space arcs between the main story arc (the number of spaces(stories) times the space length
//then subtract the number from 360 to get ALL arcs length
//then divide the ALL arcs length by number of Arc (number of stories) to get the exact length of one arc
double arcLength = (360 - (numberOfStories * spaceLength))/numberOfStories;
//be careful here when arc is a negative number
//that happens when the number of spaces is more than 360
//feel free to use what logic you want to take care of that
//note that numberOfStories should be limited too here
if(arcLength<=0){
arcLength = 360/spaceLength -1;
}
Rect rect = Rect.fromLTWH(0, 0, size.width, size.height);
//looping for number of stories to draw every story arc
for(int i =0;i<numberOfStories;i++){
//printing the arc
canvas.drawArc(
rect,
inRads(startOfArcInDegree),
//be careful here is: "double sweepAngle", not "end"
inRads(arcLength),
false,
Paint()
//here you can compare your SEEN story index with the arc index to make it grey
..color = i==0||i==1?Colors.grey:Colors.teal
..strokeWidth =14.0
..style = PaintingStyle.stroke
);
//the logic of spaces between the arcs is to start the next arc after jumping the length of space
startOfArcInDegree += arcLength + spaceLength;
}
}
}
class DottedBorderExample extends StatelessWidget {
const DottedBorderExample({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Arcs etc')),
body:Center(
child: Stack(
alignment: Alignment.center,
children: [
SizedBox(
width: 300,height: 300,
child: CustomPaint(
painter: DottedBorder(numberOfStories: 13,spaceLength:4 ),
),),
Container(child:const Center(child: Text("Some Image",style: TextStyle(fontSize: 18,color: Colors.black),)),width: 270,height: 270,decoration: const BoxDecoration(color: Colors.purple,shape: BoxShape.circle),)
],
),
),
);
}
}
void main() {
runApp(
const MaterialApp(
home: DottedBorderExample(),
),
);
}

click to look at images:
we should determine 2 things
color width
separation width\
color width can be measured through below function
double colorWidth(double radius, int statusCount, double separation)
{
return ((2 * pi * radius) - (statusCount * separation)) / statusCount;
}
2 * PI * radius >> Circumference of a circle
SO >> Circumference minus total separation pixels needed, Then result divided by total status count.
now we have the width of each status equally, To fit the circle border
measuring separation pixels width
depending on the status number to be more enhanced as WhatsApp
double separation(int statusCount) {
if (statusCount <= 20)
return 3.0;
else if (statusCount <= 30)
return 1.8;
else if (statusCount <= 60)
return 1.0;
else
return 0.3;
}
Now we add the dotted_border package to our project and import it
https://pub.dev/packages/dotted_border
import 'package:dotted_border/dotted_border.dart';
assuming we have some declarations above they are:
//each digit express a status number
List status = [1, 2, 5, 4, 9, 13, 15, 20, 30, 40, 80];
//circle radius
double radius = 27.0;
dashPattern:
we have two states
one status or more than one (multiple statuses)
dashPattern: status[index] == 1
? [
//one status
(2 * pi * (radius + 2)), // take all border
0, //zere separators
]
: [
//multiple status
colorWidth(radius + 2, status[index],
separation(status[index])),
separation(status[index]),
],
FULL CODE:
import 'dart:math';
import 'package:dotted_border/dotted_border.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'STATUS',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
List status = [1, 2, 5, 4, 9, 13, 15, 20, 30, 40, 80];
double radius = 27.0;
double colorWidth(double radius, int statusCount, double separation) {
return ((2 * pi * radius) - (statusCount * separation)) / statusCount;
}
double separation(int statusCount) {
if (statusCount <= 20)
return 3.0;
else if (statusCount <= 30)
return 1.8;
else if (statusCount <= 60)
return 1.0;
else
return 0.3;
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: ListView.separated(
itemCount: status.length,
separatorBuilder: (context, index) => Divider(
color: Colors.black,
height: 15,
),
itemBuilder: ((context, index) => Row(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child:
/// Creating a circle with a dotted border.
DottedBorder(
color: Colors.teal.shade300,
borderType: BorderType.Circle,
radius: Radius.circular(radius),
dashPattern: status[index] == 1
? [
//one status
(2 * pi * (radius + 2)),
0,
]
: [
//multiple status
colorWidth(radius + 2, status[index],
separation(status[index])),
separation(status[index]),
],
strokeWidth: 3,
child: CircleAvatar(
radius: radius,
backgroundColor: Colors.transparent,
child: CircleAvatar(
radius: radius - 2,
),
),
),
),
SizedBox(
width: 10,
),
Text(
'${status[index]}',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
],
)),
),
),
);
}
}

You can use a package named status_view:
Link:
status_view
Usage from official docs
StatusView(
radius: 40,
spacing: 15,
strokeWidth: 2,
indexOfSeenStatus: 2,
numberOfStatus: 5,
padding: 4,
centerImageUrl: "https://picsum.photos/200/300",
seenColor: Colors.grey,
unSeenColor: Colors.red,
),

Related

Flutter - Rows with Icons like a Bow

in my form I have a Container with some Rows and nested Icons. Every Icon has got a Tap event with a function.
How can I curve all Rows with Icons like this.
to obtain this result as a bow (like rainbow)?
I think there is no shortcut to do this if you want a smooth curve. You must provide some constrains that what the ratio of width and height and calculate the curve formulation. You can try to combine Stack & Align to fullfill your layout.
Here is my assumption from your image:
X axis is separated equally.
Y axis is like a circular sector which radian from 1.25 pi to 1.75 pi (a circle is 2 pi). So the total radian is pi
import 'dart:math';
class CustomRainbow extends StatelessWidget {
#override
Widget build(BuildContext context) {
List<Widget> icons = List<Icon>.filled(14, Icon(Icons.tag_faces));
return Scaffold(
backgroundColor: Colors.black87,
body: Center(
child: Container(
width: 300,
height: 130,
color: Colors.white,
child: Stack(
children: [
..._bridge(icons),
..._bridge(icons, row: 1),
..._bridge(icons, row: 2),
..._bridge(icons, row: 3),
],
),
),
),
);
}
List<Widget> _bridge(List<Widget> icons, {int row = 0}) {
double totalRad = pi;
double rowHeightFactor = 0.4;
int count = icons.length;
double width = 2.0 / (count - 1);
double singleRad = totalRad / (count + 1);
double startRad = pi * 1.5 - totalRad * 0.5;
List<Widget> result = [];
for (int i = 0; i < count; i++) {
result.add(
Align(
alignment: Alignment(
-1 + i * width, sin((startRad + singleRad * (i + 1)))) +
Alignment(0, row * rowHeightFactor),
child: icons[i],
),
);
}
return result;
}
}
You can still tune the Width, Height, totalRad and rowHeightFactor,
Make sure the Alignment value is in range of (-1.0, 1.0) and all the icons will be in the Container.
A hack could be to display row(column) and to add a transparent (default) container with the respective height for each column as the first element, to move the subsequent icons downwards.
One simple way of doing that is to build Columns inside the Row with placeholders that will ocuppy some bottom space to make taller columns:
static const double size = 40.0;
static const placeholderSize = size / 2;
static const int columnsNumber = 8;
Widget repeatedWidget() {
return IconButton(
icon: Icon( //Your icon button
Icons.tag_faces,
size: size,
),
onPressed: () {},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate( //generating a fixed number of columns with icons for the example
columnsNumber,
(index) {
return Column(
mainAxisAlignment: MainAxisAlignment.end,
children: List.generate(
6,
(index) => repeatedWidget(),
)..add( //adding a widget at the final of each Column
Column(
children: List.generate(
/*if the current index is greater or equal to half of the number
of columns return it minus index - 1, else return the index*/
index >= columnsNumber / 2
? columnsNumber - index - 1
: index,
(index) => Container( //containers that will ocuppy the bottom space of each column
height: placeholderSize,
),
),
),
),
);
},
),
),
);
}
The result:

Text label that explains what a certain button in the app would do

I need to create a text label component that will act as an in-app tutorial explanatory label in Flutter. This text label should show up above a button explaining the clicking on that button would result in creating a new entry.
I tried using a Tooltip widget to wrap the button. But tooltip shows up only when long pressed the button. I need this widget to be visible if the user doesn't already have any entries created.
Currently, I thought of giving you this short but small help about this. I just want you to see whether it is helping you in any way or not.
My idea is to trigger the tooltip programatically. Let me know tell you how to do that, although there is no available documentation to do that. I did some research too on this.
Use ensureTooltipVisible from _TooltipState using a GlobalKey to fetch it.
Typically you'd do the following field inside the widget instantiating Tooltip : final key = new GlobalKey();
Then in your tooltip you will assign this key
final _key = new GlobalKey()
ToolTip(
key: _key
)
And in the initState() you just check whether there are some entries or not:
initState(){
super.initState();
if(_yourData == null){
//that's how you trigger it
final dynamic tooltip = _key.currentState;
tooltip.ensureTooltipVisible();
}
}
initState() is the method which gets initialized when you open up your page every time. So as soon as you will open up your page, the info will be checked and hence it will show accordingly. Let me know if that helps. :)
Can make using OverlayEntry.
Here is sample code
final overlayEntry = OverlayEntry(
builder: (BuildContext context) => Positioned(
top: _yourCustomTop,
left: _yourCustomLeft,
child: FadeTransition(
opacity: CurvedAnimation(
parent: _yourController,
curve: Curves.fastOutSlowIn,
),
child: Material(
color: const Color(0x00ffffff),
child: Container(
decoration: ShapeDecoration(
color: Colors.white,
shape: TooltipShapeBorder(radius: 6, arrowArc: 0.2),
shadows: [
const BoxShadow(
color: Colors.black26,
blurRadius: 4.0,
offset: const Offset(2, 2),
),
],
),
child: SizedBox(
width: 84,
height: 44,
child: Center(
child: Text('_yourString'),
),
),
),
);
),
),
);
Overlay.of(context).insert(_overlayEntry);
TooltipShapeBorder
from here.
import 'package:flutter/material.dart';
class TooltipShapeBorder extends ShapeBorder {
final double arrowWidth;
final double arrowHeight;
final double arrowArc;
final double radius;
TooltipShapeBorder({
this.radius = 16.0,
this.arrowWidth = 20.0,
this.arrowHeight = 10.0,
this.arrowArc = 0.0,
}) : assert(arrowArc <= 1.0 && arrowArc >= 0.0);
#override
EdgeInsetsGeometry get dimensions => EdgeInsets.only(bottom: arrowHeight);
#override
Path getInnerPath(Rect rect, {TextDirection textDirection}) => null;
#override
Path getOuterPath(Rect rect, {TextDirection textDirection}) {
rect = Rect.fromPoints(rect.topLeft, rect.bottomRight - Offset(0, arrowHeight));
double x = arrowWidth, y = arrowHeight, r = 1 - arrowArc;
return Path()
..addRRect(RRect.fromRectAndRadius(rect, Radius.circular(radius)))
..moveTo(rect.bottomCenter.dx + x / 2, rect.bottomCenter.dy)
..relativeLineTo(-x / 2 * r, y * r)
..relativeQuadraticBezierTo(-x / 2 * (1 - r), y * (1 - r), -x * (1 - r), 0)
..relativeLineTo(-x / 2 * r, -y * r);
}
#override
void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {}
#override
ShapeBorder scale(double t) => this;
}
You can already show a label quite easily like this:
const Tooltip(
message: 'Hi there!',
triggerMode: TooltipTriggerMode.tap, // ensures the label appears when tapped
preferBelow: false, // use this if you want the label above the widget
child: Icon(Icons.interests),
)
You can use triggerMode: TooltipTriggerMode.manual for further customization ;)
Or you could use the showcaseview package
You can find more info for the Tooltip widget here

Stack Alignment for an indeterminate number of children

I am building an app in flutter and part of what I am doing is fetching a list of actions from a server then building buttons for each action. I am trying to display the buttons in a circle and generate a list of raised button widgets. I am then trying to use a Stack to lay out this list in a circle, but cannot figure out how to use positioning to position objects in a circle when I do not know how many objects I will have (I know it will be in a range of about 3-15).
Here is the build method for my stack. Assume all methods and lists and variables are defined correctly and work. I am just concerned about the alignment
#override
Widget build(BuildContext context) {
List<Widget> actions = [];
for(var action in widget.actionsList){
actions.add(RaisedButton(
onPressed: () => _handleAction(action[0]),
shape: CircleBorder(),
child: Text(action[1] + ': ' + action[2]),
));
}
return Scaffold(
body: Center(
child: Stack(
children: actions,
alignment: //HELP,
),
),
);
}
If you have any ideas about how to do the alignment or another way to go about this to make a circle of buttons please let me know. I really want to make a circle, but am not wedded to it impossible (which I doubt) or super convoluted.
Thanks!
Here's how I would do it.
TL;DR;
final actionsList = [
"one",
"two",
"three",
"four",
"five",
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: LayoutBuilder(
builder: (context, constraints) => Stack(
children: layedOutButtons(
centerOfCircle:
Offset(constraints.maxWidth, constraints.maxHeight) / 2,
circleRadius: 100,
),
),
),
);
}
Offset getCoordinateFromAngle({double radius, double angle}) => Offset(
radius * sin(angle),
radius * cos(angle),
);
List<Widget> layedOutButtons({
Offset centerOfCircle,
double circleRadius,
}) {
var buttonRadius = 25.0;
var dispatchAngle = pi * 2 / actionsList.length;
List<Widget> widgets = [];
var i = 0;
for (var action in actionsList) {
var position = getCoordinateFromAngle(
radius: circleRadius,
angle: dispatchAngle * i++,
);
widgets.add(
Positioned(
top: centerOfCircle.dy - position.dy - buttonRadius,
left: centerOfCircle.dx + position.dx - buttonRadius,
width: buttonRadius * 2,
height: buttonRadius * 2,
child: FloatingActionButton(
child: Text(action),
onPressed: () {},
),
),
);
}
return widgets;
}
Explanation
If you want to dispatch a number of widgets on a circle, you have to:
define centerOfCircle, the position of the center of that circle. To do so, I use the LayoutBuilder widget to get the constraints of the layout and determine the center of the Stack => (width / 2, height / 2).
define circleRadius, the radius of that circle (in my example: 100)
Give those data to layedOutButtons that will dispatch widgets on the circle with the Positioned widget.
Widget build(BuildContext context) {
return Scaffold(
body: LayoutBuilder(
builder: (context, constraints) => Stack(
children: layedOutButtons(
centerOfCircle: Offset(constraints.maxWidth, constraints.maxHeight) / 2,
circleRadius: 100,
),
),
),
);
}
List<Widget> layedOutButtons({
Offset centerOfCircle,
double circleRadius,
}) {
List<Widget> widgets = [];
for (var action in actionsList) {
widgets.add(
Positioned(
child: FloatingActionButton(
child: Text(action),
onPressed: () {},
),
),
);
}
return widgets;
}
Now, you need to define how you will dispatch the widgets on the circle, based on their number. e.g, it there's 2 widgets, dispatch them at 180° from each other (meaning one on the top of the circle, one on the bottom. If there's 4, dispatch at 90° from each other, etc. Note that it is expressed in radians (not in degrees).
var dispatchAngle = pi * 2 / actionsList.length;
Then you have to define the coordinates (x, y) of a point on a circle based on an angle (look at this).
Offset getCoordinateFromAngle({double radius, double angle}) => Offset(
radius * sin(angle),
radius * cos(angle),
);
Use that to fill the top and left attributes of the Positioned widget.
List<Widget> layedOutButtons({
Offset centerOfCircle,
double circleRadius,
}) {
var dispatchAngle = pi * 2 / actionsList.length;
List<Widget> widgets = [];
var i = 0;
for (var action in actionsList) {
var position = getCoordinateFromAngle(
radius: circleRadius,
angle: dispatchAngle * i++, //increment angle for each widget
);
widgets.add(
Positioned(
top: centerOfCircle.dy - position.dy, //something's wrong here
left: centerOfCircle.dx + position.dx, //something's wrong here
child: FloatingActionButton(
child: Text(action),
onPressed: () {},
),
),
);
}
return widgets;
}
Now everything is almost ready except that there's a misalignement. It's due to the fact that the widgets are positioned based on their top left corners. We want to refine the positioning so it match the center of our widgets.
var buttonRadius = 25.0;
Positioned(
top: centerOfCircle.dy - position.dy - buttonRadius,
left: centerOfCircle.dx + position.dx - buttonRadius,
width: buttonRadius * 2,
height: buttonRadius * 2,
child: FloatingActionButton(
child: Text(action),
onPressed: () {},
),
),

How to create a dotted border around a box in flutter?

I am building a list of boxes layouts in my app using flutter. I want dotted border around the box. I have used card widget to create the boxes. But, how can I get dotted border around the boxes?
EDIT
I have added this as a package in pub.
Now, all you need to do is
DottedBorder(
color: Colors.black,
gap: 3,
strokeWidth: 1,
child: FlutterLogo(size: 148),
)
Working Solution [Outdated]
Like tomerpacific said in this answer, Flutter does not have a default implementation for dashed border at the moment.
I worked for some time yesterday and was able to come up with a solution using CustomPainter. Hope this helps someone.
Add the DashedRect to your container, like so
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Container(
height: 400,
width: 300,
color: Colors.black12,
child: DashedRect(color: Colors.red, strokeWidth: 2.0, gap: 3.0,),
),
),
);
}
}
DashedRect.dart
import 'package:flutter/material.dart';
import 'dart:math' as math;
class DashedRect extends StatelessWidget {
final Color color;
final double strokeWidth;
final double gap;
DashedRect(
{this.color = Colors.black, this.strokeWidth = 1.0, this.gap = 5.0});
#override
Widget build(BuildContext context) {
return Container(
child: Padding(
padding: EdgeInsets.all(strokeWidth / 2),
child: CustomPaint(
painter:
DashRectPainter(color: color, strokeWidth: strokeWidth, gap: gap),
),
),
);
}
}
class DashRectPainter extends CustomPainter {
double strokeWidth;
Color color;
double gap;
DashRectPainter(
{this.strokeWidth = 5.0, this.color = Colors.red, this.gap = 5.0});
#override
void paint(Canvas canvas, Size size) {
Paint dashedPaint = Paint()
..color = color
..strokeWidth = strokeWidth
..style = PaintingStyle.stroke;
double x = size.width;
double y = size.height;
Path _topPath = getDashedPath(
a: math.Point(0, 0),
b: math.Point(x, 0),
gap: gap,
);
Path _rightPath = getDashedPath(
a: math.Point(x, 0),
b: math.Point(x, y),
gap: gap,
);
Path _bottomPath = getDashedPath(
a: math.Point(0, y),
b: math.Point(x, y),
gap: gap,
);
Path _leftPath = getDashedPath(
a: math.Point(0, 0),
b: math.Point(0.001, y),
gap: gap,
);
canvas.drawPath(_topPath, dashedPaint);
canvas.drawPath(_rightPath, dashedPaint);
canvas.drawPath(_bottomPath, dashedPaint);
canvas.drawPath(_leftPath, dashedPaint);
}
Path getDashedPath({
#required math.Point<double> a,
#required math.Point<double> b,
#required gap,
}) {
Size size = Size(b.x - a.x, b.y - a.y);
Path path = Path();
path.moveTo(a.x, a.y);
bool shouldDraw = true;
math.Point currentPoint = math.Point(a.x, a.y);
num radians = math.atan(size.height / size.width);
num dx = math.cos(radians) * gap < 0
? math.cos(radians) * gap * -1
: math.cos(radians) * gap;
num dy = math.sin(radians) * gap < 0
? math.sin(radians) * gap * -1
: math.sin(radians) * gap;
while (currentPoint.x <= b.x && currentPoint.y <= b.y) {
shouldDraw
? path.lineTo(currentPoint.x, currentPoint.y)
: path.moveTo(currentPoint.x, currentPoint.y);
shouldDraw = !shouldDraw;
currentPoint = math.Point(
currentPoint.x + dx,
currentPoint.y + dy,
);
}
return path;
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
I do not expect this to fit in with all use cases and there is a lot of room for customization and improvement. Comment if you find any bugs.
You can use dotted_border Flutter package
return DottedBorder(
borderType: BorderType.RRect,
radius: Radius.circular(20),
dashPattern: [10, 10],
color: Colors.grey,
strokeWidth: 2,
child: Card(
color: Colors.amber,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
child: Center(child: Text("hi")),
)
There is an one plugin for draw dotted border around widgets
https://pub.dev/packages/dotted_border
Using this plugin you can draw dotted or dashed border
//1. Install the plugin by add dependencies in pubspace.yaml
dotted_border: ^1.0.6
Add below code for show border
DottedBorder(
color: Colors.black,
strokeWidth: 1,
child: FlutterLogo(size: 148),
)
BorderStyle.none can be useful if you wanna apply some animation or remove\add border function onTap(like a lighting border) event or similar.

How to make flutter app responsive according to different screen size?

I am facing difficulties to make it responsive according to various screen sizes. How to make it responsive?
#override
Widget build(BuildContext context) {
return new Container(
decoration: new BoxDecoration(color: Colors.white),
child: new Stack(
children: [
new Padding(
padding: const EdgeInsets.only(bottom: 350.0),
child: new GradientAppBar(" "),
),
new Positioned(
bottom: 150.0,
height: 260.0,
left: 10.0,
right: 10.0,
child: new Padding(
padding: new EdgeInsets.all(10.0),
child: new Card(
child: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const ListTile(
title: const Text(
'LOGIN',
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 16.50,
fontFamily: "Helvetica",
fontWeight: FontWeight.bold,
color: Colors.black87,
letterSpacing: 1.00,
),
),
),
new ListTile(
leading: const Icon(Icons.person),
title: new TextField(
controller: _user1,
decoration: new InputDecoration(
labelText: ' Enter a username'),
),
),
new ListTile(
leading: const Icon(Icons.person_pin),
title: new TextField(
controller: _pass1,
decoration: new InputDecoration(
labelText: ' Enter a password'),
obscureText: true,
),
),
],
),
),
),
),
new Positioned(
bottom: 70.0,
left: 15.0,
right: 05.0,
child: new ButtonTheme.bar(
// make buttons use the appropriate styles for cards
child: new ButtonBar(
children: <Widget>[
new FlatButton(
padding: new EdgeInsets.only(right: 13.0),
child: new Text(
'REGISTER HERE',
style: new TextStyle(
color: Colors.black87,
fontFamily: "Helvetica",
fontSize: 15.00,
fontWeight: FontWeight.bold),
),
onPressed: () {
Navigator.of(context).pushNamed('/facebook');
},
),
new FlatButton(
padding: new EdgeInsets.only(right: 22.0),
child: new Text(
'FORGOT PASSWORD?',
style: new TextStyle(
color: Colors.black87,
fontFamily: "Helvetica",
fontSize: 15.00,
fontWeight: FontWeight.bold),
),
onPressed: () {
Navigator.of(context).pushNamed('/Forgot');
},
),
],
),
),
),
new Positioned(
bottom: 73.0,
height: 180.0,
left: 20.0,
right: 52.0,
child: new Padding(
padding: new EdgeInsets.all(0.00),
child: new ButtonTheme(
minWidth: 10.0,
height: 20.0,
padding: new EdgeInsets.only(right: 37.0),
child: new ButtonBar(children: <Widget>[
new CupertinoButton(
borderRadius:
const BorderRadius.all(const Radius.circular(36.0)),
padding: new EdgeInsets.only(left: 70.0),
color: const Color(0xFF426DB7),
child: new Text(
" LOGIN ",
style: new TextStyle(
color: Colors.white,
fontSize: 12.50,
fontFamily: "Handwriting",
fontWeight: FontWeight.w500,
letterSpacing: 0.00),
),
onPressed: () {})
]),
),
),
),
],
),
);
}
}
Using MediaQuery class:
MediaQueryData queryData;
queryData = MediaQuery.of(context);
MediaQuery: Establishes a subtree in which media queries resolve
to the given data.
MediaQueryData: Information about a piece of media (e.g., a
window).
To get Device Pixel Ratio:
queryData.devicePixelRatio
To get width and height of the device screen:
queryData.size.width
queryData.size.height
To get text scale factor:
queryData.textScaleFactor
Using AspectRatio class:
From doc:
A widget that attempts to size the child to a specific aspect ratio.
The widget first tries the largest width permitted by the layout
constraints. The height of the widget is determined by applying the
given aspect ratio to the width, expressed as a ratio of width to
height.
For example, a 16:9 width:height aspect ratio would have a value of
16.0/9.0. If the maximum width is infinite, the initial width is determined by applying the aspect ratio to the maximum height.
Now consider a second example, this time with an aspect ratio of 2.0
and layout constraints that require the width to be between 0.0 and
100.0 and the height to be between 0.0 and 100.0. We'll select a width of 100.0 (the biggest allowed) and a height of 50.0 (to match the
aspect ratio).
//example
new Center(
child: new AspectRatio(
aspectRatio: 100 / 100,
child: new Container(
decoration: new BoxDecoration(
shape: BoxShape.rectangle,
color: Colors.orange,
)
),
),
),
Also you can use:
LayoutBuilder
FittedBox
CustomMultiChildLayout
This class will help and then initialize the class with the init method.
import 'package:flutter/widgets.dart';
class SizeConfig {
static MediaQueryData _mediaQueryData;
static double screenWidth;
static double screenHeight;
static double blockSizeHorizontal;
static double blockSizeVertical;
static double _safeAreaHorizontal;
static double _safeAreaVertical;
static double safeBlockHorizontal;
static double safeBlockVertical;
void init(BuildContext context){
_mediaQueryData = MediaQuery.of(context);
screenWidth = _mediaQueryData.size.width;
screenHeight = _mediaQueryData.size.height;
blockSizeHorizontal = screenWidth/100;
blockSizeVertical = screenHeight/100;
_safeAreaHorizontal = _mediaQueryData.padding.left +
_mediaQueryData.padding.right;
_safeAreaVertical = _mediaQueryData.padding.top +
_mediaQueryData.padding.bottom;
safeBlockHorizontal = (screenWidth - _safeAreaHorizontal)/100;
safeBlockVertical = (screenHeight - _safeAreaVertical)/100;
}
}
then in your widgets dimension do this
Widget build(BuildContext context) {
SizeConfig().init(context);
return Container(
height: SizeConfig.safeBlockVertical * 10, //10 for example
width: SizeConfig.safeBlockHorizontal * 10, //10 for example
);}
All the credits to this post author:
https://medium.com/flutter-community/flutter-effectively-scale-ui-according-to-different-screen-sizes-2cb7c115ea0a
Easiest way to make responsive UI for different screen size is Sizer plugin.
Make responsive UI in any screen size device also tablet. Check it this plugin ⬇️
https://pub.dev/packages/sizer
.h - for widget height
.w - for widget width
.sp - for font size
Use .h, .w, .sp after value like this ⬇️
Example:
Container(
height: 10.0.h, //10% of screen height
width: 80.0.w, //80% of screen width
child: Text('Sizer', style: TextStyle(fontSize: 12.0.sp)),
);
I have build many responsive App with this plugin.
What i do is to take screen width and height and calculate a grid of 100*100 out of it to position and scale things and save it as static variables that can be reused. Works quite good in most cases. Like this:
AppConfig.width = MediaQuery.of(context).size.width;
AppConfig.height = MediaQuery.of(context).size.height;
AppConfig.blockSize = AppConfig.width / 100;
AppConfig.blockSizeVertical = AppConfig.height / 100;
Then i scale everything according to these values, like this:
double elementWidth = AppConfig.blockSize * 10.0; // 10% of the screen width
or
double fontSize = AppConfig.blockSize * 1.2;
Sometimes the safe area (notch, etc) kills a layout, so you can consider this, too:
AppConfig.safeAreaHorizontal = MediaQuery.of(context).padding.left +
MediaQuery.of(context).padding.right;
double screenWidthWithoutSafeArea = AppConfig.width - AppConfig.safeAreaHorizontal;
This worked great on some recent projects.
Check MediaQuery class
For example, to learn the size of the current media (e.g., the window containing your app), you can read the MediaQueryData.size property from the MediaQueryData returned by MediaQuery.of: MediaQuery.of(context).size.
So you can do the following:
new Container(
height: MediaQuery.of(context).size.height/2,
.. )
You can take a percentage of the width or height as input for scale size.
fontSize: MediaQuery.of(_ctxt).size.height * 0.065
Where the multiplier at the end has a value that makes the Text look good for the active emulator.
Below is how I set it up so all the scaled dimensions are centralized in one place. This way you can adjust them easily and quickly rerun with Hot Reload without having to look for the Media.of() calls throughout the code.
Create the file to store all the mappings appScale.dart
class AppScale {
BuildContext _ctxt;
AppScale(this._ctxt);
double get labelDim => scaledWidth(.04);
double get popupMenuButton => scaledHeight(.065);
double scaledWidth(double widthScale) {
return MediaQuery.of(_ctxt).size.width * widthScale;
}
double scaledHeight(double heightScale) {
return MediaQuery.of(_ctxt).size.height * heightScale;
}
}
Then reference that where ever you need the scaled value
AppScale _scale = AppScale(context);
// ...
Widget label1 = Text(
"Some Label",
style: TextStyle(fontSize: _scale.labelDim),
);
Thanks to answers in this post
After much research and testing, I have developed a solution for an app I'm currently converting from Android/iOS to Flutter.
With Android and iOS I used a 'Scaling Factor' applied to base font sizes, rendering text sizes that were relative to the screen size.
This article was very helpful: https://medium.com/flutter-community/flutter-effectively-scale-ui-according-to-different-screen-sizes-2cb7c115ea0a
I created a StatelessWidget to get the font sizes of the Material Design typographical styles. Getting device dimensions using MediaQuery, calculating a scaling factor, then resetting the Material Design text sizes. The Widget can be used to define a custom Material Design Theme.
Emulators used:
Pixel C - 9.94" Tablet
Pixel 3 - 5.46" Phone
iPhone 11 Pro Max - 5.8" Phone
With standard font sizes
With scaled font sizes
set_app_theme.dart (SetAppTheme Widget)
import 'package:flutter/material.dart';
import 'dart:math';
class SetAppTheme extends StatelessWidget {
final Widget child;
SetAppTheme({this.child});
#override
Widget build(BuildContext context) {
final _divisor = 400.0;
final MediaQueryData _mediaQueryData = MediaQuery.of(context);
final _screenWidth = _mediaQueryData.size.width;
final _factorHorizontal = _screenWidth / _divisor;
final _screenHeight = _mediaQueryData.size.height;
final _factorVertical = _screenHeight / _divisor;
final _textScalingFactor = min(_factorVertical, _factorHorizontal);
final _safeAreaHorizontal = _mediaQueryData.padding.left + _mediaQueryData.padding.right;
final _safeFactorHorizontal = (_screenWidth - _safeAreaHorizontal) / _divisor;
final _safeAreaVertical = _mediaQueryData.padding.top + _mediaQueryData.padding.bottom;
final _safeFactorVertical = (_screenHeight - _safeAreaVertical) / _divisor;
final _safeAreaTextScalingFactor = min(_safeFactorHorizontal, _safeFactorHorizontal);
print('Screen Scaling Values:' + '_screenWidth: $_screenWidth');
print('Screen Scaling Values:' + '_factorHorizontal: $_factorHorizontal ');
print('Screen Scaling Values:' + '_screenHeight: $_screenHeight');
print('Screen Scaling Values:' + '_factorVertical: $_factorVertical ');
print('_textScalingFactor: $_textScalingFactor ');
print('Screen Scaling Values:' + '_safeAreaHorizontal: $_safeAreaHorizontal ');
print('Screen Scaling Values:' + '_safeFactorHorizontal: $_safeFactorHorizontal ');
print('Screen Scaling Values:' + '_safeAreaVertical: $_safeAreaVertical ');
print('Screen Scaling Values:' + '_safeFactorVertical: $_safeFactorVertical ');
print('_safeAreaTextScalingFactor: $_safeAreaTextScalingFactor ');
print('Default Material Design Text Themes');
print('display4: ${Theme.of(context).textTheme.display4}');
print('display3: ${Theme.of(context).textTheme.display3}');
print('display2: ${Theme.of(context).textTheme.display2}');
print('display1: ${Theme.of(context).textTheme.display1}');
print('headline: ${Theme.of(context).textTheme.headline}');
print('title: ${Theme.of(context).textTheme.title}');
print('subtitle: ${Theme.of(context).textTheme.subtitle}');
print('body2: ${Theme.of(context).textTheme.body2}');
print('body1: ${Theme.of(context).textTheme.body1}');
print('caption: ${Theme.of(context).textTheme.caption}');
print('button: ${Theme.of(context).textTheme.button}');
TextScalingFactors _textScalingFactors = TextScalingFactors(
display4ScaledSize: (Theme.of(context).textTheme.display4.fontSize * _safeAreaTextScalingFactor),
display3ScaledSize: (Theme.of(context).textTheme.display3.fontSize * _safeAreaTextScalingFactor),
display2ScaledSize: (Theme.of(context).textTheme.display2.fontSize * _safeAreaTextScalingFactor),
display1ScaledSize: (Theme.of(context).textTheme.display1.fontSize * _safeAreaTextScalingFactor),
headlineScaledSize: (Theme.of(context).textTheme.headline.fontSize * _safeAreaTextScalingFactor),
titleScaledSize: (Theme.of(context).textTheme.title.fontSize * _safeAreaTextScalingFactor),
subtitleScaledSize: (Theme.of(context).textTheme.subtitle.fontSize * _safeAreaTextScalingFactor),
body2ScaledSize: (Theme.of(context).textTheme.body2.fontSize * _safeAreaTextScalingFactor),
body1ScaledSize: (Theme.of(context).textTheme.body1.fontSize * _safeAreaTextScalingFactor),
captionScaledSize: (Theme.of(context).textTheme.caption.fontSize * _safeAreaTextScalingFactor),
buttonScaledSize: (Theme.of(context).textTheme.button.fontSize * _safeAreaTextScalingFactor));
return Theme(
child: child,
data: _buildAppTheme(_textScalingFactors),
);
}
}
final ThemeData customTheme = ThemeData(
primarySwatch: appColorSwatch,
// fontFamily: x,
);
final MaterialColor appColorSwatch = MaterialColor(0xFF3787AD, appSwatchColors);
Map<int, Color> appSwatchColors =
{
50 : Color(0xFFE3F5F8),
100 : Color(0xFFB8E4ED),
200 : Color(0xFF8DD3E3),
300 : Color(0xFF6BC1D8),
400 : Color(0xFF56B4D2),
500 : Color(0xFF48A8CD),
600 : Color(0xFF419ABF),
700 : Color(0xFF3787AD),
800 : Color(0xFF337799),
900 : Color(0xFF285877),
};
_buildAppTheme (TextScalingFactors textScalingFactors) {
return customTheme.copyWith(
accentColor: appColorSwatch[300],
buttonTheme: customTheme.buttonTheme.copyWith(buttonColor: Colors.grey[500],),
cardColor: Colors.white,
errorColor: Colors.red,
inputDecorationTheme: InputDecorationTheme(border: OutlineInputBorder(),),
primaryColor: appColorSwatch[700],
primaryIconTheme: customTheme.iconTheme.copyWith(color: appColorSwatch),
scaffoldBackgroundColor: Colors.grey[100],
textSelectionColor: appColorSwatch[300],
textTheme: _buildAppTextTheme(customTheme.textTheme, textScalingFactors),
appBarTheme: customTheme.appBarTheme.copyWith(
textTheme: _buildAppTextTheme(customTheme.textTheme, textScalingFactors)),
// accentColorBrightness: ,
// accentIconTheme: ,
// accentTextTheme: ,
// appBarTheme: ,
// applyElevationOverlayColor: ,
// backgroundColor: ,
// bannerTheme: ,
// bottomAppBarColor: ,
// bottomAppBarTheme: ,
// bottomSheetTheme: ,
// brightness: ,
// buttonBarTheme: ,
// buttonColor: ,
// canvasColor: ,
// cardTheme: ,
// chipTheme: ,
// colorScheme: ,
// cupertinoOverrideTheme: ,
// cursorColor: ,
// dialogBackgroundColor: ,
// dialogTheme: ,
// disabledColor: ,
// dividerColor: ,
// dividerTheme: ,
// floatingActionButtonTheme: ,
// focusColor: ,
// highlightColor: ,
// hintColor: ,
// hoverColor: ,
// iconTheme: ,
// indicatorColor: ,
// materialTapTargetSize: ,
// pageTransitionsTheme: ,
// platform: ,
// popupMenuTheme: ,
// primaryColorBrightness: ,
// primaryColorDark: ,
// primaryColorLight: ,
// primaryTextTheme: ,
// secondaryHeaderColor: ,
// selectedRowColor: ,
// sliderTheme: ,
// snackBarTheme: ,
// splashColor: ,
// splashFactory: ,
// tabBarTheme: ,
// textSelectionHandleColor: ,
// toggleableActiveColor: ,
// toggleButtonsTheme: ,
// tooltipTheme: ,
// typography: ,
// unselectedWidgetColor: ,
);
}
class TextScalingFactors {
final double display4ScaledSize;
final double display3ScaledSize;
final double display2ScaledSize;
final double display1ScaledSize;
final double headlineScaledSize;
final double titleScaledSize;
final double subtitleScaledSize;
final double body2ScaledSize;
final double body1ScaledSize;
final double captionScaledSize;
final double buttonScaledSize;
TextScalingFactors({
#required this.display4ScaledSize,
#required this.display3ScaledSize,
#required this.display2ScaledSize,
#required this.display1ScaledSize,
#required this.headlineScaledSize,
#required this.titleScaledSize,
#required this.subtitleScaledSize,
#required this.body2ScaledSize,
#required this.body1ScaledSize,
#required this.captionScaledSize,
#required this.buttonScaledSize
});
}
TextTheme _buildAppTextTheme(
TextTheme _customTextTheme,
TextScalingFactors _scaledText) {
return _customTextTheme.copyWith(
display4: _customTextTheme.display4.copyWith(fontSize: _scaledText.display4ScaledSize),
display3: _customTextTheme.display3.copyWith(fontSize: _scaledText.display3ScaledSize),
display2: _customTextTheme.display2.copyWith(fontSize: _scaledText.display2ScaledSize),
display1: _customTextTheme.display1.copyWith(fontSize: _scaledText.display1ScaledSize),
headline: _customTextTheme.headline.copyWith(fontSize: _scaledText.headlineScaledSize),
title: _customTextTheme.title.copyWith(fontSize: _scaledText.titleScaledSize),
subtitle: _customTextTheme.subtitle.copyWith(fontSize: _scaledText.subtitleScaledSize),
body2: _customTextTheme.body2.copyWith(fontSize: _scaledText.body2ScaledSize),
body1: _customTextTheme.body1.copyWith(fontSize: _scaledText.body1ScaledSize),
caption: _customTextTheme.caption.copyWith(fontSize: _scaledText.captionScaledSize),
button: _customTextTheme.button.copyWith(fontSize: _scaledText.buttonScaledSize),
).apply(bodyColor: Colors.black);
}
main.dart (Demo App)
import 'package:flutter/material.dart';
import 'package:scaling/set_app_theme.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SetAppTheme(child: HomePage()),
);
}
}
class HomePage extends StatelessWidget {
final demoText = '0123456789';
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text('Text Scaling with SetAppTheme',
style: TextStyle(color: Colors.white),),
),
body: SingleChildScrollView(
child: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
Text(
demoText,
style: TextStyle(
fontSize: Theme.of(context).textTheme.display4.fontSize,
),
),
Text(
demoText,
style: TextStyle(
fontSize: Theme.of(context).textTheme.display3.fontSize,
),
),
Text(
demoText,
style: TextStyle(
fontSize: Theme.of(context).textTheme.display2.fontSize,
),
),
Text(
demoText,
style: TextStyle(
fontSize: Theme.of(context).textTheme.display1.fontSize,
),
),
Text(
demoText,
style: TextStyle(
fontSize: Theme.of(context).textTheme.headline.fontSize,
),
),
Text(
demoText,
style: TextStyle(
fontSize: Theme.of(context).textTheme.title.fontSize,
),
),
Text(
demoText,
style: TextStyle(
fontSize: Theme.of(context).textTheme.subtitle.fontSize,
),
),
Text(
demoText,
style: TextStyle(
fontSize: Theme.of(context).textTheme.body2.fontSize,
),
),
Text(
demoText,
style: TextStyle(
fontSize: Theme.of(context).textTheme.body1.fontSize,
),
),
Text(
demoText,
style: TextStyle(
fontSize: Theme.of(context).textTheme.caption.fontSize,
),
),
Text(
demoText,
style: TextStyle(
fontSize: Theme.of(context).textTheme.button.fontSize,
),
),
],
),
),
),
),
),
);
}
}
Place dependency in pubspec.yaml
flutter_responsive_screen: ^1.0.0
Function hp = Screen(MediaQuery.of(context).size).hp;
Function wp = Screen(MediaQuery.of(context).size).wp;
Example :
return Container(height: hp(27),weight: wp(27));
I've been knocking other people's (#datayeah & Vithani Ravi) solutions a bit hard here, so I thought I'd share my own attempt[s] at solving this variable screen density scaling problem or shut up. So I approach this problem from a solid/fixed foundation: I base all my scaling off a fixed (immutable) ratio of 2:1 (height:width). I have a helper class "McGyver" that does all the heavy lifting (and useful code finessing) across my app. This "McGyver" class contains only static methods and static constant class members.
RATIO SCALING METHOD: I scale both width & height independently based on the 2:1 Aspect Ratio. I take width & height input values and divide each by the width & height constants and finally compute an adjustment factor by which to scale the respective width & height input values. The actual code looks as follows:
import 'dart:math';
import 'package:flutter/material.dart';
class McGyver {
static const double _fixedWidth = 410; // Set to an Aspect Ratio of 2:1 (height:width)
static const double _fixedHeight = 820; // Set to an Aspect Ratio of 2:1 (height:width)
// Useful rounding method (#andyw solution -> https://stackoverflow.com/questions/28419255/how-do-you-round-a-double-in-dart-to-a-given-degree-of-precision-after-the-decim/53500405#53500405)
static double roundToDecimals(double val, int decimalPlaces){
double mod = pow(10.0, decimalPlaces);
return ((val * mod).round().toDouble() / mod);
}
// The 'Ratio-Scaled' Widget method (takes any generic widget and returns a "Ratio-Scaled Widget" - "rsWidget")
static Widget rsWidget(BuildContext ctx, Widget inWidget, double percWidth, double percHeight) {
// ---------------------------------------------------------------------------------------------- //
// INFO: Ratio-Scaled "SizedBox" Widget - Scaling based on device's height & width at 2:1 ratio. //
// ---------------------------------------------------------------------------------------------- //
final int _decPlaces = 5;
final double _fixedWidth = McGyver._fixedWidth;
final double _fixedHeight = McGyver._fixedHeight;
Size _scrnSize = MediaQuery.of(ctx).size; // Extracts Device Screen Parameters.
double _scrnWidth = _scrnSize.width.floorToDouble(); // Extracts Device Screen maximum width.
double _scrnHeight = _scrnSize.height.floorToDouble(); // Extracts Device Screen maximum height.
double _rsWidth = 0;
if (_scrnWidth == _fixedWidth) { // If input width matches fixedWidth then do normal scaling.
_rsWidth = McGyver.roundToDecimals((_scrnWidth * (percWidth / 100)), _decPlaces);
} else { // If input width !match fixedWidth then do adjustment factor scaling.
double _scaleRatioWidth = McGyver.roundToDecimals((_scrnWidth / _fixedWidth), _decPlaces);
double _scalerWidth = ((percWidth + log(percWidth + 1)) * pow(1, _scaleRatioWidth)) / 100;
_rsWidth = McGyver.roundToDecimals((_scrnWidth * _scalerWidth), _decPlaces);
}
double _rsHeight = 0;
if (_scrnHeight == _fixedHeight) { // If input height matches fixedHeight then do normal scaling.
_rsHeight = McGyver.roundToDecimals((_scrnHeight * (percHeight / 100)), _decPlaces);
} else { // If input height !match fixedHeight then do adjustment factor scaling.
double _scaleRatioHeight = McGyver.roundToDecimals((_scrnHeight / _fixedHeight), _decPlaces);
double _scalerHeight = ((percHeight + log(percHeight + 1)) * pow(1, _scaleRatioHeight)) / 100;
_rsHeight = McGyver.roundToDecimals((_scrnHeight * _scalerHeight), _decPlaces);
}
// Finally, hand over Ratio-Scaled "SizedBox" widget to method call.
return SizedBox(
width: _rsWidth,
height: _rsHeight,
child: inWidget,
);
}
}
... ... ...
Then you would individually scale your widgets (which for my perfectionist disease is ALL of my UI) with a simple static call to the "rsWidget()" method as follows:
// Step 1: Define your widget however you like (this widget will be supplied as the "inWidget" arg to the "rsWidget" method in Step 2)...
Widget _btnLogin = RaisedButton(color: Colors.blue, elevation: 9.0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(McGyver.rsDouble(context, ScaleType.width, 2.5))),
child: McGyver.rsText(context, "LOGIN", percFontSize: EzdFonts.button2_5, textColor: Colors.white, fWeight: FontWeight.bold),
onPressed: () { _onTapBtnLogin(_tecUsrId.text, _tecUsrPass.text); }, );
// Step 2: Scale your widget by calling the static "rsWidget" method...
McGyver.rsWidget(context, _btnLogin, 34.5, 10.0) // ...and Bob's your uncle!!
The cool thing is that the "rsWidget()" method returns a widget!! So you can either assign the scaled widget to another variable like _rsBtnLogin for use all over the place - or you could simply use the full McGyver.rsWidget() method call in-place inside your build() method (exactly how you need it to be positioned in the widget tree) and it will work perfectly as it should.
For those more astute coders: you will have noticed that I used two additional ratio-scaled methods McGyver.rsText() and McGyver.rsDouble() (not defined in the code above) in my RaisedButton() - so I basically go crazy with this scaling stuff...because I demand my apps to be absolutely pixel perfect at any scale or screen density!! I ratio-scale my ints, doubles, padding, text (everything that requires UI consistency across devices). I scale my texts based on width only, but specify which axis to use for all other scaling (as was done with the ScaleType.width enum used for the McGyver.rsDouble() call in the code example above).
I know this is crazy - and is a lot of work to do on the main thread - but I am hoping somebody will see my attempt here and help me find a better (more light-weight) solution to my screen density 1:1 scaling nightmares.
An Another approach :) easier for flutter web
class SampleView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: Container(
width: 200,
height: 200,
color: Responsive().getResponsiveValue(
forLargeScreen: Colors.red,
forTabletScreen : Colors.pink,
forMediumScreen: Colors.green,
forShortScreen: Colors.yellow,
forMobLandScapeMode: Colors.blue,
context: context),
// You dodn't need to provide the values for every
//parameter(except shortScreen & context)
// but default its provide the value as ShortScreen for Larger and
//mediumScreen
),
);
}
}
utility :
import 'package:flutter/widgets.dart';
class Responsive {
// function reponsible for providing value according to screensize
getResponsiveValue(
{dynamic forShortScreen,
dynamic forMediumScreen,
dynamic forLargeScreen,
dynamic forMobLandScapeMode,
dynamic forTabletScreen,
BuildContext context}) {
if (isLargeScreen(context)) {
return forLargeScreen ?? forShortScreen;
} else if (isMediumScreen(context)) {
return forMediumScreen ?? forShortScreen;
}
else if (isTabletScreen(context)) {
return forTabletScreen ?? forMediumScreen ?? forShortScreen;
}
else if (isSmallScreen(context) && isLandScapeMode(context)) {
return forMobLandScapeMode ?? forShortScreen;
} else {
return forShortScreen;
}
}
isLandScapeMode(BuildContext context) {
if (MediaQuery.of(context).orientation == Orientation.landscape) {
return true;
} else {
return false;
}
}
static bool isLargeScreen(BuildContext context) {
return getWidth(context) > 1200;
}
static bool isSmallScreen(BuildContext context) {
return getWidth(context) < 800;
}
static bool isMediumScreen(BuildContext context) {
return getWidth(context) > 800 && getWidth(context) < 1200;
}
static bool isTabletScreen(BuildContext context) {
return getWidth(context) > 450 && getWidth(context) < 800;
}
static double getWidth(BuildContext context) {
return MediaQuery.of(context).size.width;
}
}
My approach to the problem is similar to the way datayeah did it. I had a lot of hardcoded width and height values and the app looked fine on a specific device. So I got the screen height of the device and just created a factor to scale the hardcoded values.
double heightFactor = MediaQuery.of(context).size.height/708
where 708 is the height of the specific device.
Used ResponsiveBuilder or
ScreenTypeLayout
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:responsive_builder/responsive_builder.dart';
class Sample extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0,
backgroundColor: Colors.black,
),
body: ResponsiveBuilder(
builder: (context, info) {
var screenType = info.deviceScreenType;
String _text;
switch (screenType){
case DeviceScreenType.desktop: {
_text = 'Desktop';
break;
}
case DeviceScreenType.tablet: {
_text = 'Tablet';
break;
}
case DeviceScreenType.mobile: {
_text = 'Mobile';
break;
}
case DeviceScreenType.watch: {
_text = 'Watch';
break;
}
default:
return null;
}
return Center(child: Text(_text, style: TextStyle(fontSize: 32, color: Colors.black),));
},
),
);
}
}
// screen type layout
ScreenTypeLayout.builder(
mobile: MobilePage(),
tablet: TabletPage(),
desktop: DesktopPage(),
watch: Watchpage(),
);
double height, width;
height = MediaQuery.of(context).size.height;
width = MediaQuery.of(context).size.width;
Container(
height: height * 0.3,
width: width * 0.2,
child: PriorityWidget(
priorityLevel: "High",
conBackColor: ColorConstants.kMediumRedColor,
textColor: ColorConstants.kWhiteColor,
borderColor: selectedPriority == Constants.HIGH_PRIORITY ?
ColorConstants.kWhiteColor : ColorConstants.kMediumRedColor,
),
),
The Container will take 3% height of a Total Screen Height and 2% width of a Screen width
create file name (app_config.dart) in folder name(responsive_screen) in lib folder:
import 'package:flutter/material.dart';
class AppConfig {
BuildContext _context;
double _height;
double _width;
double _heightPadding;
double _widthPadding;
AppConfig(this._context) {
MediaQueryData _queryData = MediaQuery.of(_context);
_height = _queryData.size.height / 100.0;
_width = _queryData.size.width / 100.0;
_heightPadding =
_height - ((_queryData.padding.top + _queryData.padding.bottom) / 100.0);
_widthPadding =
_width - (_queryData.padding.left + _queryData.padding.right) / 100.0;
}
double rH(double v) {
return _height * v;
}
double rW(double v) {
return _width * v;
}
double rHP(double v) {
return _heightPadding * v;
}
double rWP(double v) {
return _widthPadding * v;
}
}
then:
import 'responsive_screen/app_config.dart';
...
class RandomWordsState extends State<RandomWords> {
AppConfig _ac;
...
#override
Widget build(BuildContext context) {
_ac = AppConfig(context);
...
return Scaffold(
body: Container(
height: _ac.rHP(50),
width: _ac.rWP(50),
color: Colors.red,
child: Text('Test'),
),
);
...
}
This issue can be solved using MediaQuery.of(context)
To get Screen width: MediaQuery.of(context).size.width
To get Screen height: MediaQuery.of(context).size.height
For more information about MediaQuery Widget watch,
https://www.youtube.com/watch?v=A3WrA4zAaPw
You can use MediaQuery for parent's dimension or FractionallySizedBox as containers.
In flutter 2.0 use this code under Widget build(BuildContext context)
check out this page from flutter wiki :
Creating Responsive Apps
Use the LayoutBuilder class: From its builder property, you get a
BoxConstraints. Examine the constraint's properties to decide what to
display. For example, if your maxWidth is greater than your width
breakpoint, return a Scaffold object with a row that has a list on the
left. If it's narrower, return a Scaffold object with a drawer
containing that list. You can also adjust your display based on the
device's height, the aspect ratio, or some other property. When the
constraints change (e.g. the user rotates the phone, or puts your app
into a tile UI in Nougat), the build function will rerun.
padding: EdgeInsets.only(
left: 4.0,
right: ResponsiveWidget.isSmallScreen(context) ? 4: 74, //Check for screen type
top: 10,
bottom: 40),
This is fine by Google's recommendation but may be not perfect.
Width: MediaQuery.of(context).size.width,
Height: MediaQuery.of(context).size.height,
Instead of writing Ui for multiple screen sizes you can write Ui only once with the help of this package flutter_next And it has some cool extension for rapid UI Developement and the documentation is also good.
They even have an example which is made with this package\n
Link: https://one-page-with-flutter.netlify.app/
For the clarification of #user10768752 answer,
static double screenWidth gives you some error, so you have to initialize your data like this below
import 'package:flutter/widgets.dart';
class SizeConfig{
static MediaQueryData _mediaQueryData = MediaQueryData();
static double screenWidth = 0;
static double screenHeight = 0;
static double blockSizeHorizontal = 0;
static double blockSizeVertical = 0;
static double _safeAreaHorizontal = 0;
static double _safeAreaVertical = 0;
static double safeBlockHorizontal = 0;
static double safeBlockVertical = 0;
void init(BuildContext context){
_mediaQueryData = MediaQuery.of(context);
screenWidth = _mediaQueryData.size.width;
screenHeight = _mediaQueryData.size.height;
blockSizeHorizontal = screenWidth/100;
blockSizeVertical = screenHeight/100;
_safeAreaHorizontal = _mediaQueryData.padding.left +
_mediaQueryData.padding.right;
_safeAreaVertical = _mediaQueryData.padding.top +
_mediaQueryData.padding.bottom;
safeBlockHorizontal = (screenWidth - _safeAreaHorizontal)/100;
safeBlockVertical = (screenHeight - _safeAreaVertical)/100;
}
}
And do not forget to initialize this.
#override
Widget build(BuildContext context){
SizeConfig().init(context);
return Sizebox(
width: SizeConfig.safeBlockVertical * 8,
height: SizeConfig.safeBlockVertical * 8, <-- change value depends on your usecase
);
}
An Easier Approach for Mobile Responsiveness. Basically, these functions automatically calculate the height and width percentage.
import 'dart:developer';
import 'package:flutter/material.dart';
double getMediaQueryHeight(
{required BuildContext context, required num value}) {
var size = MediaQuery.of(context).size;
//TODO Mention you Adobe Xd, Figma Height
double xdHeight = 812;
double percentage = (value / xdHeight * 100).roundToDouble() / 100;
log("height percentage : ${percentage}");
return size.height * percentage;
}
double getMediaQueryWidth({required BuildContext context, required num value}) {
var size = MediaQuery.of(context).size;
//TODO Mention you Adobe Xd, Figma width
double xdWidth = 375;
double percentage = (value / xdWidth * 100).roundToDouble() / 100;
log("width percentage : ${percentage}");
return size.width * percentage;
}
// USE CASE
Container(
color: Colors.redAccent,
alignment: Alignment.center,
height: getMediaQueryHeight(context: context, value: 232),
width: getMediaQueryWidth(context: context, value: 345),
),
Approach without context:
USECASE
Container(
height: Responsive.setHeight(value: 70),
width: Responsive.setWidth(value: 150),
),
Initialize after MaterialApp wrap:
Responsive.size = MediaQuery.of(context).size;
Utility
import 'package:flutter/material.dart';
class Responsive {
static late Size size;
//TODO assign your Xd or Figma height and width
static double xdHeight = 812;
static double xdWidth = 375;
static double setHeight({required num value}) {
double percentage = (value / xdHeight * 100).roundToDouble() / 100;
return size.height * percentage;
}
static double setWidth({required num value}) {
double percentage = (value / xdWidth * 100).roundToDouble() / 100;
return size.width * percentage;
}
}
You can use responsive_helper package to make your app responsive.
It's a very easy method to make your app responsive. Just take a look at the example page and then you'll figure it out how to use it.