Text
可以设置颜色,背景颜色,字体大小
php
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(const MaterialApp(home: HomePage()));
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Text')),
body: Text("data",style: TextStyle(color: Colors.amber,backgroundColor: Colors.green,fontSize: 20),),
);
}
}
Text进阶
less
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(const MaterialApp(home: HomePage()));
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Text 全方位知识点')),
body: ListView(
padding: const EdgeInsets.all(16),
children: const [
_Section1_Essence(),
_Section2_Style(),
_Section3_Size(),
_Section4_Weight(),
_Section5_Color(),
_Section6_FontFamily(),
_Section7_TextAlign(),
_Section8_MaxLines_Overflow(),
_Section9_TextDirection(),
_Section10_StrutStyle(),
_Section11_DefaultTextStyle(),
_Section12_RichText(),
_Section13_Summary(),
],
),
);
}
}
// ────────────────────────────────────────────────────────────────
// ① Text 的本质 ------ 显示文本的 Widget
// ────────────────────────────────────────────────────────────────
class _Section1_Essence extends StatelessWidget {
const _Section1_Essence();
@override
Widget build(BuildContext context) {
return _Card(
title: '① Text 的本质',
subtitle: '显示一段文本,核心属性 data + style',
children: [
const Text(
'Text 的核心属性:\n'
'• String data 要显示的文本内容(必填)\n'
'• TextStyle style 文字样式(可选,默认继承 Theme)\n'
'• TextAlign alignment 对齐方式\n'
'• int? maxLines 最大行数\n'
'• TextOverflow overflow 超出处理方式\n'
'• double? textScaleFactor 字号缩放\n'
'• TextDirection textDirection 文字方向\n'
'• Locale? locale 本地化\n'
'• StrutStyle? strutStyle 行高控制\n'
'• TextWidthBasis textWidthBasis 宽度计算方式\n'
'• TextHeightBehavior? textHeightBehavior 行高行为',
style: TextStyle(fontSize: 11, fontFamily: 'monospace'),
),
const SizedBox(height: 8),
Container(
color: Colors.grey.shade200,
padding: const EdgeInsets.all(12),
child: const Text('Hello Flutter Text!',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ② TextStyle ------ 文本样式详解
// ────────────────────────────────────────────────────────────────
class _Section2_Style extends StatelessWidget {
const _Section2_Style();
@override
Widget build(BuildContext context) {
return _Card(
title: '② TextStyle',
subtitle: '控制文字外观的核心类',
children: [
const Text(
'TextStyle 主要属性:\n'
'• color 文字颜色\n'
'• fontSize 字号(逻辑像素)\n'
'• fontWeight 粗细(w100~w900 / bold / normal)\n'
'• fontStyle italic / normal\n'
'• fontFamily 字体\n'
'• letterSpacing 字间距(正=拉开,负=紧凑)\n'
'• wordSpacing 词间距\n'
'• height 行高倍数(相对于 fontSize)\n'
'• decoration 下划线/删除线/上划线\n'
'• decorationColor 装饰线颜色\n'
'• decorationStyle 装饰线样式\n'
'• shadows 文字阴影\n'
'• foreground 自定义 Paint 绘制前景\n'
'• background 背景色\n'
'• overflow 文本溢出处理',
style: TextStyle(fontSize: 11, fontFamily: 'monospace'),
),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ③ fontSize ------ 字号
// ────────────────────────────────────────────────────────────────
class _Section3_Size extends StatelessWidget {
const _Section3_Size();
@override
Widget build(BuildContext context) {
return _Card(
title: '③ fontSize',
subtitle: '字号,单位是逻辑像素',
children: [
_TextRow(size: 10, label: '10'),
_TextRow(size: 12, label: '12'),
_TextRow(size: 14, label: '14'),
_TextRow(size: 16, label: '16'),
_TextRow(size: 20, label: '20'),
_TextRow(size: 28, label: '28'),
_TextRow(size: 40, label: '40'),
const SizedBox(height: 6),
const Text('默认 fontSize 是多少?\n'
'继承自 DefaultTextStyle → ThemeData.textTheme.bodyMedium → 通常 14',
style: TextStyle(fontSize: 11)),
],
);
}
}
class _TextRow extends StatelessWidget {
final double size;
final String label;
const _TextRow({required this.size, required this.label});
@override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.baseline,
textBaseline: TextBaseline.alphabetic,
children: [
SizedBox(width: 30, child: Text(label, style: const TextStyle(fontSize: 10, color: Colors.grey))),
Text('Flutter', style: TextStyle(fontSize: size)),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ④ fontWeight / fontStyle ------ 粗细和斜体
// ────────────────────────────────────────────────────────────────
class _Section4_Weight extends StatelessWidget {
const _Section4_Weight();
@override
Widget build(BuildContext context) {
return _Card(
title: '④ fontWeight / fontStyle',
subtitle: '粗细(w100~w900)和斜体',
children: [
Row(
children: [
const Text('fontWeight:', style: TextStyle(fontSize: 11, color: Colors.grey)),
const SizedBox(width: 8),
Expanded(
child: Wrap(
spacing: 8,
runSpacing: 4,
children: [
for (int w in [100, 300, 500, 700, 900])
Text('w$w',
style: TextStyle(fontSize: 14, fontWeight: FontWeight.values[w ~/ 100 - 1])),
],
),
),
],
),
const SizedBox(height: 6),
Row(
children: [
const Text('fontStyle:', style: TextStyle(fontSize: 11, color: Colors.grey)),
const SizedBox(width: 8),
const Text('normal ', style: TextStyle(fontSize: 16, fontStyle: FontStyle.normal)),
const Text('italic', style: TextStyle(fontSize: 16, fontStyle: FontStyle.italic)),
],
),
const SizedBox(height: 6),
const Text('常用快捷值:FontWeight.w400 = normal, FontWeight.w700 = bold',
style: TextStyle(fontSize: 11)),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ⑤ color / decoration ------ 颜色和装饰线
// ────────────────────────────────────────────────────────────────
class _Section5_Color extends StatelessWidget {
const _Section5_Color();
@override
Widget build(BuildContext context) {
return _Card(
title: '⑤ color / decoration',
subtitle: '文字颜色和装饰线(下划线、删除线)',
children: [
const Text('红色文字', style: TextStyle(fontSize: 16, color: Colors.red)),
const SizedBox(height: 4),
const Text('带蓝色下划线',
style: TextStyle(fontSize: 16, decoration: TextDecoration.underline, decorationColor: Colors.blue)),
const SizedBox(height: 4),
const Text('删除线',
style: TextStyle(fontSize: 16, decoration: TextDecoration.lineThrough)),
const SizedBox(height: 4),
Text('上划线 + 下划线',
style: TextStyle(fontSize: 16, decoration: TextDecoration.combine([TextDecoration.overline, TextDecoration.underline]))),
const SizedBox(height: 4),
const Text('红色删除线 + 粗虚线',
style: TextStyle(
fontSize: 16,
decoration: TextDecoration.lineThrough,
decorationColor: Colors.red,
decorationStyle: TextDecorationStyle.dashed,
decorationThickness: 2,
)),
const SizedBox(height: 4),
const Text('文字阴影',
style: TextStyle(
fontSize: 20,
color: Colors.white,
shadows: [Shadow(color: Colors.black, offset: Offset(2, 2), blurRadius: 3)],
)),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ⑥ letterSpacing / wordSpacing / height ------ 间距和行高
// ────────────────────────────────────────────────────────────────
class _Section6_FontFamily extends StatelessWidget {
const _Section6_FontFamily();
@override
Widget build(BuildContext context) {
return _Card(
title: '⑥ letterSpacing / wordSpacing / height',
subtitle: '字间距、词间距、行高',
children: [
_SpacedRow(label: 'letterSpacing: -2', letter: -2),
_SpacedRow(label: 'letterSpacing: 0', letter: 0),
_SpacedRow(label: 'letterSpacing: 4', letter: 4),
const SizedBox(height: 4),
const Text('wordSpacing: 8 词间距',
style: TextStyle(fontSize: 14, wordSpacing: 8)),
const SizedBox(height: 6),
Container(
color: Colors.amber.shade50,
padding: const EdgeInsets.all(8),
child: const Text(
'height: 1.0 行高正常\n'
'第二行',
style: TextStyle(fontSize: 14, height: 1.0),
),
),
const SizedBox(height: 4),
Container(
color: Colors.amber.shade50,
padding: const EdgeInsets.all(8),
child: const Text(
'height: 2.0 行高翻倍\n'
'第二行',
style: TextStyle(fontSize: 14, height: 2.0),
),
),
const SizedBox(height: 6),
const Text('注意:height 是 fontSize 的倍数,不是像素值!\n'
'fontSize:14, height:2.0 → 行高 = 14 × 2.0 = 28px',
style: TextStyle(fontSize: 11)),
],
);
}
}
class _SpacedRow extends StatelessWidget {
final String label;
final double letter;
const _SpacedRow({required this.label, required this.letter});
@override
Widget build(BuildContext context) {
return Row(
children: [
SizedBox(width: 110, child: Text(label, style: const TextStyle(fontSize: 10, color: Colors.grey))),
Text('Hello', style: TextStyle(fontSize: 14, letterSpacing: letter)),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ⑦ textAlign ------ 对齐方式
// ────────────────────────────────────────────────────────────────
class _Section7_TextAlign extends StatelessWidget {
const _Section7_TextAlign();
@override
Widget build(BuildContext context) {
return _Card(
title: '⑦ textAlign',
subtitle: '文本在可用空间内的对齐方式',
children: [
_AlignDemo(label: 'left', align: TextAlign.left),
_AlignDemo(label: 'center', align: TextAlign.center),
_AlignDemo(label: 'right', align: TextAlign.right),
_AlignDemo(label: 'justify', align: TextAlign.justify),
_AlignDemo(label: 'start', align: TextAlign.start),
_AlignDemo(label: 'end', align: TextAlign.end),
const SizedBox(height: 4),
const Text('start/end 区别:\n'
'• LTR(英文)下 start=left, end=right\n'
'• RTL(阿拉伯文)下 start=right, end=left\n'
'• left/right 永远是物理方向,不随语言改变',
style: TextStyle(fontSize: 11)),
],
);
}
}
class _AlignDemo extends StatelessWidget {
final String label;
final TextAlign align;
const _AlignDemo({required this.label, required this.align});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 2),
child: Row(
children: [
SizedBox(width: 70, child: Text(label, style: const TextStyle(fontSize: 11, color: Colors.grey))),
Expanded(
child: Container(
color: Colors.grey.shade200,
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
child: Text('Hello world', textAlign: align, style: const TextStyle(fontSize: 13)),
),
),
],
),
);
}
}
// ────────────────────────────────────────────────────────────────
// ⑧ maxLines / overflow ------ 最大行数和超出处理
// ────────────────────────────────────────────────────────────────
class _Section8_MaxLines_Overflow extends StatelessWidget {
const _Section8_MaxLines_Overflow();
@override
Widget build(BuildContext context) {
const longText = 'Flutter 是 Google 推出的跨平台UI框架,'
'可以用一套代码同时开发 iOS、Android、Web、Windows、macOS、Linux 应用。'
'它使用 Dart 语言,拥有优秀的性能和丰富的组件库。';
return _Card(
title: '⑧ maxLines / overflow',
subtitle: '控制最大行数和超出部分的显示',
children: [
const Text('原始文本(不限制):', style: TextStyle(fontSize: 11, color: Colors.grey)),
const Text(longText, style: TextStyle(fontSize: 13)),
const SizedBox(height: 6),
const Text('maxLines: 2 + overflow: ellipsis(省略号):',
style: TextStyle(fontSize: 11, color: Colors.grey)),
const Text(longText, maxLines: 2, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 13)),
const SizedBox(height: 6),
const Text('maxLines: 2 + overflow: clip(裁剪):',
style: TextStyle(fontSize: 11, color: Colors.grey)),
const Text(longText, maxLines: 2, overflow: TextOverflow.clip, style: TextStyle(fontSize: 13)),
const SizedBox(height: 6),
const Text('maxLines: 2 + overflow: fade(渐隐):',
style: TextStyle(fontSize: 11, color: Colors.grey)),
const Text(longText, maxLines: 2, overflow: TextOverflow.fade, style: TextStyle(fontSize: 13)),
const SizedBox(height: 6),
const Text('softWrap: false(不换行,一行显示):',
style: TextStyle(fontSize: 11, color: Colors.grey)),
const Text(longText, softWrap: false, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 13)),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ⑨ textDirection ------ 文字方向
// ────────────────────────────────────────────────────────────────
class _Section9_TextDirection extends StatelessWidget {
const _Section9_TextDirection();
@override
Widget build(BuildContext context) {
return _Card(
title: '⑨ textDirection',
subtitle: '文字方向:LTR 从左到右 / RTL 从右到左',
children: [
Row(
children: [
Expanded(
child: Column(
children: [
Container(
color: Colors.grey.shade200,
padding: const EdgeInsets.all(4),
child: const Text('Hello World',
textDirection: TextDirection.ltr, style: TextStyle(fontSize: 14)),
),
const SizedBox(height: 2),
const Text('LTR', style: TextStyle(fontSize: 10)),
],
),
),
const SizedBox(width: 8),
Expanded(
child: Column(
children: [
Container(
color: Colors.grey.shade200,
padding: const EdgeInsets.all(4),
child: const Text('Hello World',
textDirection: TextDirection.rtl, style: TextStyle(fontSize: 14)),
),
const SizedBox(height: 2),
const Text('RTL', style: TextStyle(fontSize: 10)),
],
),
),
],
),
const SizedBox(height: 6),
const Text('默认继承 Directionality(context),MaterialApp 下是 TextDirection.ltr',
style: TextStyle(fontSize: 11)),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ⑩ strutStyle ------ 控制行高的另一种方式
// ────────────────────────────────────────────────────────────────
class _Section10_StrutStyle extends StatelessWidget {
const _Section10_StrutStyle();
@override
Widget build(BuildContext context) {
return _Card(
title: '⑩ strutStyle',
subtitle: '用行高、字号等统一控制多行文本的垂直布局',
children: [
const Text('StrutStyle 的作用:', style: TextStyle(fontSize: 12, fontWeight: FontWeight.bold)),
const Text(
'StrutStyle 可以强制所有行使用相同的最小高度,\n'
'即使某些行只有小号字体或空行,垂直对齐依然整齐。\n\n'
'常用属性:\n'
'• fontSize 基础字号\n'
'• height 行高倍数\n'
'• leading 行间距\n'
'• forceStrutHeight 强制应用 strutHeight\n\n'
'对比:用 TextStyle(height:) 是给每段文字设行高;\n'
'StrutStyle 是给 Text widget 里的所有行统一设行高。',
style: TextStyle(fontSize: 11),
),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ⑪ DefaultTextStyle ------ 统一设置默认文本样式
// ────────────────────────────────────────────────────────────────
class _Section11_DefaultTextStyle extends StatelessWidget {
const _Section11_DefaultTextStyle();
@override
Widget build(BuildContext context) {
return _Card(
title: '⑪ DefaultTextStyle',
subtitle: '用 InheritedWidget 给子树内所有 Text 设置默认样式',
children: [
DefaultTextStyle(
style: const TextStyle(color: Colors.blue, fontSize: 16, fontWeight: FontWeight.bold),
child: Container(
color: Colors.grey.shade200,
padding: const EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('我继承了 DefaultTextStyle'),
const Text('我也是蓝色粗体'),
const Text('我可以在子组件里覆盖', style: TextStyle(color: Colors.red, fontSize: 12)),
],
),
),
),
const SizedBox(height: 6),
const Text('原理:Text 内部会调 DefaultTextStyle.of(context)\n'
'MaterialApp 已经内置了 ThemeData.textTheme,\n'
'所以大多数时候我们用 Theme.of(context).textTheme 就够了',
style: TextStyle(fontSize: 11)),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ⑫ RichText ------ 富文本,用 TextSpan 给不同片段设不同样式
// ────────────────────────────────────────────────────────────────
class _Section12_RichText extends StatelessWidget {
const _Section12_RichText();
@override
Widget build(BuildContext context) {
return _Card(
title: '⑫ RichText / TextSpan',
subtitle: '一段文字内,不同片段可以有不同样式',
children: [
RichText(
text: const TextSpan(
style: TextStyle(fontSize: 14, color: Colors.black),
children: [
TextSpan(text: '你好,'),
TextSpan(text: 'Flutter', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.blue, fontSize: 18)),
TextSpan(text: ' 欢迎来到 '),
TextSpan(text: 'TextSpan',
style: TextStyle(color: Colors.red, decoration: TextDecoration.underline)),
TextSpan(text: ' 的世界!'),
],
),
),
const SizedBox(height: 8),
const Text('Text.rich 简写:', style: TextStyle(fontSize: 11, color: Colors.grey)),
Text.rich(
const TextSpan(
children: [
TextSpan(text: '原价:', style: TextStyle(color: Colors.grey)),
TextSpan(text: '¥199',
style: TextStyle(decoration: TextDecoration.lineThrough, color: Colors.grey)),
TextSpan(text: ' 现价:', style: TextStyle(color: Colors.grey)),
TextSpan(text: '¥99',
style: TextStyle(color: Colors.red, fontSize: 20, fontWeight: FontWeight.bold)),
],
),
),
const SizedBox(height: 8),
const Text('TextSpan 还可以插入 WidgetSpan:', style: TextStyle(fontSize: 11, color: Colors.grey)),
Text.rich(
TextSpan(
children: [
const TextSpan(text: '评分:', style: TextStyle(fontSize: 14)),
WidgetSpan(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
for (int i = 0; i < 5; i++)
const Icon(Icons.star, color: Colors.amber, size: 16),
],
),
),
const TextSpan(text: ' 5.0', style: TextStyle(fontSize: 14)),
],
),
),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ⑬ 完整总结
// ────────────────────────────────────────────────────────────────
class _Section13_Summary extends StatelessWidget {
const _Section13_Summary();
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.purple.shade50,
borderRadius: BorderRadius.circular(8),
),
child: const Text(
'📌 Text 完整总结\n\n'
'1. 基本属性:data / style / textAlign / maxLines / overflow\n\n'
'2. TextStyle 核心属性:\n'
' fontSize / fontWeight / fontStyle / color\n'
' letterSpacing / wordSpacing / height\n'
' decoration / shadows\n\n'
'3. TextOverflow 四种:\n'
' clip(裁剪)/ fade(渐隐)/ ellipsis(省略号)/ visible(显示)\n\n'
'4. 对齐:\n'
' textAlign 行内对齐(left/center/right/start/end/justify)\n'
' textDirection LTR/RTL\n\n'
'5. 高级用法:\n'
' Text.rich / RichText + TextSpan → 富文本\n'
' DefaultTextStyle → 子树统一默认样式\n'
' StrutStyle → 统一行高\n\n'
'6. 继承链:Text → DefaultTextStyle → ThemeData.textTheme\n'
' 未设的样式会从上层继承,最后 fallback 到默认样式',
style: TextStyle(fontSize: 12),
),
);
}
}
// ────────────────────────────────────────────────────────────────
// 通用卡片组件
// ────────────────────────────────────────────────────────────────
class _Card extends StatelessWidget {
final String title;
final String subtitle;
final List<Widget> children;
const _Card({required this.title, this.subtitle = '', required this.children});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.grey.shade300),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 6,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
if (subtitle.isNotEmpty) ...[
const SizedBox(height: 2),
Text(subtitle,
style: TextStyle(fontSize: 11, color: Colors.grey.shade600, fontStyle: FontStyle.italic)),
],
const SizedBox(height: 10),
...children,
],
),
);
}
}
Image
Image.asset() 加载项目资源目录assets中的图片,需要在pubspec.yaml中指定
Image.network() 网络图片
Image.file() 本地文件
Image.memory() 内存中的图片
width/height 设置图片宽高
fit Boxfit 拉伸,裁切
bash
pubspec.yaml需要配置之后才能使用Image.asset()
assets:
- lib/Vehicles/ #目录下的所有文件,一定要加后面的斜杆,否则编译报错
- lib/images/fox.png #单个文件
typescript
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(const MaterialApp(home: HomePage()));
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Image 全方位知识点')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
// 错误,开头不要带/
// Image.asset("/lib/images/fox.png"),
Image.asset("lib/images/fox.png"),
Image.asset("lib/Vehicles/car.png"),
],
),
);
}
}
Image进阶
less
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(const MaterialApp(home: HomePage()));
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Image 全方位知识点')),
body: ListView(
padding: const EdgeInsets.all(16),
children: const [
_Section1_Essence(),
_Section2_ImageProvider(),
_Section3_Fit(),
_Section4_Alignment(),
_Section5_Repeat(),
_Section6_CenterSlice(),
_Section7_ColorAndBlend(),
_Section8_WidthHeight(),
_Section9_FrameBuilder(),
_Section10_NetworkImage(),
_Section11_MemoryImage(),
_Section12_BackgroundImage(),
_Section13_Summary(),
],
),
);
}
}
// ────────────────────────────────────────────────────────────────
// ① Image 的本质 ------ 显示图像的 Widget
// ────────────────────────────────────────────────────────────────
class _Section1_Essence extends StatelessWidget {
const _Section1_Essence();
@override
Widget build(BuildContext context) {
return _Card(
title: '① Image 的本质',
subtitle: '显示图像的 Widget,核心属性 image(ImageProvider)',
children: [
const Text(
'Image 的核心属性:\n'
'• ImageProvider image 图片数据源(必填)\n'
'• double? width 宽度\n'
'• double? height 高度\n'
'• BoxFit? fit 图片如何适配容器\n'
'• Alignment alignment 对齐(默认 center)\n'
'• ImageRepeat repeat 重复方式\n'
'• Rect? centerSlice 九宫格拉伸区域\n'
'• Color? color 颜色过滤\n'
'• BlendMode? colorBlendMode 混合模式\n'
'• Widget Function(FrameBuilder)? frameBuilder 帧构建\n'
'• Widget Function(LoadingBuilder)? loadingBuilder 加载中\n'
'• Widget Function(ErrorBuilder)? errorBuilder 加载失败\n'
'• Duration fadeDuration 淡入时长\n'
'• bool matchTextDirection 是否随文字方向翻转\n'
'• bool gaplessPlayback 是否保持旧帧(GIF/WebP)\n'
'• String? semanticLabel 语义描述(无障碍)',
style: TextStyle(fontSize: 11, fontFamily: 'monospace'),
),
const SizedBox(height: 8),
// 用一个 flutter logo 作为示例
Container(
color: Colors.grey.shade200,
height: 80,
alignment: Alignment.center,
child: const Image(
image: AssetImage('assets/icon.png'),
width: 50,
height: 50,
errorBuilder: _errorFallback,
),
),
const SizedBox(height: 4),
const Text('示例:加载本地资源图', style: TextStyle(fontSize: 10)),
],
);
}
}
Widget _errorFallback(
BuildContext context,
Object error,
StackTrace? stackTrace,
) {
return Container(
width: 50,
height: 50,
color: Colors.grey,
alignment: Alignment.center,
child: const Icon(Icons.image, color: Colors.white, size: 24),
);
}
// ────────────────────────────────────────────────────────────────
// ② ImageProvider ------ 四种图片来源
// ────────────────────────────────────────────────────────────────
class _Section2_ImageProvider extends StatelessWidget {
const _Section2_ImageProvider();
@override
Widget build(BuildContext context) {
return _Card(
title: '② ImageProvider ------ 四种图片来源',
subtitle: 'Image.asset / Image.network / Image.memory / Image.file',
children: [
const Text(
'四种快捷构造:',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
),
const SizedBox(height: 4),
const Text(
'// 1. 本地资源(pubspec.yaml 配置 assets)\n'
'Image.asset("assets/logo.png")\n\n'
'// 2. 网络图片\n'
'Image.network("https://example.com/img.png")\n\n'
'// 3. 内存图片(Uint8List bytes)\n'
'Image.memory(bytes)\n\n'
'// 4. 文件图片(File 对象)\n'
'Image.file(File("/path/to/img.png"))',
style: TextStyle(fontSize: 11, fontFamily: 'monospace'),
),
const SizedBox(height: 6),
const Text(
'本质:这四个都是 Image(image: XxxImage(...)) 的简写,\n'
'XxxImage 是 ImageProvider 的子类,负责异步加载图片数据。',
style: TextStyle(fontSize: 11),
),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ③ fit ------ 图片如何适配容器(最重要的属性!)
// ────────────────────────────────────────────────────────────────
class _Section3_Fit extends StatelessWidget {
const _Section3_Fit();
@override
Widget build(BuildContext context) {
return _Card(
title: '③ fit ------ 图片如何适配容器',
subtitle: 'BoxFit.none / contain / cover / fill / fitWidth / fitHeight',
children: [
Row(
children: [
Expanded(
child: _FitDemo(label: 'none', fit: BoxFit.none),
),
Expanded(
child: _FitDemo(label: 'contain', fit: BoxFit.contain),
),
Expanded(
child: _FitDemo(label: 'cover', fit: BoxFit.cover),
),
],
),
const SizedBox(height: 4),
Row(
children: [
Expanded(
child: _FitDemo(label: 'fill', fit: BoxFit.fill),
),
Expanded(
child: _FitDemo(label: 'fitWidth', fit: BoxFit.fitWidth),
),
Expanded(
child: _FitDemo(label: 'fitHeight', fit: BoxFit.fitHeight),
),
],
),
const SizedBox(height: 6),
const Text(
'fit 六值对比:\n'
'• none 原尺寸居中,超出裁剪,不足留空\n'
'• contain 保持比例,完整显示,留空(❌不变形)\n'
'• cover 保持比例,铺满容器,裁剪(❌不变形)\n'
'• fill 强制拉满容器,可能变形(⚠️ 变形)\n'
'• fitWidth 宽度撑满,高度按比例\n'
'• fitHeight 高度撑满,宽度按比例',
style: TextStyle(fontSize: 11),
),
],
);
}
}
class _FitDemo extends StatelessWidget {
final String label;
final BoxFit fit;
const _FitDemo({required this.label, required this.fit});
@override
Widget build(BuildContext context) {
return Column(
children: [
Container(
height: 80,
color: Colors.blueGrey.shade100,
child: Image.asset(
'assets/icon.png',
fit: fit,
errorBuilder: _errorIcon,
),
),
const SizedBox(height: 2),
Text(label, style: const TextStyle(fontSize: 9)),
],
);
}
}
Widget _errorIcon(BuildContext context, Object error, StackTrace? stackTrace) {
return const Center(child: Icon(Icons.image, size: 32, color: Colors.grey));
}
// ────────────────────────────────────────────────────────────────
// ④ alignment ------ 对齐位置(配合 fit 使用)
// ────────────────────────────────────────────────────────────────
class _Section4_Alignment extends StatelessWidget {
const _Section4_Alignment();
@override
Widget build(BuildContext context) {
return _Card(
title: '④ alignment',
subtitle: '对齐位置,配合 fit: cover / none / contain 使用',
children: [
const Text(
'对齐对 fit 的影响:\n'
'• fit: cover + alignment.topLeft → 从左上角开始裁剪\n'
'• fit: cover + Alignment.center → 从中间裁剪(默认)\n'
'• fit: cover + Alignment.bottomRight → 从右下角裁剪\n'
'• fit: none + alignment → 原尺寸图片在容器内的位置\n'
'• fit: contain 时 alignment 影响"留空"的方向',
style: TextStyle(fontSize: 11),
),
const SizedBox(height: 6),
Row(
children: [
Expanded(
child: Column(
children: [
Container(
height: 80,
color: Colors.grey.shade200,
child: const Align(
alignment: Alignment.topLeft,
child: _DemoImage(),
),
),
const SizedBox(height: 2),
const Text('topLeft', style: TextStyle(fontSize: 9)),
],
),
),
const SizedBox(width: 4),
Expanded(
child: Column(
children: [
Container(
height: 80,
color: Colors.grey.shade200,
child: const Align(
alignment: Alignment.center,
child: _DemoImage(),
),
),
const SizedBox(height: 2),
const Text('center(默认)', style: TextStyle(fontSize: 9)),
],
),
),
const SizedBox(width: 4),
Expanded(
child: Column(
children: [
Container(
height: 80,
color: Colors.grey.shade200,
child: const Align(
alignment: Alignment.bottomRight,
child: _DemoImage(),
),
),
const SizedBox(height: 2),
const Text('bottomRight', style: TextStyle(fontSize: 9)),
],
),
),
],
),
],
);
}
}
class _DemoImage extends StatelessWidget {
const _DemoImage();
@override
Widget build(BuildContext context) {
return Container(
width: 40,
height: 40,
color: Colors.red,
alignment: Alignment.center,
child: const Text(
'图',
style: TextStyle(color: Colors.white, fontSize: 12),
),
);
}
}
// ────────────────────────────────────────────────────────────────
// ⑤ repeat ------ 重复方式
// ────────────────────────────────────────────────────────────────
class _Section5_Repeat extends StatelessWidget {
const _Section5_Repeat();
@override
Widget build(BuildContext context) {
return _Card(
title: '⑤ repeat',
subtitle: '当图片比容器小时,是否/如何重复',
children: [
Row(
children: [
Expanded(
child: _RepeatDemo(
label: 'noRepeat',
repeat: ImageRepeat.noRepeat,
),
),
Expanded(
child: _RepeatDemo(label: 'repeat', repeat: ImageRepeat.repeat),
),
Expanded(
child: _RepeatDemo(label: 'repeatX', repeat: ImageRepeat.repeatX),
),
Expanded(
child: _RepeatDemo(label: 'repeatY', repeat: ImageRepeat.repeatY),
),
],
),
const SizedBox(height: 6),
const Text(
'repeat 需要满足两个条件:\n'
'1. fit 让图片比容器小(比如 fit: none 或 contain)\n'
'2. alignment 没有让图片居中填满\n\n'
'实际开发中 repeat 用得很少,通常用于平铺背景图案。',
style: TextStyle(fontSize: 11),
),
],
);
}
}
class _RepeatDemo extends StatelessWidget {
final String label;
final ImageRepeat repeat;
const _RepeatDemo({required this.label, required this.repeat});
@override
Widget build(BuildContext context) {
return Column(
children: [
Container(
height: 80,
color: Colors.white,
child: Image.asset(
'assets/icon.png',
fit: BoxFit.none,
repeat: repeat,
errorBuilder: _errorIcon,
),
),
const SizedBox(height: 2),
Text(label, style: const TextStyle(fontSize: 9)),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ⑥ centerSlice ------ 九宫格拉伸(圆角/边框图片神器)
// ────────────────────────────────────────────────────────────────
class _Section6_CenterSlice extends StatelessWidget {
const _Section6_CenterSlice();
@override
Widget build(BuildContext context) {
return _Card(
title: '⑥ centerSlice',
subtitle: '九宫格拉伸,让圆角图片可伸缩而不变形',
children: [
const Text(
'centerSlice 源码解释:\n'
'把图片分成 9 块(3×3 网格),拉伸时:\n'
'• 四个角:不拉伸,保持原样\n'
'• 四条边:只拉伸一边\n'
'• 中间:双向拉伸\n\n'
'这是做圆角 Button、气泡消息等需要可伸缩背景的关键技术!',
style: TextStyle(fontSize: 11),
),
const SizedBox(height: 6),
// 用一个 Container 模拟九宫格效果
Row(
children: [
Expanded(
child: Container(
height: 60,
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(12),
),
alignment: Alignment.center,
child: const Text('小', style: TextStyle(color: Colors.white)),
),
),
const SizedBox(width: 6),
Expanded(
child: Container(
height: 60,
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(12),
),
alignment: Alignment.center,
child: const Text(
'大尺寸的内容会导致变形?不,因为我们用了 borderRadius',
style: TextStyle(color: Colors.white, fontSize: 9),
),
),
),
],
),
const SizedBox(height: 6),
const Text(
'实际 centerSlice 用法:\n'
'Image.asset("bubble.png",\n'
' centerSlice: Rect.fromLTRB(20, 20, 20, 20),\n'
' fit: BoxFit.fill,\n'
')',
style: TextStyle(fontSize: 11, fontFamily: 'monospace'),
),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ⑦ color + colorBlendMode ------ 颜色过滤
// ────────────────────────────────────────────────────────────────
class _Section7_ColorAndBlend extends StatelessWidget {
const _Section7_ColorAndBlend();
@override
Widget build(BuildContext context) {
return _Card(
title: '⑦ color + colorBlendMode',
subtitle: '给图片加颜色滤镜',
children: [
Row(
children: [
Expanded(
child: Column(
children: [
Container(
height: 60,
color: Colors.grey.shade200,
child: const _DemoImage(),
),
const SizedBox(height: 2),
const Text('原图', style: TextStyle(fontSize: 9)),
],
),
),
const SizedBox(width: 4),
Expanded(
child: Column(
children: [
Container(
height: 60,
color: Colors.grey.shade200,
child: const _ColoredDemoImage(color: Colors.red),
),
const SizedBox(height: 2),
const Text('color: red', style: TextStyle(fontSize: 9)),
],
),
),
const SizedBox(width: 4),
Expanded(
child: Column(
children: [
Container(
height: 60,
color: Colors.grey.shade200,
child: const _ColoredDemoImage(
color: Colors.blue,
blendMode: BlendMode.multiply,
),
),
const SizedBox(height: 2),
const Text('multiply', style: TextStyle(fontSize: 9)),
],
),
),
],
),
const SizedBox(height: 6),
const Text(
'color + colorBlendMode 常用于:\n'
'• 给图标/图片换色(比如把黑色图标变白色)\n'
'• 给图片加半透明遮罩\n'
'• 给 disabled 状态降低饱和度',
style: TextStyle(fontSize: 11),
),
],
);
}
}
class _ColoredDemoImage extends StatelessWidget {
final Color color;
final BlendMode blendMode;
const _ColoredDemoImage({
required this.color,
this.blendMode = BlendMode.srcIn,
});
@override
Widget build(BuildContext context) {
return ColorFiltered(
colorFilter: ColorFilter.mode(color, blendMode),
child: const _DemoImage(),
);
}
}
// ────────────────────────────────────────────────────────────────
// ⑧ width / height ------ 尺寸设置
// ────────────────────────────────────────────────────────────────
class _Section8_WidthHeight extends StatelessWidget {
const _Section8_WidthHeight();
@override
Widget build(BuildContext context) {
return _Card(
title: '⑧ width / height',
subtitle: '设置图片的显示尺寸',
children: [
const Text(
'规则:\n'
'• 只设 width:height 按图片原始比例自动计算\n'
'• 只设 height:width 按图片原始比例自动计算\n'
'• 都设:强制拉伸(变形),除非配合 fit\n'
'• 都不设:使用图片原始尺寸\n\n'
'Container vs Image 设尺寸:\n'
'• Container(width:100, child: Image(fit: fill)) ← 容器决定尺寸\n'
'• Image(width:100) ← Image 自己决定\n\n'
'更推荐用 Container + fit 组合,更灵活。',
style: TextStyle(fontSize: 11),
),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ⑨ frameBuilder ------ 加载中显示占位
// ────────────────────────────────────────────────────────────────
class _Section9_FrameBuilder extends StatelessWidget {
const _Section9_FrameBuilder();
@override
Widget build(BuildContext context) {
return _Card(
title: '⑨ frameBuilder / loadingBuilder',
subtitle: '图片加载中/加载失败时显示占位 Widget',
children: [
const Text(
'三个 builder 回调:\n\n'
'1. frameBuilder:\n'
' 图片加载完成后的帧构建\n'
' 常用于淡入动画\n\n'
'2. loadingBuilder(网络图片):\n'
' 网络请求进行中显示 loading\n'
' 常用于展示 CircularProgressIndicator\n\n'
'3. errorBuilder:\n'
' 加载失败时显示占位\n'
' 常用于显示错误图标或 placeholder\n\n'
'示例代码:',
style: TextStyle(fontSize: 11),
),
const SizedBox(height: 4),
Container(
padding: const EdgeInsets.all(8),
color: Colors.amber.shade50,
child: const Text(
'Image.network(\n'
' url,\n'
' loadingBuilder: (ctx, child, progress) {\n'
' if (progress == null) return child;\n'
' return CircularProgressIndicator();\n'
' },\n'
' errorBuilder: (ctx, err, stk) {\n'
' return Icon(Icons.broken_image);\n'
' },\n'
')',
style: TextStyle(fontSize: 11, fontFamily: 'monospace'),
),
),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ⑩ NetworkImage 特殊属性
// ────────────────────────────────────────────────────────────────
class _Section10_NetworkImage extends StatelessWidget {
const _Section10_NetworkImage();
@override
Widget build(BuildContext context) {
return _Card(
title: '⑩ NetworkImage 特殊属性',
subtitle: '网络图片的额外控制',
children: [
const Text(
'Image.network 额外支持:\n\n'
'• loadingBuilder 加载中占位\n'
'• errorBuilder 加载失败占位\n'
'• headers 自定义 HTTP 请求头\n'
'• cacheWidth / cacheHeight 解码时缩放(省内存!)\n\n'
'cacheWidth / cacheHeight 很重要:\n'
'图片原始 4000×3000,但只显示在 200×150 的 Image 里\n'
'不设 cacheWidth → 解码 4000×3000 → 吃内存\n'
'设 cacheWidth: 400 → 只解码 400×300 → 省 100 倍内存',
style: TextStyle(fontSize: 11),
),
const SizedBox(height: 6),
Container(
padding: const EdgeInsets.all(8),
color: Colors.amber.shade50,
child: const Text(
'Image.network(\n'
' url,\n'
' cacheWidth: 400, // ← 解码时缩放到 400px 宽\n'
' cacheHeight: 300,\n'
')',
style: TextStyle(fontSize: 11, fontFamily: 'monospace'),
),
),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ⑪ MemoryImage ------ 内存图片(Uint8List)
// ────────────────────────────────────────────────────────────────
class _Section11_MemoryImage extends StatelessWidget {
const _Section11_MemoryImage();
@override
Widget build(BuildContext context) {
return _Card(
title: '⑪ MemoryImage',
subtitle: '从 Uint8List 字节数据加载图片',
children: [
const Text(
'典型场景:\n'
'• 用户选择图片后(File → bytes → MemoryImage)\n'
'• 网络下载图片后(http.Response.bodyBytes)\n'
'• 本地数据库读取 blob\n\n'
'示例流程:',
style: TextStyle(fontSize: 11),
),
const SizedBox(height: 4),
Container(
padding: const EdgeInsets.all(8),
color: Colors.amber.shade50,
child: const Text(
'// 从网络下载\n'
'final response = await http.get(url);\n'
'// 直接用 bodyBytes\n'
'Image.memory(response.bodyBytes)\n\n'
'// 从文件读取\n'
'final file = File("/path/to/img.png");\n'
'final bytes = await file.readAsBytes();\n'
'Image.memory(bytes)',
style: TextStyle(fontSize: 11, fontFamily: 'monospace'),
),
),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ⑫ DecorationImage ------ 配合 Container 使用
// ────────────────────────────────────────────────────────────────
class _Section12_BackgroundImage extends StatelessWidget {
const _Section12_BackgroundImage();
@override
Widget build(BuildContext context) {
return _Card(
title: '⑫ DecorationImage',
subtitle: 'Container 的 decoration 里放图片',
children: [
Container(
height: 100,
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.circular(12),
image: const DecorationImage(
image: AssetImage('assets/icon.png'),
fit: BoxFit.cover,
alignment: Alignment.center,
onError: _onDecoError,
),
),
child: Container(
alignment: Alignment.bottomLeft,
padding: const EdgeInsets.all(8),
child: const Text(
'Container + DecorationImage',
style: TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
),
const SizedBox(height: 6),
const Text(
'两者区别:\n'
'Image Widget → 只有图片,自己就是布局元素\n'
'DecorationImage → Container 的背景,上面可以叠其他 Widget\n'
'\n'
'需要:圆角边框 + 背景图 + 前景文字\n'
'→ Container(decoration: BoxDecoration(image: DecorationImage(...)))',
style: TextStyle(fontSize: 11),
),
],
);
}
}
void _onDecoError(Object exception, StackTrace? stackTrace) {
// 装饰图片加载失败的回调
}
// ────────────────────────────────────────────────────────────────
// ⑬ 完整总结
// ────────────────────────────────────────────────────────────────
class _Section13_Summary extends StatelessWidget {
const _Section13_Summary();
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.green.shade50,
borderRadius: BorderRadius.circular(8),
),
child: const Text(
'📌 Image 完整总结\n\n'
'1. 四种来源:\n'
' Image.asset / network / memory / file\n\n'
'2. fit 六值:\n'
' none / contain(不变形)/ cover(裁剪)\n'
' fill(变形)/ fitWidth / fitHeight\n\n'
'3. 重要属性:\n'
' width / height / alignment / repeat\n'
' centerSlice(九宫格)/ color + colorBlendMode\n\n'
'4. 网络图片:\n'
' loadingBuilder / errorBuilder\n'
' cacheWidth / cacheHeight(省内存!)\n\n'
'5. 与 Container 配合:\n'
' Container + DecorationImage → 背景图\n'
' Container(borderRadius + clipBehavior) + Image → 圆角图\n\n'
'6. 性能优化:\n'
' cacheWidth/cacheHeight 解码时缩放\n'
' 避免使用过大的原始图片\n'
' 使用 CachedNetworkImage 实现磁盘缓存',
style: TextStyle(fontSize: 12),
),
);
}
}
// ────────────────────────────────────────────────────────────────
// 通用卡片组件
// ────────────────────────────────────────────────────────────────
class _Card extends StatelessWidget {
final String title;
final String subtitle;
final List<Widget> children;
const _Card({
required this.title,
this.subtitle = '',
required this.children,
});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.grey.shade300),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 6,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
),
if (subtitle.isNotEmpty) ...[
const SizedBox(height: 2),
Text(
subtitle,
style: TextStyle(
fontSize: 11,
color: Colors.grey.shade600,
fontStyle: FontStyle.italic,
),
),
],
const SizedBox(height: 10),
...children,
],
),
);
}
}
TextFiled
less
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(const MaterialApp(home: HomePage()));
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('登录')),
body: MainPage(),
);
}
}
class MainPage extends StatefulWidget {
MainPage({Key? key}) : super(key: key);
@override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
final TextEditingController _accountController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(20),
color: Colors.white,
child: Column(
children: [
TextField(
controller: _accountController,
decoration: InputDecoration(
fillColor: Colors.amber,
filled: true,
hintText: "账号",
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
borderSide: BorderSide.none,
),
contentPadding: EdgeInsets.all(20),
),
onChanged: (value){
},
onSubmitted: (value){
},
),
SizedBox(height: 20),
TextField(
controller: _passwordController,
obscureText: true, // 密码输入框,看不到实际内容
decoration: InputDecoration(
fillColor: Colors.amber,
filled: true,
hintText: "密码",
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
borderSide: BorderSide.none,
),
contentPadding: EdgeInsets.all(20),
),
),
SizedBox(height: 20),
Container(
height: 50,
width: .infinity,
decoration: BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(20),
),
child: TextButton(
onPressed: () {
print("${_accountController.text} ${_passwordController.text}");
},
child: Text("登录", style: TextStyle(color: Colors.white)),
),
),
],
),
);
}
}
TextField进阶
less
import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(const MaterialApp(home: HomePage()));
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('TextField 全方位知识点')),
body: ListView(
padding: const EdgeInsets.all(16),
children: const [
_Section1_Essence(),
_Section2_Controller(),
_Section3_Decoration(),
_Section4_BorderStyles(),
_Section5_FocusAndKeyboard(),
_Section6_InputFormatters(),
_Section7_ObscureText(),
_Section8_MaxLength(),
_Section9_TextInputAction(),
_Section10_ReadOnlyEnabled(),
_Section11_Multiline(),
_Section12_Selection(),
_Section13_Summary(),
],
),
);
}
}
// ────────────────────────────────────────────────────────────────
// ① TextField 本质 ------ 输入框的核心属性
// ────────────────────────────────────────────────────────────────
class _Section1_Essence extends StatelessWidget {
const _Section1_Essence();
@override
Widget build(BuildContext context) {
return _Card(
title: '① TextField 本质',
subtitle: '文本输入框,核心属性一览',
children: const [
Text(
'TextField 核心属性:\n'
'• TextEditingController controller 控制器(获取/设置文本)\n'
'• InputDecoration decoration 外观装饰(边框/标签/提示)\n'
'• TextInputType keyboardType 键盘类型\n'
'• TextInputAction textInputAction 回车键类型\n'
'• bool obscureText 密码模式\n'
'• bool readOnly 只读\n'
'• bool enabled 是否可用\n'
'• int? maxLength 最大输入长度\n'
'• int? maxLines 最大行数(null=无限)\n'
'• int? minLines 最小行数\n'
'• List<TextInputFormatter>? inputFormatters 输入过滤器\n'
'• ValueChanged<String>? onChanged 内容变化回调\n'
'• ValueChanged<String>? onSubmitted 提交(回车)回调\n'
'• VoidCallback? onEditingComplete 编辑完成回调\n'
'• FocusNode? focusNode 焦点节点\n'
'• TextAlign textAlign 文字对齐\n'
'• TextDirection textDirection 文字方向\n'
'• bool autofocus 是否自动聚焦\n'
'• bool autocorrect 是否自动纠正\n'
'• bool enableSuggestions 是否启用联想\n'
'• Widget? suffix / prefix / suffixIcon / prefixIcon\n'
'• String? hintText / labelText / helperText / errorText / counterText',
style: TextStyle(fontSize: 10.5, fontFamily: 'monospace'),
),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ② TextEditingController ------ 控制器(最重要的交互方式)
// ────────────────────────────────────────────────────────────────
class _Section2_Controller extends StatefulWidget {
const _Section2_Controller();
@override
State<_Section2_Controller> createState() => _Section2_ControllerState();
}
class _Section2_ControllerState extends State<_Section2_Controller> {
final _controller = TextEditingController();
@override
void dispose() {
_controller.dispose(); // ⚠️ 必须释放!内存泄漏
super.dispose();
}
@override
Widget build(BuildContext context) {
return _Card(
title: '② TextEditingController',
subtitle: '控制器:获取/设置/监听文本,⚠️ dispose 必须调用',
children: [
TextField(
controller: _controller,
decoration: const InputDecoration(
hintText: '输入点什么试试',
border: OutlineInputBorder(),
),
onChanged: (v) => setState(() {}),
),
const SizedBox(height: 8),
Text('当前内容:"${_controller.text}"'),
const SizedBox(height: 8),
Row(
children: [
ElevatedButton(
onPressed: () {
_controller.text = 'Hello Flutter'; // ✅ 设置值
},
child: const Text('设置文本'),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () {
_controller.clear(); // ✅ 清空
},
child: const Text('清空'),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () {
final text = _controller.text;
_controller.value = TextEditingValue(
text: text,
selection: TextSelection(baseOffset: 0, extentOffset: text.length),
);
},
child: const Text('全选'),
),
],
),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.all(8),
color: Colors.amber.shade50,
child: const Text(
'// 3 种常用操作:\n'
'controller.text = "新值"; // 设置文本\n'
'controller.clear(); // 清空\n'
'controller.addListener(() {}) // 监听变化(比 onChanged 灵活)\n'
'\n'
'// ⚠️ 必须在 dispose 中释放!\n'
'@override\n'
'void dispose() {\n'
' controller.dispose();\n'
' super.dispose();\n'
'}',
style: TextStyle(fontSize: 10.5, fontFamily: 'monospace'),
),
),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ③ InputDecoration ------ 外观装饰全解
// ────────────────────────────────────────────────────────────────
class _Section3_Decoration extends StatelessWidget {
const _Section3_Decoration();
@override
Widget build(BuildContext context) {
return _Card(
title: '③ InputDecoration',
subtitle: '外观装饰:hint / label / icon / filled / contentPadding',
children: const [
TextField(
decoration: InputDecoration(
icon: Icon(Icons.person),
prefixIcon: Icon(Icons.email),
hintText: '请输入邮箱',
hintStyle: TextStyle(color: Colors.grey),
labelText: '邮箱地址',
labelStyle: TextStyle(color: Colors.blue),
filled: true,
fillColor: Colors.amber,
border: OutlineInputBorder(),
contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 15),
helperText: '我们不会分享您的邮箱',
helperStyle: TextStyle(color: Colors.green, fontSize: 10),
counterText: '0/50',
),
),
SizedBox(height: 6),
Text('InputDecoration 完整属性:', style: TextStyle(fontSize: 11, fontWeight: FontWeight.bold)),
Text(
'icon 左边外部图标(在输入框外)\n'
'prefixIcon 输入框内部左边图标\n'
'prefixText 输入框内部左边文字\n'
'suffixIcon 输入框内部右边图标\n'
'suffixText 输入框内部右边文字\n'
'suffix 右边任意 Widget(如清除按钮)\n'
'hintText 占位提示文字(未输入时显示)\n'
'labelText 浮动标签(输入框上方,聚焦时缩小浮动)\n'
'helperText 底部帮助文字\n'
'errorText 底部错误文字(显示时边框变红)\n'
'counterText 右下角计数器文字\n'
'filled 是否填充背景色\n'
'fillColor 背景填充色\n'
'contentPadding 内容内边距',
style: TextStyle(fontSize: 10.5),
),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ④ 三种 Border ------ underline / outline / none
// ────────────────────────────────────────────────────────────────
class _Section4_BorderStyles extends StatelessWidget {
const _Section4_BorderStyles();
@override
Widget build(BuildContext context) {
return _Card(
title: '④ Border 三种样式',
subtitle: 'UnderlineInputBorder / OutlineInputBorder / InputBorder.none',
children: [
const Text('① UnderlineInputBorder(默认):只有底部一条线', style: TextStyle(fontSize: 11)),
const SizedBox(height: 4),
const TextField(
decoration: InputDecoration(
hintText: 'UnderlineInputBorder',
border: UnderlineInputBorder(),
enabledBorder: UnderlineInputBorder(borderSide: BorderSide(color: Colors.grey)),
focusedBorder: UnderlineInputBorder(borderSide: BorderSide(color: Colors.blue, width: 2)),
),
),
const SizedBox(height: 10),
const Text('② OutlineInputBorder(带圆角边框):', style: TextStyle(fontSize: 11)),
const SizedBox(height: 4),
TextField(
decoration: InputDecoration(
hintText: 'OutlineInputBorder',
border: const OutlineInputBorder(),
enabledBorder: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
borderSide: BorderSide(color: Colors.grey),
),
focusedBorder: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
borderSide: BorderSide(color: Colors.blue, width: 2),
),
),
),
const SizedBox(height: 10),
const Text('③ InputBorder.none(无边框):', style: TextStyle(fontSize: 11)),
const SizedBox(height: 4),
const TextField(
decoration: InputDecoration(
hintText: 'InputBorder.none',
border: InputBorder.none,
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
filled: true,
fillColor: Colors.amber,
),
),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.all(8),
color: Colors.amber.shade50,
child: const Text(
'// 4 种边框状态可单独设置:\n'
'border: 默认边框\n'
'enabledBorder: 非聚焦状态边框\n'
'focusedBorder: 聚焦状态边框\n'
'errorBorder: 出错时边框\n'
'disabledBorder: 禁用时边框',
style: TextStyle(fontSize: 10.5, fontFamily: 'monospace'),
),
),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ⑤ FocusNode & 键盘类型 ------ 控制焦点和弹出什么键盘
// ────────────────────────────────────────────────────────────────
class _Section5_FocusAndKeyboard extends StatefulWidget {
const _Section5_FocusAndKeyboard();
@override
State<_Section5_FocusAndKeyboard> createState() => _Section5_FocusAndKeyboardState();
}
class _Section5_FocusAndKeyboardState extends State<_Section5_FocusAndKeyboard> {
final _focusNode1 = FocusNode();
final _focusNode2 = FocusNode();
@override
void dispose() {
_focusNode1.dispose();
_focusNode2.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return _Card(
title: '⑤ FocusNode & 键盘类型',
subtitle: '控制焦点切换 & 弹出数字/邮箱/电话等不同键盘',
children: [
TextField(
focusNode: _focusNode1,
decoration: const InputDecoration(
hintText: '点击"聚焦第一个"按钮',
border: OutlineInputBorder(),
labelText: '普通键盘',
),
textInputAction: TextInputAction.next,
onSubmitted: (_) => FocusScope.of(context).requestFocus(_focusNode2),
),
const SizedBox(height: 8),
TextField(
focusNode: _focusNode2,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
hintText: '这里弹出数字键盘',
border: OutlineInputBorder(),
labelText: '数字键盘',
),
),
const SizedBox(height: 8),
Row(
children: [
ElevatedButton(
onPressed: () => FocusScope.of(context).requestFocus(_focusNode1),
child: const Text('聚焦第一个'),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () => FocusScope.of(context).requestFocus(_focusNode2),
child: const Text('聚焦第二个'),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () => FocusScope.of(context).unfocus(),
child: const Text('失焦'),
),
],
),
const SizedBox(height: 8),
const Text('TextInputType 常见值:', style: TextStyle(fontSize: 11, fontWeight: FontWeight.bold)),
const Text(
'• text 普通文本键盘(默认)\n'
'• number 数字键盘\n'
'• phone 电话键盘(带 * #)\n'
'• emailAddress 邮箱键盘(带 @)\n'
'• url URL 键盘(带 / .)\n'
'• datetime 日期时间键盘\n'
'• multiline 多行(配合 maxLines)',
style: TextStyle(fontSize: 10.5),
),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ⑥ inputFormatters ------ 输入过滤器(限制输入内容)
// ────────────────────────────────────────────────────────────────
class _Section6_InputFormatters extends StatelessWidget {
const _Section6_InputFormatters();
@override
Widget build(BuildContext context) {
return _Card(
title: '⑥ inputFormatters',
subtitle: '限制输入:只允许数字/长度/正则',
children: [
const Text('限制长度:', style: TextStyle(fontSize: 11)),
const TextField(
decoration: InputDecoration(
hintText: '最多 5 个字符',
border: OutlineInputBorder(),
),
inputFormatters: [],
// 用 LengthLimitingTextInputFormatter(5) 可限制长度
),
const SizedBox(height: 6),
const Text('只允许数字:', style: TextStyle(fontSize: 11)),
const TextField(
decoration: InputDecoration(
hintText: '只能输入数字',
border: OutlineInputBorder(),
),
keyboardType: TextInputType.number,
),
const SizedBox(height: 6),
Container(
padding: const EdgeInsets.all(8),
color: Colors.amber.shade50,
child: const Text(
'// 常用输入过滤器:\n'
'inputFormatters: [\n'
' LengthLimitingTextInputFormatter(11), // 限制长度\n'
' FilteringTextInputFormatter.digitsOnly, // 只允许数字\n'
' FilteringTextInputFormatter.allow(RegExp(r"[a-zA-Z]")), // 只允许字母\n'
' FilteringTextInputFormatter.deny(RegExp(r"[!@#]")), // 禁止特殊字符\n'
']\n\n'
'// 导入:import "package:flutter/services.dart";',
style: TextStyle(fontSize: 10.5, fontFamily: 'monospace'),
),
),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ⑦ obscureText ------ 密码模式
// ────────────────────────────────────────────────────────────────
class _Section7_ObscureText extends StatefulWidget {
const _Section7_ObscureText();
@override
State<_Section7_ObscureText> createState() => _Section7_ObscureTextState();
}
class _Section7_ObscureTextState extends State<_Section7_ObscureText> {
bool _obscure = true;
@override
Widget build(BuildContext context) {
return _Card(
title: '⑦ obscureText ------ 密码模式',
subtitle: '显示/隐藏密码内容',
children: [
TextField(
obscureText: _obscure,
decoration: InputDecoration(
hintText: '输入密码',
border: const OutlineInputBorder(),
suffixIcon: IconButton(
icon: Icon(_obscure ? Icons.visibility_off : Icons.visibility),
onPressed: () => setState(() => _obscure = !_obscure),
),
),
),
const SizedBox(height: 8),
const Text('obscureText: true → 显示为 ••••\nobscuringCharacter: "*" → 自定义遮盖字符',
style: TextStyle(fontSize: 11)),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ⑧ maxLength ------ 最大输入长度 + counter
// ────────────────────────────────────────────────────────────────
class _Section8_MaxLength extends StatelessWidget {
const _Section8_MaxLength();
@override
Widget build(BuildContext context) {
return _Card(
title: '⑧ maxLength',
subtitle: '最大输入长度,自动显示右下角计数器',
children: const [
TextField(
maxLength: 20,
decoration: InputDecoration(
hintText: '最多 20 个字符',
border: OutlineInputBorder(),
counterText: '', // 设为空字符串可隐藏计数器
),
),
SizedBox(height: 6),
Text('不设 maxLength 时可无限输入。\n'
'设了之后右下角自动显示 "N/20"。\n'
'想隐藏计数器:counterText设为空字符串',
style: TextStyle(fontSize: 11)),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ⑨ textInputAction ------ 回车键行为
// ────────────────────────────────────────────────────────────────
class _Section9_TextInputAction extends StatelessWidget {
const _Section9_TextInputAction();
@override
Widget build(BuildContext context) {
return _Card(
title: '⑨ textInputAction',
subtitle: '回车键显示什么文字/图标(搜索/完成/下一步...)',
children: [
TextField(
textInputAction: TextInputAction.search,
decoration: const InputDecoration(
hintText: '回车 = 搜索',
border: OutlineInputBorder(),
),
onSubmitted: (v) => debugPrint('搜索: $v'),
),
SizedBox(height: 6),
TextField(
textInputAction: TextInputAction.next,
decoration: InputDecoration(
hintText: '回车 = 下一步',
border: OutlineInputBorder(),
),
),
SizedBox(height: 6),
TextField(
textInputAction: TextInputAction.done,
decoration: InputDecoration(
hintText: '回车 = 完成(收起键盘)',
border: OutlineInputBorder(),
),
),
SizedBox(height: 6),
Text('TextInputAction 枚举值:\n'
'none / unspecified / done / go / search / send\n'
'next / previous / continueAction / join / route / emergencyCall / newline',
style: TextStyle(fontSize: 11)),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ⑩ readOnly / enabled ------ 只读 vs 禁用
// ────────────────────────────────────────────────────────────────
class _Section10_ReadOnlyEnabled extends StatelessWidget {
const _Section10_ReadOnlyEnabled();
@override
Widget build(BuildContext context) {
return _Card(
title: '⑩ readOnly vs enabled',
subtitle: '只读可聚焦可选择;禁用不可聚焦不可选择',
children: [
TextField(
readOnly: true,
controller: _demoController1,
decoration: const InputDecoration(
labelText: 'readOnly: true',
hintText: '可聚焦、可复制、可选择,但不可编辑',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 8),
TextField(
enabled: false,
controller: _demoController2,
decoration: const InputDecoration(
labelText: 'enabled: false',
hintText: '灰色,不可聚焦、不可交互',
border: OutlineInputBorder(),
),
),
const SizedBox(height: 8),
const Text('区别总结:', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 11)),
const Text('• readOnly: true → 可聚焦、可选择复制,但不能改内容\n'
'• enabled: false → 灰色、不可聚焦、完全不可交互\n'
'• 只读适合:展示已有数据(如收货地址)\n'
'• 禁用适合:表单未填完时锁定按钮',
style: TextStyle(fontSize: 11)),
],
);
}
}
final _demoController1 = TextEditingController(text: '只读,点我试试');
final _demoController2 = TextEditingController(text: '禁用状态');
// ────────────────────────────────────────────────────────────────
// ⑪ minLines / maxLines ------ 多行输入
// ────────────────────────────────────────────────────────────────
class _Section11_Multiline extends StatelessWidget {
const _Section11_Multiline();
@override
Widget build(BuildContext context) {
return _Card(
title: '⑪ 多行输入',
subtitle: 'minLines 最少行数 / maxLines 最多行数(null=无限)',
children: const [
TextField(
minLines: 3,
maxLines: 6,
expands: false,
decoration: InputDecoration(
hintText: '至少 3 行,最多 6 行。超出后可滚动',
border: OutlineInputBorder(),
labelText: '评论内容',
alignLabelWithHint: true,
),
),
SizedBox(height: 8),
Text('多行配置:', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 11)),
Text('• maxLines: null → 无限行(不推荐,可能很吃内存)\n'
'• maxLines: 1 → 单行(默认,不能回车换行)\n'
'• maxLines: 5 → 最多 5 行,超出自动滚动\n'
'• minLines: 3 → 至少显示 3 行高度\n'
'• expands: true → 撑满父级高度(配合 minLines/maxLines 必须一个为 null)\n'
'• keyboardType: TextInputType.multiline → 多行键盘(可有可无)',
style: TextStyle(fontSize: 11)),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ⑫ 选区控制 ------ selection / cursorWidth / cursorColor
// ────────────────────────────────────────────────────────────────
class _Section12_Selection extends StatelessWidget {
const _Section12_Selection();
@override
Widget build(BuildContext context) {
return _Card(
title: '⑫ 选区 & 光标控制',
subtitle: 'controller.value.selection / cursorColor / cursorRadius',
children: [
const TextField(
decoration: InputDecoration(
hintText: '自定义光标',
border: OutlineInputBorder(),
),
cursorColor: Colors.blue,
cursorWidth: 3,
cursorRadius: Radius.circular(2),
showCursor: true,
enableInteractiveSelection: true,
),
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.all(8),
color: Colors.amber.shade50,
child: const Text(
'// 光标控制:\n'
'cursorColor: Colors.red // 光标颜色\n'
'cursorWidth: 2.0 // 光标宽度\n'
'cursorRadius: Radius.circular(4) // 光标圆角\n'
'showCursor: false // 隐藏光标\n'
'enableInteractiveSelection: false // 禁用选择/复制/粘贴\n\n'
'// 程序化设置选区:\n'
'controller.value = TextEditingValue(\n'
' text: controller.text,\n'
' selection: TextSelection(\n'
' baseOffset: 0,\n'
' extentOffset: controller.text.length,\n'
' ),\n'
');',
style: TextStyle(fontSize: 10.5, fontFamily: 'monospace'),
),
),
],
);
}
}
// ────────────────────────────────────────────────────────────────
// ⑬ 完整总结
// ────────────────────────────────────────────────────────────────
class _Section13_Summary extends StatelessWidget {
const _Section13_Summary();
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.orange.shade50,
borderRadius: BorderRadius.circular(8),
),
child: const Text(
'📌 TextField 完整总结\n\n'
'1. 四大回调:\n'
' onChanged(每次输入)/ onEditingComplete(提交时)\n'
' onSubmitted(回车提交)/ controller.addListener(任意变化)\n\n'
'2. 三种 Border:\n'
' UnderlineInputBorder / OutlineInputBorder / none\n\n'
'3. ⚠️ 必做清理:\n'
' controller.dispose() 必须在 State.dispose 中调用\n'
' focusNode.dispose() 同样需要释放\n\n'
'4. 键盘类型:\n'
' text / number / phone / emailAddress / url / datetime\n\n'
'5. InputFormatter:\n'
' LengthLimitingTextInputFormatter / digitsOnly / allow / deny\n\n'
'6. 只读 vs 禁用:\n'
' readOnly → 可聚焦可复制不可改\n'
' enabled: false → 灰色不可交互\n\n'
'7. 多行:\n'
' maxLines: null 无限行;maxLines: 1 单行\n'
' minLines + maxLines 控制高度范围\n\n'
'8. 与 TextFormField 的区别:\n'
' TextField = 纯输入框\n'
' TextFormField = TextField + 表单验证(Form 中使用)',
style: TextStyle(fontSize: 12),
),
);
}
}
// ────────────────────────────────────────────────────────────────
// 通用卡片组件
// ────────────────────────────────────────────────────────────────
class _Card extends StatelessWidget {
final String title;
final String subtitle;
final List<Widget> children;
const _Card({required this.title, this.subtitle = '', required this.children});
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.grey.shade300),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 6,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold)),
if (subtitle.isNotEmpty) ...[
const SizedBox(height: 2),
Text(subtitle,
style: TextStyle(fontSize: 11, color: Colors.grey.shade600, fontStyle: FontStyle.italic)),
],
const SizedBox(height: 10),
...children,
],
),
);
}
}