flutter specifying width, height, paddings, margins etc in percentages? - flutter

Is there a way in flutter which allows the widths, heights, paddings, margins etc being specified in percentages of screen dimensions instead of writing calculations in every widget?
Passing around a provider or extending some base class for this trivial thing is not feeling right.

The only known solution is to use MediaQuery. I implemented a helper class that I just call the methods for:
class SizeManager {
var _context;
double _screenHeight;
double _screenWidth;
SizeManager(this._context) {
_screenHeight = MediaQuery.of(_context).size.height;
_screenWidth = MediaQuery.of(_context).size.width;
}
double scaledHeight(double value) {
return value * _screenHeight / 100;
}
double scaledWidth(double value) {
return value * _screenWidth / 100;
}
}
In every build(), just before the return, call it like:
SizeManager sizeManager = new SizeManager(context);
And whenever you want to set margins/padding/size:
Container(
padding: EdgeInsets.symmetric(vertical: sizeManager.scaledHeight(2.5)), //Gives a 2.5 % height padding
),

Related

Real height in flutter?

I am trying to retrieve the real height in Flutter. I tried different options:
MediaQuery.of(context).size.height * MediaQuery.of(context).devicePixelRatio
or
WidgetsBinding.instance.window.physicalSize.height
I add these 2 lines in a new Flutter app (from scratch, just the new counter app screen that flutter creates)
I also tried to use a global key, and it does not work (meaning by that that I get the same result). I am testing it in a Samsung a10, which, according to wikipedia, has a 720 x 1520 pixels. The width I have no problem in calculating it, but the height, is always giving me 1424.0. Why I am not getting the full height? Is happening me with more phone models.
Please see the documentation for the physicalSize property:
This value does not take into account any on-screen keyboards or other system UI. The padding and viewInsets properties provide information about how much of each side of the view may be obscured by system UI.
try to use this utility class it gives me the right result
class ScreenUtil {
static ScreenUtil instance = new ScreenUtil();
int width;
int height;
bool allowFontScaling;
static MediaQueryData _mediaQueryData;
static double _screenWidth;
static double _screenHeight;
static double _screenHeightNoPadding;
static double _pixelRatio;
static double _statusBarHeight;
static double _bottomBarHeight;
static double _textScaleFactor;
static Orientation _orientation;
ScreenUtil({
this.width = 1080,
this.height = 1920,
this.allowFontScaling = false,
});
static ScreenUtil getInstance() {
return instance;
}
void init(BuildContext context) {
MediaQueryData mediaQuery = MediaQuery.of(context);
_mediaQueryData = mediaQuery;
_pixelRatio = mediaQuery.devicePixelRatio;
_screenWidth = mediaQuery.size.width;
_screenHeight = mediaQuery.size.height;
_statusBarHeight = mediaQuery.padding.top;
_bottomBarHeight = mediaQuery.padding.bottom;
_textScaleFactor = mediaQuery.textScaleFactor;
_orientation = mediaQuery.orientation;
_screenHeightNoPadding =
mediaQuery.size.height - _statusBarHeight - _bottomBarHeight;
}
static MediaQueryData get mediaQueryData => _mediaQueryData;
static double get textScaleFactory => _textScaleFactor;
static double get pixelRatio => _pixelRatio;
static Orientation get orientation => _orientation;
static double get screenWidth => _screenWidth;
static double get screenHeight => _screenHeight;
static double get screenWidthPx => _screenWidth * _pixelRatio;
static double get screenHeightPx => _screenHeight * _pixelRatio;
static double get screenHeightNoPadding => _screenHeightNoPadding;
static double get statusBarHeight => _statusBarHeight * _pixelRatio;
static double get bottomBarHeight => _bottomBarHeight * _pixelRatio;
get scaleWidth => _screenWidth / instance.width;
get scaleHeight => _screenHeight / instance.height;
setWidth(int width) => width * scaleWidth;
setHeight(int height) => height * scaleHeight;
setSp(int fontSize) => allowFontScaling
? setWidth(fontSize)
: setWidth(fontSize) / _textScaleFactor;
}
in your build method first call
ScreenUtil().init(context);
then you can call ScreenUtil.screenHeight

Snapping Behavior Custom ScrollPhysics

After some tinkering and Googling all over the place I became super frustrated with the API / lack of documentation for ScrollPhysics.
On Android you can use what's called a SnapHelper inside your RecyclerView (analogous to a ListView in Flutter) that will automatically snap to a certain position.
The SnapHelper does this on a position based API.
You can ask which View is currently in your chosen ViewPort and get its position and ask the RecyclerView to animate to that position.
Flutter on the other hand wants us to work with logical pixels, which makes this super trivial, common pattern difficult to implement.
All the solutions I found was to use items inside the list that have a fixed width/height and don't account for flinging gestures.
tl;dr How to implement this ๐Ÿ‘†in Flutter so it works for any item in a ListView?
I'm including the poor mans version we are currently using.
Which works, but not like we are used to on Android.
Especially passing the itemWidth is an eyesore
class SnappingListScrollPhysics extends ScrollPhysics {
final double itemWidth;
const SnappingListScrollPhysics({
#required this.itemWidth,
ScrollPhysics parent,
}) : super(parent: parent);
#override
SnappingListScrollPhysics applyTo(ScrollPhysics ancestor) => SnappingListScrollPhysics(
parent: buildParent(ancestor),
itemWidth: itemWidth,
);
double _getItem(ScrollPosition position) => (position.pixels) / itemWidth;
double _getPixels(ScrollPosition position, double item) => min(item * itemWidth, position.maxScrollExtent);
double _getTargetPixels(ScrollPosition position, Tolerance tolerance, double velocity) {
double item = _getItem(position);
if (velocity < -tolerance.velocity) {
item -= 0.5;
} else if (velocity > tolerance.velocity) {
item += 0.5;
}
return _getPixels(position, item.roundToDouble());
}
#override
Simulation createBallisticSimulation(ScrollMetrics position, double velocity) {
// If we're out of range and not headed back in range, defer to the parent
// ballistics, which should put us back in range at a page boundary.
if ((velocity <= 0.0 && position.pixels <= position.minScrollExtent) ||
(velocity >= 0.0 && position.pixels >= position.maxScrollExtent)) {
return super.createBallisticSimulation(position, velocity);
}
final Tolerance tolerance = this.tolerance;
final double target = _getTargetPixels(position, tolerance, velocity);
if (target != position.pixels) {
return ScrollSpringSimulation(spring, position.pixels, target, velocity, tolerance: tolerance);
}
return null;
}
#override
bool get allowImplicitScrolling => false;
}

Change size of text according to screen width in Flutter

I want the text size to remain the same however i want it to be bigger when on a larger screen width device.
MediaQueryData queryData;
#override
Widget build(BuildContext context) {
queryData = MediaQuery.of(context);
Text("Hello.",
style: TextStyle(
fontSize: queryData.size.width, color: Colors.white),
),
}
However, it became so big when i tried queryData.size.width. in this case i want the text to expand according to different devices screens.
You just need to multiply the result from the media query by a constant.
double width = MediaQuery.of(context).size.width;
return Text(
"Hello.",
style: TextStyle(
fontSize: width * 0.2,
color: Colors.white,
),
);
Define a class named sizeconfig:
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;
}
}
And in your page initialize this class using SizeConfig().init(context)
then on your fontsize :SizeConfig.screenWidth * .02.
Your question is relevant also for Containers, Buttons, etc... So I suggest you to check how this amazing open source (using the alibaba redux) solved this issue using an utility-adapter file in several places like in the account header page.
Another excellent option is to check the flutter-screen-util dependency, they have a good example there.
You can use flutter_screenutil to set font size responsive to any screen layout also with this package you can make your other widgets responsive by its setWidth() and setHeight() methods,
You can check its documentation here : flutter_screenutil
We can use view vw and vh unit to provide font-size, which will make your text more visible with different viewport size.
h1 {
font-size: 5.9vw;
}
h2 {
font-size: 3.0vh;
}
p {
font-size: 2vmin;
}
1vw = 1% of viewport width
1vh = 1% of viewport height
1vmin = 1vw or 1vh, whichever is smaller
1vmax = 1vw or 1vh, whichever is larger

How to use classes and methods in Java

I'm fairly new to Java and coding, but up until this point in the Java material, I haven't ran into any problems. What I really can't wrap my head around is how classes and methods actually work. I've been attempting to implement this over the last several hours to no avail:
Implement the class called Cylinder shown in UML below. The constructor accepts and initializes the radius and height for the Cylinder, while accessors and mutators allow them to be changed after object construction. The class also include methods that calculate and return the volume and surface area of the Cylinder. Lastly, it contains a toString method that returns the name of the shape, its radius, and its height. Create a main method which instantiates 4 Cylinder objects (any parameters), display them with toString(), change one parameter (your choice) in each, and display them again.
UML:
This is the code that I currently have:
class Cylinder {
private double radius;
private double height;
// Method #1
private void rad (){
}
// Constructor
public Cylinder (double radius, double height){
this.radius = radius;
this.height = height;
}
// Method #2 for calculating volume.
double calcVolume (){
double volume = Math.PI * Math.pow(radius, 2) * height;
return volume;
}
// Method #3 for calculating surface area.
double calcArea (){
double area = (2 * Math.PI * radius * height) + (2 * Math.PI * Math.pow(radius, 2));
return area;
}
// toString method.
public String toString (){
StringBuilder sb = new Stringbuilder();
sb.append(radius).append(height);
return sb.toString();
}
}
public class Murray_A03Q1 {
public static void main(String[] args) {
Cylinder cylinder1 = new Cylinder(5, "can");
System.out.println(cylinder1);
Cylinder cylinder2 = new Cylinder(6, "cup");
Cylinder cylinder3 = new Cylinder(7, "jar");
Cylinder cylinder4 = new Cylinder(8, "paper roll");
}
}
What I really don't understand is how to use the 'get' and 'set' methods. Additionally, I'm not completely sure how to implement the toString method.
The following errors that I can't figure out how to correct are:
The constructor Cylinder() is undefined for -
Cylinder cylinder1 = new Cylinder();
Stringbuilder can't be resolved to a type for -
StringBuilder sb = new Stringbuilder();
Thank you for your help!
What I really don't understand is how to use the 'get' and 'set' methods.
The purpose of getters and setters is for encapsulation. They allow you to get or set the values of the class' variables without having to declare them as being public.
For example, if you were to have
public double radius;
public double height;
You will be able to access them as
cylinder1.radius = 1;
cylinder2.height = 10;
int a = cylinder3.radius;
int b = cylinder3.height + cylinder4.radius;
etc.
Now if instead we had
private double radius;
private double height;
The above code will fail. Which is why we need getters and setters. As the names imply, getters "get" the variable.
public double getHeight() {
return height;
}
While setters "set" the variable.
public void setHeight(double height) {
this.height = height;
}
As for why we do it this way, theres a lot of information on that.
Additionally, I'm not completely sure how to implement the toString method.
As for how to implement the toString() method, all it has to do is return a String. But as for what the String contains, there is no hard rule for that. A good start would be to return the radius and height, like you have done.
The constructor Cylinder() is undefined for - Cylinder cylinder1 = new Cylinder();
Your constructor is public Cylinder (double radius, double height). However, you are trying to make a cylinder that's an int and a String.
Cylinder cylinder1 = new Cylinder(5, "can");
Either
Change the constructor to something like public Cylinder(double radius, String name); or
The instantiate your Cylinders with two doubles, a radius and a height.
e.g.
Cylinder cylinder1 = new Cylinder(1.0, 2.0);
Stringbuilder can't be resolved to a type for - StringBuilder sb = new Stringbuilder();
The only problem here is that you forgot to capitalize the b. It's StringBuilder not Stringbuilder.
An example of get and set methods:
double getRadius() {
return radius;
}
void setRadius(r) {
radius = r;
}
The constructor Cylinder() is undefined for - Cylinder cylinder1 = new
Cylinder();
Something is attempting to invoke the default constructor (a constructor with no parameters). You can either find out exactly where this error is called, or else add a default constructor:
Cylinder() {
this.radius = 0;
this.height = 0;
}
Stringbuilder can't be resolved to a type for - StringBuilder sb = new Stringbuilder();
StringBuilder is actually the class java.lang.StringBuilder. Place this text at the top of your file to help it resolve the name.
import java.lang.StringBuilder;

Custom ProgressBar widget

I am trying to do something similar to this but in Android.
In Android I can extend the ProgressBar but I am doubting of how to add the TextViews on top. In iphone it was easy because I can use absolute positions, but not here.
Any ideas?
EDIT:
I decided to use SeekBar instead of ProgressBar to add the thumb drawable. I commented below. Some points to notice:
I am using hardcoded values, actually three but it can be more or less.
When the thumb is moved it moves to 50 but it should move to the different options.
I am using pixels instead of dpi. I should fix that.
I need to solve the lack of animation when the thumb moves.
My progress so far:
public class SliderFrameLayout extends FrameLayout implements OnSeekBarChangeListener {
private SeekBar mSlider;
private Context mContext;
private int mSize = 3;
private TextView[] mTextViews;
private String[] mTexts = {"Nafta", "Gas", "Gasoil"};
public SliderFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
setWillNotDraw(false);
mTextViews = new TextView[mSize];
addSlider();
addTextViews();
}
private void addTextViews() {
for ( int i=0 ; i < mSize ; i++ ) {
TextView tv;
tv = new TextView(mContext);
tv.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT));
tv.setText(mTexts[i]);
mTextViews[i] = tv;
addView(tv);
}
}
private void addSlider() {
FrameLayout fl = new FrameLayout(mContext, null);
LayoutParams params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
fl.setLayoutParams(params);
fl.setPadding(30, 30, 30, 0);
mSlider = new SeekBar(mContext, null);
LayoutParams lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
//lp.setMargins(30, 30, 30, 0);
//mSlider.setPadding(30, 30, 30, 0);
mSlider.setLayoutParams(lp);
//mSlider.setMax(mSize-1);
mSlider.setThumbOffset(30);
//mSlider.setProgressDrawable(mContext.getResources().getDrawable(R.drawable.slider_track));
//mSlider.setThumb(mContext.getResources().getDrawable(R.drawable.slider_thumb));
mSlider.setOnSeekBarChangeListener(this);
//addView(mSlider);
fl.addView(mSlider);
addView(fl);
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Rect rectf = new Rect();
mSlider.getLocalVisibleRect(rectf);
Log.d("WIDTH :",String.valueOf(rectf.width()));
Log.d("HEIGHT :",String.valueOf(rectf.height()));
Log.d("left :",String.valueOf(rectf.left));
Log.d("right :",String.valueOf(rectf.right));
Log.d("top :",String.valueOf(rectf.top));
Log.d("bottom :",String.valueOf(rectf.bottom));
int sliderWidth = mSlider.getWidth();
int padding = sliderWidth / (mSize-1);
for ( int i=0 ; i < mSize ; i++ ) {
TextView tv = mTextViews[i];
tv.setPadding(i* padding, 0, 0, 0);
}
}
#Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
// TODO Auto-generated method stub
}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {
// TODO Auto-generated method stub
}
#Override
public void onStopTrackingTouch(SeekBar seekBar) {
Log.d("SEEK", "value: " + seekBar.getProgress());
seekBar.setProgress(50);
}
}
You're already overriding onDraw, why not just draw the text strings yourself? Rather than go through the overhead of adding TextViews and messing with the padding, just use canvas.drawText to physically draw the text strings in the right place.
You can specify the style and color of the text using a Paint object:
Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
textPaint.setColor(r.getColor(R.color.text_color));
textPaint.setFakeBoldText(true);
textPaint.setSubpixelText(true);
textPaint.setTextAlign(Align.LEFT);
And get the exact positioning by using the measureText method on that Paint object to find what width a particular string would be when drawn on a canvas:
textWidth = (int)textPaint.measureText(mTexts[i]);
Then you can iterate over your array of text strings and draw each string in the right place.
#Override
protected void onDraw(Canvas canvas) {
int myWidth = getMeasuredWidth()-LEFT_PADDING-RIGHT_PADDING;
int separation = myWidth / (mSize-1);
for (int i = 0; i++; i < mSize) {
int textWidth = (int)textPaint.measureText(mTexts[i]);
canvas.drawText(mTexts[i],
LEFT_PADDING+(i*separation)-(int)(textWidth/2),
TOP_PADDING,
textPaint);
}
}
You'll probably want to do the measurements in onMeasure instead of onDraw, and you should probably only measure the width of each string when you change the text or the paint, but I've put it all in one place to (hopefully) make it easier to follow.
Just a try. You could put your custom progressBar inside a FrameLayout then, inside this layout, you have to add three TextViews with fill_parent as width.
Then you can align the three texts in this way: left, center and right. Your texts shouldn't overwrite and you can adjust a little their position using margins.
You can use this... not exact what you looking for... but really easy to set up and customize...
You can set the TextViews to have:
android:layout_width="0dp"
android:layout_weight="1"
This will let the android framework take care of the placing of the TextViews and save you from manually calculating the padding of each one.