Flutter螢幕適配
目前移動端的裝置已經非常多,並且不同的裝置手機螢幕也不相同。
目前做移動端開發都要針對不同的裝置進行一定的適配,無論是移動原生開發、小程式、H5頁面。
Flutter中如何針對不同的手機螢幕來進行適配呢?我們一起來聊聊這個話題。
一. Flutter單位
1.1. Flutter中的單位
在進行Flutter開發時,我們通常不需要傳入尺寸的單位,那麼Flutter使用的是什麼單位呢?
Flutter使用的是類似於iOS中的點pt,也就是point。 所以我們經常說iPhone6的尺寸是375x667,但是它的解析度其實是750x1334。 因為iPhone6的dpr(devicePixelRatio)是2.0,iPhone6plus的dpr是3.0
在Flutter開發中,我們使用的是對應的邏輯解析度
1.2. Flutter裝置資訊
獲取螢幕上的一些資訊,可以通過MediaQuery:
// 1.媒體查詢資訊
final mediaQueryData = MediaQuery.of(context);
// 2.獲取寬度和高度
final screenWidth = mediaQueryData.size.width;
final screenHeight = mediaQueryData.size.height;
final physicalWidth = window.physicalSize.width;
final physicalHeight = window.physicalSize.height;
final dpr = window.devicePixelRatio;
print("螢幕width:$screenWidth height:$screenHeight");
print("解析度: $physicalWidth - $physicalHeight");
print("dpr: $dpr");
// 3.狀態列的高度
// 有劉海的螢幕:44 沒有劉海的螢幕為20
final statusBarHeight = mediaQueryData.padding.top;
// 有劉海的螢幕:34 沒有劉海的螢幕0
final bottomHeight = mediaQueryData.padding.bottom;
print("狀態列height: $statusBarHeight 底部高度:$bottomHeight");
複製程式碼
獲取一些裝置相關的資訊,可以使用官方提供的一個庫:
dependencies:
device_info: ^0.4.2+1
複製程式碼
二. 適配方案
2.1. 適配概述
假如我們有下面這樣一段程式碼:
在螢幕中間顯示一個200*200的Container Container中有一段文字是30
class HYHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("首頁"),
),
body: Center(
child: Container(
width: 200,
height: 200,
color: Colors.red,
alignment: Alignment.center,
child: Text("Hello World", style: TextStyle(fontSize: 30, color: Colors.white),),
),
),
);
}
}
複製程式碼
上面的程式碼在不同螢幕上會有不同的表現:
很明顯,如果按照上面的規則,在iPhone5上面,尺寸過大,在iPhone6plus上面尺寸過小
在開發中,我們應該可以根據不同的螢幕來完成尺寸的縮放
在前端開發中,針對不同的螢幕常見的適配方案有下面幾種:
rem: rem是給根標籤(HTML標籤)設定一個字型大小; 但是不同的螢幕要動畫設定不同的字型大小(可以通過媒體查詢,也可以通過js動態計算); 其它所有的單位都使用rem單位(相對於根標籤);
vw、wh: vw和vh是將螢幕(視口)分成100等份,一個1vw相當於是1%的大小; 其它所有的單位都使用vw或wh單位;
rpx: rpx是小程式中的適配方案,它將750px作為設計稿,1rpx=螢幕寬度/750; 其它所有的單位都使用rpx單位;
這裡我採用小程式的rpx來完成Flutter的適配
2.2. rpx適配
小程式中rpx的原理是什麼呢?
不管是什麼螢幕,統一分成750份 在iPhone5上:1rpx = 320/750 = 0.4266 ≈ 0.42px 在iPhone6上: 1rpx = 375/750 = 0.5px 在iPhone6plus上:1rpx = 414/750 = 0.552px
那麼我們就可以通過上面的計算方式,算出一個rpx,再將自己的size和rpx單位相乘即可:
比如100px的寬度:100 * 2 * rpx 在iPhone5上計算出的結果是84px 在iPhone6上計算出的結果是100px 在iPhone6plus上計算出的結果是110.4px
我們自己來封裝一個工具類:
工具類需要進行初始化,傳入context 可以通過傳入context,利用媒體查詢獲取螢幕的寬度和高度 也可以傳入一個可選的引數,以什麼尺寸作為設計稿
class HYSizeFit {
static MediaQueryData _mediaQueryData;
static double screenWidth;
static double screenHeight;
static double rpx;
static double px;
static void initialize(BuildContext context, {double standardWidth = 750}) {
_mediaQueryData = MediaQuery.of(context);
screenWidth = _mediaQueryData.size.width;
screenHeight = _mediaQueryData.size.height;
rpx = screenWidth / standardWidth;
px = screenWidth / standardWidth * 2;
}
// 按照畫素來設定
static double setPx(double size) {
return HYSizeFit.rpx * size * 2;
}
// 按照rxp來設定
static double setRpx(double size) {
return HYSizeFit.rpx * size;
}
}
複製程式碼
初始化HYSizeFit類的屬性:
注意:必須在已經有MaterialApp的Widget中使用context,否則是無效的
class HYHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 初始化HYSizeFit
HYSizeFit.initialize(context);
return null;
}
}
複製程式碼
使用rpx來完成螢幕適配:
class HYHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
HYSizeFit.initialize(context);
return Scaffold(
appBar: AppBar(
title: Text("首頁"),
),
body: Center(
child: Container(
width: HYSizeFit.setPx(200),
height: HYSizeFit.setRpx(400),
color: Colors.red,
alignment: Alignment.center,
child: Text("Hello World", style: TextStyle(fontSize: HYSizeFit.setPx(30), color: Colors.white),),
),
),
);
}
}
複製程式碼
我們來看一下實現效果:
2.3. 最佳實踐
如果每次我們需要將現在的寬度或者高度,去使用HYSizeFit.setPx(200)或者HYSizeFit.setRpx(400)類似的方式去適配,顯然看起來非常麻煩。
有沒有更好的方案可以實現了?比如 200.px或者400.rpx,非常的清晰簡潔
當然可以,我們需要依賴Dart語言的一個特性:extension
Dart從2.7.0開始,可以通過extension來給現有的類進行擴充套件(事實上Swift裡面也有) 對現有的類包括:自定義的類、第三方庫的類、系統的類
比如我們現在對String型別擴充套件:
擴充套件一個parseInt的方法,當然內部呼叫的是int.parse(this),只是呼叫者變成了String本身
// 步驟一:擴充套件程式碼
extension NumberParsing on String {
int parseInt() {
return int.parse(this);
}
// ···
}
// 步驟二:呼叫程式碼
// 匯入擴充套件類對應的模組
import 'string_apis.dart';
// 使用裡面的方法
print('42'.padLeft(5)); // 使用String原有的方法
print('42'.parseInt()); // 使用String擴充套件的方法
複製程式碼
顯然,數字(比如200、200.0)有對應的包裝類int、double,我們可以對其進行擴充套件:
1.對int型別擴充套件
import '../shared/size_fit.dart';
extension IntFit on int {
double get px {
return HYSizeFit.setPx(this.toDouble());
}
double get rpx {
return HYSizeFit.setRpx(this.toDouble());
}
}
複製程式碼
2.對double型別擴充套件
import '../shared/size_fit.dart';
extension DoubleFit on double {
double get px {
return HYSizeFit.setPx(this);
}
double get rpx {
return HYSizeFit.setRpx(this);
}
}
複製程式碼
如何使用了?
import './extension/double_extension.dart';
import './extension/int_extension.dart';
print(200.px); // 在不同螢幕下200px是不同的值
print(400.rpx); // 在不同螢幕下400rpx是不同的值
複製程式碼
備註:所有內容首發於公眾號,之後除了Flutter也會更新其他技術文章,TypeScript、React、Node、uniapp、mpvue、資料結構與演算法等等,也會更新一些自己的學習心得等,歡迎大家關注