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

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

相关推荐
91刘仁德6 小时前
MYSQL 事务原理及使用
android·mysql·adb
一笑的小酒馆8 小时前
AndroidKMP之瀑布流实现
android
RainyJiang8 小时前
AI时代下,Android的边界正在消失
android·openai·ai编程
Android-Flutter9 小时前
android 内存抖动详解
android
代码的小搬运工10 小时前
KVO学习
学习·ios·cocoa
方白羽10 小时前
为什么 Android 非要用 Intent 传值?
android·ios·harmonyos
dora12 小时前
iOS开发新手的第一行代码
ios
方白羽13 小时前
Android17 重写 MessageQueue,解决 Handler 隐性卡顿
android·app
蜡台13 小时前
Kotlin 零基础完整版实战教程|从语法入门到Android工程实战
android·开发语言·kotlin
律宏阔14 小时前
Android 车载 USB 开发笔记:USB Host、USB 串口、USB-CAN、HID 与系统 API
android