Flutter插件mfm的特性与使用方法

概述

此插件是用于Flutter的MFM渲染器。MFM是一种类似于Markdown的标记语言,主要用于Misskey平台。该插件不依赖于WebView。

功能特性

该插件支持以下MFM语法:

  • 引用块(>
  • 代码块(内联、块)
    • mfm_renderer 不会高亮代码块,可以使用来自pub.dev的任何高亮库。
  • 居中块
  • 文本装饰(加粗、大号字体、斜体、小号字体、更改字体、删除线、背景色、前景色)
  • 表情符号代码、Unicode表情符号代码
    • mfm_render 不会构建Image小部件,可以通过传递builder参数到MFM小部件。
  • 纯文本内联块
  • 数学块、数学内联块
    • Misskey不支持这些语法但未进行处理。
  • 提及、标签、URL链接
  • 搜索语法
  • 翻译项目($[scale$[position$[flip
  • 模糊效果
  • 注音($[ruby]
  • 显示绝对时间($[unixtime]
  • MFM动画
    • rainbowshakejellytwitchbouncejumpspinspakle
  • 将某些项目喵化(如将なんなん转换为にゃんにゃん
  • 内联边框($[border]

开始使用

pubspec.yaml文件中添加依赖项:

flutter pub add mfm

使用示例

以下是一个完整的示例,展示如何在Flutter应用中使用mfm插件。

示例代码
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter_highlighting/flutter_highlighting.dart';
import 'package:flutter_highlighting/themes/github-dark.dart';
import 'package:highlighting/languages/all.dart';
import 'package:mfm/mfm.dart';
import 'package:http/http.dart' as http;

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

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

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text("MFM Rendering Example")),
        body: const MfmRenderingExample(),
      ),
    );
  }
}

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

  [@override](/user/override)
  State<StatefulWidget> createState() => MfmRenderingExampleState();
}

class MfmRenderingExampleState extends State<MfmRenderingExample> {
  final textController = TextEditingController();
  Map<String, String> emojiList = {};
  final focusNode = FocusNode();

  [@override](/user/override)
  void initState() {
    super.initState();
    textController.addListener(() {
      setState(() {});
    });
  }

  [@override](/user/override)
  void didChangeDependencies() {
    super.didChangeDependencies();

    Future(() async {
      String? host;
      await showDialog(
        context: context,
        builder: (context) => AlertDialog(
          content: TextFormField(
            onFieldSubmitted: (data) {
              host = data;
              Navigator.of(context).pop();
            },
            decoration: const InputDecoration(
                hintText:
                    "请输入Misskey服务器主机名(例如misskey.io)以获取表情符号。"),
          ),
        ),
      );
      if (host == null) return;

      final response = await http.get(
          Uri(scheme: "https", host: host, pathSegments: ["api", "emojis"]));
      setState(() {
        emojiList = Map.fromEntries(
            (jsonDecode(response.body)["emojis"] as List)
                .map((e) => MapEntry(e["name"] as String, e["url"] as String)));
        focusNode.requestFocus();
      });
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.start,
      mainAxisSize: MainAxisSize.max,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Expanded(
          child: TextField(
            controller: textController,
            maxLines: null,
            expands: true,
            focusNode: focusNode,
            keyboardType: TextInputType.multiline,
            decoration:
                const InputDecoration(contentPadding: EdgeInsets.all(10)),
          ),
        ),
        Expanded(
          child: DecoratedBox(
            decoration: BoxDecoration(
                border: Border(
                    left: BorderSide(color: Theme.of(context).dividerColor))),
            child: SingleChildScrollView(
              child: Padding(
                padding: const EdgeInsets.all(10),
                child: Mfm(
                  mfmText: textController.text,
                  emojiBuilder: (context, emoji, style) {
                    final emojiData = emojiList[emoji];
                    if (emojiData == null) {
                      return Text.rich(TextSpan(text: emoji, style: style));
                    } else {
                      // 如果找到表情符号数据,则显示
                      return Image.network(
                        emojiData,
                        height: (style?.fontSize ?? 1) * 2,
                      );
                    }
                  },
                  codeBlockBuilder: (context, code, lang) {
                    final language = allLanguages[lang]?.id ?? "javascript";
                    return SizedBox(
                      width: double.infinity,
                      child: SingleChildScrollView(
                        scrollDirection: Axis.horizontal,
                        child: HighlightView(
                          code,
                          languageId: language,
                          theme: githubDarkTheme,
                          padding: const EdgeInsets.all(10),
                        ),
                      ),
                    );
                  },
                  mentionTap: (username, host, acct) {
                    ScaffoldMessenger.of(context)
                        .showSnackBar(SnackBar(content: Text("$acct tapped.")));
                  },
                  linkTap: (url) {
                    ScaffoldMessenger.of(context)
                        .showSnackBar(SnackBar(content: Text("@$url tapped.")));
                  },
                  hashtagTap: (url) {
                    ScaffoldMessenger.of(context)
                        .showSnackBar(SnackBar(content: Text("@$url tapped.")));
                  },
                ),
              ),
            ),
          ),
        ),
      ],
    );
  }
}

更多关于Flutter插件mfm的特性与使用方法的实战教程也可以访问 https://www.itying.com/category-92-b0.html

回到顶部