前言

React Native项目中,所有的长度是没有单位。但设计师会给你基准设计稿,一般都是750px基准版本设计稿,如何在不同分辨率下的设备下进行很好的展示呢?

移动操作系统为了适配不同屏幕尺寸和密度的设备,会将应用的用户界面调整为适应其显示的屏幕,也就是会对用户界面进行缩放和大小调整。针对说明,了解一些基本概念:

  • 屏幕尺寸:按屏幕对角测量的实际物理尺寸。
  • 屏幕密度:屏幕物理区域中的像素量;通常称为 dpi(每英寸点数)。
  • 分辨率:屏幕上物理像素的总数。
  • 密度无关像素(dp):在定义 UI 布局时应使用的虚拟像素单位,用于以密度无关方式表示布局维度或位置。

密度独立像素(density-independent pixels, dp)

如果采用固定的绝对像素进行页面布局的话,在不同分辨率的设备上页面的大小会不一样。比如,对于640px的宽度,在iPhone4(640×960)上就已经撑满了整个屏幕,但是在iPhone6(750x1334)上却不能,这当然是不可接受的。好在操作系统为提供了密度独立像素。

以 Android 为例,在 Android 系统中,为简化为多种屏幕设计用户界面的方式,将实际屏幕尺寸和密度的范围分为:

  • 四种通用尺寸:小、正常、大和超大
  • 六种通用的密度:
    • ldpi(低)~120dpi
    • mdpi(中)~160dpi
    • hdpi(高)~240dpi
    • xhdpi(超高)~320dpi
    • xxhdpi(超超高)~480dpi
    • xxxhdpi(超超超高)~640dpi

通用的尺寸和密度按照基线配置(即正常尺寸和 mdpi(中)密度)排列。每种通用的尺寸和密度都涵盖一个实际屏幕尺寸和密度范围。例如, 两部都报告正常屏幕尺寸的设备在手动测量时,实际屏幕尺寸和高宽比可能略有不同。类似地,对于两台报告 hdpi 屏幕密度的设备,其实际像素密度可能略有不同。 Android 将这些差异抽象概括到应用,使开发者可以提供为通用尺寸和密度设计的 UI,让系统按需要处理任何最终调整。

在为不同的屏幕尺寸设计 UI 时,开发者会发现每种设计都需要最小空间。因此,上述每种通用的屏幕尺寸都关联了系统定义的最低分辨率。这些最小尺寸以“dp”单位表示 —- 在定义布局时应使用相同的单位 —- 这样系统无需担心屏幕密度的变化。

  • 超大屏幕至少为 960dp x 720dp
  • 大屏幕至少为 640dp x 480dp
  • 正常屏幕至少为 470dp x 320dp
  • 小屏幕至少为 426dp x 320dp

密度独立像素等于 160 dpi 屏幕上的一个物理像素,这是系统为“中”密度屏幕假设的基线密度。在运行时,系统 根据使用中屏幕的实际密度按需要以透明方式处理 dp 单位的任何缩放 。dp 单位转换为屏幕像素很简单: px = dp * (dpi / 160)。 例如,在 240 dpi 屏幕上,1 dp 等于 1.5 物理像素。在定义应用的 UI 时应始终使用 dp 单位 ,以确保在不同密度的屏幕上正常显示 UI。

密度独立性

密度独立像素的密度独立性具体是什么含义呢?应用显示在密度不同的屏幕上时,如果它保持用户界面元素的物理尺寸(从 用户的视角)不变,便可实现“密度独立性” 。

保持密度独立性很重要,因为如果没有此功能,UI 元素(例如按钮)在低密度屏幕上看起来较大,在高密度屏幕上看起来较小。这些 密度相关的大小变化可能给应用布局和易用性带来问题。

上图表示不支持不同密度的示例应用在低、中、高密度屏幕上的显示情况。

上图表示良好支持不同密度(密度独立)的示例应用在低、中、高密度屏幕上的显示情况。

Android 系统可帮助应用以两种方式实现密度独立性:

  • 系统根据当前屏幕密度扩展 dp 单位数
  • 系统在必要时可根据当前屏幕密度将可绘制对象资源扩展到适当的大小

在图1中,文本视图和位图可绘制对象具有以像素(px单位)指定的尺寸,因此视图的物理尺寸在低密度屏幕上更大,在高密度 屏幕上更小。这是因为,虽然实际屏幕尺寸可能相同,但高密度屏幕的每英寸像素更多(同样多的像素在一个更小的区域内)。在图2中,布局尺寸以密度独立的像素(dp单位)指定。由于密度独立像素的基线是中密度屏幕,因此具有中密度屏幕的设备看起来与图1一样。但对于低密度和高密度屏幕,系统将分别增加和减少密度独立像素值,以适应屏幕。

大多数情况下,确保应用中的屏幕独立性很简单,只需以适当的密度独立像素(dp 单位)指定所有 布局尺寸值。系统然后根据适用于当前屏幕密度的缩放比例适当地缩放位图可绘制对象,以适当的大小显示。但位图缩放可能导致模糊,为避免这些问题,应为不同的密度提供替代的位图资源。

解决方案

如何将一个基准是750px的稿子,适配到各个不同尺寸和密度的屏幕上呢?在设计稿中,元素的尺寸与总宽度有一个相对关系,在设备屏幕中,元素的实际宽度的 dp 数量与设备总宽度的 dp 数量有一个相对关系。只需要建立这两个相对关系的映射关系就可以了。

React Native中,有如下几个模块可以帮助实现:

import { Dimensions, PixelRatio } from 'react-native';
 
const {height, width} = Dimensions.get('window'); // 获取窗口的宽、高度,以 dp 为单位。
 
PixelRatio.get(); // 获取设备的像素密度
PixelRatio.getFontScale(); // 获取设备的文字缩放比率

在上面的代码中通过Dimensions.get('window')获取实际设备的宽度,此时只需要建立一个比例关系即可确定对应于这个设备,实际应该设置的元素尺寸的dp值了。

const BASE_LINE = 750; // 设计稿是 750
const EL_WIDTH_DEVICE = width / BASE_LINE * EL_WIDTH_DESIGN;

对于文字的字号大小,设计师一般不希望文字根据尺寸做过多的缩放,因此需要根据系统自身定义的文字缩放比率来设置。

const TEXT_FONT_SIZE = PixelRatio.getFontScale() * FONT_SIZE_DESIGN;

以上方案只是展示基本思路,因为项目复杂性,有条件可以封装调用即可。

对于位图在高清屏下存在模糊的情况,需要根据屏幕的像素密度提供不同尺寸的替代位图,并按需加载。屏幕像素密度可以通过 PixelRatio.get() 获取。
要为不同的密度创建替代位图可绘制对象,应遵循六种通用密度之间的 3:4:6:8:12:16 缩放比率。例如,如果默认位图是对中密度屏幕使用 48x48 像素,则所有不同的尺寸应为:

  • 36x36 (0.75x) 用于低密度
  • 48x48(1.0x 基线)用于中密度
  • 72x72 (1.5x) 用于高密度
  • 96x96 (2.0x) 用于超高密度
  • 144x144 (3.0x) 用于超超高密度
  • 192x192 (4.0x) 用于超超超高密度

对应的判断条件为:

  • PixelRatio.get() === 1
    • mdpi Android devices
  • PixelRatio.get() === 1.5
    • hdpi Android devices
  • PixelRatio.get() === 2
    • iPhone 4, 4S
    • iPhone 5, 5c, 5s
    • iPhone 6, 7, 8
    • xhdpi Android devices
  • PixelRatio.get() === 3
    • iPhone 6 Plus, 7 Plus, 8 Plus
    • iPhone X
    • Pixel, Pixel 2
    • xxhdpi Android devices
  • PixelRatio.get() === 3.5
    • Nexus 6
    • Pixel XL, Pixel 2 XL
    • xxxhdpi Android devices