Flutter螢幕適配

coderwhy發表於2020-06-03

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
iPhone裝置引數
iPhone裝置引數

在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的對應關係
rpx的對應關係

那麼我們就可以通過上面的計算方式,算出一個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),),
        ),
      ),
    );
  }
}
複製程式碼

我們來看一下實現效果:

rpx適配方案
rpx適配方案

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、資料結構與演算法等等,也會更新一些自己的學習心得等,歡迎大家關注

公眾號
公眾號

相關文章