984 字
5 分钟
Flutter Day02: Image 与 TextField 组件
Flutter Day02: Image 与 TextField 组件
本文整理 Flutter 中常用的图片组件 Image 和输入组件 TextField,包含核心属性与实用示例。
1. Image 组件详解
Image 用于从不同来源(网络、本地资源、文件系统、内存)加载并显示图片。
1.1 常见构造函数(图片来源)
| 构造函数 | 描述 | 示例 |
|---|---|---|
Image.asset() | 从项目资源目录(在 pubspec.yaml 中配置)加载图片 | Image.asset('assets/images/logo.png') |
Image.network() | 从网络 URL 加载图片,支持缓存 | Image.network('https://picsum.photos/300/200') |
Image.file() | 从设备本地文件系统加载图片 | Image.file(File('/path/to/image.png')) |
Image.memory() | 从内存字节数组 Uint8List 加载图片 | Image.memory(bytes) |
1.2 核心属性
| 属性 | 说明 | 常见值/建议 |
|---|---|---|
fit | 控制图片如何适应容器(最重要) | BoxFit.cover(常用)、contain、fill、fitWidth、fitHeight |
width / height | 图片显示宽高(逻辑像素) | 如 width: 120, height: 120 |
alignment | 图片在容器内对齐方式 | 默认 Alignment.center |
color + colorBlendMode | 给图片着色 | 常用于单色图标或蒙层效果 |
repeat | 图片小于容器时平铺 | ImageRepeat.repeat、repeatX、repeatY |
frameBuilder | 自定义加载过程(如渐显) | 可配合动画提高体验 |
1.3 fit 快速理解
BoxFit.cover: 等比缩放并填满容器,可能裁剪。BoxFit.contain: 等比缩放完整显示,可能留白。BoxFit.fill: 强制填满,可能拉伸变形。BoxFit.fitWidth: 宽度撑满,高度等比缩放。BoxFit.fitHeight: 高度撑满,宽度等比缩放。
1.4 示例: 常见图片加载与占位
import 'dart:typed_data';import 'package:flutter/material.dart';
class ImageDemoPage extends StatelessWidget { const ImageDemoPage({super.key, required this.memoryBytes});
final Uint8List memoryBytes;
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Image Demo')), body: ListView( padding: const EdgeInsets.all(16), children: [ const Text('1) Asset 图片'), Image.asset( 'assets/images/logo.png', height: 120, fit: BoxFit.contain, ), const SizedBox(height: 16),
const Text('2) 网络图片(带加载中提示和错误兜底)'), Image.network( 'https://picsum.photos/400/200', height: 120, fit: BoxFit.cover, loadingBuilder: (context, child, progress) { if (progress == null) return child; return const SizedBox( height: 120, child: Center(child: CircularProgressIndicator()), ); }, errorBuilder: (context, error, stackTrace) => const SizedBox( height: 120, child: Center(child: Text('图片加载失败')), ), ), const SizedBox(height: 16),
const Text('3) 内存图片'), Image.memory( memoryBytes, height: 120, fit: BoxFit.cover, ), ], ), ); }}1.5 实战建议
- 网络图片尽量加
loadingBuilder和errorBuilder,避免白屏。 - 头像、卡片封面常用
BoxFit.cover。 - 需要渐显效果可使用
FadeInImage。 - Flutter 默认有内存缓存;需要磁盘持久化缓存可用
cached_network_image。
2. TextField 组件详解
TextField 是 Material Design 的文本输入框,支持丰富装饰、输入控制和事件回调。
2.1 核心属性
| 属性 | 描述 | 示例 / 备注 |
|---|---|---|
controller | 绑定 TextEditingController,用于读写文本 | 大多数业务场景必备 |
decoration | 输入框外观配置 | InputDecoration(...) |
keyboardType | 键盘类型 | TextInputType.emailAddress、number |
textInputAction | 键盘右下角动作按钮 | TextInputAction.search、done、next |
obscureText | 是否隐藏输入内容 | 密码输入设为 true |
onChanged | 文本变化回调 | 实时搜索、校验 |
onSubmitted | 点击动作按钮回调 | 提交表单、搜索 |
2.2 InputDecoration 常见字段
labelText: 浮动标签。hintText: 占位提示。border: 边框样式(如OutlineInputBorder())。prefixIcon/suffixIcon: 前后图标。errorText: 错误提示(非null时边框变红)。
2.3 示例: 登录表单(含焦点切换与基础校验)
import 'package:flutter/material.dart';
class LoginFormPage extends StatefulWidget { const LoginFormPage({super.key});
@override State<LoginFormPage> createState() => _LoginFormPageState();}
class _LoginFormPageState extends State<LoginFormPage> { final _emailController = TextEditingController(); final _passwordController = TextEditingController(); final _passwordFocus = FocusNode();
String? _emailError; String? _passwordError;
@override void dispose() { _emailController.dispose(); _passwordController.dispose(); _passwordFocus.dispose(); super.dispose(); }
void _submit() { final email = _emailController.text.trim(); final password = _passwordController.text;
setState(() { _emailError = email.contains('@') ? null : '请输入正确的邮箱'; _passwordError = password.length >= 6 ? null : '密码至少 6 位'; });
if (_emailError == null && _passwordError == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('登录请求已提交')), ); } }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('TextField Demo')), body: Padding( padding: const EdgeInsets.all(16), child: Column( children: [ TextField( controller: _emailController, keyboardType: TextInputType.emailAddress, textInputAction: TextInputAction.next, onSubmitted: (_) => _passwordFocus.requestFocus(), decoration: InputDecoration( labelText: '邮箱', hintText: '请输入邮箱地址', prefixIcon: const Icon(Icons.email_outlined), border: const OutlineInputBorder(), errorText: _emailError, ), ), const SizedBox(height: 12), TextField( controller: _passwordController, focusNode: _passwordFocus, obscureText: true, textInputAction: TextInputAction.done, onSubmitted: (_) => _submit(), decoration: InputDecoration( labelText: '密码', hintText: '请输入密码', prefixIcon: const Icon(Icons.lock_outline), border: const OutlineInputBorder(), errorText: _passwordError, ), ), const SizedBox(height: 16), SizedBox( width: double.infinity, child: FilledButton( onPressed: _submit, child: const Text('登录'), ), ), ], ), ), ); }}2.4 实战建议
- 优先使用
TextEditingController获取输入值。 - 在
StatefulWidget中记得dispose()控制器和FocusNode。 - 表单较复杂时建议配合
Form+TextFormField+validator。 - 搜索框可用
onChanged,提交行为可用onSubmitted。
Flutter Day02: Image 与 TextField 组件
https://zgq1008.github.io/posts/flutter/flutter_day02/ Flutter 系列导航