Flutter文本折叠与展开插件readmore的使用

发布于 1周前 作者 sinazl 来自 Flutter

Flutter文本折叠与展开插件readmore的使用

readmore 是一个Flutter插件,它允许对文本进行展开和折叠操作,并且可以通过 Annotation 特性为文本中的特定模式(如话题标签、URL和提及)添加样式和交互功能。

使用方法

添加依赖

首先,在项目的 pubspec.yaml 文件中添加 readmore 依赖:

dependencies:
  readmore: ^3.0.0

然后在 Dart 代码中导入该库:

import 'package:readmore/readmore.dart';

基本示例

下面是一个简单的例子,演示了如何使用 ReadMoreText 小部件来创建一段可以折叠或展开的文本。默认情况下,只显示两行文本,用户点击 “Show more” 后可查看全部内容,再次点击则会收起。

ReadMoreText(
  'Flutter is Google’s mobile UI open source framework to build high-quality native (super fast) interfaces for iOS and Android apps with the unified codebase.',
  trimMode: TrimMode.Line, // 按行数截断
  trimLines: 2,            // 显示2行后折叠
  colorClickableText: Colors.pink, // 折叠/展开按钮的颜色
  trimCollapsedText: 'Show more', // 折叠时显示的文字
  trimExpandedText: 'Show less',  // 展开时显示的文字
  moreStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.bold), // 自定义按钮样式
);

使用Annotations特性

为了增强文本内容的互动性和功能性,你可以通过 annotations 参数定义自定义样式和交互规则给定某些模式,例如话题标签、URL 和提及等。

ReadMoreText(
  'This is a sample text with a #hashtag, a mention <@123>, and a URL: https://example.com.',
  trimMode: TrimMode.Line,
  trimLines: 2,
  colorClickableText: Colors.pink,
  annotations: [
    Annotation(
      regExp: RegExp(r'#([a-zA-Z0-9_]+)'), // 匹配话题标签
      spanBuilder: ({required String text, TextStyle? textStyle}) => TextSpan(
        text: text,
        style: textStyle?.copyWith(color: Colors.blue),
      ),
    ),
    Annotation(
      regExp: RegExp(r'<@(\d+)>'), // 匹配提及
      spanBuilder: ({required String text, TextStyle? textStyle}) => TextSpan(
        text: 'User123',
        style: textStyle?.copyWith(color: Colors.green),
        recognizer: TapGestureRecognizer()..onTap = () {
          // 处理点击事件,比如跳转到用户资料页
        },
      ),
    ),
    // 可以继续添加更多类型的注解...
  ],
  moreStyle: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
);

完整示例Demo

这里提供了一个完整的应用示例,展示了如何结合设置选项和多种类型的文本注释来实现更复杂的交互效果。

import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:readmore/readmore.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primaryColor: const Color(0xFF02BB9F),
        primaryColorDark: const Color(0xFF167F67),
      ),
      title: 'Read More Text',
      home: const DemoApp(),
    );
  }
}

class DemoApp extends StatefulWidget {
  const DemoApp({super.key});

  @override
  State<DemoApp> createState() => _DemoAppState();
}

class _DemoAppState extends State<DemoApp> {
  final isCollapsed = ValueNotifier<bool>(false);

  // 用户ID映射表
  final userMap = {
    123: 'Android',
    456: 'iOS',
  };

  var _trimMode = TrimMode.Line;
  int _trimLines = 3;
  int _trimLength = 240;

  void _incrementTrimLines() => setState(() => _trimLines++);
  void _decrementTrimLines() => setState(() => _trimLines = _trimLines > 1 ? _trimLines - 1 : 1);
  void _incrementTrimLength() => setState(() => _trimLength++);
  void _decrementTrimLength() => setState(() => _trimLength = _trimLength > 1 ? _trimLength - 1 : 1);

  void _showMessage(String message) {
    ScaffoldMessenger.of(context)
      ..hideCurrentSnackBar()
      ..showSnackBar(SnackBar(content: Text(message)));
  }

  @override
  void dispose() {
    super.dispose();
    isCollapsed.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Read More Text')),
      body: DefaultTextStyle.merge(
        style: const TextStyle(fontSize: 14),
        child: DraggableDivider(
          child: SingleChildScrollView(
            child: _buildContent(),
          ),
        ),
      ),
    );
  }

  Column _buildContent() {
    return Column(
      children: [
        _buildSettings(),
        Padding(
          key: const Key('showMore'),
          padding: const EdgeInsets.all(16),
          child: ReadMoreText(
            'Flutter是Google的移动UI开源框架,用于构建高质量的原生(超级快)界面,适用于iOS和Android应用程序,具有统一的代码库。',
            trimMode: _trimMode,
            trimLines: _trimLines,
            trimLength: _trimLength,
            preDataText: 'AMANDA',
            preDataTextStyle: const TextStyle(fontWeight: FontWeight.w500),
            style: const TextStyle(color: Colors.black),
            colorClickableText: Colors.pink,
            trimCollapsedText: '...Show more',
            trimExpandedText: ' show less',
          ),
        ),
        const Divider(color: Color(0xFF167F67)),
        Padding(
          padding: const EdgeInsets.all(16),
          child: ReadMoreText(
            'Flutter(https://flutter.dev/)拥有自己的UI组件以及渲染引擎,可以在&<@123>&和&<@456>&平台上运行 &<@999>& http://google.com #read_more。大多数这些UI组件遵循#Material Design指南。',
            trimMode: _trimMode,
            trimLines: _trimLines,
            trimLength: _trimLength,
            style: const TextStyle(color: Colors.black),
            colorClickableText: Colors.pink,
            trimCollapsedText: '...Expand',
            trimExpandedText: ' Collapse ',
            annotations: [
              // URL处理
              Annotation(
                regExp: RegExp(r'(?:(?:https?|ftp)://)?[\w/\-?=%.]+\.[\w/\-?=%.]+'),
                spanBuilder: ({
                  required String text,
                  TextStyle? textStyle,
                }) {
                  return TextSpan(
                    text: text,
                    style: (textStyle ?? const TextStyle()).copyWith(
                      decoration: TextDecoration.underline,
                      color: Colors.green,
                    ),
                    recognizer: TapGestureRecognizer()..onTap = () => _showMessage(text),
                  );
                },
              ),
              // 提及处理
              Annotation(
                regExp: RegExp(r'<@(\d+)>'),
                spanBuilder: ({
                  required String text,
                  TextStyle? textStyle,
                }) {
                  final user = userMap[int.tryParse(text.substring(2, text.length - 1))];
                  if (user == null) {
                    return TextSpan(
                      text: '@unknown user',
                      style: (textStyle ?? const TextStyle()).copyWith(fontWeight: FontWeight.bold),
                      recognizer: TapGestureRecognizer()..onTap = () => _showMessage('User not found'),
                    );
                  }
                  return TextSpan(
                    text: '@$user',
                    style: (textStyle ?? const TextStyle()).copyWith(
                      decoration: TextDecoration.underline,
                      color: Colors.redAccent,
                    ),
                    recognizer: TapGestureRecognizer()..onTap = () => _showMessage('@$user'),
                    children: [if (user == 'iOS') const TextSpan(text: 'Extra')],
                  );
                },
              ),
              // 话题标签处理
              Annotation(
                regExp: RegExp('#(?:[a-zA-Z0-9_]+)'),
                spanBuilder: ({
                  required String text,
                  TextStyle? textStyle,
                }) {
                  return TextSpan(
                    text: text,
                    style: (textStyle ?? const TextStyle()).copyWith(
                      color: Colors.blueAccent,
                      height: 1.5,
                      letterSpacing: 5,
                    ),
                    recognizer: TapGestureRecognizer()..onTap = () => _showMessage(text),
                  );
                },
              ),
            ],
          ),
        ),
        const Divider(color: Color(0xFF167F67)),
        Padding(
          padding: const EdgeInsets.all(16),
          child: ReadMoreText(
            'Flutter框架通过组合小部件来构建布局,所有你程序化构造的东西都是一个小部件,它们被编译在一起以创建用户界面。',
            trimMode: _trimMode,
            trimLines: _trimLines,
            trimLength: _trimLength,
            isCollapsed: isCollapsed,
            style: const TextStyle(color: Colors.black),
            colorClickableText: Colors.pink,
            trimCollapsedText: '...Read more',
            trimExpandedText: ' Less',
          ),
        ),
        ValueListenableBuilder(
          valueListenable: isCollapsed,
          builder: (context, value, child) {
            return Center(
              child: ElevatedButton(
                onPressed: () => isCollapsed.value = !isCollapsed.value,
                child: Text('is collapsed: $value'),
              ),
            );
          },
        ),
      ],
    );
  }

  Column _buildSettings() {
    return Column(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        const Text('Trim Mode'),
        Padding(
          padding: const EdgeInsets.all(8),
          child: SegmentedButton<TrimMode>(
            segments: const [
              ButtonSegment<TrimMode>(
                value: TrimMode.Length,
                label: Text('Length'),
              ),
              ButtonSegment<TrimMode>(
                value: TrimMode.Line,
                label: Text('Line'),
              ),
            ],
            selected: {_trimMode},
            onSelectionChanged: (Set<TrimMode> newSelection) {
              setState(() {
                _trimMode = newSelection.first;
              });
            },
          ),
        ),
        if (_trimMode == TrimMode.Length)
          Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              IconButton(icon: const Icon(Icons.remove), onPressed: _decrementTrimLength),
              Text('$_trimLength'),
              IconButton(icon: const Icon(Icons.add), onPressed: _incrementTrimLength),
            ],
          ),
        if (_trimMode == TrimMode.Line)
          Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              IconButton(icon: const Icon(Icons.remove), onPressed: _decrementTrimLines),
              Text('$_trimLines'),
              IconButton(icon: const Icon(Icons.add), onPressed: _incrementTrimLines),
            ],
          ),
      ],
    );
  }
}

// 下面是一些辅助类,用于实现拖动分隔线和垂直文本展示的功能。
class DraggableDivider extends StatefulWidget {
  const DraggableDivider({super.key, required this.child});
  final Widget child;
  @override
  State<DraggableDivider> createState() => _DraggableDividerState();
}

class _DraggableDividerState extends State<DraggableDivider> {
  final double dividerWidth = 10;
  late double _leftWidth;
  final double _minWidth = 20;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    final screenWidth = MediaQuery.sizeOf(context).width;
    setState(() {
      _leftWidth = screenWidth - _minWidth - dividerWidth;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        SizedBox(width: _leftWidth, child: widget.child),
        GestureDetector(
          onHorizontalDragUpdate: (details) {
            setState(() {
              final newWidth = _leftWidth + details.delta.dx;
              final screenWidth = MediaQuery.of(context).size.width;
              if (newWidth >= _minWidth && (screenWidth - newWidth - dividerWidth) >= _minWidth) {
                _leftWidth = newWidth;
              }
            });
          },
          child: Container(color: Colors.grey, width: 10),
        ),
        const Expanded(child: Center(child: VerticalText('Drag Test'))),
      ],
    );
  }
}

class VerticalText extends StatelessWidget {
  const VerticalText(this.text, {super.key});
  final String text;
  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: text.split('').map((letter) => Text(letter, textAlign: TextAlign.center)).toList(),
    );
  }
}

这个完整的例子不仅实现了基本的文本折叠与展开功能,还包含了对不同文本格式的支持,如超链接、提及和话题标签,并提供了直观的方式让用户调整显示的行数或字符长度。此外,它还包括了一个拖拽分隔线的小部件,允许用户调整屏幕两侧内容的比例。


更多关于Flutter文本折叠与展开插件readmore的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter文本折叠与展开插件readmore的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用readmore插件来实现文本折叠与展开功能的示例代码。

首先,确保你已经在pubspec.yaml文件中添加了readmore依赖:

dependencies:
  flutter:
    sdk: flutter
  readmore: ^2.1.0  # 请根据需要检查最新版本号

然后运行flutter pub get来安装依赖。

接下来是一个完整的示例代码,展示如何使用readmore插件:

import 'package:flutter/material.dart';
import 'package:readmore/readmore.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Readmore Example',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('Readmore Example'),
        ),
        body: Center(
          child: ReadMoreText(
            // 示例长文本
            text: '''
              Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.

              Curabitur sodales ligula in libero. Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, aliquet nec, mattis pulvinar, nulla. Donec porttitor dui non leo. Suspendisse potenti.

              In eleifend quam a odio. In hac habitasse platea dictumst. Maecenas ut massa quis augue luctus tincidunt. Nulla mollis molestie lorem. Quisque ut erat. Curabitur gravida nisi at nibh. In hac habitasse platea dictumst. Aliquam augue quam, sollicitudin vitae, consectetuer eget, rutrum at, lorem.
            ''',
            moreText: 'Read more',
            lessText: 'Read less',
            trimLines: 5,  // 初始显示的行数
            style: TextStyle(fontSize: 16),
            onMore: () {
              // 点击“Read more”时的回调
              print('Read more clicked!');
            },
            onLess: () {
              // 点击“Read less”时的回调
              print('Read less clicked!');
            },
          ),
        ),
      ),
    );
  }
}

在这个示例中,我们使用了ReadMoreText小部件来显示一个长文本。trimLines属性指定了初始显示的行数,moreTextlessText属性分别用于自定义“Read more”和“Read less”按钮的文本。onMoreonLess是可选的回调函数,当点击相应按钮时会触发。

运行这个示例应用,你会看到一个初始只显示前几行的文本,并带有“Read more”按钮。点击按钮后,文本会展开显示全部内容,同时按钮文本变为“Read less”。再次点击按钮,文本会折叠回初始显示的状态。

回到顶部