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

©️2020 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值