一、引言

在前两篇基础与进阶解析的基础上,本文聚焦业务级实战场景,围绕TextContainer组件解决「响应式适配」「多设备兼容」「业务组件落地」三大核心问题,所有代码均可直接复制运行,帮助你将基础组件能力转化为实际开发中的生产力,覆盖电商、社交、表单等高频业务场景。

二、响应式 Text:适配多屏幕 / 暗黑模式 / 系统字体

实际开发中,文本需适配不同屏幕尺寸、系统字体设置、暗黑模式,以下是一站式解决方案:

dart

import 'package:flutter/material.dart';

void main() {
  runApp(const ResponsiveTextApp());
}

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

  @override
  Widget build(BuildContext context) {
    // 获取屏幕宽度(用于响应式字体大小)
    final screenWidth = MediaQuery.of(context).size.width;
    // 获取系统文本缩放比例(限制最大缩放,避免排版错乱)
    final textScaleFactor = min(MediaQuery.of(context).textScaleFactor, 1.2);

    return MaterialApp(
      // 配置主题(支持暗黑模式)
      theme: ThemeData(
        brightness: Brightness.light,
        textTheme: const TextTheme(
          // 自定义文本主题(可全局复用)
          bodyLarge: TextStyle(fontSize: 18, color: Colors.black87),
          bodyMedium: TextStyle(fontSize: 16, color: Colors.black54),
        ),
      ),
      darkTheme: ThemeData(
        brightness: Brightness.dark,
        textTheme: const TextTheme(
          bodyLarge: TextStyle(fontSize: 18, color: Colors.white),
          bodyMedium: TextStyle(fontSize: 16, color: Colors.white70),
        ),
      ),
      themeMode: ThemeMode.system, // 跟随系统暗黑模式
      home: Scaffold(
        appBar: AppBar(title: const Text("响应式Text实战")),
        body: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              // 标题文本:根据屏幕宽度动态调整字体大小
              Text(
                "响应式标题",
                style: TextStyle(
                  fontSize: screenWidth < 375 ? 20 : 24, // 小屏(如iPhone SE)20px,大屏24px
                  fontWeight: FontWeight.bold,
                  // 结合主题色(全局统一,便于维护)
                  color: Theme.of(context).primaryColor,
                ),
                textScaleFactor: textScaleFactor, // 适配系统字体缩放
              ),
              const SizedBox(height: 12),
              // 正文文本:复用主题样式,适配暗黑模式
              Text(
                "这是适配暗黑模式的正文文本,系统字体放大时自动限制缩放比例,小屏手机字体自动缩小,保证不同设备下排版一致。",
                style: Theme.of(context).textTheme.bodyMedium?.copyWith(
                      height: 1.5, // 固定行高,避免缩放后行间距错乱
                      letterSpacing: 0.5,
                    ),
                textAlign: TextAlign.justify,
                textScaleFactor: textScaleFactor,
              ),
              const SizedBox(height: 12),
              // 富文本也适配暗黑模式
              RichText(
                text: TextSpan(
                  style: Theme.of(context).textTheme.bodyMedium,
                  children: [
                    const TextSpan(text: "价格:"),
                    TextSpan(
                      text: "¥99.00",
                      style: TextStyle(
                        color: Theme.of(context).colorScheme.secondary,
                        fontWeight: FontWeight.bold,
                        fontSize: 18,
                      ),
                    ),
                    const TextSpan(text: " (限时优惠)"),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
核心亮点:
  1. 屏幕适配:通过MediaQuery获取屏幕宽度,动态调整字体大小,适配小屏 / 大屏设备;
  2. 暗黑模式:通过ThemeData/darkTheme配置文本样式,全局复用且自动切换;
  3. 字体缩放保护:限制textScaleFactor最大值,避免用户调大系统字体导致文本溢出;
  4. 主题复用:通过Theme.of(context).textTheme统一管理文本样式,减少重复代码。

三、响应式 Container:多设备兼容与动态样式

Container的响应式核心是「基于屏幕尺寸动态调整约束」,而非固定宽高,以下是实战代码:

dart

import 'package:flutter/material.dart';

void main() {
  runApp(const ResponsiveContainerApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text("响应式Container实战")),
        body: LayoutBuilder(
          // LayoutBuilder:获取父组件的约束(比MediaQuery更精准)
          builder: (context, constraints) {
            // 父组件可用宽度(适配不同父容器)
            final parentWidth = constraints.maxWidth;
            // 动态计算Container宽度(占父组件80%)
            final containerWidth = parentWidth * 0.8;
            // 动态计算高度(宽高比16:9,适配不同屏幕)
            final containerHeight = containerWidth * 9 / 16;

            return Center(
              child: Container(
                // 响应式尺寸
                width: containerWidth,
                height: containerHeight,
                // 适配暗黑模式的装饰
                decoration: BoxDecoration(
                  color: Theme.of(context).cardColor,
                  borderRadius: BorderRadius.circular(12),
                  boxShadow: [
                    BoxShadow(
                      color: Theme.of(context).shadowColor.withOpacity(0.2),
                      blurRadius: 8,
                      offset: const Offset(0, 2),
                    )
                  ],
                ),
                // 内边距也响应式(按宽度比例)
                padding: EdgeInsets.all(parentWidth * 0.04),
                alignment: Alignment.center,
                child: Text(
                  "宽高比16:9的响应式容器\n父容器宽度:${parentWidth.toStringAsFixed(0)}px\n容器宽度:${containerWidth.toStringAsFixed(0)}px",
                  style: Theme.of(context).textTheme.bodyLarge,
                  textAlign: TextAlign.center,
                ),
              ),
            );
          },
        ),
      ),
    );
  }
}
核心亮点:
  1. LayoutBuilder 替代 MediaQuery:直接获取父组件的约束(而非全局屏幕尺寸),适配嵌套场景;
  2. 宽高比固定:通过宽高比=16:9实现自适应,避免不同屏幕下容器变形;
  3. 主题化装饰cardColor/shadowColor跟随主题,暗黑模式自动适配;
  4. 响应式内边距:内边距按父组件宽度比例设置,保持视觉一致性。

四、业务级实战案例(可直接复用)

4.1 案例 1:电商商品卡片(Text+Container+Image)

实现电商 APP 核心的商品卡片,适配不同屏幕且支持暗黑模式:

dart

import 'package:flutter/material.dart';

void main() {
  runApp(const ProductCardApp());
}

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

  // 封装商品卡片组件
  Widget _buildProductCard({
    required String imageUrl,
    required String title,
    required String price,
    required String sales,
  }) {
    return Container(
      margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
      width: 160, // 卡片宽度(可结合LayoutBuilder动态调整)
      decoration: BoxDecoration(
        color: Theme.of(context).cardColor,
        borderRadius: BorderRadius.circular(8),
        boxShadow: [
          BoxShadow(
            color: Theme.of(context).shadowColor.withOpacity(0.1),
            blurRadius: 4,
            offset: const Offset(0, 2),
          )
        ],
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // 商品图片容器
          Container(
            width: double.infinity,
            height: 120,
            decoration: BoxDecoration(
              borderRadius: const BorderRadius.vertical(top: Radius.circular(8)),
              image: DecorationImage(
                image: NetworkImage(imageUrl),
                fit: BoxFit.cover,
              ),
            ),
          ),
          // 商品信息区域
          Padding(
            padding: const EdgeInsets.all(8),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                // 商品标题(最多2行,溢出省略)
                Text(
                  title,
                  style: Theme.of(context).textTheme.bodyMedium,
                  maxLines: 2,
                  overflow: TextOverflow.ellipsis,
                ),
                const SizedBox(height: 4),
                // 价格
                Text(
                  price,
                  style: TextStyle(
                    color: Theme.of(context).colorScheme.primary,
                    fontSize: 16,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 2),
                // 销量
                Text(
                  sales,
                  style: TextStyle(
                    fontSize: 12,
                    color: Theme.of(context).hintColor,
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primaryColor: Colors.red,
        cardColor: Colors.white,
        shadowColor: Colors.black12,
        hintColor: Colors.black45,
        textTheme: const TextTheme(bodyMedium: TextStyle(fontSize: 14, color: Colors.black87)),
      ),
      darkTheme: ThemeData(
        primaryColor: Colors.redAccent,
        cardColor: Colors.grey[850],
        shadowColor: Colors.black38,
        hintColor: Colors.white54,
        textTheme: const TextTheme(bodyMedium: TextStyle(fontSize: 14, color: Colors.white)),
      ),
      themeMode: ThemeMode.system,
      home: Scaffold(
        appBar: AppBar(title: const Text("电商商品卡片")),
        body: SingleChildScrollView(
          padding: const EdgeInsets.all(8),
          child: Row(
            // 横向滚动的商品列表
            children: [
              _buildProductCard(
                imageUrl: "https://img11.360buyimg.com/n1/jfs/t1/19003/36/25945/197599/649e3099F801c8350/8091998c42989782.jpg",
                title: "2025新款夏季短袖T恤男纯棉宽松百搭上衣",
                price: "¥59.9",
                sales: "已售10万+",
              ),
              _buildProductCard(
                imageUrl: "https://img14.360buyimg.com/n1/jfs/t1/18779/31/26327/133725/64c3f762F08588888/8091998c42989782.jpg",
                title: "无线蓝牙耳机入耳式降噪高音质超长续航",
                price: "¥129.0",
                sales: "已售5万+",
              ),
              _buildProductCard(
                imageUrl: "https://img10.360buyimg.com/n1/jfs/t1/18824/36/26611/195799/649e3099F801c8350/8091998c42989782.jpg",
                title: "家用保温杯316不锈钢大容量便携水杯",
                price: "¥89.9",
                sales: "已售8万+",
              ),
            ],
          ),
        ),
      ),
    );
  }
}

4.2 案例 2:表单输入框(Container+TextField+Text)

实现 APP 中常见的表单输入框,包含标签、输入框、提示文本:

dart

import 'package:flutter/material.dart';

void main() {
  runApp(const FormInputApp());
}

class FormInputApp extends StatefulWidget {
  const FormInputApp({super.key});

  @override
  State<FormInputApp> createState() => _FormInputAppState();
}

class _FormInputAppState extends State<FormInputApp> {
  // 输入框控制器
  final TextEditingController _phoneController = TextEditingController();
  // 输入验证状态
  bool _isPhoneValid = true;

  // 验证手机号
  void _validatePhone() {
    setState(() {
      _isPhoneValid = RegExp(r'^1[3-9]\d{9}$').hasMatch(_phoneController.text);
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text("表单输入框实战")),
        body: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            children: [
              // 手机号输入框
              Container(
                decoration: BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.circular(8),
                  border: Border.all(
                    color: _isPhoneValid ? Colors.grey[200]! : Colors.red,
                    width: 1,
                  ),
                  boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 2)],
                ),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    // 输入框标签
                    const Padding(
                      padding: EdgeInsets.only(left: 12, top: 8, bottom: 4),
                      child: Text(
                        "手机号码",
                        style: TextStyle(fontSize: 14, color: Colors.black87),
                      ),
                    ),
                    // 输入框
                    TextField(
                      controller: _phoneController,
                      keyboardType: TextInputType.phone,
                      onChanged: (value) => _validatePhone(),
                      decoration: const InputDecoration(
                        hintText: "请输入11位手机号",
                        border: InputBorder.none, // 隐藏原生边框
                        contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
                        hintStyle: TextStyle(color: Colors.black45, fontSize: 14),
                      ),
                      style: const TextStyle(fontSize: 16, color: Colors.black),
                    ),
                    // 错误提示文本
                    if (!_isPhoneValid)
                      Padding(
                        padding: const EdgeInsets.only(left: 12, bottom: 8),
                        child: Text(
                          "请输入有效的手机号码",
                          style: TextStyle(fontSize: 12, color: Colors.red),
                        ),
                      ),
                  ],
                ),
              ),
              const SizedBox(height: 20),
              // 提交按钮
              Container(
                width: double.infinity,
                height: 50,
                decoration: BoxDecoration(
                  color: _isPhoneValid ? Colors.blue : Colors.grey,
                  borderRadius: BorderRadius.circular(25),
                ),
                child: TextButton(
                  onPressed: _isPhoneValid
                      ? () => debugPrint("提交手机号:${_phoneController.text}")
                      : null, // 验证不通过则禁用按钮
                  child: const Text(
                    "提交",
                    style: TextStyle(color: Colors.white, fontSize: 18),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  @override
  void dispose() {
    _phoneController.dispose(); // 释放控制器
    super.dispose();
  }
}

4.3 案例 3:社交消息气泡(Text+Container+Stack)

实现微信 / QQ 风格的消息气泡,区分左右对齐、不同颜色:

dart

import 'package:flutter/material.dart';

void main() {
  runApp(const MessageBubbleApp());
}

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

  // 封装消息气泡组件
  Widget _buildMessageBubble({
    required String content,
    required bool isSelf, // 是否是自己发送的消息
  }) {
    return Align(
      // 自己发送的消息右对齐,对方的左对齐
      alignment: isSelf ? Alignment.centerRight : Alignment.centerLeft,
      child: Container(
        // 最大宽度占屏幕70%
        constraints: const BoxConstraints(maxWidth: 250),
        margin: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
        padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
        decoration: BoxDecoration(
          color: isSelf ? Colors.blue : Colors.grey[200],
          borderRadius: BorderRadius.circular(12).copyWith(
            // 气泡圆角适配:自己发送的消息左下角直角,对方的右下角直角
            bottomLeft: isSelf ? const Radius.circular(2) : Radius.circular(12),
            bottomRight: isSelf ? Radius.circular(12) : const Radius.circular(2),
          ),
        ),
        child: Text(
          content,
          style: TextStyle(
            color: isSelf ? Colors.white : Colors.black87,
            fontSize: 16,
            height: 1.4,
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text("消息气泡实战")),
        body: Column(
          children: [
            Expanded(
              child: ListView(
                padding: const EdgeInsets.symmetric(vertical: 8),
                children: [
                  _buildMessageBubble(
                    content: "你好,请问Flutter的消息气泡怎么实现?",
                    isSelf: false,
                  ),
                  _buildMessageBubble(
                    content: "可以用Container结合Align组件,通过borderRadius调整圆角,constraints限制最大宽度。",
                    isSelf: true,
                  ),
                  _buildMessageBubble(
                    content: "谢谢!还有个问题,怎么适配暗黑模式?",
                    isSelf: false,
                  ),
                  _buildMessageBubble(
                    content: "通过Theme.of(context)获取主题色,替换气泡的背景色和文本色即可。",
                    isSelf: true,
                  ),
                ],
              ),
            ),
            // 输入框区域
            Container(
              padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
              color: Colors.grey[100],
              child: Row(
                children: [
                  Expanded(
                    child: Container(
                      decoration: BoxDecoration(
                        color: Colors.white,
                        borderRadius: BorderRadius.circular(20),
                      ),
                      child: const TextField(
                        decoration: InputDecoration(
                          hintText: "请输入消息",
                          border: InputBorder.none,
                          contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
                        ),
                      ),
                    ),
                  ),
                  const SizedBox(width: 8),
                  Container(
                    width: 40,
                    height: 40,
                    decoration: const BoxDecoration(
                      color: Colors.blue,
                      shape: BoxShape.circle,
                    ),
                    child: const Icon(Icons.send, color: Colors.white, size: 20),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

五、性能优化:Text&Container 避坑与提效

5.1 核心优化技巧

  1. 使用 const 构造器:静态文本 / 容器用const修饰,避免每次重建:

    dart

    // 优化前
    Text("静态文本");
    // 优化后
    const Text("静态文本");
    
  2. 复用 TextStyle:避免重复创建样式,全局定义或用Theme复用:

    dart

    // 全局样式
    final TextStyle cardTitleStyle = const TextStyle(fontSize: 16, fontWeight: FontWeight.bold);
    // 复用样式
    Text("标题", style: cardTitleStyle);
    
  3. 减少 Container 嵌套:避免「Container→Padding→Container」冗余嵌套,合并属性:

    dart

    // 优化前(冗余)
    Container(
      padding: EdgeInsets.all(8),
      child: Container(
        color: Colors.white,
        child: Text("内容"),
      ),
    );
    // 优化后(合并)
    Container(
      padding: EdgeInsets.all(8),
      color: Colors.white,
      child: Text("内容"),
    );
    
  4. 避免不必要的 decoration 重建:静态装饰提前定义:

    dart

    final BoxDecoration cardDecoration = BoxDecoration(
      borderRadius: BorderRadius.circular(8),
      color: Colors.white,
    );
    // 复用装饰
    Container(decoration: cardDecoration, child: Text("内容"));
    

5.2 进阶避坑

问题 原因 解决方案
消息气泡文字换行异常 Container 的 constraints 未限制最大宽度 给 Container 加constraints: BoxConstraints(maxWidth: 250)
表单输入框边框闪烁 每次输入重建 border 将 border 样式提前定义,避免每次 onChanged 重建
响应式文本在平板上过大 仅用屏幕宽度适配,未限制最大字体 结合min(fontSize, 24)限制最大字体大小
Container 的阴影在低端机卡顿 阴影 blurRadius 过大 降低blurRadius(如 8→4),或仅在高端机显示阴影

六、总结与拓展

  1. 核心落地思路
    • Text:围绕「适配」(屏幕 / 暗黑 / 缩放)+「复用」(主题 / 样式)+「溢出处理」构建业务文本;
    • Container:围绕「约束」(constraints)+「装饰」(decoration)+「响应式尺寸」实现布局容器;
    • 组合技巧:文本负责内容展示,容器负责布局与样式,结合StatefulWidget实现交互,结合Theme实现全局统一。
  2. 拓展学习
    • 结合ListView.builder实现商品列表的懒加载(避免一次性创建大量卡片);
    • 结合Provider/Bloc实现表单状态的全局管理;
    • 结合CachedNetworkImage优化商品卡片的图片加载(替代原生NetworkImage)。

欢迎大家加入[开源鸿蒙跨平台开发者社区](https://openharmonycrossplatform.csdn.net),一起共建开源鸿蒙跨平台生态。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐