Appearance
屏幕适配(实战篇)
本篇介绍在实战项目中如何做屏幕适配:何时需要适配、在哪儿初始化、以及如何按设计稿(如 750 逻辑宽)写尺寸。原理与单位说明见 Flutter屏幕适配。
为什么要在项目里做适配
- 设计稿通常按某一基准宽度(如 750px)出图,直接写死
width: 200在不同设备上视觉比例不一致。 - 通过「基准宽度 + 当前屏幕宽度」换算成一套比例系数,用系数参与宽高、字号计算,即可在不同屏幕上保持相近的视觉比例。
方案一:自封装 rpx 工具类(与 base 篇一致)
以设计稿宽度 750 为基准,在项目里放一个工具类,在「已有 MaterialApp 的 context」处初始化一次,之后用 setRpx(size) / setPx(size) 代替写死的数字。
工具类代码
在 lib/utils/size_fit.dart(或 lib/utils/screen_util.dart)中:
dart
import 'package:flutter/material.dart';
class SizeFit {
static MediaQueryData? _mediaQueryData;
static double screenWidth = 0;
static double screenHeight = 0;
/// 1rpx = screenWidth / designWidth
static double rpx = 0;
/// 1px = rpx * 2(设计稿 750 下 1px ≈ 2rpx 的换算,可按设计约定调整)
static double px = 0;
static void initialize(BuildContext context, {double designWidth = 750}) {
_mediaQueryData = MediaQuery.of(context);
screenWidth = _mediaQueryData!.size.width;
screenHeight = _mediaQueryData!.size.height;
rpx = screenWidth / designWidth;
px = rpx * 2;
}
static double setRpx(double size) => rpx * size;
static double setPx(double size) => px * size;
}在项目中何时初始化
必须在能拿到「已挂载 MaterialApp 的 BuildContext」的地方调用一次 SizeFit.initialize(context),通常有两种做法:
- 方式 A:在根页面(如
HomeScreen)的build里第一行调用,保证进入首页后所有子页面拿到的MediaQuery已稳定。 - 方式 B:在
MaterialApp的builder里对根 Widget 包一层,在builder的 context 里初始化(注意builder的 context 已是 Material 上下文)。
示例(方式 A,根页面 build 内):
dart
@override
Widget build(BuildContext context) {
SizeFit.initialize(context);
return Scaffold(
body: Container(
width: SizeFit.setRpx(200),
height: SizeFit.setRpx(400),
child: Text('Hello', style: TextStyle(fontSize: SizeFit.setPx(28))),
),
);
}设计稿上标注「宽度 200、高度 400、字号 28」时,就写 setRpx(200)、setRpx(400)、setPx(28),无需再心算。
设计稿约定
- 若设计稿宽度是 750:
designWidth保持 750,标注多少就传多少给setRpx。 - 若设计稿是 375:创建时写
SizeFit.initialize(context, designWidth: 375),同样按标注值传即可。
保持「设计稿宽度 = designWidth」、全项目统一用 setRpx/setPx,即可在项目中形成一套可维护的适配规范。
方案二:使用 flutter_screenutil
不想自己维护工具类时,可使用 flutter_screenutil。
依赖
在 pubspec.yaml 中:
yaml
dependencies:
flutter_screenutil: ^5.9.0初始化(必须)
在 MaterialApp 的 builder 里用 ScreenUtilInit 包住根 Widget,并设置设计稿尺寸(与方案一一致,按设计稿宽度来):
dart
return MaterialApp(
builder: (context, child) {
return ScreenUtilInit(
designSize: const Size(750, 1334),
minTextAdapt: true,
splitScreenMode: true,
child: child ?? const SizedBox.shrink(),
);
},
home: const HomeScreen(),
);designSize 的 width 与设计稿宽度一致(如 750),height 可按设计稿高度填,主要用于竖屏下高度相关换算。
使用
在任意子组件的 build 里:
- 宽度:
100.w表示设计稿 100 逻辑宽在当前设备的等效宽度。 - 高度:
200.h表示设计稿 200 逻辑高的等效高度。 - 字号:
28.sp表示 28 的字体大小(会随系统字体缩放可选处理)。 - 半径等:
12.r可同时适配宽高的圆角、图标等。
示例:
dart
Container(
width: 200.w,
height: 400.h,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.r),
),
child: Text('Hello', style: TextStyle(fontSize: 28.sp)),
)这样项目里所有尺寸都按设计稿数字 + .w/.h/.sp/.r,即可完成实战中的屏幕适配。
安全区域(刘海、底部横条)
无论用方案一还是方案二,都要注意避开刘海和底部横条,避免内容被遮挡。在 Flutter 中通过 MediaQuery.of(context).padding 获取安全区:
dart
final padding = MediaQuery.of(context).padding;
Padding(
padding: EdgeInsets.only(
top: padding.top,
bottom: padding.bottom,
),
child: yourContent,
)或直接使用已考虑安全区的 SafeArea:
dart
SafeArea(child: yourContent)列表、全屏页等建议在根布局包一层 SafeArea,再在内部用 SizeFit 或 ScreenUtil 做尺寸换算。
小结
- 实战中适配的核心:设计稿基准宽度 + 当前屏幕宽度 得到比例,用比例换算宽高和字号。
- 方案一:自封装
SizeFit,在根页面或MaterialApp.builder里initialize(context)一次,用setRpx/setPx写尺寸。 - 方案二:使用
flutter_screenutil,在MaterialApp.builder里包ScreenUtilInit,用.w/.h/.sp/.r写尺寸。 - 同时用
SafeArea或MediaQuery.padding处理刘海与底部安全区。
按上述任选一种方式在项目中统一使用,即可满足新手实战中的屏幕适配需求。更多原理见 Flutter屏幕适配。