#

可选择文本

一、SelectableText简介与核心特性

SelectableText是Flutter中一个重要的文本组件,它允许用户选择、复制和粘贴文本,是Text组件的交互增强版。在移动应用中,文本选择功能在很多场景下都非常重要,比如代码展示、地址信息、产品编号、API密钥等,用户可能需要复制这些文本到其他地方使用。SelectableText提供了完整的文本选择功能,包括长按选择、拖动调整选择范围、复制到剪贴板、全选等操作,极大地方便了用户的使用体验。

SelectableText vs Text的对比分析:

特性 Text SelectableText
文本选择 不支持 支持
复制功能 不支持 支持
工具栏 不显示 可配置
光标显示 不支持 支持
富文本 支持 支持
使用场景 标题、标签、静态文本 代码、地址、密钥等需要复制的文本
性能开销 稍高
交互性 无交互 支持长按、拖动等手势

文本组件选择

需要文本选择?

SelectableText

需要富文本?

Text.rich

Text

可选择文本

可复制

可自定义工具栏

多种样式

不支持选择

纯文本显示

性能最优

SelectableText的核心优势在于它继承了Text的所有样式和布局能力,同时添加了文本选择功能。这意味着你可以在使用SelectableText时,像使用Text一样设置字体大小、颜色、行高、对齐方式等样式属性。SelectableText还支持TextSpan富文本,可以在同一个组件中显示多种样式的文本,并且这些文本都可以被选择和复制。

二、SelectableText基础用法与参数详解

SelectableText提供了多个构造函数和参数,满足不同的使用需求。最基本的用法是使用SelectableText(String data)构造函数,直接传入要显示的文本字符串。这种用法最简单,适合纯文本场景,文本会按照默认样式显示,支持文本选择和复制功能。

SelectableText支持与Text相同的样式属性,包括style、textAlign、maxLines、overflow等。style属性用于设置文本的样式,可以设置字体大小、颜色、字重、字体风格等。textAlign用于设置文本的对齐方式,支持左对齐、右对齐、居中对齐等。maxLines用于限制文本的最大行数,配合overflow参数可以控制文本溢出的显示方式。

showCursor参数用于控制是否显示光标,默认为false。当设置为true时,SelectableText会显示一个闪烁的光标,光标的位置和样式可以通过cursorColor、cursorWidth、cursorHeight、cursorRadius等参数自定义。光标功能主要用于编辑场景,但在SelectableText中更多是视觉提示作用,告诉用户这段文本是可以交互的。

// 简单可选择文本
SelectableText(
  '这段文本可以被选择和复制。',
  style: TextStyle(fontSize: 16),
)

// 带完整参数的可选择文本
SelectableText(
  '这是一段带样式的可选择文本,支持长按选择、拖动调整、复制到剪贴板等功能。',
  style: TextStyle(
    fontSize: 18,
    color: Colors.blue.shade800,
    fontWeight: FontWeight.w500,
    height: 1.5,
    letterSpacing: 0.5,
  ),
  textAlign: TextAlign.left,
  maxLines: null,
  overflow: TextOverflow.visible,
  showCursor: true,
  cursorColor: Colors.blue,
  cursorWidth: 2.0,
  cursorHeight: 24,
  cursorRadius: Radius.circular(2),
)

SelectableText的参数详解:

参数 类型 默认值 说明
data String 必需 要显示的文本
style TextStyle DefaultTextStyle 文本样式
textAlign TextAlign TextAlign.start 文本对齐方式
maxLines int? null 最大行数
overflow TextOverflow TextOverflow.clip 溢出处理方式
showCursor bool false 是否显示光标
cursorColor Color themeData.cursorColor 光标颜色
cursorWidth double 2.0 光标宽度
cursorHeight double? null 光标高度
cursorRadius Radius? null 光标圆角
enableInteractiveSelection bool true 是否启用交互选择
toolbarOptions ToolbarOptions 默认选项 工具栏选项

三、工具栏配置与自定义操作

SelectableText提供了一个可选的上下文工具栏,在用户长按选择文本后会显示。工具栏包含复制、全选、粘贴等操作按钮,用户可以点击这些按钮快速执行相应操作。通过toolbarOptions参数,可以自定义工具栏显示哪些操作按钮,或者通过contextMenuBuilder参数完全自定义工具栏的外观和行为。

ToolbarOptions是一个类,包含四个布尔属性:copy(复制)、selectAll(全选)、paste(粘贴)、cut(剪切)。默认情况下,所有操作都是启用的。如果你只想保留部分操作,可以禁用不需要的操作。比如,在显示产品编号或API密钥时,通常只需要复制功能,可以禁用粘贴和剪切操作。

contextMenuBuilder参数允许完全自定义工具栏。它是一个函数,接收context和editableTextState作为参数,返回一个widget。你可以返回任何自定义widget,比如自定义的PopupMenu、自定义的操作按钮列表,甚至是一个完全不同的UI。如果不想要工具栏,可以返回一个空的Container来隐藏工具栏。

// 默认工具栏
SelectableText(
  '这段文本使用默认工具栏',
  style: TextStyle(fontSize: 16),
)

// 自定义工具栏选项
SelectableText(
  '这段文本只允许复制',
  style: TextStyle(fontSize: 16),
  toolbarOptions: ToolbarOptions(
    copy: true,
    selectAll: true,
    paste: false,  // 禁用粘贴
    cut: false,    // 禁用剪切
  ),
)

// 自定义工具栏外观
SelectableText(
  '这段文本使用自定义工具栏',
  style: TextStyle(fontSize: 16),
  contextMenuBuilder: (context, editableTextState) {
    return Container(
      decoration: BoxDecoration(
        color: Colors.blue,
        borderRadius: BorderRadius.circular(8),
        boxShadow: [
          BoxShadow(
            color: Colors.black26,
            blurRadius: 10,
            offset: Offset(0, 2),
          ),
        ],
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          TextButton.icon(
            onPressed: () {
              editableTextState.copySelection(SelectionChangedCause.toolbar);
              Navigator.of(context).pop();
            },
            icon: Icon(Icons.copy, color: Colors.white),
            label: Text('复制', style: TextStyle(color: Colors.white)),
          ),
        ],
      ),
    );
  },
)

// 隐藏工具栏(完全禁用选择)
SelectableText(
  '这段文本可以显示但不显示工具栏',
  style: TextStyle(fontSize: 16),
  enableInteractiveSelection: false,  // 禁用选择功能
)

工具栏的响应流程:

剪贴板 工具栏 SelectableText 用户 剪贴板 工具栏 SelectableText 用户 长按文本 显示选择手柄 显示工具栏 点击复制 复制文本 返回成功 隐藏工具栏 显示复制成功提示

四、SelectableText.rich富文本支持

SelectableText.rich构造函数支持使用TextSpan创建富文本。TextSpan允许在同一个文本组件中显示多种样式的文本,每个TextSpan可以独立设置颜色、字体大小、字重等样式。最重要的是,使用SelectableText.rich创建的富文本仍然支持完整的文本选择功能,用户可以选择跨越多个TextSpan的文本内容。

TextSpan可以包含一个children列表,每个子元素都是一个TextSpan或WidgetSpan。WidgetSpan允许在文本中插入任意的widget,比如图片、图标等。但是需要注意的是,WidgetSpan不支持选择,用户选择文本时会跳过WidgetSpan。这是Flutter框架的限制,因为widget的边界计算比文本复杂得多。

TextSpan还支持recognizer属性,可以添加手势识别器。最常见的用法是添加TapGestureRecognizer,实现文本的点击事件。这种技术可以创建可点击的链接、标签等交互元素。需要注意的是, recognizer会与文本选择功能冲突,如果设置了recognizer,该段文本将无法被选择。

// 基础富文本
SelectableText.rich(
  TextSpan(
    style: TextStyle(fontSize: 16, color: Colors.black87),
    children: [
      TextSpan(text: '欢迎使用'),
      TextSpan(
        text: ' Flutter ',
        style: TextStyle(
          color: Colors.blue,
          fontWeight: FontWeight.bold,
          fontSize: 20,
        ),
      ),
      TextSpan(text: '框架!\n'),
      TextSpan(
        text: '这段富文本可以被完整选择。',
        style: TextStyle(
          color: Colors.grey.shade700,
          fontStyle: FontStyle.italic,
        ),
      ),
    ],
  ),
)

// 带交互的富文本
SelectableText.rich(
  TextSpan(
    style: TextStyle(fontSize: 16),
    children: [
      TextSpan(text: '点击'),
      TextSpan(
        text: ' 这里',
        style: TextStyle(
          color: Colors.blue,
          decoration: TextDecoration.underline,
        ),
        recognizer: TapGestureRecognizer()
          ..onTap = () {
            print('这里被点击了');
            // 可以执行导航、显示对话框等操作
          },
      ),
      TextSpan(text: '会触发点击事件,'),
      TextSpan(text: '但这段文本'),
      TextSpan(
        text: ' 可以被选择。',
        style: TextStyle(
          color: Colors.green,
          fontWeight: FontWeight.bold,
        ),
      ),
    ],
  ),
)

// 带图标和不同样式的富文本
SelectableText.rich(
  TextSpan(
    style: TextStyle(fontSize: 16, height: 1.8),
    children: [
      WidgetSpan(
        child: Padding(
          padding: EdgeInsets.only(right: 8),
          child: Icon(Icons.check_circle, color: Colors.green, size: 20),
        ),
      ),
      TextSpan(
        text: 'Flutter是一个优秀的跨平台框架\n',
        style: TextStyle(fontWeight: FontWeight.bold),
      ),
      WidgetSpan(
        child: Padding(
          padding: EdgeInsets.only(right: 8),
          child: Icon(Icons.star, color: Colors.amber, size: 20),
        ),
      ),
      TextSpan(
        text: '它使用Dart语言\n',
        style: TextStyle(color: Colors.blue),
      ),
      WidgetSpan(
        child: Padding(
          padding: EdgeInsets.only(right: 8),
          child: Icon(Icons.favorite, color: Colors.red, size: 20),
        ),
      ),
      TextSpan(
        text: '热重载功能大大提高了开发效率',
        style: TextStyle(color: Colors.purple),
      ),
    ],
  ),
)

五、实际应用场景与最佳实践

SelectableText在实际应用中有许多使用场景,每个场景都有其特定的需求和最佳实践。了解这些场景和最佳实践,可以帮助开发者更好地使用SelectableText,提供更好的用户体验。

代码展示场景

在技术文档、教程应用中,代码展示是一个常见场景。代码通常需要使用等宽字体显示,并带有背景色以便于区分。使用SelectableText可以让用户轻松复制代码到编辑器中运行。代码通常比较长,应该设置maxLines为null或足够大的值,避免被截断。

地址信息场景

电商、外卖应用中,收货地址是一个常见场景。地址信息通常包含姓名、电话、详细地址、邮编等多行文本。使用SelectableText可以让用户快速复制地址到其他应用中,比如地图导航或分享给他人。地址信息应该使用合适的行高和字体大小,确保可读性。

产品编号/密钥场景

在电商、金融应用中,产品编号、订单号、API密钥等需要复制的文本是一个常见场景。这些文本通常是较长的字符串,用户很难手动输入。使用SelectableText可以大大提高用户体验,用户可以一键复制这些文本。这些文本通常使用等宽字体显示,并限制最大行数,避免占用过多空间。

日志/调试信息场景

在开发工具、系统监控应用中,日志和调试信息是一个常见场景。这些信息通常很长,包含多行文本,用户可能需要复制其中的某些部分进行分析。使用SelectableText可以让用户灵活选择需要的日志片段。日志通常使用等宽字体和深色背景,模拟终端的显示效果。

// 场景1: 代码展示
Container(
  padding: EdgeInsets.all(16),
  decoration: BoxDecoration(
    color: Colors.grey.shade900,
    borderRadius: BorderRadius.circular(8),
  ),
  child: Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Row(
        children: [
          Icon(Icons.code, color: Colors.green, size: 20),
          SizedBox(width: 8),
          Text(
            'main.dart',
            style: TextStyle(
              color: Colors.green,
              fontFamily: 'monospace',
              fontSize: 14,
            ),
          ),
          Spacer(),
          IconButton(
            icon: Icon(Icons.copy, color: Colors.grey, size: 16),
            onPressed: () {
              // 自定义复制按钮
              Clipboard.setData(ClipboardData(text: codeSnippet));
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text('代码已复制')),
              );
            },
          ),
        ],
      ),
      SizedBox(height: 12),
      SelectableText(
        '''void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomePage(),
    );
  }
}''',
        style: TextStyle(
          fontFamily: 'monospace',
          color: Colors.green.shade400,
          fontSize: 13,
          height: 1.6,
        ),
      ),
    ],
  ),
)

// 场景2: 地址信息卡片
Card(
  elevation: 2,
  child: Padding(
    padding: EdgeInsets.all(16),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          children: [
            Icon(Icons.location_on, color: Colors.red, size: 20),
            SizedBox(width: 8),
            Text(
              '收货地址',
              style: TextStyle(
                fontWeight: FontWeight.bold,
                fontSize: 18,
              ),
            ),
            Spacer(),
            IconButton(
              icon: Icon(Icons.edit, color: Colors.blue),
              onPressed: () {
                // 编辑地址
              },
            ),
          ],
        ),
        Divider(height: 24),
        SelectableText(
          '张三\n'
          '138****8888\n'
          '北京市朝阳区xxx街道xxx号xxx小区\n'
          '邮编:100000',
          style: TextStyle(
            fontSize: 15,
            color: Colors.grey.shade800,
            height: 1.6,
            letterSpacing: 0.3,
          ),
        ),
      ],
    ),
  ),
)

// 场景3: 产品编号
Container(
  padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
  decoration: BoxDecoration(
    color: Colors.blue.shade50,
    border: Border.all(color: Colors.blue.shade200),
    borderRadius: BorderRadius.circular(8),
  ),
  child: Row(
    children: [
      Text(
        '产品编号:',
        style: TextStyle(
          fontWeight: FontWeight.bold,
          color: Colors.grey.shade700,
        ),
      ),
      SizedBox(width: 8),
      Expanded(
        child: SelectableText(
          'SKU-2024-0012345678',
          style: TextStyle(
            color: Colors.blue,
            fontFamily: 'monospace',
            fontSize: 14,
            letterSpacing: 0.5,
          ),
          toolbarOptions: ToolbarOptions(
            copy: true,
            selectAll: true,
          ),
        ),
      ),
      IconButton(
        icon: Icon(Icons.copy, color: Colors.blue, size: 18),
        onPressed: () {
          Clipboard.setData(
            ClipboardData(text: 'SKU-2024-0012345678'),
          );
        },
        padding: EdgeInsets.zero,
        constraints: BoxConstraints(),
      ),
    ],
  ),
)

// 场景4: API密钥显示
Card(
  child: Padding(
    padding: EdgeInsets.all(16),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          'API密钥',
          style: TextStyle(
            fontWeight: FontWeight.bold,
            fontSize: 16,
          ),
        ),
        SizedBox(height: 12),
        Container(
          padding: EdgeInsets.all(12),
          decoration: BoxDecoration(
            color: Colors.grey.shade100,
            border: Border.all(color: Colors.grey.shade300),
            borderRadius: BorderRadius.circular(4),
          ),
          child: SelectableText(
            'sk_live_51ABC1234567890XYZabcdefg1234567890',
            style: TextStyle(
              fontFamily: 'monospace',
              fontSize: 13,
              color: Colors.grey.shade800,
            ),
            showCursor: true,
            cursorColor: Colors.blue,
          ),
        ),
        SizedBox(height: 8),
        Text(
          '请妥善保管您的密钥,不要泄露给他人',
          style: TextStyle(
            fontSize: 12,
            color: Colors.orange,
          ),
        ),
      ],
    ),
  ),
)

六、性能优化与注意事项

虽然SelectableText功能强大,但在使用时也需要注意一些性能和体验问题。合理的优化和注意事项可以让应用更加流畅,用户体验更好。

性能优化建议

  1. 避免过长的文本:SelectableText对于非常长的文本(比如超过1000行)可能会有性能问题。如果需要显示超长文本,考虑使用分页、虚拟滚动或懒加载。

  2. 谨慎使用富文本:TextSpan嵌套过深或数量过多会增加渲染开销。保持TextSpan结构简单,避免不必要的嵌套。

  3. 禁用不必要的交互:如果某些文本不需要选择功能,使用Text代替SelectableText,减少性能开销。

  4. 控制工具栏复杂度:自定义工具栏时,避免过于复杂的widget,保持简洁轻量。

体验优化建议

  1. 视觉区分:给SelectableText添加独特的样式(如背景色、边框、图标),让用户直观地知道这段文本是可以选择的。

  2. 提供复制按钮:除了工具栏,还可以提供一个可见的复制按钮,用户无需长按就能快速复制,提升体验。

  3. 显示复制成功提示:用户复制文本后,显示一个短暂的提示(如Snackbar),告知用户操作成功。

  4. 限制行数:对于可能很长的文本,设置maxLines参数,避免占用过多屏幕空间。如果用户需要查看完整内容,提供一个"展开"按钮。

常见问题与解决方案

问题 原因 解决方案
选择文本时卡顿 文本过长或富文本复杂 减少文本长度或简化TextSpan
工具栏不显示 enableInteractiveSelection设为false 设置为true或移除此参数
复制功能不工作 应用没有剪贴板权限 确保应用有正确权限
光标不显示 showCursor设为false 设置showCursor为true
富文本无法选择 TextSpan设置了recognizer 移除recognizer或使用普通TextSpan

短文本<1000字符

长文本>=1000字符

是否需要SelectableText?

用户需要选择/复制?

文本长度?

使用Text

直接使用SelectableText

考虑分页或虚拟滚动

需要富文本?

使用SelectableText.rich

使用SelectableText

简化TextSpan结构

添加视觉区分

提供复制按钮

七、完整示例与最佳实践总结

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('可选择文本示例')),
      body: ListView(
        padding: EdgeInsets.all(16),
        children: [
          _buildSection('基础用法'),
          _buildBasicExamples(),
          SizedBox(height: 24),
          _buildSection('富文本'),
          _buildRichText(),
          SizedBox(height: 24),
          _buildSection('实际应用'),
          _buildRealWorldExamples(),
        ],
      ),
    );
  }

  Widget _buildSection(String title) {
    return Padding(
      padding: EdgeInsets.only(bottom: 16),
      child: Text(
        title,
        style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
      ),
    );
  }

  Widget _buildBasicExamples() {
    return Column(
      children: [
        _buildExampleCard(
          '简单文本',
          SelectableText(
            '这段文本可以被选择和复制。',
            style: TextStyle(fontSize: 14),
          ),
          Colors.blue,
        ),
        SizedBox(height: 16),
        _buildExampleCard(
          '带光标文本',
          SelectableText(
            '这段文本显示光标',
            showCursor: true,
            cursorColor: Colors.green,
            style: TextStyle(fontSize: 14),
          ),
          Colors.green,
        ),
      ],
    );
  }

  Widget _buildExampleCard(String title, Widget content, Color color) {
    return Container(
      padding: EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: color.withOpacity(0.1),
        border: Border.all(color: color.withOpacity(0.3)),
        borderRadius: BorderRadius.circular(8),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              Icon(Icons.info_outline, color: color, size: 20),
              SizedBox(width: 8),
              Text(
                title,
                style: TextStyle(
                  fontWeight: FontWeight.bold,
                  color: color.shade700,
                ),
              ),
            ],
          ),
          SizedBox(height: 12),
          content,
        ],
      ),
    );
  }

  Widget _buildRichText() {
    return Container(
      padding: EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.orange.shade50,
        border: Border.all(color: Colors.orange.shade200),
        borderRadius: BorderRadius.circular(8),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text('富文本', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
          SizedBox(height: 12),
          SelectableText.rich(
            TextSpan(
              style: TextStyle(fontSize: 14, color: Colors.black87),
              children: [
                TextSpan(text: '欢迎使用'),
                TextSpan(
                  text: ' Flutter ',
                  style: TextStyle(
                    color: Colors.blue,
                    fontWeight: FontWeight.bold,
                    fontSize: 18,
                  ),
                ),
                TextSpan(text: '框架!\n'),
                TextSpan(
                  text: '这段富文本支持多种样式,并且可以被完整选择。',
                  style: TextStyle(color: Colors.grey.shade700),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildRealWorldExamples() {
    return Column(
      children: [
        // 代码展示
        _buildCodeBlock(),
        SizedBox(height: 16),
        // 地址信息
        _buildAddressCard(),
        SizedBox(height: 16),
        // 产品编号
        _buildProductCode(),
      ],
    );
  }

  Widget _buildCodeBlock() {
    final codeSnippet = '''void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomePage(),
    );
  }
}''';

    return Card(
      elevation: 2,
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Icon(Icons.code, color: Colors.grey.shade700),
                SizedBox(width: 8),
                Text('代码示例', style: TextStyle(fontWeight: FontWeight.bold)),
                Spacer(),
                IconButton(
                  icon: Icon(Icons.copy, color: Colors.grey),
                  onPressed: () {
                    Clipboard.setData(ClipboardData(text: codeSnippet));
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(content: Text('代码已复制')),
                    );
                  },
                ),
              ],
            ),
            SizedBox(height: 12),
            Container(
              width: double.infinity,
              padding: EdgeInsets.all(12),
              decoration: BoxDecoration(
                color: Colors.grey.shade900,
                borderRadius: BorderRadius.circular(4),
              ),
              child: SelectableText(
                codeSnippet,
                style: TextStyle(
                  fontFamily: 'monospace',
                  color: Colors.green.shade400,
                  fontSize: 12,
                  height: 1.5,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildAddressCard() {
    return Card(
      elevation: 2,
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Icon(Icons.location_on, color: Colors.red),
                SizedBox(width: 8),
                Text('收货地址', style: TextStyle(fontWeight: FontWeight.bold)),
              ],
            ),
            SizedBox(height: 12),
            SelectableText(
              '张三\n'
              '138****8888\n'
              '北京市朝阳区xxx街道xxx号\n'
              '邮编:100000',
              style: TextStyle(
                fontSize: 14,
                color: Colors.grey.shade700,
                height: 1.6,
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildProductCode() {
    final productCode = 'SKU-2024-0012345678';
    return Card(
      elevation: 2,
      child: ListTile(
        leading: Icon(Icons.qr_code, color: Colors.blue),
        title: Text('产品编号'),
        subtitle: Row(
          children: [
            Expanded(
              child: SelectableText(
                productCode,
                style: TextStyle(
                  color: Colors.blue,
                  fontFamily: 'monospace',
                  fontSize: 13,
                ),
              ),
            ),
            IconButton(
              icon: Icon(Icons.copy, color: Colors.blue, size: 20),
              onPressed: () {
                Clipboard.setData(ClipboardData(text: productCode));
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(content: Text('产品编号已复制')),
                );
              },
            ),
          ],
        ),
      ),
    );
  }
}

最佳实践总结

实践 说明 效果
合理使用 只在需要时使用SelectableText 避免误操作,节省性能
视觉区分 给SelectableText添加独特样式 提示用户可以选择
提供快捷操作 添加复制按钮,避免长按 提升用户体验
限制文本长度 对长文本使用maxLines或分页 避免性能问题和空间浪费
简化富文本 保持TextSpan结构简单 提高性能和稳定性
反馈提示 显示复制成功提示 增强用户反馈
自定义工具栏 根据场景自定义工具栏 提供更贴合场景的操作

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐