# Text组件实现长按全选内容并弹出自定义菜单

#### 问题现象

如何对Text组件绑定长按手势，实现文本内容全选并弹出自定义菜单的功能？

#### 背景知识

Text组件用于显示一段文本的组件，可以包含文字、图片等。该组件提供了bindSelectionMenu接口用于设置自定义选择菜单。bindSelectionMenu长按响应时长为600ms，长按达到时长后弹出自定义菜单。为了触发组件长按手势，可为组件绑定LongPressGesture事件，触发长按手势的最少手指数为1，最短长按时间若不设置，默认值为500毫秒。即达到500毫秒或者自定义时间时，会触发手势回调。

#### 解决方案

1.  通过bindSelectionMenu设置自定义菜单选项，同时注意如下关键点：
    *   设置spanType为TextSpanType.DEFAULT，响应多种文本类型。
    *   设置responseType为TextResponseType.DEFAULT，所有选中场景都触发自定义菜单。
    *   自定义菜单关闭时，取消选中区域。
2.  通过LongPressGesture触发长按手势回调，将选中区域设置为所有文本。

代码实现如下：

```
@Entry
@Component
struct TextExample {
  optionsPopup: string[] = ['搜索', '复制', '粘贴'];
  controller: TextController = new TextController();
  options: TextOptions = { controller: this.controller };
  @State start: number = -1; // 选中区域开始光标
  @State end: number = -1; // 选中区域结束光标

  build() {
    Column() {
      Column() {
        Text(undefined, this.options) {
          // 将Text内容设置为字图混合形式
          Span('Hello World')
            .fontSize(36)
          ImageSpan($r('app.media.startIcon'))
            .width('120px')
            .height('120px')
            .objectFit(ImageFit.Fill)
            .verticalAlign(ImageSpanAlignment.CENTER);
        }
        .selection(this.start, this.end) // 选中区域
        .copyOption(CopyOptions.InApp) // 设置可复制
        .bindSelectionMenu(TextSpanType.DEFAULT, this.LongPressTextCustomMenu, TextResponseType.DEFAULT) // 自定义菜单
        .gesture(
          LongPressGesture({ duration: 300 })
            .onAction(() => {
              // 调整选中区域，全选文本
              this.start = 0;
              this.end = 12;
            })
        )
        .width('100%')
        .height(150);
      }
      .width('100%')
      .backgroundColor(Color.White)
      .alignItems(HorizontalAlign.Start)
      .padding(25);
    }
    .height('100%');
  }

  // 自定义菜单
  @Builder
  LongPressTextCustomMenu() {
    Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceEvenly, alignItems: ItemAlign.Center }) {
      ForEach(this.optionsPopup, (item: string, index) => {
        Text(item).height('100%')
          .onClick(() => {
            // 取消选中
            this.start = -1;
            this.end = -1;
            // 关闭自定义菜单
            this.controller.closeSelectionMenu();
          });
        // 设置间隔
        if (index < this.optionsPopup.length - 1) {
          Divider().height(10).vertical(true);
        }
      });
    }
    .width(150)
    .height(40)
    .padding(10)
    .shadow({
      radius: 20,
      color: '#f3f5f7',
      offsetX: 0,
      offsetY: 10
    })
    .borderRadius(20)
  }
}
```

运行效果如下：

#### 常见FAQ

Q：为什么不使用onAppear做弹出菜单前的全选或者onDisappear做关闭菜单后的取消选中操作？

A：菜单的重新生成会触发onAppear和onDisappear，重新生成的场景包括选中区域文本类型的变化（由纯文字变为图文混合等）。因此当拖动光标触发上述场景，onAppear会导致选中区域强制为全选，onDisappear会导致选中区域强制取消，不符合交互逻辑。

Q：为什么将responseType为TextResponseType.LONG\_PRESS，长按后弹出系统菜单，滑动光标后变为自定义菜单？

A：通过section设置的光标滑动，会被认为是TextResponseType.SELECT，因此会触发系统菜单。滑动过程中需要手指长按，因此被识别为长按，唤起自定义菜单。

Q：点击文本外的按钮触发弹窗事件，Text的文本选中菜单消失了但是不走onDisappear回调，是什么原因？

A：系统弹窗弹出，文本菜单消失，只是隐藏了，依旧在组件树上，并没有销毁，不会走onDisappear回调，会触发onMenuHide事件。

Q：RichText是否能使用自定义菜单？

A：RichText无法通过自定义菜单控制文本选择，推荐使用Web组件或者使用RichEditor组件，禁用编辑功能和键盘，再实现自定义菜单。

Q：如何给TextInput的复制粘贴功能添加自定义功能点？

A：可以使用editMenuOptions设置自定义菜单扩展项。参考示例文本扩展自定义菜单。

Q：Text组件内自定义Span无法添加点击事件，如何实现点击文本弹出菜单的功能？

A：通过onTextSelectionChange()方法来实现，根据选中的文本弹出菜单，文本选择的位置发生变化时，触发该回调。详情可参考官网：示例8（文本绑定自定义菜单）。