Flutter标签管理插件flutter_taggy的使用

Flutter标签管理插件flutter_taggy的使用

目录

功能

计划功能 ⏳

  • 批量处理:同时写入多个文件。
  • 编辑文件名:根据轨道标题重命名文件。
  • TaggyFileResult:所有公共API应返回通用结果; 一个TaggyFile类型的值或一个TaggyError类型的错误。
  • 转换标签

开始使用

  • 安装包。
  • 阅读用法部分以探索Taggy的功能。
  • 更多细节:查看示例应用

安装

运行以下命令:

flutter pub add flutter_taggy

用法

初始化

import 'package:flutter_taggy/flutter_taggy.dart';

void main(){
  // 添加此行
  Taggy.initialize();
  
  runApp(MyApp());
}

关于TaggyFile

  • 它给我们提供了关于我们要读取或写入的文件的一些额外信息,因此除了Tag列表外,我们还得到了:

    • 文件大小(字节)。
    • FileType:是否为(flac, wav, mpeg等)。
    • AudioInfo,另一种类型,包含音频轨道的属性。
  • 您可以通过调用formatAsAString()来格式化一个TaggyFile实例:

    输出示例
    TaggyFile: {
        size: 12494053 bytes ~ 12.2 MB,
        fileType: FileType.Mpeg
        primaryTagType: TagType.Id3v2,
        tags: {
        count: 1,
        items: 
          [ Tag(
                tagType: Id3v2,
                trackTitle: Fine Line,
                trackArtist: Eminem,
                trackNumber: 9,
                trackTotal: 1,
                discTotal: null,
                discNumber: null,
                album: SHADYXV,
                albumArtist: Various Artists,
                genre: null,
                language: null,
                year: null,
                recordingDate: null,
                originalReleaseDate: null,
                has lyrics: true,
                pictures: {
                  count: 1,
                  items: [ Picture(
                    picType: PictureType.CoverFront,
                    picData(Bytes): 168312,
                    mimeType: MimeType.Jpeg,
                    width: 1000,
                    height: 1000,
                    colorDepth: 24,
                    numColors: 0,
                    )],
                },
              ),
          ],
        },
        audio: AudioInfo(
        channelMask: 3,
        channels: 2,
        sampleRate: 44100,
        audioBitrate: 321,
        overallBitrate: 326,
        bitDepth: null,
        durationSec: 306,
        ),
    }
    

读取标签

  • 读取所有标签:

    ```dart const path = 'path/to/audio/file.mp3';

    final TaggyFile taggyFile = await Taggy.readAll(path); // 您可以使用getter,底层是[taggyFile.tags.firstOrNull] print(taggyFile.firstTagIfAny);

    // 或轻松访问所有返回的标签 for (var tag in taggyFile.tags) { print(tag.tagType); }

    </li>
    <li>
    <p><strong>读取主标签:</strong></p>
    ```dart
    final path = 'path/to/audio/file.mp3';
    final TaggyFile taggyFile = await Taggy.readPrimary(path);
    
  • 读取任意标签:

    它类似于readPrimary,但返回的TaggyFile.tags可能为空。

    ```dart const path = 'path/to/audio/file.mp3'; final TaggyFile taggyFile = await Taggy.readAny(path);

    // 您也可以使用[formatAsString],我们仍然得到一个[TaggyFile]。 print(taggyFile.formatAsString());

    // 您可能想检查是否有任何标签 final hasTags = taggyFile.tags.isNotEmpty(); // 或使用getter final Tag? tag = taggyFile.firstTagIfAny;

    </li>
    </ul>
    
    ### 写入标签
    <ul>
    <li>
    <p><strong>关于指定<code>TagType</code></strong></p>
    <p>创建新<code>Tag</code>实例时需要一个标签类型。您可以通过以下方式指定:</p>
    <ul>
    <li>
    <p>检查基于其类型(扩展名)支持的<code>TagType</code>。参见此<a href="https://github.com/Serial-ATA/lofty-rs/blob/main/SUPPORTED_FORMATS.md">表</a>。</p>
    </li>
    <li>
    <p>使用函数<code>Taggy.writePrimary()</code>
    并传递一个<code>Tag</code>,其类型为<code>TagType.FilePrimaryType</code>,如下面的示例所示。</p>
    </li>
    </ul>
    </li>
    <li>
    <details>
    
      <summary>创建新标签的示例</summary>
    
    ```dart
    Tag getTagInstance(TagType tagType){
      return Tag(
        tagType: tagType,
        album: 'Some Album',
        trackTitle: 'some Track',
        trackArtist: 'Some Artist',
        trackTotal: 10,
        trackNumber: 1,
        discNumber: 1,
        discTotal: 2,
        year: 2023,
        recordingDate: '1/3/2019',
        language: 'EN',
        pictures: [
          Picture(
            // 零用于演示如何提供图片的数据。
            picData: Uint8List.fromList([0, 0, 0, 0]),
            mimeType: MimeType.Jpeg,
            picType: PictureType.CoverFront,
            width: 1000,
            height: 800,
          ),
        ],
      );
    }
    
  • 写入主标签:

    ```dart final path = 'path/to/audio/file.mp3';

    final tagToWrite = getTagInstance(TagType.FilePrimaryType);

    final TaggyFile taggyFile = await Taggy.writePrimary( path: path, tag: tagToWrite, keepOthers: false);

    // 成功后,[taggyFile.tags]将包含新添加的标签。 // 注意:此标签可能不包含与[tagToWrite]相同的属性。 final pTag = taggyFile.primaryTag;

    </li>
    <li>
    <p><strong>写入多个标签</strong>:</p>
    <p>在大多数情况下,您会使用<code>Taggy.writePrimary()</code>来添加或编辑音频标签元数据,
    但您也可以提供多个标签写入同一文件。</p>
    ```dart
    final path = 'path/to/audio/file.mp3';
    
    final tags = [
      getTagInstance(TagType.FilePrimaryType),
      getTagInstance(TagType.Id3v1),
    ];
          
    final TaggyFile taggyFile = await Taggy.writeAll(
      path: path, tags: tags, overrideExistent: true);
    

删除标签

  • 删除特定标签

    通过指定标签类型,可以从文件中删除标签。

    ```dart final path = 'path/to/audio/file.mp3';

    // 要删除的标签类型 final tagType = TagType.Ape;

    final TaggyFile taggyFile = await Taggy.removeTag(path: path, tagType: tagType);

    </li>
    <li>
    <h4>删除所有标签</h4>
    ```dart
    final path = 'path/to/audio/file.mp3';
    final TaggyFile taggyFile = await Taggy.removeAll(path: path);
      
    print(taggyFile.tags);
    // 输出为空[]
    

反馈与贡献

  • 🐛 发现问题或遇到错误?请检查现有问题或创建新问题。

  • 💪🏻 想要贡献?感谢您的参与!您可以从阅读贡献指南开始。

  • 🙏🏻 您也可以通过点赞此仓库并在Pub.dev点赞此包来贡献,我们非常感激。

致谢


示例代码

import 'dart:math';

import 'package:example/utils.dart';
import 'package:file_picker/file_picker.dart' as fp;
import 'package:flutter/material.dart';
import 'package:flutter_taggy/flutter_taggy.dart';

/// Taggy的主题颜色
const kTaggyColor = Color(0xFF44A5DD);

extension BuildContextExtension on BuildContext {
  ColorScheme get colorScheme => Theme.of(this).colorScheme;

  double get screenWidth => MediaQuery.of(this).size.width;
}

void main() {
  // 在使用[flutter_taggy]之前记得调用[initialize]
  Taggy.initialize();
  runApp(const MyApp());
}

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

  // 这个小部件是你的应用程序的根。
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "flutter_taggy Demo",
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: kTaggyColor),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  [@override](/user/override)
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool isPickingAFile = false;
  String? pickedFilePath;

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: context.colorScheme.primaryContainer,
        title: const Text("Taggy Demo"),
      ),
      body: Center(
        child: ListView(
          children: [
            FileSelectionSection(
                onSelectFile: onSelectFile, pickedFilePath: pickedFilePath),
            if (pickedFilePath != null) ...[
              const ListTile(
                title: Text(
                  '第二步:',
                  style: TextStyle(fontWeight: FontWeight.w500),
                ),
                subtitle: Text(
                  '从功能区选择您想要探索的操作:',
                ),
              ),
              ReadTagsSection(filePath: pickedFilePath!),
              const SizedBox(height: 10),
              WritingTagsSection(filePath: pickedFilePath!),
              const SizedBox(height: 10),
              RemoveTagsSection(filePath: pickedFilePath!),
            ],
          ],
        ),
      ),
    );
  }

  Future<void> onSelectFile() async {
    if (isPickingAFile) {
      return;
    } else {
      isPickingAFile = true;
      final pickedFile = await fp.FilePicker.platform.pickFiles(
        allowCompression: false,
        type: fp.FileType.custom,
        lockParentWindow: true,
        allowedExtensions: [
          'FLAC',
          'Flac',
          'aac',
          'mp3',
          'mp4',
          'wav',
        ],
      );
      isPickingAFile = false;
      // 我们只需要已选文件的路径
      setState(() {
        pickedFilePath = pickedFile?.paths.first;
      });
    }
  }
}

class FileSelectionSection extends StatelessWidget {
  const FileSelectionSection({
    super.key,
    required this.onSelectFile,
    required this.pickedFilePath,
  });

  final void Function() onSelectFile;
  final String? pickedFilePath;

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.start,
      children: <Widget>[
        const ListTile(
          title: Text('第一步:', style: TextStyle(fontWeight: FontWeight.w500)),
          subtitle: Text(
            '您需要指定要执行操作的文件路径。',
          ),
        ),
        Expanded(
          flex: 0,
          child: Wrap(
            alignment: WrapAlignment.start,
            runAlignment: WrapAlignment.center,
            children: [
              const Text(
                '已选文件路径: ',
                style:
                    TextStyle(fontWeight: FontWeight.w500, color: kTaggyColor),
              ),
              Text('"${pickedFilePath ?? 'None'}"'),
              SizedBox(width: MediaQuery.of(context).size.width * .3),
              TextButton.icon(
                onPressed: onSelectFile,
                label: const Text("选择音频文件"),
                icon: const Icon(Icons.file_open_outlined),
              ),
            ],
          ),
        ),
        const Divider(),
      ],
    );
  }
}

class ReadTagsSection extends StatelessWidget {
  const ReadTagsSection({super.key, required this.filePath});

  final String filePath;

  [@override](/user/override)
  Widget build(BuildContext context) {
    return SectionCard(
      title: '读取标签',
      iconData: Icons.sticky_note_2_outlined,
      content: ButtonBar(
        alignment: MainAxisAlignment.center,
        children: [
          FilledButton.tonal(
            onPressed: () =>
                handleTaggyMethodCall(context, Taggy.readAll(filePath)),
            child: const Text('读取所有'),
          ),
          FilledButton.tonal(
            onPressed: () =>
                handleTaggyMethodCall(context, Taggy.readPrimary(filePath)),
            child: const Text('读取主标签'),
          ),
          FilledButton.tonal(
            onPressed: () =>
                handleTaggyMethodCall(context, Taggy.readAny(filePath)),
            child: const Text('读取任意'),
          ),
        ],
      ),
    );
  }
}

class WritingTagsSection extends StatefulWidget {
  const WritingTagsSection({super.key, required this.filePath});

  final String filePath;

  [@override](/user/override)
  State<WritingTagsSection> createState() => _WritingTagsSectionState();
}

class _WritingTagsSectionState extends State<WritingTagsSection> {
  double tagsToWriteCount = 1;
  List<Tag> tags = [];
  Tag tag = const Tag(tagType: TagType.FilePrimaryType, pictures: []);
  bool keepOtherTags = false;

  [@override](/user/override)
  Widget build(BuildContext context) {
    return SectionCard(
      title: '写入标签',
      iconData: Icons.edit_outlined,
      content: Padding(
        padding: const EdgeInsets.all(12.0),
        child: SizedBox(
          width: context.screenWidth * .8,
          child: Wrap(
            alignment: WrapAlignment.spaceBetween,
            spacing: 10,
            runSpacing: 12,
            children: [
              Text(
                '作为主标签写入:',
                style: TextStyle(
                  fontSize: 14,
                  fontWeight: FontWeight.w500,
                  color: context.colorScheme.onPrimaryContainer,
                ),
              ),
              FilledButton.tonal(
                onPressed: () {
                  showDialog(
                    context: context,
                    builder: (context) {
                      return Dialog(
                        child: Container(
                          width: min(context.screenWidth * .8, 500),
                          padding: const EdgeInsets.symmetric(
                              vertical: 16, horizontal: 18),
                          child: Column(
                            children: [
                              Row(
                                mainAxisAlignment:
                                    MainAxisAlignment.spaceBetween,
                                children: [
                                  const Text(
                                    '标签属性',
                                    style: TextStyle(
                                      fontSize: 16,
                                      fontWeight: FontWeight.w500,
                                    ),
                                  ),
                                  FilledButton(
                                    onPressed: () =>
                                        Navigator.of(context).pop(),
                                    child: const Text('完成'),
                                  ),
                                ],
                              ),
                              Expanded(
                                child: ListView(
                                  shrinkWrap: true,
                                  padding:
                                      const EdgeInsets.symmetric(vertical: 30),
                                  children: [
                                    TagPropTextField(
                                      label: '曲目标题',
                                      initialValue: tag.trackTitle,
                                      onChanged: (value) {
                                        tag = tag.copyWith(trackTitle: value);
                                      },
                                    ),
                                    TagPropTextField(
                                      label: '曲目艺术家',
                                      initialValue: tag.trackArtist,
                                      onChanged: (value) {
                                        tag = tag.copyWith(trackArtist: value);
                                      },
                                    ),
                                    TagPropTextField(
                                      label: '曲目专辑',
                                      initialValue: tag.album,
                                      onChanged: (value) {
                                        tag = tag.copyWith(album: value);
                                      },
                                    ),
                                    TagPropTextField(
                                      label: '专辑艺术家',
                                      initialValue: tag.albumArtist,
                                      onChanged: (value) {
                                        tag = tag.copyWith(albumArtist: value);
                                      },
                                    ),
                                    TagPropTextField(
                                      label: '制作人',
                                      initialValue: tag.producer,
                                      onChanged: (value) {
                                        tag = tag.copyWith(producer: value);
                                      },
                                    ),
                                    TagPropTextField(
                                      label: '发行日期',
                                      initialValue: tag.originalReleaseDate,
                                      onChanged: (value) {
                                        tag = tag.copyWith(
                                            originalReleaseDate: value);
                                      },
                                    ),
                                    TagPropTextField(
                                      label: '录制日期',
                                      initialValue: tag.recordingDate,
                                      onChanged: (value) {
                                        tag =
                                            tag.copyWith(recordingDate: value);
                                      },
                                    ),
                                    TagPropTextField(
                                      label: '年份',
                                      initialValue: tag.year?.toString(),
                                      onChanged: (value) {
                                        if (value != null) {
                                          tag = tag.copyWith(
                                              year: int.tryParse(value));
                                        } else {
                                          tag = tag.copyWith(year: null);
                                        }
                                      },
                                    ),
                                    TagPropTextField(
                                      label: '曲目编号',
                                      initialValue: tag.trackNumber?.toString(),
                                      onChanged: (value) {
                                        if (value != null) {
                                          tag = tag.copyWith(
                                              trackNumber: int.tryParse(value));
                                        } else {
                                          tag = tag.copyWith(trackNumber: null);
                                        }
                                      },
                                    ),
                                    TagPropTextField(
                                      label: '总曲目数',
                                      initialValue: tag.trackTotal?.toString(),
                                      onChanged: (value) {
                                        if (value != null) {
                                          tag = tag.copyWith(
                                              trackTotal: int.tryParse(value));
                                        } else {
                                          tag = tag.copyWith(trackTotal: null);
                                        }
                                      },
                                    ),
                                    TagPropTextField(
                                      label: '光盘编号',
                                      initialValue: tag.discNumber?.toString(),
                                      onChanged: (value) {
                                        if (value != null) {
                                          tag = tag.copyWith(
                                              discNumber: int.tryParse(value));
                                        } else {
                                          tag = tag.copyWith(discNumber: null);
                                        }
                                      },
                                    ),
                                    TagPropTextField(
                                      label: '光盘总数',
                                      initialValue: tag.discTotal?.toString(),
                                      onChanged: (value) {
                                        if (value != null) {
                                          tag = tag.copyWith(
                                              discTotal: int.tryParse(value));
                                        } else {
                                          tag = tag.copyWith(discTotal: null);
                                        }
                                      },
                                    ),
                                    TagPropTextField(
                                      label: '流派',
                                      initialValue: tag.genre,
                                      onChanged: (value) {
                                        tag = tag.copyWith(genre: value);
                                      },
                                    ),
                                    TagPropTextField(
                                      label: '语言',
                                      initialValue: tag.language,
                                      onChanged: (value) {
                                        tag = tag.copyWith(language: value);
                                      },
                                    ),
                                    TagPropTextField(
                                      label: '歌词',
                                      initialValue: tag.lyrics,
                                      onChanged: (value) {
                                        tag = tag.copyWith(lyrics: value);
                                      },
                                    ),
                                  ],
                                ),
                              ),
                            ],
                          ),
                        ),
                      );
                    },
                  );
                },
                child: const Text('查看/编辑"要写入的标签"'),
              ),
              SizedBox(
                width: 230,
                child: CheckboxListTile(
                  title: const Text(
                    '应保留其他标签: ',
                    style: TextStyle(fontSize: 12),
                  ),
                  value: keepOtherTags,
                  onChanged: (value) {
                    setState(() => keepOtherTags = value ?? false);
                  },
                ),
              ),
              FilledButton.tonal(
                onPressed: () {
                  handleTaggyMethodCall(
                    context,
                    Taggy.writePrimary(
                      path: widget.filePath,
                      tag: tag,
                      keepOthers: keepOtherTags,
                    ),
                  );
                },
                child: const Text('写入文件'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class TagPropTextField extends StatelessWidget {
  const TagPropTextField({
    super.key,
    this.initialValue,
    required this.label,
    required this.onChanged,
  });

  final String? initialValue;
  final String label;
  final void Function(String?) onChanged;

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 6.0),
      child: TextFormField(
        onChanged: onChanged,
        initialValue: initialValue,
        decoration: InputDecoration(
          label: Text(label),
          border: OutlineInputBorder(
            borderSide: BorderSide(color: context.colorScheme.secondary),
          ),
        ),
      ),
    );
  }
}

class RemoveTagsSection extends StatefulWidget {
  const RemoveTagsSection({super.key, required this.filePath});

  final String filePath;

  [@override](/user/override)
  State<RemoveTagsSection> createState() => _RemoveTagsSectionState();
}

class _RemoveTagsSectionState extends State<RemoveTagsSection> {
  TagType tagToRemoveType = TagType.FilePrimaryType;

  [@override](/user/override)
  Widget build(BuildContext context) {
    return SectionCard(
      title: '删除标签',
      iconData: Icons.sticky_note_2_outlined,
      content: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 20.0, vertical: 8),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: [
                const Text(
                  '删除所有标签:',
                  style: TextStyle(
                    fontWeight: FontWeight.w500,
                  ),
                ),
                FilledButton.tonal(
                  onPressed: () =>
                      handleTaggyMethodCall(
                          context, Taggy.removeAll(widget.filePath)),
                  child: const Text('删除所有'),
                ),
              ],
            ),
            Padding(
              padding: const EdgeInsets.symmetric(vertical: 16),
              child: Row(
                children: [
                  const Text(
                    '按标签类型删除:',
                    style: TextStyle(
                      fontWeight: FontWeight.w500,
                    ),
                  ),
                  const Spacer(),
                  DropdownMenu(
                    onSelected: (type) {
                      if (type != null) {
                        setState(() => tagToRemoveType = type);
                      }
                    },
                    hintText: '选择标签类型',
                    dropdownMenuEntries: TagType.values
                        .map((e) => DropdownMenuEntry(
                              value: e,
                              label: e.name,
                            ))
                        .toList(),
                  ),
                  const SizedBox(width: 20),
                  FilledButton.tonal(
                    onPressed: () {
                      handleTaggyMethodCall(
                        context,
                        Taggy.removeTag(
                            path: widget.filePath, tagType: tagToRemoveType),
                      );
                    },
                    child: const Text("删除"),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class SectionCard extends StatelessWidget {
  const SectionCard({
    super.key,
    required this.title,
    required this.iconData,
    required this.content,
  });

  final String title;
  final IconData iconData;
  final Widget content;

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        color: context.colorScheme.primaryContainer.withOpacity(.2),
        borderRadius: BorderRadius.circular(14),
      ),
      margin: const EdgeInsets.all(12),
      child: Column(
        children: [
          ListTile(
            leading: Icon(iconData, color: context.colorScheme.primary),
            title: Text(
              title,
              style: TextStyle(
                fontWeight: FontWeight.w500,
                color: context.colorScheme.onPrimaryContainer,
              ),
            ),
          ),
          content,
        ],
      ),
    );
  }
}

更多关于Flutter标签管理插件flutter_taggy的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter标签管理插件flutter_taggy的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中使用flutter_taggy插件来实现标签管理功能的一个简单示例。flutter_taggy是一个用于创建和管理标签的Flutter包,非常适合在需要标签输入功能的应用中使用。

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

dependencies:
  flutter:
    sdk: flutter
  flutter_taggy: ^x.y.z  # 请替换为最新版本号

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

接下来,在你的Dart文件中,你可以按照以下方式使用flutter_taggy

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Taggy Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: TaggyDemoScreen(),
    );
  }
}

class TaggyDemoScreen extends StatefulWidget {
  @override
  _TaggyDemoScreenState createState() => _TaggyDemoScreenState();
}

class _TaggyDemoScreenState extends State<TaggyDemoScreen> {
  final TextEditingController _controller = TextEditingController();
  List<String> _tags = [];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Taggy Demo'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            TagInput(
              controller: _controller,
              suggestions: ['Flutter', 'Dart', 'React', 'Angular', 'Vue'],
              initialSuggestions: ['Flutter', 'Dart'],
              onDeleted: (tag) {
                setState(() {
                  _tags.remove(tag);
                });
              },
              onSubmit: (String tag) {
                if (tag.isNotEmpty) {
                  setState(() {
                    _tags.add(tag);
                    _controller.clear();
                  });
                }
              },
              decoration: InputDecoration(
                border: OutlineInputBorder(),
                labelText: 'Enter a tag',
                suffixIcon: IconButton(
                  icon: Icon(Icons.clear),
                  onPressed: () {
                    _controller.clear();
                  },
                ),
              ),
            ),
            SizedBox(height: 16),
            Text('Selected Tags:', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
            Wrap(
              spacing: 4,
              runSpacing: 4,
              children: List.generate(
                _tags.length,
                (index) => Chip(
                  label: Text(_tags[index]),
                  onDeleted: () {
                    setState(() {
                      _tags.removeAt(index);
                    });
                  },
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

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

代码解释:

  1. 依赖添加:在pubspec.yaml中添加flutter_taggy依赖。
  2. 主应用:创建一个简单的Flutter应用,包含一个主屏幕TaggyDemoScreen
  3. 状态管理:使用StatefulWidget来管理标签的添加和删除。
  4. TagInput组件:使用TagInput组件来接收用户输入的标签。
    • controller:用于管理文本输入。
    • suggestions:提供自动完成的建议标签列表。
    • initialSuggestions:初始显示的建议标签列表。
    • onDeleted:当用户删除一个标签时的回调。
    • onSubmit:当用户提交一个新标签时的回调。
    • decoration:用于自定义输入框的外观。
  5. 显示已选标签:使用Wrap组件和Chip组件来显示用户已经选择的标签,并允许用户删除这些标签。

这个示例展示了如何使用flutter_taggy插件来创建一个简单的标签管理系统,包括标签的输入、自动完成、删除和显示。你可以根据自己的需求进一步自定义和扩展这个示例。

回到顶部