前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Flutter 大小单位详解

Flutter 大小单位详解

作者头像
arcticfox
发布2020-06-28 11:37:20
9430
发布2020-06-28 11:37:20
举报

关于Flutter 大小所使用的单位,官方文档没有给出非常明确的解释,因此一直存在模糊的说法,许多从事安卓开发者直接将之解释为安卓开发所用的单位dp,我认为这是非常不明智且不准确的说法,这个不准确不在于实质的数值,而在于概念的混淆!这样极容易对初学者造成误导,从事web前端或iOS原生开发的人,并没有dp的概念,当他们学习Flutter时,必须强行去理解dp的概念,且在iOS或web平台上时也解释为dp,那就是错误的。

应当如何理解Flutter 的大小单位?

官方文档中有对 devicePixelRatio[1]属性的描述,devicePixelRatio 即每个逻辑像素的设备像素数,其中有一句概括的话

设备像素也被称为物理像素。逻辑像素也被称为与设备无关或与分辨率无关的像素。

也就是说,物理像素px = 逻辑像素 * devicePixelRatio

在另一篇专门写给Android 开发者的文档中 Flutter for Android developers[2],有如下说明

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

代码语言:javascript
复制
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

代码语言:javascript
复制
// 通过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

代码语言:javascript
复制
- (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[3]

该值反映了从默认逻辑坐标空间转换到本界面设备坐标空间所需的比例系数。默认的逻辑坐标空间是用点来衡量的。对于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

代码语言:javascript
复制
  @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[4]

  • 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.
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-06-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程之路从0到1 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 应当如何理解Flutter 的大小单位?
  • Flutter的逻辑像素是如何计算出来的?
    • Android 平台
      • iOS 平台
        • Web 平台
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档