Flutter、TextFormField 中的超链接

问题描述 投票:0回答:1

我想知道是否有人知道如何在 TextFormField 内使超链接可点击。

该场景是一个笔记应用程序,其中每个笔记的文本始终位于 TextFormField 内。我想让用户可以单击他们自己编写的链接,而无需摆脱 TextFormField。

我尝试搜索各种包,但我发现的只是简单的文本小部件,而不是 TextFormField。

flutter hyperlink textformfield
1个回答
0
投票

结果

image

说明

受到我的其他答案的启发, 我想出了一个可能对您有所帮助的解决方案。它对我来说效果很好。

诀窍是使用

extended_text_field
包。它允许您自定义文本在文本字段中的显示方式。用法如下:

步骤1

创建自定义

TextFormField
:

class ClickableLinkTextFormField extends StatefulWidget {
  // ... 
  @override
  _ClickableLinkTextFormFieldState createState() => _ClickableLinkTextFormFieldState();
}

class _ClickableLinkTextFormFieldState extends State<ClickableLinkTextFormField> {
  late TextEditingController _controller;

  @override
  Widget build(BuildContext context) {
    return FormField<String>(
      builder: (FormFieldState<String> state) {
        return ExtendedTextField(
          controller: _controller,
          specialTextSpanBuilder: MySpecialTextSpanBuilder(),
         // ...
        );
      },
    );
  }

 
}

第2步:

class MySpecialTextSpanBuilder extends SpecialTextSpanBuilder {
  // regex from https://stackoverflow.com/a/3809435/9438149
  final RegExp _urlRegExp = RegExp(
    r'[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)',
    caseSensitive: false,
  );

  @override
  TextSpan build(String data, {TextStyle? textStyle, SpecialTextGestureTapCallback? onTap}) {
    // This is where we detect and make links clickable
  }

}

基本上,此类会遍历您的文本,查找 URL,并将它们包装在

GestureDetector
中,以便它们可以单击。点击链接后,它将在浏览器中打开。

第3步

要使用它,只需用以下内容替换常规 TextFormField 即可:

ClickableLinkTextFormField(
  initialValue: 'Check out https://flutter.dev',
  onChanged: (value) {},
  // ... 
)

正则表达式可能需要根据您正在处理的链接类型进行调整。

代码

这是一个完整的可运行代码片段

import 'package:flutter/material.dart';
import 'package:extended_text_field/extended_text_field.dart';
import 'package:url_launcher/url_launcher.dart';

void main() => runApp(MyApp());

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

class MyHomePage extends StatefulWidget {
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final GlobalKey<FormState> _formKey = GlobalKey<FormState>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Form(
          key: _formKey,
          child: Column(
            children: <Widget>[
              ClickableLinkTextFormField(
                initialValue: 'This is a clickable link: https://flutter.dev\n',
                onChanged: (value) {},
                onSaved: (value) {
                  print('Saved: $value');
                  return null;
                },
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Please enter some text';
                  }
                  return null;
                },
                decoration: InputDecoration(
                  labelText: 'Note',
                  border: OutlineInputBorder(),
                ),
              ),
              SizedBox(height: 20),
              ElevatedButton(
                onPressed: () {
                  if (_formKey.currentState!.validate()) {
                    _formKey.currentState!.save();
                  }
                },
                child: Text('Submit'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class MySpecialTextSpanBuilder extends SpecialTextSpanBuilder {
  final RegExp _urlRegExp = RegExp(
    // https://stackoverflow.com/a/3809435/9438149
    r'[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)',
    caseSensitive: false,
  );

  @override
  TextSpan build(String data,
      {TextStyle? textStyle, SpecialTextGestureTapCallback? onTap}) {
    final List<InlineSpan> spans = [];
    final List<String> lines = data.split('\n');

    for (int i = 0; i < lines.length; i++) {
      if (i > 0) {
        spans.add(TextSpan(text: '\n'));
      }

      final List<String> words = lines[i].split(' ');
      for (int j = 0; j < words.length; j++) {
        final word = words[j];
        if (_urlRegExp.hasMatch(word)) {
          spans.add(
            WidgetSpan(
              // Wrap the URL in a GestureDetector to make it clickable
              child: GestureDetector(
                onTap: () async {
                  final Uri uri = Uri.parse(word);
                  if (await canLaunchUrl(uri)) {
                    await launchUrl(uri);
                  }
                },
                child: Text(
                  word,
                  style: TextStyle(
                    color: Colors.blue,
                    decoration: TextDecoration.underline,
                  ),
                ),
              ),
            ),
          );
        } else {
          // If the word is not a URL, just add it as a normal TextSpan
          spans.add(TextSpan(text: word, style: textStyle));
        }
        // Add a space after each word, except the last one
        if (j < words.length - 1) {
          spans.add(TextSpan(text: ' ', style: textStyle));
        }
      }
    }
    // Return the TextSpan with all the spans

    return TextSpan(children: spans, style: textStyle);
  }

  @override
  SpecialText? createSpecialText(String flag,
      {TextStyle? textStyle,
      SpecialTextGestureTapCallback? onTap,
      required int index}) {
    return null;
  }
}

class ClickableLinkTextFormField extends StatefulWidget {
  final String initialValue;
  final ValueChanged<String>? onChanged;
  final void Function(String?)? onSaved;
  final String? Function(String?)? validator;
  final InputDecoration? decoration;

  const ClickableLinkTextFormField({
    Key? key,
    required this.initialValue,
    this.onChanged,
    this.onSaved, // No longer required
    this.validator,
    this.decoration,
  }) : super(key: key);

  @override
  _ClickableLinkTextFormFieldState createState() =>
      _ClickableLinkTextFormFieldState();
}

class _ClickableLinkTextFormFieldState
    extends State<ClickableLinkTextFormField> {
  late TextEditingController _controller;

  @override
  void initState() {
    super.initState();
    _controller = TextEditingController(text: widget.initialValue);
  }

  @override
  Widget build(BuildContext context) {
    return FormField<String>(
      initialValue: widget.initialValue,
      validator: widget.validator,
      onSaved: widget.onSaved,
      builder: (FormFieldState<String> state) {
        return ExtendedTextField(
          controller: _controller,
          onChanged: (value) {
            state.didChange(value);
            widget.onChanged?.call(value);
          },
          specialTextSpanBuilder: MySpecialTextSpanBuilder(),
          maxLines: null,
          decoration: widget.decoration?.copyWith(
                errorText: state.hasError ? state.errorText : null,
              ) ??
              InputDecoration(
                errorText: state.hasError ? state.errorText : null,
              ),
        );
      },
    );
  }

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

不要忘记将

extended_text_field
url_launcher 添加到您的
pubspec.yaml

© www.soinside.com 2019 - 2024. All rights reserved.