Flutter ——flutter_screenutil 屏幕适配

flutter_screenutil 是 Flutter 生态中最常用的屏幕适配库,核心原理是基于设计稿尺寸做等比例缩放 ,支持宽度 / 高度适配、字体适配、边框适配等,能快速解决不同设备的 UI 适配问题。下面从安装配置、核心用法、高级技巧、避坑指南四个维度全面解析。

一、基础准备:安装与初始化

1. 安装依赖

pubspec.yaml 中添加最新版本依赖:

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter
  flutter_screenutil: ^5.9.0 # 推荐使用最新稳定版

执行安装命令:

arduino 复制代码
flutter pub get

2. 全局初始化(关键步骤)

必须在 APP 入口处初始化,指定设计稿的基准尺寸(通常以 iPhone 14/15 的 390×844 或 iPhone 13 的 375×812 为例):

Dart 复制代码
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) {
    // 初始化ScreenUtil
    return ScreenUtilInit(
      // 设计稿尺寸(单位:px),根据你的UI设计稿修改
      designSize: const Size(390, 844), 
      // 适配规则:默认宽度适配,也可设为BoxConstraints.tightFor(height: ...)
      minTextAdapt: true, // 开启字体适配
      splitScreenMode: true, // 支持分屏适配
      builder: (context, child) {
        return MaterialApp(
          title: 'ScreenUtil示例',
          theme: ThemeData(primarySwatch: Colors.blue),
          home: child, // 传递子页面
          builder: (context, widget) {
            // 解决字体缩放导致的适配问题
            ScreenUtil.init(context);
            return MediaQuery(
              data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
              child: widget!,
            );
          },
        );
      },
      child: const HomePage(), // 你的首页
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('屏幕适配示例')),
      body: const Center(child: AdaptingWidget()),
    );
  }
}

二、核心用法:适配各种 UI 属性

flutter_screenutil 提供了两种常用适配方式:扩展方法 (推荐)和静态方法,下面是最常用的适配场景:

1. 尺寸适配(宽 / 高 / 边距 / 圆角)

Dart 复制代码
class AdaptingWidget extends StatelessWidget {
  const AdaptingWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Padding(
      // 边距适配:设计稿上的20px → 自动缩放
      padding: EdgeInsets.all(20.w), 
      child: Container(
        // 宽度适配:设计稿上的300px
        width: 300.w, 
        // 高度适配:设计稿上的150px
        height: 150.h, 
        // 圆角适配:设计稿上的16px
        decoration: BoxDecoration(
          color: Colors.blue,
          borderRadius: BorderRadius.circular(16.r), 
          // 边框宽度适配:设计稿上的2px
          border: Border.all(width: 2.w, color: Colors.white), 
        ),
        // 内边距适配
        child: Padding(
          padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 8.h),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              // 字体适配:设计稿上的18px
              Text(
                '适配后的文字',
                style: TextStyle(fontSize: 18.sp), 
              ),
              SizedBox(height: 10.h), // 间距适配
              // 图标大小适配
              Icon(Icons.phone, size: 24.sp, color: Colors.white),
            ],
          ),
        ),
      ),
    );
  }
}

2. 核心扩展方法说明

扩展方法 作用 示例
num.w 宽度适配(等比例缩放) 300.w → 设计稿 300px 宽度适配到当前设备
num.h 高度适配(等比例缩放) 150.h → 设计稿 150px 高度适配到当前设备
num.r 圆角 / 半径适配(优先按宽度适配) 16.r → 设计稿 16px 圆角适配
num.sp 字体大小适配(支持最小字体限制) 18.sp → 设计稿 18px 字体适配
num.sw 屏幕宽度占比(0-1) 0.8.sw → 占屏幕宽度 80%
num.sh 屏幕高度占比(0-1) 0.5.sh → 占屏幕高度 50%

3. 静态方法(无上下文时使用)

如果在工具类、初始化代码等无context的场景下,可使用静态方法:

Dart 复制代码
// 获取适配后的尺寸
double width = ScreenUtil().setWidth(300);
double height = ScreenUtil().setHeight(150);
double fontSize = ScreenUtil().setSp(18);

// 获取屏幕信息
double screenWidth = ScreenUtil().screenWidth; // 设备实际宽度
double screenHeight = ScreenUtil().screenHeight; // 设备实际高度
double pixelRatio = ScreenUtil().pixelRatio; // 设备像素比

三、高级技巧:适配优化与特殊场景

1. 字体适配优化

默认的sp适配可能在小屏设备上字体过小,可设置最小字体限制:

Dart 复制代码
// 方式1:全局初始化时设置
ScreenUtilInit(
  designSize: const Size(390, 844),
  minTextAdapt: true,
  // 自定义字体适配规则
  builder: (context, child) {
    // 设置最小字体为12sp
    ScreenUtil.init(context, minTextSize: 12);
    return child!;
  },
  child: const HomePage(),
);

// 方式2:单独设置字体(强制最小字体)
Text(
  '最小12sp的文字',
  style: TextStyle(
    fontSize: 10.spMin, // 即使适配后小于12sp,也显示12sp
  ),
);

2. 横竖屏适配

处理横竖屏切换时的适配更新:

Dart 复制代码
class OrientationPage extends StatefulWidget {
  const OrientationPage({super.key});

  @override
  State<OrientationPage> createState() => _OrientationPageState();
}

class _OrientationPageState extends State<OrientationPage> {
  @override
  Widget build(BuildContext context) {
    // 横竖屏切换时重新初始化
    ScreenUtil.init(context);
    return Scaffold(
      body: Center(
        child: Text(
          '屏幕宽度:${ScreenUtil().screenWidth.w}\n屏幕高度:${ScreenUtil().screenHeight.h}',
          style: TextStyle(fontSize: 16.sp),
        ),
      ),
    );
  }

  // 监听横竖屏切换
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    final orientation = MediaQuery.of(context).orientation;
    print('当前方向:$orientation');
  }
}

3. ListView/GridView 适配

避免滚动组件因适配导致的布局异常:

Dart 复制代码
// ListView 适配示例
ListView.builder(
  // 高度适配
  itemExtent: 80.h, 
  itemCount: 10,
  itemBuilder: (context, index) {
    return ListTile(
      leading: Icon(Icons.person, size: 32.sp),
      title: Text('列表项 $index', style: TextStyle(fontSize: 16.sp)),
      subtitle: Text('副标题', style: TextStyle(fontSize: 12.sp)),
      contentPadding: EdgeInsets.symmetric(horizontal: 16.w),
    );
  },
);

// GridView 适配示例
GridView.count(
  crossAxisCount: 2, // 列数
  crossAxisSpacing: 10.w, // 列间距
  mainAxisSpacing: 10.h, // 行间距
  childAspectRatio: 16/9, // 宽高比(无需适配)
  padding: EdgeInsets.all(10.w),
  children: List.generate(4, (index) {
    return Container(
      width: (ScreenUtil().screenWidth - 30.w) / 2, // 手动计算宽度
      height: 100.h,
      color: Colors.grey,
    );
  }),
);

4. 自定义适配规则

针对特殊 UI(如固定宽高比的图片),可混合使用sw/sh

Dart 复制代码
// 保持16:9比例的图片
Container(
  width: 0.9.sw, // 占屏幕宽度90%
  height: (0.9.sw) * 9 / 16, // 按16:9计算高度
  child: Image.network(
    'https://example.com/image.jpg',
    fit: BoxFit.cover,
  ),
);

四、避坑指南:常见问题与解决方案

1. 适配后布局变形

问题 解决方案
部分 UI 元素过大 / 过小 检查设计稿尺寸是否设置正确(如把 dp 当成 px);优先用w/h而非sw/sh
字体缩放导致布局错乱 MaterialAppbuilder中设置textScaleFactor: 1.0,禁用系统字体缩放
圆角适配不一致 统一使用r而非w/hr会自动适配宽高比

2. 无上下文时初始化失败

  • 错误场景:在main函数中直接调用ScreenUtil().setWidth(300)

  • 解决方案:先初始化WidgetsFlutterBinding,再手动初始化ScreenUtil

    Dart 复制代码
    void main() async {
      WidgetsFlutterBinding.ensureInitialized();
      // 手动初始化(指定设计稿尺寸)
      await ScreenUtil.ensureScreenSize();
      ScreenUtil.init(
        BoxConstraints(
          maxWidth: WidgetsBinding.instance.window.physicalSize.width / WidgetsBinding.instance.window.devicePixelRatio,
          maxHeight: WidgetsBinding.instance.window.physicalSize.height / WidgetsBinding.instance.window.devicePixelRatio,
        ),
        designSize: const Size(390, 844),
      );
      runApp(const MyApp());
    }

3. 分屏 / 折叠屏适配

  • 开启splitScreenMode: true(初始化时);

  • 避免使用固定的sh(屏幕高度占比),优先用MediaQuery获取可用高度:

    arduino 复制代码
    double availableHeight = MediaQuery.of(context).size.height - MediaQuery.of(context).padding.top - kToolbarHeight;

总结

  1. 核心流程 :先在ScreenUtilInit中指定设计稿尺寸,再通过w/h/r/sp扩展方法实现 UI 适配,覆盖 90% 的适配场景;
  2. 最佳实践 :字体用sp(开启最小字体限制)、尺寸用w/h、圆角用r、占比用sw/sh
  3. 避坑要点 :禁用系统字体缩放、横竖屏切换时重新初始化、无上下文时先初始化WidgetsFlutterBinding

如果需要针对某一特殊场景(如适配折叠屏、自定义设计稿尺寸、国际化多语言适配)的代码示例,我可以进一步补充。

相关推荐
Haha_bj2 小时前
Flutter ——device_info_plus详解
android·flutter·ios
前端小伙计2 小时前
Android/Flutter 项目统一构建配置最佳实践
android·flutter
Mr_sun.3 小时前
Day09——入退管理-入住-2
android·java·开发语言
ujainu4 小时前
告别杂乱!Flutter + OpenHarmony 鸿蒙记事本的标签与分类管理(三)
android·flutter·openharmony
常利兵5 小时前
Android内存泄漏:成因剖析与高效排查实战指南
android
·云扬·5 小时前
MySQL 8.0 Redo Log 归档与禁用实战指南
android·数据库·mysql
野生技术架构师5 小时前
SQL语句性能优化分析及解决方案
android·sql·性能优化
山水域6 小时前
SKAdNetwork 6.0 深度实战:多窗口转化值(Conversion Value)建模与数据分层架构
ios
doupoa6 小时前
内存指针是什么?为什么指针还要有偏移量?
android·c++