Flutter 大小單位詳解

關於Flutter 大小所使用的單位,官方文檔沒有給出非常明確的解釋,因此一直存在模糊的說法,許多從事安卓開發者直接將之解釋爲安卓開發所用的單位dp,我認爲這是非常不明智且不準確的說法,這個不準確不在於實質的數值,而在於概念的混淆!這樣極容易對初學者造成誤導,從事web前端或iOS原生開發的人,並沒有dp的概念,當他們學習Flutter時,必須強行去理解dp的概念,且在iOS或web平臺上時也解釋爲dp,那就是錯誤的。

應當如何理解Flutter 的大小單位?

官方文檔中有對 devicePixelRatio屬性的描述,devicePixelRatio 即每個邏輯像素的設備像素數,其中有一句概括的話

設備像素也被稱爲物理像素。邏輯像素也被稱爲與設備無關或與分辨率無關的像素。

也就是說,物理像素px = 邏輯像素 * devicePixelRatio

在另一篇專門寫給Android 開發者的文檔中 Flutter for Android developers,有如下說明

Flutter follows a simple density-based format like iOS. Assets might be 1.0x, 2.0x, 3.0x, or any other multiplier. Flutter doesn’t have dps but there are logical pixels, which are basically the same as device-independent pixels.

翻譯過來,就是:Flutter像iOS一樣遵循一個簡單的基於密度的格式。Assets 可能是1.0x,2.0x,3.0x,或者其他任何倍數。Flutter沒有dps,但有邏輯像素,這與設備獨立像素基本相同。

到這裏我們大概能明白Flutter官方的意思,Flutter框架希望提供一個新的尺寸單位的概念,稱爲邏輯像素,然後讓大家忘記原生開發中的單位。這是因爲Flutter作爲一個跨平臺的框架,必須抽離出一個新的單位,用以適配不同的平臺,如果還去使用原生的單位概念,就會造成混淆或屏幕適配的問題。

結論,在Flutter的語境下,不應該將邏輯像素直接描述爲原生開發中的單位概念

Flutter的邏輯像素是如何計算出來的?

關於devicePixelRatio屬性的值,一直沒有明確的資料說明,許多人使用比較樸素的辦法,直接打印不同平臺的值用以發現規律。我認爲這不是一個好主意,作爲一個專業的程序員,應該從源碼中找到答案,實際上devicePixelRatio值的計算很容易找到對應的源碼

Android 平臺

Flutter 引擎源碼 shell/platform/android/io/flutter/view/FlutterView.java

public FlutterView(Context context, AttributeSet attrs, FlutterNativeView nativeView) {
    super(context, attrs);

    Activity activity = getActivity(getContext());
    if (activity == null) {
      throw new IllegalArgumentException("Bad context");
    }

    if (nativeView == null) {
      mNativeView = new FlutterNativeView(activity.getApplicationContext());
    } else {
      mNativeView = nativeView;
    }

    dartExecutor = mNativeView.getDartExecutor();
    flutterRenderer = new FlutterRenderer(mNativeView.getFlutterJNI());
    mIsSoftwareRenderingEnabled = mNativeView.getFlutterJNI().nativeGetIsSoftwareRenderingEnabled();
    mMetrics = new ViewportMetrics();
    // 通過Java代碼獲取平臺中的density值
    mMetrics.devicePixelRatio = context.getResources().getDisplayMetrics().density;
    // ...... 省略 ......
}

獲取到density值後,又通過JNI將值傳給引擎層的C++代碼
源碼 shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java

// 通過java的jni本地方法傳給C++ 
private native void nativeSetViewportMetrics(
      long nativePlatformViewId,
      float devicePixelRatio,
      int physicalWidth,
      int physicalHeight,
      int physicalPaddingTop,
      int physicalPaddingRight,
      int physicalPaddingBottom,
      int physicalPaddingLeft,
      int physicalViewInsetTop,
      int physicalViewInsetRight,
      int physicalViewInsetBottom,
      int physicalViewInsetLeft,
      int systemGestureInsetTop,
      int systemGestureInsetRight,
      int systemGestureInsetBottom,
      int systemGestureInsetLeft);

這裏對C++代碼就不在追蹤,有興趣可以去看engine/shell/platform/android/platform_view_android_jni_impl.cc

在Flutter中,devicePixelRatio屬性由ui.Window類提供,我們知道,這個Window正是Flutter Framework連接宿主操作系統的接口。因此,dart代碼中獲取的devicePixelRatio屬性正是引擎層從原生平臺中獲取的。

iOS 平臺

引擎源碼 engine/shell/platform/darwin/ios/framework/Source/FlutterViewController.mm

- (void)viewDidLayoutSubviews {
  CGSize viewSize = self.view.bounds.size;
  CGFloat scale = [UIScreen mainScreen].scale;

  // Purposefully place this not visible.
  _scrollView.get().frame = CGRectMake(0.0, 0.0, viewSize.width, 0.0);
  _scrollView.get().contentOffset = CGPointMake(kScrollViewContentSize, kScrollViewContentSize);

  // First time since creation that the dimensions of its view is known.
  bool firstViewBoundsUpdate = !_viewportMetrics.physical_width;
  
  // 在iOS 上,device_pixel_ratio 的值是一個縮放比
  _viewportMetrics.device_pixel_ratio = scale;
  _viewportMetrics.physical_width = viewSize.width * scale;
  _viewportMetrics.physical_height = viewSize.height * scale;
// ....... 省略 .......
}

可以看到,device_pixel_ratio的值是[UIScreen mainScreen].scale,關於這個值,蘋果的開發者文檔有描述 UIScreen 的 scale

該值反映了從默認邏輯座標空間轉換到本界面設備座標空間所需的比例係數。默認的邏輯座標空間是用點來衡量的。對於Retina顯示器,比例因子可能是3.0或2.0,一個點可以分別用9個或4個像素表示。對於標準分辨率顯示器,比例係數爲1.0,一個點等於一個像素。

簡單說就是

  • scale == 1 :代表320 x 480 的分辨率(iphone4之前的設備,非Retain屏幕)
  • scale == 2 :代表640 x 960 的分辨率(Retain屏幕)
  • scale == 3 :代表1242 x 2208 的分辨率

Web 平臺

引擎源碼 engine/lib/web_ui/lib/src/engine/window.dart


  @override
  double get devicePixelRatio => _debugDevicePixelRatio != null
      ? _debugDevicePixelRatio
      : browserDevicePixelRatio;

  /// Returns device pixel ratio returned by browser.
  static double get browserDevicePixelRatio {
    double ratio = html.window.devicePixelRatio;
    // Guard against WebOS returning 0.
    return (ratio == null || ratio == 0.0) ? 1.0 : ratio;
  }

可以看到,調用的是html.window.devicePixelRatio,這裏的html.window實際上是Dart語言SDK中的類,描述的是瀏覽器中的window。關於瀏覽器中的devicePixelRatio屬性值,可以看Dart 官方文檔給出的解釋 devicePixelRatio

  • IE and Firefox don’t support the property at all. I assume the next versions will implement it.
  • Opera desktop on retina devices give 1, while it should be 2. I assume the next Opera version will fix this.
  • Opera Mobile 10 does not support it, but 12 implements the property correctly.
  • UC always gives 1, but UC is quite confused when it comes to viewport properties.
  • Chrome implemented this property only recently on retina devices. Chrome 19 incorrectly returns 1; Chrome 22 correctly returns 2.
  • MeeGo WebKit (Nokia N9/N950) does something horrible: it changes the value from 1 to 1.5 when you apply a meta viewport.

視頻課程

博主發佈的相關視頻課程

Flutter全棧式開發之Dart 編程指南
二維碼

Flutter 全棧式開發指南

公衆號:編程之路從0到1

編程之路從0到1

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章