Skip to content

尺寸与单位

Flutter 中的尺寸数值没有单位后缀,初学者常常困惑:设计稿标注 16px,Flutter 里写 16,它们到底什么关系?本文帮你彻底搞清楚。

核心概念:逻辑像素

Flutter 中所有尺寸(widthheightpaddingfontSize 等)使用的都是逻辑像素(Logical Pixels),也叫设备独立像素(dp / dip)。

dart
// 这些 16 都是逻辑像素,不带单位
SizedBox(width: 16, height: 16)
EdgeInsets.all(16)
TextStyle(fontSize: 16)

关键点: 逻辑像素是一个抽象单位,同一数值在不同设备上会自动映射到不同的物理像素,以保证视觉大小一致。

逻辑像素 vs 物理像素

概念说明示例
逻辑像素Flutter 代码中使用的单位width: 100
物理像素屏幕上实际的发光点100 × 2.0 = 200 个物理像素
设备像素比物理像素 / 逻辑像素devicePixelRatio = 2.0

它们的关系:

物理像素 = 逻辑像素 × 设备像素比(devicePixelRatio)

设备像素比速查

设备类型devicePixelRatio说明
低分辨率1.01 逻辑像素 = 1 物理像素
普通手机2.01 逻辑像素 = 2×2 = 4 物理像素
高端手机3.01 逻辑像素 = 3×3 = 9 物理像素
超高分屏3.5 / 4.0部分旗舰机型
dart
// 获取当前设备的像素比
final dpr = MediaQuery.of(context).devicePixelRatio; // 如 2.0、3.0

各平台单位对比

理解了逻辑像素后,一个自然的疑问是:Flutter 的逻辑像素和 Android 的 dp、iOS 的 pt、Web 的 CSS px 是什么关系?

平台单位名称本质与 Flutter 的关系
Flutter逻辑像素(无后缀)设备独立像素
Androiddp (density-independent pixels)设备独立像素1:1 相同
iOSpt (point)设备独立像素1:1 相同
Web (CSS)px (CSS pixel)CSS 像素1:1 相同
设计稿px(物理像素)实际渲染像素需除以 devicePixelRatio

简单记忆:Flutter 的数值 = Android 的 dp = iOS 的 pt = Web 的 CSS px,它们都是逻辑像素,数值互通。

设计稿换算

初学者最关心的问题:设计稿标注的 px,怎么转成 Flutter 的值?关键在于判断设计稿用的是逻辑像素还是物理像素。

判断设计稿类型

看设计稿的画布宽度即可判断:

设计稿画布宽度类型换算方式
375 / 360 / 390 等逻辑像素(1x 图)直接使用
750 / 720 等物理像素(2x 图)除以 2
1125 / 1242 等物理像素(3x 图)除以 3

大多数现代设计工具(Figma、Sketch)的画布本身使用逻辑像素,设计稿中 16px 的含义就是「16 个逻辑像素」,和 Flutter 的 16 完全等价。

1:1 设计稿(最常见)

Figma 等工具默认以逻辑像素为基准,1:1 直接使用

dart
// 设计稿标注 200px 宽、16px 字号
Container(width: 200, height: 44)  // 直接写 200
Text('Hello', style: TextStyle(fontSize: 16))  // 直接写 16

2x / 3x 设计稿

部分设计稿使用物理像素标注(常见于 2 倍图),需要除以倍率

dart
// 设计稿标注 400px(2x 图),实际逻辑像素 = 400 / 2 = 200
Container(width: 400 / 2)  // → 200

// 更通用的写法
final designWidth = 400;  // 设计稿上的值
final logicalWidth = designWidth / 2;  // 转换后的值

屏幕适配

设计稿换算解决的是「数值怎么转」的问题。但不同手机屏幕宽度不同(375、390、414……),如果只做换算而不做适配,UI 在非标准宽度的设备上就会出现偏差。屏幕适配解决的是「不同设备上如何保持一致的视觉效果」。

手动适配方案

按设计稿宽度与实际设备宽度的比例进行缩放:

dart
import 'package:flutter/material.dart';

class ManualAdaptPage extends StatelessWidget {
  const ManualAdaptPage({super.key});

  @override
  Widget build(BuildContext context) {
    // 设计稿基准宽度(如 375)
    const double designWidth = 375.0;

    // 获取当前屏幕逻辑宽度
    // ─── ★ 获取屏幕宽度 ──────────────
    final screenWidth = MediaQuery.of(context).size.width;
    // ─── ☆ 获取屏幕宽度 ──────────────

    // 缩放比例
    // ─── ★ 计算缩放比例 ──────────────
    final scale = screenWidth / designWidth;
    // ─── ☆ 计算缩放比例 ──────────────

    return Scaffold(
      appBar: AppBar(title: const Text('手动适配')),
      body: Padding(
        // 使用:设计稿 16px → 实际设备上的适配值
        padding: EdgeInsets.all(16 * scale),
        child: Column(
          children: [
            SizedBox(
              width: 200 * scale,
              height: 44 * scale,
              child: ElevatedButton(
                onPressed: () {},
                child: Text('按钮', style: TextStyle(fontSize: 16 * scale)),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

封装适配工具类

dart
class ScreenUtil {
  static late double _scale;
  static late double _screenWidth;
  static late double _screenHeight;

  /// 初始化,在 MaterialApp 的 builder 中调用
  static void init(BuildContext context) {
    _screenWidth = MediaQuery.of(context).size.width;
    _screenHeight = MediaQuery.of(context).size.height;
    _scale = _screenWidth / 375; // 设计稿宽度 375
  }

  /// 按设计稿宽度等比缩放
  static double w(double val) => val * _scale;

  /// 按设计稿高度等比缩放
  static double h(double val) => val * (_screenHeight / 812); // 设计稿高度 812

  /// 字号缩放
  static double sp(double val) => val * _scale;
}

// 使用
ScreenUtil.init(context);
SizedBox(width: ScreenUtil.w(200), height: ScreenUtil.w(44))
TextStyle(fontSize: ScreenUtil.sp(16))

注意

等比缩放不是万能方案。在大屏设备(如平板、桌面)上,简单的线性缩放会导致元素过大。推荐结合 LayoutBuilderMediaQuery 做响应式布局。

插件方案

手动封装工具类能满足基本需求,但社区有成熟的插件提供更完善的功能。

插件对比

特性flutter_screenutilauto_size_textresponsive_framework
核心能力按设计稿等比缩放文字自适应容器大小响应式断点布局
适配思路缩放缩放(仅文字)断点切换
适用场景手机端严格还原设计稿文字不溢出/不截断手机+平板+桌面多端
学习成本极低
侵入性低(.w / .h 后缀)极低(替换 Text)中(需包装层级)
多端适配不适合大屏仅文字专为此设计
pub likes5.0k5.0k3.3k
周下载量272k938k121k
维护状态✅ 活跃⚠️ 4年未更新⚠️ 20个月未更新

使用最多的是 flutter_screenutil,它是国内 Flutter 社区屏幕适配的事实标准,大多数教程和项目都使用它。

选择建议:

  • 手机端严格还原设计稿(推荐)flutter_screenutil,社区最流行,资料最多
  • 只需要文字自适应auto_size_text,虽久未更新但功能稳定,无替代品
  • 手机/平板/桌面多端适配responsive_framework,断点布局最成熟
  • 组合使用(推荐)flutter_screenutil(尺寸适配)+ auto_size_text(文字防溢出)

flutter_screenutil

最流行的屏幕适配插件,按设计稿宽高进行等比缩放。

安装:

bash
flutter pub add flutter_screenutil

初始化:

dart
void main() {
  // 在 runApp 之前初始化,设置设计稿尺寸
  ScreenUtil.init(
    const BoxConstraints(
      minWidth: 0,
      maxWidth: 375,  // 设计稿宽度
      minHeight: 0,
      maxHeight: 812, // 设计稿高度
    ),
  );
  runApp(const MyApp());
}

// 如果需要在运行时重新适配(如屏幕旋转),在 MaterialApp 的 builder 中:
MaterialApp(
  builder: (context, child) {
    ScreenUtil.init(context); // 使用屏幕实际尺寸
    return child!;
  },
)

使用方式:

dart
// 宽度适配 — .w
SizedBox(width: 200.w)    // 设计稿 200px → 自动按比例缩放

// 高度适配 — .h
SizedBox(height: 44.h)     // 设计稿 44px → 自动按比例缩放

// 字号适配 — .sp
TextStyle(fontSize: 16.sp) // 设计稿 16px → 自动按比例缩放

// 圆角适配 — .r
BorderRadius.circular(8.r) // 设计稿 8px → 自动按比例缩放

// 内边距
EdgeInsets.all(16.w)                              // 四周统一
EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h) // 对称

// 方向间距
SizedBox(width: 8.w)   // 水平间距
SizedBox(height: 12.h) // 垂直间距

完整示例:

dart
class MyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: EdgeInsets.all(16.w),         // 内边距适配
        child: Column(
          children: [
            Container(
              width: 343.w,                     // 宽度适配
              height: 44.h,                     // 高度适配
              decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(8.r), // 圆角适配
                color: Colors.blue,
              ),
              child: Center(
                child: Text(
                  '按钮',
                  style: TextStyle(fontSize: 16.sp),     // 字号适配
                ),
              ),
            ),
            SizedBox(height: 12.h),             // 间距适配
            Text(
              '正文内容',
              style: TextStyle(fontSize: 14.sp),
            ),
          ],
        ),
      ),
    );
  }
}

是否允许字体随系统缩放:

dart
// 默认跟随系统缩放,如需禁用:
ScreenUtil.init(
  designSize: const Size(375, 812),
  minTextAdapt: true,     // 最小字号限制
);

// 单个文字控制
Text('固定字号', style: TextStyle(fontSize: 16.sp),)
// 改为不跟随系统:
Text('固定字号', style: TextStyle(fontSize: ScreenUtil().setSp(16, allowFontScaling: false)),)

最佳实践

  • 所有尺寸统一加后缀(.w / .h / .sp / .r),不要混用
  • 水平方向用 .w,垂直方向用 .h,字号用 .sp,圆角用 .r
  • 不需要适配的值不要加后缀(如固定 1px 分割线:SizedBox(height: 0.5)

auto_size_text

文字自适应插件,让文字自动缩放以填充可用空间,避免溢出或截断。

安装:

bash
flutter pub add auto_size_text

使用方式:

dart
// 基础用法 — 文字自动缩放以填满宽度
AutoSizeText(
  '这是一段很长的文字,会自动缩小字号以适应容器宽度',
  style: TextStyle(fontSize: 16),  // 最大字号
  maxLines: 2,                     // 最多行数
)

// 指定字号范围
AutoSizeText(
  '标题文字',
  style: TextStyle(fontSize: 24),  // 最大字号 24
  minFontSize: 12,                 // 最小字号 12(不会再更小)
  maxLines: 1,
)

// 按组同步缩放 — 多个文字保持相同缩放比例
AutoSizeGroup group = AutoSizeGroup();
Column(
  children: [
    AutoSizeText('短文字', group: group, style: TextStyle(fontSize: 20)),
    AutoSizeText('这是一段非常非常长的文字', group: group, style: TextStyle(fontSize: 20)),
  ],
)
// 两行文字会缩小到相同字号,保持视觉统一

// 富文本
AutoSizeText.rich(
  TextSpan(
    text: '普通文字',
    style: TextStyle(fontSize: 16),
    children: [
      TextSpan(text: '加粗', style: TextStyle(fontWeight: FontWeight.bold)),
    ],
  ),
  maxLines: 1,
)

常见场景:

dart
// 按钮文字自适应
SizedBox(
  width: 120.w,
  height: 40.h,
  child: AutoSizeText(
    '确认提交',
    style: TextStyle(fontSize: 16.sp),
    maxLines: 1,
  ),
)

// 卡片标题自适应
Card(
  child: SizedBox(
    width: 160.w,
    child: AutoSizeText(
      '这是一个可能很长的卡片标题',
      style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold),
      maxLines: 2,
      minFontSize: 12,
      overflow: TextOverflow.ellipsis,
    ),
  ),
)

responsive_framework

响应式布局插件,通过断点机制在不同屏幕宽度下切换不同的布局结构。

安装:

bash
flutter pub add responsive_framework

初始化:

dart
MaterialApp(
  builder: (context, child) => ResponsiveBreakpoints.builder(
    child: child!,
    breakpoints: [
      const Breakpoint(start: 0, end: 450, name: MOBILE),
      const Breakpoint(start: 451, end: 800, name: TABLET),
      const Breakpoint(start: 801, end: 1920, name: DESKTOP),
      const Breakpoint(start: 1921, end: double.infinity, name: '4K'),
    ],
  ),
  home: const HomePage(),
)

使用方式:

dart
// 方式 1:根据断点显示不同组件
ResponsiveBreakpoints.of(context).isMobile    // 是否手机
ResponsiveBreakpoints.of(context).isTablet    // 是否平板
ResponsiveBreakpoints.of(context).isDesktop   // 是否桌面
ResponsiveBreakpoints.of(context).largerThan(TABLET)  // 大于平板

// 方式 2:根据断点返回不同值
ResponsiveValue<double>(
  context,
  defaultValue: 16,
  valueWhen: [
    const ValueWhen.is(TABLET, 24),
    const ValueWhen.is(DESKTOP, 32),
  ],
).value

// 方式 3:自动缩放 — 根据屏幕宽度线性缩放
// 比手动 .w/.h 更灵活,大屏不会无限放大
ResponsiveScaledBox(
  width: 375, // 设计稿宽度
  child: MyPageContent(),
)

完整示例 — 手机/平板/桌面三端布局:

dart
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ResponsiveBreakpoints.of(context).isDesktop
          ? _DesktopLayout()    // 三栏布局
          : ResponsiveBreakpoints.of(context).isTablet
              ? _TabletLayout() // 两栏布局
              : _MobileLayout(), // 单栏布局
  }
}

// 或者更简洁的写法
class AdaptiveGrid extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final crossAxisCount = ResponsiveValue<int>(
      context,
      defaultValue: 2,   // 手机 2 列
      valueWhen: [
        const ValueWhen.is(TABLET, 3),   // 平板 3 列
        const ValueWhen.is(DESKTOP, 4), // 桌面 4 列
      ],
    ).value;

    return GridView.count(
      crossAxisCount: crossAxisCount!,
      children: [...],
    );
  }
}

常用尺寸速查

间距

用途推荐值示例
极小间距4图标与文字之间
小间距8同行元素间距
常规间距12 / 16卡片内边距
中等间距20 / 24区块之间
大间距32 / 48页面区域分隔

字号

用途推荐值
辅助文字12
正文14 / 16
小标题16 / 18
标题20 / 24
大标题28 / 32

圆角

用途推荐值
按钮/输入框4 / 8
卡片8 / 12
对话框16
全宽容器0(直角)

常见问题

Flutter 代码中为什么不写 px?

Flutter 的 double 值本身就是逻辑像素,不需要单位后缀。这和 CSS 中必须写 16px 不同:

css
/* CSS — 必须写单位 */
width: 200px;
font-size: 16px;
dart
// Flutter — 数值即逻辑像素,无需单位
width: 200
fontSize: 16

为什么设计稿上的值直接用就对了?

因为现代设计工具(Figma、Sketch)的画布本身使用的就是逻辑像素。设计稿中 16px 的含义是「16 个逻辑像素」,和 Flutter 的 16 完全等价。

什么时候需要除以 devicePixelRatio?

只有当设计稿使用物理像素标注时才需要。判断方法见 判断设计稿类型

SizedBox(height: 8) 中的 8 到底多大?

8 个逻辑像素。在 devicePixelRatio = 2.0 的设备上,它占 16 个物理像素;在 devicePixelRatio = 3.0 的设备上,它占 24 个物理像素。但肉眼看到的视觉大小是一样的——这正是逻辑像素的意义。

相关链接

内部导航:

外部资源:

基于 Flutter 官方文档整理