239 lines
8.5 KiB
Dart
239 lines
8.5 KiB
Dart
import 'package:flutter/gestures.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||
|
||
/// ============================================================
|
||
/// Text 文本组件各种细节演示页
|
||
///
|
||
/// 一、两种使用方式:
|
||
/// 1. 直接运行本页面:runApp(const MaterialApp(home: TextDemoPage()))
|
||
/// 2. 复制某段核心代码到任意 Scaffold 的 body 中。
|
||
///
|
||
/// 二、Text 全家桶速记:
|
||
/// 单样式文字 → Text
|
||
/// 富文本拼接 → Text.rich + TextSpan
|
||
/// 全局默认样式 → DefaultTextStyle(MaterialApp 自带,Text 会继承)
|
||
///
|
||
/// 三、Flutter 3.x 注意:
|
||
/// textScaleFactor 已废弃 → 用 textScaler / MediaQuery.textScalerOf(context)
|
||
/// 颜色的 withOpacity 已废弃 → 用 withValues(alpha: x)
|
||
/// ============================================================
|
||
class TextDemoPage extends StatelessWidget {
|
||
const TextDemoPage({super.key});
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
appBar: AppBar(title: const Text('Text 文本组件 Demo'), backgroundColor: Colors.blue),
|
||
body: SingleChildScrollView(
|
||
padding: EdgeInsets.all(16.w),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
const _SectionTitle('1. 基础样式:fontSize / color / fontWeight / fontStyle'),
|
||
Text(
|
||
'默认样式',
|
||
style: TextStyle(fontSize: 16.sp),
|
||
),
|
||
SizedBox(height: 8.h),
|
||
Text(
|
||
'加粗 红色 斜体',
|
||
style: TextStyle(
|
||
fontSize: 16.sp,
|
||
fontWeight: FontWeight.bold,
|
||
color: Color(0xFFE53935),
|
||
fontStyle: FontStyle.italic,
|
||
),
|
||
),
|
||
SizedBox(height: 24.h),
|
||
|
||
const _SectionTitle('2. 对齐 textAlign(需有宽度约束)'),
|
||
Container(
|
||
width: 300.w,
|
||
color: const Color(0xFFF5F7FA),
|
||
child: const Column(
|
||
// 关键:stretch 让每个子组件横向拉满,textAlign 才有空间生效
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
Text('左对齐', textAlign: TextAlign.left),
|
||
Text('居中对齐', textAlign: TextAlign.center),
|
||
Text('右对齐', textAlign: TextAlign.right),
|
||
],
|
||
),
|
||
),
|
||
SizedBox(height: 24.h),
|
||
|
||
const _SectionTitle('3. 截断:maxLines + overflow(三件套必须配齐)'),
|
||
// 要点:Text 必须有【有界宽度】溢出才生效,所以外面套了固定宽的 Container
|
||
Container(
|
||
width: 300.w,
|
||
color: const Color(0xFFF5F7FA),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'ellipsis:这是一段非常非常非常长的文字,用来演示溢出省略号的效果,看看能不能截断显示',
|
||
maxLines: 1,
|
||
overflow: TextOverflow.ellipsis, // 末尾加省略号
|
||
),
|
||
SizedBox(height: 8.h),
|
||
Text(
|
||
'clip:这是一段非常非常非常长的文字,用来演示溢出直接裁剪的效果,看看能不能截断显示',
|
||
maxLines: 1,
|
||
overflow: TextOverflow.clip, // 直接裁掉,不加省略号
|
||
),
|
||
SizedBox(height: 8.h),
|
||
Text(
|
||
// visible:文字真·溢出,直接画出灰色盒子边界(Container 默认不裁剪)
|
||
'softWrap:false:文字横向溢出灰色盒子边界,直接画到盒子外面去',
|
||
softWrap: false, // 禁止自动换行
|
||
overflow: TextOverflow.visible,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
SizedBox(height: 24.h),
|
||
|
||
const _SectionTitle('4. 行高 height(对比默认行高)'),
|
||
Container(
|
||
width: 300.w,
|
||
color: const Color(0xFFF5F7FA),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
'默认行高\n第二行内容',
|
||
style: TextStyle(fontSize: 16.sp),
|
||
),
|
||
SizedBox(height: 8.h),
|
||
Text(
|
||
'height:1.2 紧凑\n第二行内容',
|
||
style: TextStyle(fontSize: 16.sp, height: 1.2),
|
||
),
|
||
SizedBox(height: 8.h),
|
||
Text(
|
||
'height:2 松散\n第二行内容',
|
||
style: TextStyle(fontSize: 16.sp, height: 2),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
SizedBox(height: 24.h),
|
||
|
||
const _SectionTitle('5. 字间距 letterSpacing / 词间距 wordSpacing'),
|
||
Text(
|
||
'字间距 4:Flutter 框架真棒',
|
||
style: TextStyle(fontSize: 16.sp, letterSpacing: 4),
|
||
),
|
||
SizedBox(height: 8.h),
|
||
Text(
|
||
'词间距 8:Flutter Framework Great',
|
||
style: TextStyle(fontSize: 16.sp, wordSpacing: 8),
|
||
),
|
||
SizedBox(height: 24.h),
|
||
|
||
const _SectionTitle('6. 富文本:Text.rich + TextSpan'),
|
||
// 正确写法:recognizer 放 StatefulWidget 里创建并 dispose
|
||
const _AgreementRichText(),
|
||
SizedBox(height: 24.h),
|
||
|
||
const _SectionTitle('7. 字体缩放 textScaler(Flutter 3 新写法)'),
|
||
// 旧写法 textScaleFactor 已废弃;textScaler 按倍数放大
|
||
Text(
|
||
'线性放大 1.5 倍',
|
||
style: TextStyle(fontSize: 14.sp),
|
||
textScaler: TextScaler.linear(1.5),
|
||
),
|
||
SizedBox(height: 8.h),
|
||
// 跟随系统设置:MaterialApp 已按系统字体缩放,也可显式读取
|
||
Builder(
|
||
builder: (context) {
|
||
final ts = MediaQuery.textScalerOf(context);
|
||
return Text(
|
||
'当前系统缩放:${ts.scale(14).toStringAsFixed(1)}px(原本14px)',
|
||
style: TextStyle(fontSize: 14.sp),
|
||
);
|
||
},
|
||
),
|
||
],
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 区块标题
|
||
class _SectionTitle extends StatelessWidget {
|
||
const _SectionTitle(this.title);
|
||
|
||
final String title;
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Padding(
|
||
padding: EdgeInsets.only(bottom: 12.h),
|
||
child: Text(
|
||
title,
|
||
style: TextStyle(
|
||
fontSize: 16.sp,
|
||
fontWeight: FontWeight.bold,
|
||
color: Color(0xFF333333),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|
||
|
||
/// 富文本协议段落 —— StatefulWidget 版本
|
||
/// 关键点:TapGestureRecognizer 必须在 dispose() 里释放,防止内存泄漏
|
||
class _AgreementRichText extends StatefulWidget {
|
||
const _AgreementRichText();
|
||
|
||
@override
|
||
State<_AgreementRichText> createState() => _AgreementRichTextState();
|
||
}
|
||
|
||
class _AgreementRichTextState extends State<_AgreementRichText> {
|
||
// late final:整个 State 生命周期只创建一次,复用同一个 recognizer
|
||
late final TapGestureRecognizer _agreementRecognizer = TapGestureRecognizer()
|
||
..onTap = _handleAgreementTap;
|
||
|
||
void _handleAgreementTap() {
|
||
// 点击回调可能晚于 dispose(比如点击瞬间页面被切走),用 mounted 兜底
|
||
if (!mounted) return;
|
||
ScaffoldMessenger.of(context).showSnackBar(
|
||
const SnackBar(content: Text('点了《用户协议》')),
|
||
);
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
// 关键:释放 recognizer,与 createState 一一对应
|
||
_agreementRecognizer.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Text.rich(
|
||
TextSpan(
|
||
children: [
|
||
TextSpan(
|
||
text: '已阅读并同意',
|
||
style: TextStyle(fontSize: 14.sp, color: Color(0xFF888888)),
|
||
),
|
||
TextSpan(
|
||
text: '《用户协议》',
|
||
style: TextStyle(
|
||
fontSize: 14.sp,
|
||
color: Color(0xFF3B82F6),
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
recognizer: _agreementRecognizer,
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
}
|