my_extended_text_selection_controls.dart 10.8 KB

import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

///
///  create by zmtzawqlp on 2019/8/3
///
///
///
typedef MyCustomCallBack = void Function(Object? object);

const double _kHandleSize = 22.0;

// Padding between the toolbar and the anchor.
const double _kToolbarContentDistanceBelow = _kHandleSize - 2.0;
const double _kToolbarContentDistance = 8.0;

/// Android Material styled text selection controls.
class MyTextSelectionControls extends TextSelectionControls {
  /// Returns the size of the Material handle.
  ///
  ///
  final MyCustomCallBack? callback;
  MyTextSelectionControls({this.callback});

  @override
  Size getHandleSize(double textLineHeight) =>
      const Size(_kHandleSize, _kHandleSize);

  /// Builder for material-style copy/paste text selection toolbar.
  @override
  Widget buildToolbar(
    BuildContext context,
    Rect globalEditableRegion,
    double textLineHeight,
    Offset selectionMidpoint,
    List<TextSelectionPoint> endpoints,
    TextSelectionDelegate delegate,
    ValueListenable<ClipboardStatus>? clipboardStatus,
    // ClipboardStatusNotifier? clipboardStatus,
    Offset? lastSecondaryTapDownPosition,
  ) {
    return _TextSelectionControlsToolbar(
      globalEditableRegion: globalEditableRegion,
      textLineHeight: textLineHeight,
      selectionMidpoint: selectionMidpoint,
      endpoints: endpoints,
      delegate: delegate,
      clipboardStatus: clipboardStatus,
      handleCut: canCut(delegate) ? () => handleCut(delegate) : null,
      handleCopy: canCopy(delegate) ? () => handleCopy(delegate) : null,
      handlePaste: () => null,
      // handlePaste: canPaste(delegate) ? () => handlePaste(delegate) : null,
      handleSelectAll:
          canSelectAll(delegate) ? () => handleSelectAll(delegate) : null,
      handleLike: () async {
        print('dkjfkdfjk');
        var clipboardData =
            await Clipboard.getData(Clipboard.kTextPlain); //获取粘贴板中的文本
        if (clipboardData != null) {
          print(clipboardData.text); //打印内容
          if (callback != null) {
            callback!(clipboardData.text);
          }
        } else {
          if (callback != null) {
            callback!('');
          }
        }
        delegate.hideToolbar();
        // delegate.userUpdateTextEditingValue(
        //   delegate.textEditingValue
        //       .copyWith(selection: const TextSelection.collapsed(offset: 0)),
        //   SelectionChangedCause.toolbar,
        // );
        // launch(
        //     'mailto:zmtzawqlp@live.com?subject=extended_text_share&body=${delegate.textEditingValue.text}');
        // delegate.hideToolbar();
        // delegate.userUpdateTextEditingValue(
        //   delegate.textEditingValue
        //       .copyWith(selection: const TextSelection.collapsed(offset: 0)),
        //   SelectionChangedCause.toolbar,
        // );
      },
    );
  }

  /// Builder for material-style text selection handles.
  @override
  Widget buildHandle(
      BuildContext context, TextSelectionHandleType type, double textLineHeight,
      [VoidCallback? onTap, double? startGlyphHeight, double? endGlyphHeight]) {
    final Widget handle = SizedBox(
        // width: _kHandleSize,
        // height: _kHandleSize,
        // child: Image.asset(
        //   'assets/add_store.png',
        // ),
        );

    // [handle] is a circle, with a rectangle in the top left quadrant of that
    // circle (an onion pointing to 10:30). We rotate [handle] to point
    // straight up or up-right depending on the handle type.
    switch (type) {
      case TextSelectionHandleType.left: // points up-right
        return Transform.rotate(
          angle: math.pi / 4.0,
          child: handle,
        );
      case TextSelectionHandleType.right: // points up-left
        return Transform.rotate(
          angle: -math.pi / 4.0,
          child: handle,
        );
      case TextSelectionHandleType.collapsed: // points up
        return handle;
    }
  }

  /// Gets anchor for material-style text selection handles.
  ///
  /// See [TextSelectionControls.getHandleAnchor].
  @override
  Offset getHandleAnchor(TextSelectionHandleType type, double textLineHeight,
      [double? startGlyphHeight, double? endGlyphHeight]) {
    switch (type) {
      case TextSelectionHandleType.left:
        return const Offset(_kHandleSize, 0);
      case TextSelectionHandleType.right:
        return Offset.zero;
      default:
        return const Offset(_kHandleSize / 2, -4);
    }
  }

  @override
  bool canSelectAll(TextSelectionDelegate delegate) {
    // Android allows SelectAll when selection is not collapsed, unless
    // everything has already been selected.
    final TextEditingValue value = delegate.textEditingValue;
    return delegate.selectAllEnabled &&
        value.text.isNotEmpty &&
        !(value.selection.start == 0 &&
            value.selection.end == value.text.length);
  }

  // @override
  // bool canPaste(TextSelectionDelegate delegate) {
  //   // Android allows SelectAll when selection is not collapsed, unless
  //   // everything has already been selected.
  //   delegate.pasteEnabled
  //   final TextEditingValue value = delegate.textEditingValue;
  //   return delegate.selectAllEnabled &&
  //       value.text.isNotEmpty &&
  //       !(value.selection.start == 0 &&
  //           value.selection.end == value.text.length);
  // }
}

// The label and callback for the available default text selection menu buttons.
class _TextSelectionToolbarItemData {
  const _TextSelectionToolbarItemData({
    this.label,
    this.onPressed,
  });

  final String? label;
  final VoidCallback? onPressed;
}

// The highest level toolbar widget, built directly by buildToolbar.
class _TextSelectionControlsToolbar extends StatefulWidget {
  const _TextSelectionControlsToolbar({
    this.clipboardStatus,
    this.delegate,
    this.endpoints,
    this.globalEditableRegion,
    this.handleCut,
    this.handleCopy,
    this.handlePaste,
    this.handleSelectAll,
    this.selectionMidpoint,
    this.textLineHeight,
    this.handleLike,
  });

  final ValueListenable<ClipboardStatus>? clipboardStatus;
  final TextSelectionDelegate? delegate;
  final List<TextSelectionPoint>? endpoints;
  final Rect? globalEditableRegion;
  final VoidCallback? handleCut;
  final VoidCallback? handleCopy;
  final VoidCallback? handlePaste;
  final VoidCallback? handleSelectAll;
  final VoidCallback? handleLike;
  final Offset? selectionMidpoint;
  final double? textLineHeight;

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

class _TextSelectionControlsToolbarState
    extends State<_TextSelectionControlsToolbar> 
    // with TickerProviderStateMixin
     {
  void _onChangedClipboardStatus() {
    setState(() {
      // Inform the widget that the value of clipboardStatus has changed.
    });
  }

  @override
  void initState() {
    super.initState();
    widget.clipboardStatus?.addListener(_onChangedClipboardStatus);
  }

  @override
  void didUpdateWidget(_TextSelectionControlsToolbar oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.clipboardStatus != oldWidget.clipboardStatus) {
      widget.clipboardStatus?.addListener(_onChangedClipboardStatus);
      oldWidget.clipboardStatus?.removeListener(_onChangedClipboardStatus);
    }
  }

  @override
  void dispose() {
    super.dispose();
    widget.clipboardStatus?.removeListener(_onChangedClipboardStatus);
  }

  @override
  Widget build(BuildContext context) {
    // If there are no buttons to be shown, don't render anything.
    if (widget.handleCut == null &&
        widget.handleCopy == null &&
        widget.handlePaste == null &&
        widget.handleSelectAll == null) {
      return const SizedBox.shrink();
    }
    // If the paste button is desired, don't render anything until the state of
    // the clipboard is known, since it's used to determine if paste is shown.
    if (widget.handlePaste != null &&
        widget.clipboardStatus?.value == ClipboardStatus.unknown) {
      return const SizedBox.shrink();
    }

    // Calculate the positioning of the menu. It is placed above the selection
    // if there is enough room, or otherwise below.
    final TextSelectionPoint startTextSelectionPoint = widget.endpoints![0];
    final TextSelectionPoint endTextSelectionPoint =
        widget.endpoints!.length > 1 ? widget.endpoints![1] : widget.endpoints![0];
    final Offset anchorAbove = Offset(
      widget.globalEditableRegion!.left + widget.selectionMidpoint!.dx,
      widget.globalEditableRegion!.top +
          startTextSelectionPoint.point.dy -
          widget.textLineHeight! -
          _kToolbarContentDistance,
    );
    final Offset anchorBelow = Offset(
      widget.globalEditableRegion!.left + widget.selectionMidpoint!.dx,
      widget.globalEditableRegion!.top +
          endTextSelectionPoint.point.dy +
          _kToolbarContentDistanceBelow,
    );

    // Determine which buttons will appear so that the order and total number is
    // known. A button's position in the menu can slightly affect its
    // appearance.
    assert(debugCheckHasMaterialLocalizations(context));
    final MaterialLocalizations localizations =
        MaterialLocalizations.of(context);
    final List<_TextSelectionToolbarItemData> itemDatas =
        <_TextSelectionToolbarItemData>[
      if (widget.handleCut != null)
        _TextSelectionToolbarItemData(
          label: localizations.cutButtonLabel,
          onPressed: widget.handleCut,
        ),
      if (widget.handleCopy != null)
        _TextSelectionToolbarItemData(
          label: localizations.copyButtonLabel,
          onPressed: widget.handleCopy,
        ),
      // if (widget.handlePaste != null &&
      //     widget.clipboardStatus?.value == ClipboardStatus.pasteable)
      // _TextSelectionToolbarItemData(
      //   label: localizations.pasteButtonLabel,
      //   onPressed: widget.handlePaste,
      // ),
      if (widget.handleSelectAll != null)
        _TextSelectionToolbarItemData(
          label: localizations.selectAllButtonLabel,
          onPressed: widget.handleSelectAll,
        ),
      _TextSelectionToolbarItemData(
        label: '粘贴',
        onPressed: widget.handleLike,
      ),
    ];

    // If there is no option available, build an empty widget.
    if (itemDatas.isEmpty) {
      return const SizedBox(width: 0.0, height: 0.0);
    }

    return TextSelectionToolbar(
      anchorAbove: anchorAbove,
      anchorBelow: anchorBelow,
      children: itemDatas
          .asMap()
          .entries
          .map((MapEntry<int, _TextSelectionToolbarItemData> entry) {
        return TextSelectionToolbarTextButton(
          padding: TextSelectionToolbarTextButton.getPadding(
              entry.key, itemDatas.length),
          onPressed: entry.value.onPressed,
          child: Text(entry.value.label!),
        );
      }).toList(),
    );
  }
}