尺寸与单位
Flutter 中的尺寸数值没有单位后缀,初学者常常困惑:设计稿标注 16px,Flutter 里写 16,它们到底什么关系?本文帮你彻底搞清楚。
核心概念:逻辑像素
Flutter 中所有尺寸(width、height、padding、fontSize 等)使用的都是逻辑像素(Logical Pixels),也叫设备独立像素(dp / dip)。
// 这些 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.0 | 1 逻辑像素 = 1 物理像素 |
| 普通手机 | 2.0 | 1 逻辑像素 = 2×2 = 4 物理像素 |
| 高端手机 | 3.0 | 1 逻辑像素 = 3×3 = 9 物理像素 |
| 超高分屏 | 3.5 / 4.0 | 部分旗舰机型 |
// 获取当前设备的像素比
final dpr = MediaQuery.of(context).devicePixelRatio; // 如 2.0、3.0各平台单位对比
理解了逻辑像素后,一个自然的疑问是:Flutter 的逻辑像素和 Android 的 dp、iOS 的 pt、Web 的 CSS px 是什么关系?
| 平台 | 单位名称 | 本质 | 与 Flutter 的关系 |
|---|---|---|---|
| Flutter | 逻辑像素(无后缀) | 设备独立像素 | — |
| Android | dp (density-independent pixels) | 设备独立像素 | 1:1 相同 |
| iOS | pt (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 直接使用:
// 设计稿标注 200px 宽、16px 字号
Container(width: 200, height: 44) // 直接写 200
Text('Hello', style: TextStyle(fontSize: 16)) // 直接写 162x / 3x 设计稿
部分设计稿使用物理像素标注(常见于 2 倍图),需要除以倍率:
// 设计稿标注 400px(2x 图),实际逻辑像素 = 400 / 2 = 200
Container(width: 400 / 2) // → 200
// 更通用的写法
final designWidth = 400; // 设计稿上的值
final logicalWidth = designWidth / 2; // 转换后的值屏幕适配
设计稿换算解决的是「数值怎么转」的问题。但不同手机屏幕宽度不同(375、390、414……),如果只做换算而不做适配,UI 在非标准宽度的设备上就会出现偏差。屏幕适配解决的是「不同设备上如何保持一致的视觉效果」。
手动适配方案
按设计稿宽度与实际设备宽度的比例进行缩放:
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)),
),
),
],
),
),
);
}
}封装适配工具类
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))注意
等比缩放不是万能方案。在大屏设备(如平板、桌面)上,简单的线性缩放会导致元素过大。推荐结合 LayoutBuilder 或 MediaQuery 做响应式布局。
插件方案
手动封装工具类能满足基本需求,但社区有成熟的插件提供更完善的功能。
插件对比
| 特性 | flutter_screenutil | auto_size_text | responsive_framework |
|---|---|---|---|
| 核心能力 | 按设计稿等比缩放 | 文字自适应容器大小 | 响应式断点布局 |
| 适配思路 | 缩放 | 缩放(仅文字) | 断点切换 |
| 适用场景 | 手机端严格还原设计稿 | 文字不溢出/不截断 | 手机+平板+桌面多端 |
| 学习成本 | 低 | 极低 | 中 |
| 侵入性 | 低(.w / .h 后缀) | 极低(替换 Text) | 中(需包装层级) |
| 多端适配 | 不适合大屏 | 仅文字 | 专为此设计 |
| pub likes | 5.0k | 5.0k | 3.3k |
| 周下载量 | 272k | 938k | 121k |
| 维护状态 | ✅ 活跃 | ⚠️ 4年未更新 | ⚠️ 20个月未更新 |
使用最多的是 flutter_screenutil,它是国内 Flutter 社区屏幕适配的事实标准,大多数教程和项目都使用它。
选择建议:
- 手机端严格还原设计稿(推荐) →
flutter_screenutil,社区最流行,资料最多 - 只需要文字自适应 →
auto_size_text,虽久未更新但功能稳定,无替代品 - 手机/平板/桌面多端适配 →
responsive_framework,断点布局最成熟 - 组合使用(推荐) →
flutter_screenutil(尺寸适配)+auto_size_text(文字防溢出)
flutter_screenutil
最流行的屏幕适配插件,按设计稿宽高进行等比缩放。
安装:
flutter pub add flutter_screenutil初始化:
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!;
},
)使用方式:
// 宽度适配 — .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) // 垂直间距完整示例:
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),
),
],
),
),
);
}
}是否允许字体随系统缩放:
// 默认跟随系统缩放,如需禁用:
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
文字自适应插件,让文字自动缩放以填充可用空间,避免溢出或截断。
安装:
flutter pub add auto_size_text使用方式:
// 基础用法 — 文字自动缩放以填满宽度
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,
)常见场景:
// 按钮文字自适应
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
响应式布局插件,通过断点机制在不同屏幕宽度下切换不同的布局结构。
安装:
flutter pub add responsive_framework初始化:
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(),
)使用方式:
// 方式 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(),
)完整示例 — 手机/平板/桌面三端布局:
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 — 必须写单位 */
width: 200px;
font-size: 16px;// 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 个物理像素。但肉眼看到的视觉大小是一样的——这正是逻辑像素的意义。
相关链接
内部导航:
- MediaQuery 媒体查询 — 获取屏幕尺寸和设备像素比
- 布局约束模型 — 理解布局中的约束与尺寸
- SizedBox 尺寸盒子 — 设置固定尺寸的组件
- Container 容器 — 最常用的布局容器
外部资源:
- flutter_screenutil — 屏幕适配插件(最流行)
- auto_size_text — 文字自适应插件
- responsive_framework — 响应式布局插件
