问题描述
我正在做一个颤抖的UI,现在在模拟器上看起来不错,但是如果屏幕尺寸不同,恐怕它会坏掉。防止这种情况的最佳实践是什么,尤其是使用gridview时。
I'm doing a UI in flutter and right now it look great on my emulator but I'm afraid it will break if screen size is different. What is the best practice to prevent this, especially using gridview.
这是我正在尝试做的UI(目前仅剩左侧部分):
Here is the UI I'm trying to do (only the left part for now) :
我现在拥有的代码正在运行。每个项目都在一个容器中,其中两个是 Gridview :
The code I have right now that is working. Each item is in a Container and 2 of them are a Gridview :
Expanded(
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(height: 100),
Container( // Top text
margin: const EdgeInsets.only(left: 20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("Hey,",
style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 25)),
Text("what's up ?", style: TextStyle(fontSize: 25)),
SizedBox(height: 10),
],
),
),
Container( // First gridview
height: MediaQuery.of(context).size.height/2,
child: GridView.count(
crossAxisCount: 3,
scrollDirection: Axis.horizontal,
crossAxisSpacing: 10,
mainAxisSpacing: 10,
padding: const EdgeInsets.all(10),
children: List.generate(9, (index) {
return Center(
child: ButtonTheme(
minWidth: 100.0,
height: 125.0,
child: RaisedButton(
splashColor: Color.fromRGBO(230, 203, 51, 1),
color: (index!=0)?Colors.white:Color.fromRGBO(201, 22, 25, 1),
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: <Widget>[
Image.asset(
'assets/in.png',
fit: BoxFit.cover,
),
Text("Eat In",
style: TextStyle(
fontWeight:
FontWeight.bold))
]),
onPressed: () {
},
shape: RoundedRectangleBorder(
borderRadius:
new BorderRadius.circular(
20.0)))));
}))),
Container( // Bottom Text
margin: const EdgeInsets.only(left: 20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(height: 10),
Text("Popular",
style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 25)),
SizedBox(height: 10),
],
),
),
Container( // Second Gridview
height: MediaQuery.of(context).size.height/5,
child: GridView.count(
crossAxisCount: 2,
scrollDirection: Axis.horizontal,
children: List.generate(9, (index) {
return Center(
child: ButtonTheme(
minWidth: 100.0,
height: 125.0,
child: FlatButton(
color: Colors.white,
child: Column(
mainAxisAlignment:
MainAxisAlignment.center,
children: <Widget>[
Image.asset(
'assets/logo.png',
fit: BoxFit.cover,
),
Text("Name")
]),
onPressed: () {},
shape: RoundedRectangleBorder(
borderRadius:
new BorderRadius.circular(
20.0)))));
})))
],
),
),
flex: 3,
)
此代码的最佳做法是确保屏幕高度较小时,所有内容仍然适合?
What is the best practice for this code to be sure that if the screen height is smaller everything will still fit ?
推荐答案
比例缩放解决方案[Flutter移动应用]
所以我相信您正在寻找缩放比例该解决方案可以在保持UI比例(即比例)不变的同时向上和向下缩放以适合不同的屏幕密度。实现此目标的方法是将比例缩放解决方案应用于您的项目。
Ratio-Scaling Solution [ Flutter mobile apps ]
So I believe you're looking for a scaling solution that maintains the proportions (i.e. ratios) of your UI intact while scaling up and down to fit different screen densities. The way to achieve this is to apply a Ratio-Scaling solution to your project.
比率缩放过程概述 :
Outline of Ratio-Scaling Process:
步骤1 :以像素为单位定义固定的缩放比例[Height:Width => 2:1 ratio]。
步骤2 :指定您的应用是否为全屏应用(即定义状态栏是否在您的高度缩放中起作用)。
第3步:缩放整个用户界面(从应用程序栏到最小的文本),使用以下过程[代码]根据百分比计算。
Step 1: Define a fixed scaling ratio [Height:Width => 2:1 ratio] in pixels.
Step 2: Specify whether your app is a full screen app or not (i.e. define whether the Status Bar plays a role in your height scaling).
Step 3: Scale your entire UI (from the App bar to the tiniest text) on the basis of percentages using the following process [code].
重要代码单位:
=> McGyver [在MacGyver上运行]-执行重要比例缩放的类。 / p>
IMPORTANT CODE unit:
=> McGyver [ a play on 'MacGyver' ] - the class that does the important ratio-scaling.
// Imports: Third-Party.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
// Imports: Local [internal] packages.
import 'package:pixel_perfect/utils/stringr.dart';
import 'package:pixel_perfect/utils/enums_all.dart';
// Exports: Local [internal] packages.
export 'package:pixel_perfect/utils/enums_all.dart';
// 'McGyver' - the ultimate cool guy (the best helper class any app can ask for).
class McGyver {
static final TAG_CLASS_ID = "McGyver";
static double _fixedWidth; // Defined in pixels !!
static double _fixedHeight; // Defined in pixels !!
static bool _isFullScreenApp = false; // Define whether app is a fullscreen app [true] or not [false] !!
static void hideSoftKeyboard() {
SystemChannels.textInput.invokeMethod("TextInput.hide");
}
static double roundToDecimals(double val, int places) {
double mod = pow(10.0, places);
return ((val * mod).round().toDouble() / mod);
}
static Orientation setScaleRatioBasedOnDeviceOrientation(BuildContext ctx) {
Orientation scaleAxis;
if(MediaQuery.of(ctx).orientation == Orientation.portrait) {
_fixedWidth = 420; // Ration: 1 [width]
_fixedHeight = 840; // Ration: 2 [height]
scaleAxis = Orientation.portrait; // Shortest axis == width !!
} else {
_fixedWidth = 840; // Ration: 2 [width]
_fixedHeight = 420; // Ration: 1 [height]
scaleAxis = Orientation.landscape; // Shortest axis == height !!
}
return scaleAxis;
}
static int rsIntW(BuildContext ctx, double scaleValue) {
// ---------------------------------------------------------------------------------------- //
// INFO: Ratio-Scaled integer - Scaling based on device's width. //
// ---------------------------------------------------------------------------------------- //
final double _origVal = McGyver.rsDoubleW(ctx, scaleValue);
return McGyver.roundToDecimals(_origVal, 0).toInt();
}
static int rsIntH(BuildContext ctx, double scaleValue) {
// ---------------------------------------------------------------------------------------- //
// INFO: Ratio-Scaled integer - Scaling based on device's height. //
// ---------------------------------------------------------------------------------------- //
final double _origVal = McGyver.rsDoubleH(ctx, scaleValue);
return McGyver.roundToDecimals(_origVal, 0).toInt();
}
static double rsDoubleW(BuildContext ctx, double wPerc) {
// ------------------------------------------------------------------------------------------------------- //
// INFO: Ratio-Scaled double - scaling based on device's screen width in relation to fixed width ration. //
// INPUTS: - 'ctx' [context] -> BuildContext //
// - 'wPerc' [double] -> Value (as a percentage) to be ratio-scaled in terms of width. //
// OUTPUT: - 'rsWidth' [double] -> Ratio-scaled value. //
// ------------------------------------------------------------------------------------------------------- //
final int decimalPlaces = 14; //* NB: Don't change this value -> has big effect on output result accuracy !!
Size screenSize = MediaQuery.of(ctx).size; // Device Screen Properties (dimensions etc.).
double scrnWidth = screenSize.width.floorToDouble(); // Device Screen maximum Width (in pixels).
McGyver.setScaleRatioBasedOnDeviceOrientation(ctx); //* Set Scale-Ratio based on device orientation.
double rsWidth = 0; //* OUTPUT: 'rsWidth' == Ratio-Scaled Width (in pixels)
if (scrnWidth == _fixedWidth) {
//* Do normal 1:1 ratio-scaling for matching screen width (i.e. '_fixedWidth' vs. 'scrnWidth') dimensions.
rsWidth = McGyver.roundToDecimals(scrnWidth * (wPerc / 100), decimalPlaces);
} else {
//* Step 1: Calculate width difference based on width scale ration (i.e. pixel delta: '_fixedWidth' vs. 'scrnWidth').
double wPercRatioDelta = McGyver.roundToDecimals(100 - ((scrnWidth / _fixedWidth) * 100), decimalPlaces); // 'wPercRatioDelta' == Width Percentage Ratio Delta !!
//* Step 2: Calculate primary ratio-scale adjustor (in pixels) based on input percentage value.
double wPxlsInpVal = (wPerc / 100) * _fixedWidth; // 'wPxlsInpVal' == Width in Pixels of Input Value.
//* Step 3: Calculate secondary ratio-scale adjustor (in pixels) based on primary ratio-scale adjustor.
double wPxlsRatDelta = (wPercRatioDelta / 100) * wPxlsInpVal; // 'wPxlsRatDelta' == Width in Pixels of Ratio Delta (i.e. '_fixedWidth' vs. 'scrnWidth').
//* Step 4: Finally -> Apply ratio-scales and return value to calling function / instance.
rsWidth = McGyver.roundToDecimals((wPxlsInpVal - wPxlsRatDelta), decimalPlaces);
}
return rsWidth;
}
static double rsDoubleH(BuildContext ctx, double hPerc) {
// ------------------------------------------------------------------------------------------------------- //
// INFO: Ratio-Scaled double - scaling based on device's screen height in relation to fixed height ration. //
// INPUTS: - 'ctx' [context] -> BuildContext //
// - 'hPerc' [double] -> Value (as a percentage) to be ratio-scaled in terms of height. //
// OUTPUT: - 'rsHeight' [double] -> Ratio-scaled value. //
// ------------------------------------------------------------------------------------------------------- //
final int decimalPlaces = 14; //* NB: Don't change this value -> has big effect on output result accuracy !!
Size scrnSize = MediaQuery.of(ctx).size; // Device Screen Properties (dimensions etc.).
double scrnHeight = scrnSize.height.floorToDouble(); // Device Screen maximum Height (in pixels).
double statsBarHeight = MediaQuery.of(ctx).padding.top; // Status Bar Height (in pixels).
McGyver.setScaleRatioBasedOnDeviceOrientation(ctx); //* Set Scale-Ratio based on device orientation.
double rsHeight = 0; //* OUTPUT: 'rsHeight' == Ratio-Scaled Height (in pixels)
if (scrnHeight == _fixedHeight) {
//* Do normal 1:1 ratio-scaling for matching screen height (i.e. '_fixedHeight' vs. 'scrnHeight') dimensions.
rsHeight = McGyver.roundToDecimals(scrnHeight * (hPerc / 100), decimalPlaces);
} else {
//* Step 1: Calculate height difference based on height scale ration (i.e. pixel delta: '_fixedHeight' vs. 'scrnHeight').
double hPercRatioDelta = McGyver.roundToDecimals(100 - ((scrnHeight / _fixedHeight) * 100), decimalPlaces); // 'hPercRatioDelta' == Height Percentage Ratio Delta !!
//* Step 2: Calculate height of Status Bar as a percentage of the height scale ration (i.e. 'statsBarHeight' vs. '_fixedHeight').
double hPercStatsBar = McGyver.roundToDecimals((statsBarHeight / _fixedHeight) * 100, decimalPlaces); // 'hPercStatsBar' == Height Percentage of Status Bar !!
//* Step 3: Calculate primary ratio-scale adjustor (in pixels) based on input percentage value.
double hPxlsInpVal = (hPerc / 100) * _fixedHeight; // 'hPxlsInpVal' == Height in Pixels of Input Value.
//* Step 4: Calculate secondary ratio-scale adjustors (in pixels) based on primary ratio-scale adjustor.
double hPxlsStatsBar = (hPercStatsBar / 100) * hPxlsInpVal; // 'hPxlsStatsBar' == Height in Pixels of Status Bar.
double hPxlsRatDelta = (hPercRatioDelta / 100) * hPxlsInpVal; // 'hPxlsRatDelta' == Height in Pixels of Ratio Delat (i.e. '_fixedHeight' vs. 'scrnHeight').
//* Step 5: Check if '_isFullScreenApp' is true and adjust 'Status Bar' scalar accordingly.
double hAdjStatsBarPxls = _isFullScreenApp ? 0 : hPxlsStatsBar; // Set to 'zero' if FULL SCREEN APP !!
//* Step 6: Finally -> Apply ratio-scales and return value to calling function / instance.
rsHeight = McGyver.roundToDecimals(hPxlsInpVal - (hPxlsRatDelta + hAdjStatsBarPxls), decimalPlaces);
}
return rsHeight;
}
static Widget rsWidget(BuildContext ctx, Widget inWidget,
double percWidth, double percHeight, {String viewID}) {
// ---------------------------------------------------------------------------------------------- //
// INFO: Ratio-Scaled "SizedBox" Widget - Scaling based on device's width & height. //
// ---------------------------------------------------------------------------------------------- //
return SizedBox(
width: Scalar.rsDoubleW(ctx, percWidth),
height: Scalar.rsDoubleH(ctx, percHeight),
child: inWidget,
);
}
//* SPECIAL 'rsWidget' that has both its height & width ratio-scaled based on 'width' alone !!
static Widget rsWidgetW(BuildContext ctx, Widget inWidget,
double percWidth, double percHeight, {String viewID}) {
// ---------------------------------------------------------------------------------------------- //
// INFO: Ratio-Scaled "SizedBox" Widget - Scaling based on device's width ONLY !! //
// ---------------------------------------------------------------------------------------------- //
return SizedBox(
width: Scalar.rsDoubleW(ctx, percWidth),
height: Scalar.rsDoubleW(ctx, percHeight),
child: inWidget,
);
}
static Widget rsText(BuildContext ctx, String text, {double fontSize,
Color textColor, Anchor txtLoc, FontWeight fontWeight}) {
// ---------------------------------------------------------------------------------------- //
// INFO: Ratio-Scaled Text Widget - Default Font Weight == NORMAL !! //
// ---------------------------------------------------------------------------------------- //
// Scale the Font Size (based on device's screen width).
double txtScaleFactor = MediaQuery.of(ctx).textScaleFactor;
double _rsFontSize = (fontSize != null) ? McGyver.rsDoubleW(ctx, fontSize) : McGyver.rsDoubleW(ctx, 2.5);
TextAlign _txtLoc;
if (txtLoc == Anchor.left) {
_txtLoc = TextAlign.left;
} else if (txtLoc == Anchor.middle) {
_txtLoc = TextAlign.center;
} else {
_txtLoc = TextAlign.right;
}
return Text(
text,
textAlign: _txtLoc,
style: TextStyle(
fontFamily: Stringr.strAppFontFamily,
fontSize: (_rsFontSize / txtScaleFactor) * 1.0,
color: (textColor != null) ? textColor : Colors.black,
fontWeight: (fontWeight != null) ? fontWeight : FontWeight.normal,
),
);
}
}
McGyver类涵盖了步骤1和步骤2中概述的整个过程。 比例缩放过程 中的2 。 然后要做的只是在构建过程中应用步骤3 ,如下所示...
AppBar代码段:[在图像中创建AppBar的代码-图1-上方]
The McGyver class covers the entire process outlined under Steps 1 & 2 of the Ratio-Scaling Process. All that is then left to do is to apply Step 3 in the build process as follows...
AppBar Code Snippet: [ code that creates the AppBar in the image - Fig. 1 - above ]
Container(
color: Colors.blue[500],
width: McGyver.rsDoubleW(con, 100.5),
height: McGyver.rsDoubleH(con, 8.5),
child: Row(
children: <Widget>[
//* Hamburger Button => Button 1.
Padding(
padding: EdgeInsets.fromLTRB(_padLeft, _padTop, 0, _padBottom),
child: Container(
color: Colors.yellow,
width: _appBarBtnsWidth,
height: _appBarBtnsHeight,
child: Center(child: McGyver.rsText(context, "1", fontSize: 5.5, fontWeight: FontWeight.bold, textColor: Colors.red),),
),
),
//* AppBar Info Text (center text).
Padding(
padding: EdgeInsets.only(left: McGyver.rsDoubleW(con, 3.5), right: McGyver.rsDoubleW(con, 3.5)),
child: Container(
// color: Colors.pink,
width: McGyver.rsDoubleW(context, 52.5),
child: McGyver.rsText(con, "100% Ratio-Scaled UI", fontSize: 4.5, textColor: Colors.white, fontWeight: FontWeight.bold, txtLoc: Anchor.left),
),
),
//* Right Button Group - LEFT Button => Button 2.
Padding(
padding: EdgeInsets.fromLTRB(McGyver.rsDoubleW(con, 0), _padTop, McGyver.rsDoubleH(con, 1.5), _padBottom),
child: Container(
color: Colors.black,
width: _appBarBtnsWidth,
height: _appBarBtnsHeight,
child: Center(child: McGyver.rsText(context, "2", fontSize: 5.5, fontWeight: FontWeight.bold, textColor: Colors.white),),
),
),
//* Right Button Group - RIGHT Button => Button 3.
Padding(
padding: EdgeInsets.fromLTRB(McGyver.rsDoubleW(con, 0), _padTop, 0, _padBottom),
child: Container(
color: Colors.pink,
width: _appBarBtnsWidth,
height: _appBarBtnsHeight,
child: Center(child: McGyver.rsText(context, "3", fontSize: 5.5, fontWeight: FontWeight.bold, textColor: Colors.yellow),),
),
),
],
),
),
比率缩放代码的限制
扩展解决方案在所有经过测试的设备上都工作得非常好[7台物理设备和1个模拟器]-但显然存在以下问题:
1. 文本
2. 填充
3. 最高纵横比
-文本比例因子无效(被此代码停用)-使用 McGyver.rsText()
功能时,没有SP用于文本。您希望您的UI在任何比例或屏幕密度下都具有准确的比例。
-在Flutter(和一般的Android设备)中,使用 padding 进行了一些奇怪的缩放[幕后]。
-具有极高的古怪的长宽比(即,宽:高像素比)的设备也会导致UI的比例发生某种程度的变形。
除了这三个问题之外,这种比例缩放方法效果很好,足以让我将其用作所有flutter项目中唯一的缩放解决方案。我希望它可以帮助其他程序员完成与我相同的任务。始终欢迎对此方法/代码进行任何改进。
Limitations of Ratio-Scaling Code
This ratio-scaling solution worked surprisingly well on ALL devices tested [7 physical devices & 1 emulator] - but it clearly has some issues with:
1. Text
2. Padding
3. Extreme Aspect Ratios
- Text scale factor is nullified (deactivated by this code) - so no SP for text when using the McGyver.rsText()
feature. You want your UI to have the exact proportions at ANY scale or screen density.
- There is some weird scaling [ going on behind the scenes ] with padding in Flutter (and Android in general).
- Devices with extremely weird aspect ratios (i.e. width:height pixel ratios) also cause the proportions of the UI to distort somewhat.
Aside from these three issues this ratio-scaling approach works well enough for me to use it as my only scaling solution in all my flutter projects. I hope it will help other programmers on the same quest as I am. Any improvement to this approach / code is always welcomed.
这篇关于根据Flutter中的不同屏幕尺寸有效缩放此UI的最佳实践的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!