【Flutter】SelectionArea之实现文本全选复制功能

概要

通过SelectionArea实现一个可复用的文本的全选、复制功能的widget组件

实现思路

目标明确,为了以后的易用性、可扩展性,单独抽出来一个widget类,功能内聚。

  1. 对外提供一个方法wrap,接受一个子widget(Text文本)、是否全选变量selectAll、是否复制变量copy,通过这两个变量控制是否显示全选、复制功能
  2. 当前文本是否全选变量selectAllEnable,只有当selectAll=true&&selectAllEnable=true时,显示全选功能

源码分析

1.SelectionArea源码:

     const SelectionArea({
     super.key,
     this.focusNode,
     this.selectionControls,
     this.contextMenuBuilder = _defaultContextMenuBuilder,
     this.magnifierConfiguration,
     this.onSelectionChanged,
     required this.child,
     });

分析一下我们需要的参数child必转、onSelectionChanged选中文本回调、contextMenuBuilder 菜单列表。
2.onSelectionChanged源码:

final ValueChanged<SelectedContent?>? onSelectionChanged;

回调回来一个可空的SelectedContent
3.contextMenuBuilder源码:

   static Widget _defaultContextMenuBuilder(BuildContext context, SelectableRegionState selectableRegionState) {
   return AdaptiveTextSelectionToolbar.selectableRegion(
   selectableRegionState: selectableRegionState,
   );
   }

(1)、讲一下SelectableRegionState参数:

   //1.实现TextSelectionDelegate 接口的全选、复制
   class SelectableRegionState extends State<SelectableRegion> with TextSelectionDelegate implements SelectionRegistrar {
   //2._SelectAllAction、_CopySelectionAction放Map里准备初始化
   late final Map<Type, Action<Intent>> _actions = <Type, Action<Intent>>{
   SelectAllTextIntent: _makeOverridable(_SelectAllAction(this)),
   CopySelectionTextIntent: _makeOverridable(_CopySelectionAction(this)),
   ...
   }; 

   
   void selectAll([SelectionChangedCause? cause]) {
   _clearSelection();
   _selectable?.dispatchSelectionEvent(const SelectAllSelectionEvent());
   if (cause == SelectionChangedCause.toolbar) {
   _showToolbar();
   _showHandles();
   }
   _updateSelectedContentIfNeeded();
   }

   (
   'Use `contextMenuBuilder` instead. '
   'This feature was deprecated after v3.3.0-0.5.pre.',
   )
   
   void copySelection(SelectionChangedCause cause) {
   _copy();
   _clearSelection();
   }

   
   Widget build(BuildContext context) {
   assert(debugCheckHasOverlay(context));
   Widget result = SelectionContainer(
   registrar: this,
   delegate: _selectionDelegate,
   child: widget.child,
   );
   if (kIsWeb) {
   result = PlatformSelectableRegionContextMenu(
   child: result,
   );
   }
   return CompositedTransformTarget(
   link: _toolbarLayerLink,
   child: RawGestureDetector(
   gestures: _gestureRecognizers,
   behavior: HitTestBehavior.translucent,
   excludeFromSemantics: true,
   child: Actions(
   actions: _actions,//初始化_actions如:全选、复制
   child: Focus(
   includeSemantics: false,
   focusNode: widget.focusNode,
   child: result,
   ),
   ),
   ),
   );
   }

   }
  1. TextSelectionDelegate属于text_input类
      mixin TextSelectionDelegate {
      ...
      //全选
      void selectAll(SelectionChangedCause cause);
      //复制
      void copySelection(SelectionChangedCause cause);
      }
  1. _SelectAllAction、_CopySelectionAction
      class _SelectAllAction extends _NonOverrideAction<SelectAllTextIntent> {
      _SelectAllAction(this.state);

      final SelectableRegionState state;
      //invokeAction回调到SelectableRegionState的selectAll实现全选
      
      void invokeAction(SelectAllTextIntent intent, [BuildContext? context]) {
      state.selectAll(SelectionChangedCause.keyboard);
      }
      }

      class _CopySelectionAction extends _NonOverrideAction<CopySelectionTextIntent> {
      _CopySelectionAction(this.state);

      final SelectableRegionState state;
      //invokeAction回调到SelectableRegionState的_copy实现复制
      
      void invokeAction(CopySelectionTextIntent intent, [BuildContext? context]) {
      state._copy();
      }
      }

(2)、_defaultContextMenuBuilder返回的是一个widget
我们最终返回AdaptiveTextSelectionToolbar.buttonItems:

      const AdaptiveTextSelectionToolbar.buttonItems({
      super.key,
      required this.buttonItems,
      required this.anchors,
      }) : children = null;

buttonItems是ContextMenuButtonItem菜单集合,anchors是 contextMenuAnchors。

具体实现

main.dart代码

import 'package:flutter/material.dart';
import 'package:selection_area/ib_selection_area.dart';

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

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

  // This widget is the root of your application.
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SelectionArea实现文本全选复制功能',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'SelectionArea实现文本全选复制功能'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final String content =
      "5G的设想最早在2008年就由美国提出来了,但是当时4G还没有建设好,因此各国真正坐下来讨论5G的标准其实是在2012年前后。\n注:主管标准制定的机构叫做3GPP(第三代合作伙伴计划),它成立于1998年,最初只是为各国协调3G通信标准的组织,后来就负责其历代移动通信标准的制定了。今天世界上主要的通信厂家,包括中国的华为等企业,都在其中。    标准的制定过程和我们编写软件,或者写书很相像。参与者各自提供自己的想法,大家讨论、修改,形成共同的意见。由于技术不断发展,大家的想法也在改变,运营商的需求也不断增加,因此总有新的东西加进来。于是,主管标准制定的机构也就是3GPP,在某一个阶段,就必须冻结所有的需求,然后发布一个版本,叫做一个Release,中文常常把它简写成R。\n关于5G的标准,目前大家讨论的是15版(3GPP-R15)。在这个标准中,将5G的建设分为了两步走,这两步走得都很艰辛。第一步经过78次开会,无数的讨价还价和妥协,最后在去年底总算是确定下来了。第二步,在2019年6月份才确定。注:第一步是所谓的非独立组网模式NSA,即采用现有4G作为核心网,4G为主,5G为辅,对应的标准则是3GPP-R15-NSA,这是设想的前期做法。第二步是独立组网模式SA,5G作为核心网,只有5G基站工作,对应的标准是3GPP-R15-SA。这部分标准2019年6月份在高通所在地圣地亚哥才正式确定。";

  get _listView => ListView(
        padding:
            const EdgeInsets.only(left: 12, right: 12, top: 15, bottom: 15),
        children: [_textContent],
      );

  get _textContent => IBSelectionArea.wrap(Text(
        content,
        style: const TextStyle(fontSize: 16),
      ));

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: _listView,
    );
  }
}

ib_selection_area.dart代码

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

///文本全选、复制
class IBSelectionArea {
  static Widget wrap(Text child, {bool selectAll = true, bool copy = true}) {
    //选择的文本
    var selectedText = '';
    return SelectionArea(
      child: child,
      onSelectionChanged: (SelectedContent? selectContent) =>
          selectedText = selectContent?.plainText ?? "",
      contextMenuBuilder:
          (BuildContext context, SelectableRegionState selectableRegionState) {
        bool selectAllEnable = false;
        //如果还有可选的内容则展示全选菜单
        if (selectedText.length < (child.data?.length ?? 0)) {
          selectAllEnable = true;
        }
        final List<ContextMenuButtonItem> buttonItems = [
          if (selectAll && selectAllEnable)
            ContextMenuButtonItem(
                label: '全选',
                onPressed: () {
                  selectableRegionState
                      .selectAll(SelectionChangedCause.toolbar);
                }),
          if (copy)
            ContextMenuButtonItem(
                label: '复制',
                onPressed: () {
                  selectableRegionState
                      .copySelection(SelectionChangedCause.toolbar);
                })
        ];
        return AdaptiveTextSelectionToolbar.buttonItems(
          buttonItems: buttonItems,
          anchors: selectableRegionState.contextMenuAnchors,
        );
      },
    );
  }
}

源码

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值