Flutter Text&Container 实战进阶:响应式布局与业务场景落地
·
一、引言
在前两篇基础与进阶解析的基础上,本文聚焦业务级实战场景,围绕Text和Container组件解决「响应式适配」「多设备兼容」「业务组件落地」三大核心问题,所有代码均可直接复制运行,帮助你将基础组件能力转化为实际开发中的生产力,覆盖电商、社交、表单等高频业务场景。
二、响应式 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: " (限时优惠)"),
],
),
),
],
),
),
),
);
}
}
核心亮点:
- 屏幕适配:通过
MediaQuery获取屏幕宽度,动态调整字体大小,适配小屏 / 大屏设备; - 暗黑模式:通过
ThemeData/darkTheme配置文本样式,全局复用且自动切换; - 字体缩放保护:限制
textScaleFactor最大值,避免用户调大系统字体导致文本溢出; - 主题复用:通过
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,
),
),
);
},
),
),
);
}
}
核心亮点:
- LayoutBuilder 替代 MediaQuery:直接获取父组件的约束(而非全局屏幕尺寸),适配嵌套场景;
- 宽高比固定:通过
宽高比=16:9实现自适应,避免不同屏幕下容器变形; - 主题化装饰:
cardColor/shadowColor跟随主题,暗黑模式自动适配; - 响应式内边距:内边距按父组件宽度比例设置,保持视觉一致性。
四、业务级实战案例(可直接复用)
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 核心优化技巧
- 使用 const 构造器:静态文本 / 容器用
const修饰,避免每次重建:dart
// 优化前 Text("静态文本"); // 优化后 const Text("静态文本"); - 复用 TextStyle:避免重复创建样式,全局定义或用
Theme复用:dart
// 全局样式 final TextStyle cardTitleStyle = const TextStyle(fontSize: 16, fontWeight: FontWeight.bold); // 复用样式 Text("标题", style: cardTitleStyle); - 减少 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("内容"), ); - 避免不必要的 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),或仅在高端机显示阴影 |
六、总结与拓展
- 核心落地思路:
Text:围绕「适配」(屏幕 / 暗黑 / 缩放)+「复用」(主题 / 样式)+「溢出处理」构建业务文本;Container:围绕「约束」(constraints)+「装饰」(decoration)+「响应式尺寸」实现布局容器;- 组合技巧:文本负责内容展示,容器负责布局与样式,结合
StatefulWidget实现交互,结合Theme实现全局统一。
- 拓展学习:
- 结合
ListView.builder实现商品列表的懒加载(避免一次性创建大量卡片); - 结合
Provider/Bloc实现表单状态的全局管理; - 结合
CachedNetworkImage优化商品卡片的图片加载(替代原生NetworkImage)。
- 结合
欢迎大家加入[开源鸿蒙跨平台开发者社区](https://openharmonycrossplatform.csdn.net),一起共建开源鸿蒙跨平台生态。
更多推荐



所有评论(0)