Skip to content

MediaQuery(媒体查询)

MediaQuery 是 Flutter 中获取设备信息的核心工具——屏幕多大、像素密度多少、键盘是否弹出、系统是深色还是浅色……这些信息都通过它获取。

核心理解

MediaQuery 的工作方式类似于 CSS 的 @media 查询,但更灵活——它不仅提供信息,还能监听变化(如屏幕旋转、键盘弹出)。

dart
// 获取 MediaQuery 数据
final mediaQuery = MediaQuery.of(context);

性能提示

MediaQuery.of(context) 会监听所有媒体数据的变化。如果你只需要某个属性(如 size),当键盘弹出导致 viewInsets 变化时,你的 Widget 也会重建。如果需要优化性能,可以使用 MediaQuery.sizeOf(context) 等专用方法(Flutter 3.8+)。

常用属性一览

属性返回类型说明典型值
sizeSize屏幕逻辑尺寸Size(375, 812)
size.widthdouble屏幕宽度375、390、414
size.heightdouble屏幕高度812、844、896
devicePixelRatiodouble设备像素比2.0、3.0
textScaleFactordouble系统字体缩放倍率(旧 API)1.0 ~ 2.0
textScalerTextScaler系统字体缩放(新 API,Flutter 3.16+)TextScaler.linear(1.0)
platformBrightnessBrightness系统亮度模式Brightness.light / dark
orientationOrientation屏幕方向portrait / landscape
paddingEdgeInsets安全区内边距top: 47~59, bottom: 34
viewPaddingEdgeInsets不受键盘影响的安全区内边距padding
viewInsetsEdgeInsets被临时 UI 遮挡的区域(键盘)bottom: 0~336
alwaysUse24HourFormatbool是否 24 小时制
shortestSidedouble宽高中较短的一边
dart
final mq = MediaQuery.of(context);
mq.size                    // Size(375.0, 812.0)
mq.devicePixelRatio        // 3.0
mq.platformBrightness       // Brightness.light
mq.padding.top              // 59.0(刘海屏)
mq.viewInsets.bottom        // 0.0(键盘未弹出)

判断屏幕大小与响应式布局

屏幕类型判断

根据屏幕宽度判断设备类型,这是响应式布局的基础:

dart
import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    // ─── ★ 获取屏幕宽度 ──────────────
    final width = MediaQuery.of(context).size.width;
    // ─── ☆ 获取屏幕宽度 ──────────────
    String deviceType;

    if (width < 600) {
      deviceType = '手机';
    } else if (width < 1024) {
      deviceType = '平板';
    } else {
      deviceType = '桌面';
    }

    return Scaffold(
      appBar: AppBar(title: const Text('屏幕类型判断')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('屏幕宽度: ${width.toStringAsFixed(0)}'),
            Text('设备类型: $deviceType', style: const TextStyle(fontSize: 24)),
          ],
        ),
      ),
    );
  }
}

用最短边更可靠

使用 shortestSide(宽高中的较小值)判断,可以避免横竖屏切换导致布局跳变:

dart
final shortestSide = MediaQuery.of(context).size.shortestSide;
if (shortestSide < 600) {
  // 手机布局(无论横竖屏)
}

响应式布局组件

封装一个根据屏幕宽度自动切换布局的组件:

dart
class ResponsiveLayout extends StatelessWidget {
  final Widget mobile;
  final Widget? tablet;
  final Widget desktop;

  const ResponsiveLayout({
    super.key,
    required this.mobile,
    this.tablet,
    required this.desktop,
  });

  @override
  Widget build(BuildContext context) {
    final width = MediaQuery.of(context).size.width;
    if (width >= 1024) return desktop;
    if (width >= 600) return tablet ?? mobile;
    return mobile;
  }
}

// 使用
ResponsiveLayout(
  mobile: const MobileLayout(),
  tablet: const TabletLayout(),
  desktop: const DesktopLayout(),
)

LayoutBuilder:基于父组件约束的响应式

MediaQuery 获取的是整个屏幕的信息。但在嵌套布局中,你通常需要根据父组件分配给你的空间来决定布局,这时用 LayoutBuilder

dart
LayoutBuilder(
  builder: (context, constraints) {
    // constraints 是父组件给你的约束
    if (constraints.maxWidth > 600) {
      return Row(children: [LeftPanel(), RightPanel()]);
    } else {
      return Column(children: [TopPanel(), BottomPanel()]);
    }
  },
)
工具获取的是什么适用场景
MediaQuery整个屏幕的尺寸页面级布局切换
LayoutBuilder父组件的约束组件级自适应

适配键盘

当软键盘弹出时,viewInsets.bottom 会变成键盘的高度。这是处理键盘遮挡问题的核心数据。

dart
import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    // 获取键盘高度
    // ─── ★ viewInsets.bottom ──────────────
    final keyboardHeight = MediaQuery.of(context).viewInsets.bottom;
    // ─── ☆ viewInsets.bottom ──────────────

    // 判断键盘是否弹出
    final isKeyboardOpen = keyboardHeight > 0;

    return Scaffold(
      appBar: AppBar(title: const Text('键盘适配')),
      body: Padding(
        padding: EdgeInsets.only(bottom: keyboardHeight),
        child: Column(
          children: [
            const Expanded(child: Center(child: Text('内容区域'))),
            Text(isKeyboardOpen ? '键盘已弹出,高度: $keyboardHeight' : '键盘未弹出'),
            const TextField(decoration: InputDecoration(hintText: '点击弹出键盘')),
          ],
        ),
      ),
    );
  }
}

Scaffold 自动处理键盘

大多数情况下,ScaffoldresizeToAvoidBottomInset: true(默认值)会自动把 body 区域缩小以避开键盘,你不需要手动处理:

dart
Scaffold(
  // resizeToAvoidBottomInset 默认就是 true,body 会自动缩小避开键盘
  body: Column(
    children: [
      Expanded(child: ListView(...)),       // 列表可滚动
      TextField(),                          // 输入框不会被键盘遮挡
    ],
  ),
)

手动处理键盘

在特殊场景(如聊天页面底部固定输入栏)中,可能需要手动处理:

dart
// 键盘弹出时,让底部输入栏跟着上移
final keyboardHeight = MediaQuery.of(context).viewInsets.bottom;

Column(
  children: [
    Expanded(child: ListView(...)),
    Container(
      padding: EdgeInsets.only(bottom: keyboardHeight),  // 键盘高度作为底部内边距
      child: TextField(),
    ),
  ],
)

padding vs viewPadding vs viewInsets

这三个属性容易混淆,理解它们的区别对处理安全区和键盘至关重要:

属性含义键盘未弹出时键盘弹出时
padding永久性系统 UI 遮挡的区域bottom: 34(主页指示器)bottom: 0(被键盘「挤掉」了)
viewPadding同 padding,但不受键盘影响bottom: 34bottom: 34(值不变)
viewInsets临时性系统 UI 遮挡(主要是键盘)bottom: 0bottom: 336(键盘高度)
dart
final mq = MediaQuery.of(context);

// 键盘未弹出时:
mq.padding.bottom       // 34(主页指示器)
mq.viewPadding.bottom   // 34(同上)
mq.viewInsets.bottom    // 0(没有键盘)

// 键盘弹出时:
mq.padding.bottom       // 0!(padding 被键盘覆盖了)
mq.viewPadding.bottom   // 34(值不变)
mq.viewInsets.bottom    // 336(键盘高度)

简单记忆:

  • padding = 安全区(会被键盘影响)
  • viewPadding = 安全区(不会被键盘影响)
  • viewInsets = 键盘(只在键盘弹出时有值)

和 SafeArea 的关系

SafeArea 默认使用 padding。如果需要在键盘弹出时仍然保持底部安全区(如侧边栏),使用 SafeArea(maintainBottomViewPadding: true),它会改用 viewPadding。详见 安全区(Safe Area)

系统字体缩放

用户可以在系统设置中调大字体(如无障碍功能)。textScaleFactor / textScaler 就是系统字体缩放倍率。

dart
final scaleFactor = MediaQuery.of(context).textScaleFactor;  // 如 1.3
// 文字实际大小 = fontSize × textScaleFactor
// 如 TextStyle(fontSize: 16),在 1.3 倍缩放下,实际显示为 16 × 1.3 = 20.8

禁用系统字体缩放

如果用户设置的超大字体破坏了你的布局,可以在特定子树中禁用缩放:

dart
MediaQuery(
  data: MediaQuery.of(context).copyWith(
    textScaler: TextScaler.linear(1.0),  // 固定 1.0 倍缩放
  ),
  child: child,
)

Flutter 3.16 变更

textScaleFactor 在 Flutter 3.16 中废弃,改用 textScaler: TextScaler.linear(value)TextScaler 支持 Android 14 的非线性字体缩放,textScaleFactor 仅支持线性缩放。

深色/浅色模式判断

MediaQuery 可以获取系统的亮度模式:

dart
// 获取系统当前是深色还是浅色模式
final isDark = MediaQuery.of(context).platformBrightness == Brightness.dark;

注意

platformBrightness 反映的是系统的亮度设置,而不是应用实际使用的主题。如果你的应用强制使用亮色模式(themeMode: ThemeMode.light),系统是暗色但应用实际显示亮色,此时 platformBrightness 仍是 Brightness.dark

要判断应用实际使用的主题,应该用:

dart
final isDark = Theme.of(context).colorScheme.brightness == Brightness.dark;

详见 主题(Theme) 文档的深色模式章节。

常见用法速查

场景代码
屏幕宽度MediaQuery.of(context).size.width
屏幕高度MediaQuery.of(context).size.height
设备像素比MediaQuery.of(context).devicePixelRatio
判断手机/平板/桌面width < 600 / < 1024 / ≥ 1024
键盘高度MediaQuery.of(context).viewInsets.bottom
键盘是否弹出viewInsets.bottom > 0
安全区顶部高度MediaQuery.of(context).padding.top
系统字体缩放倍率MediaQuery.of(context).textScaler
系统深色/浅色MediaQuery.of(context).platformBrightness
禁用字体缩放MediaQuery(data: copyWith(textScaler: TextScaler.linear(1.0)))
父组件约束布局LayoutBuilder(builder: (ctx, constraints) {...})

相关链接

基于 Flutter 官方文档整理