Skip to content

屏幕适配(实战篇)

本篇介绍在实战项目中如何做屏幕适配:何时需要适配、在哪儿初始化、以及如何按设计稿(如 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:在 MaterialAppbuilder 里对根 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),无需再心算。

设计稿约定

  • 若设计稿宽度是 750designWidth 保持 750,标注多少就传多少给 setRpx
  • 若设计稿是 375:创建时写 SizeFit.initialize(context, designWidth: 375),同样按标注值传即可。

保持「设计稿宽度 = designWidth」、全项目统一用 setRpx/setPx,即可在项目中形成一套可维护的适配规范。

方案二:使用 flutter_screenutil

不想自己维护工具类时,可使用 flutter_screenutil

依赖

pubspec.yaml 中:

yaml
dependencies:
  flutter_screenutil: ^5.9.0

初始化(必须)

MaterialAppbuilder 里用 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,再在内部用 SizeFitScreenUtil 做尺寸换算。

小结

  • 实战中适配的核心:设计稿基准宽度 + 当前屏幕宽度 得到比例,用比例换算宽高和字号。
  • 方案一:自封装 SizeFit,在根页面或 MaterialApp.builderinitialize(context) 一次,用 setRpx/setPx 写尺寸。
  • 方案二:使用 flutter_screenutil,在 MaterialApp.builder 里包 ScreenUtilInit,用 .w/.h/.sp/.r 写尺寸。
  • 同时用 SafeAreaMediaQuery.padding 处理刘海与底部安全区。

按上述任选一种方式在项目中统一使用,即可满足新手实战中的屏幕适配需求。更多原理见 Flutter屏幕适配