“android:textIsSelectable=”true”在RecyclerView中的TextView上不起作用”

38

我知道在xml中设置android:textIsSelectable="true"可以显示本地文本选择弹出窗口,并且我一直在我的应用程序中使用它。但是,当我尝试在附加到RecyclerView的视图中设置相同的属性时,发现它已经不起作用了。 每当我尝试选择文本时,就会出现以下日志-

我知道在XML中将android:textIsSelectable="true"设置给TextView可以显示本地文本选择弹出窗口,我在我的应用程序中一直在使用它。但是当我尝试在附加到RecyclerView上的视图中设置相同的属性时,发现它不再起作用。 每当我尝试选择文本时,下面的日志就会出现 -

TextView: TextView does not support text selection. Action mode cancelled.

我不知道为什么?为什么它可以在其他屏幕上运行,但是不能与RecyclerView一起使用。我阅读了多篇帖子-

TextView中的android:textIsSelectable="true"在listview中无效

textview textIsSelectable="true"在Listview中无效

android:textIsSelectable="true"适用于ListView中的TextView无效

但是我遇到了这篇文章——

Android: " TextView不支持文本选择。操作模式取消"

@hungkk的回复对我有用。他的解决方案建议将TextView的宽度从match_parent更改为wrap_content

我知道我可以这样做,但我的问题是为什么这样可以解决问题,因为这对我来说看起来很奇怪。而且,如果我想保持宽度为match_parent,该怎么办。

欢迎任何意见。


1
你对选择做了什么?(Marty或Shadab)。我使用View.OnClickListener()时,无论是match_parent还是wrap_content,都没有任何问题。 - Gary99
1
有些帖子中提到,当回收视图重用单元格时,如果TextView设置为match_parent,则会禁用可选择文本功能。您是否尝试过将android:textIsSelectable="true"替换为android:inputType="textMultiLine"作为解决方法? - fmaccaroni
你找到任何解决方案了吗?这个问题是6年前提出的,但我仍然面临同样的问题。 - Khushbu Shah
10个回答

14
在RecyclerView的主父布局中,添加属性:android:descendantFocusability="beforeDescendants"。然后在行项目的TextView中添加。
android:textIsSelectable="true"

14

我发现RecyclerView中的TextView在第一次可以被选中,但是当ViewHolder被回收或适配器执行了notifyDataSetChanged后,所有的文本视图都不能被选中。 我发现这个解决方案对我有效。

yourTextView.setText("your text");
yourTextView.setTextIsSelectable(false);
yourTextView.measure(-1, -1);//you can specific other values.
yourTextView.setTextIsSelectable(true);

为什么这样做?因为我已经调试并在Android源代码中发现了一些逻辑:

TextView.java:

public void setTextIsSelectable(boolean selectable) {
    if (!selectable && mEditor == null) return; // false is default value with no edit data

    createEditorIfNeeded();
    if (mEditor.mTextIsSelectable == selectable) return;

    mEditor.mTextIsSelectable = selectable;
    setFocusableInTouchMode(selectable);
    setFocusable(FOCUSABLE_AUTO);
    setClickable(selectable);
    setLongClickable(selectable);

    // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null

    setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
    setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);

    // Called by setText above, but safer in case of future code changes
    mEditor.prepareCursorControllers();
}

Editor.java

void prepareCursorControllers() {
    boolean windowSupportsHandles = false;

    ViewGroup.LayoutParams params = mTextView.getRootView().getLayoutParams();
    if (params instanceof WindowManager.LayoutParams) {
        WindowManager.LayoutParams windowParams = (WindowManager.LayoutParams) params;
        windowSupportsHandles = windowParams.type < WindowManager.LayoutParams.FIRST_SUB_WINDOW
                || windowParams.type > WindowManager.LayoutParams.LAST_SUB_WINDOW;
    }

    boolean enabled = windowSupportsHandles && mTextView.getLayout() != null;
    mInsertionControllerEnabled = enabled && isCursorVisible();
    **mSelectionControllerEnabled = enabled && mTextView.textCanBeSelected();**

    if (!mInsertionControllerEnabled) {
        hideInsertionPointCursorController();
        if (mInsertionPointCursorController != null) {
            mInsertionPointCursorController.onDetached();
            mInsertionPointCursorController = null;
        }
    }

    if (!mSelectionControllerEnabled) {
        stopTextActionMode();
        if (mSelectionModifierCursorController != null) {
            mSelectionModifierCursorController.onDetached();
            mSelectionModifierCursorController = null;
        }
    }
}

---> TextView.java
/**
 * Test based on the <i>intrinsic</i> charateristics of the TextView.
 * The text must be spannable and the movement method must allow for arbitary selection.
 *
 * See also {@link #canSelectText()}.
 */
boolean textCanBeSelected() {
    // prepareCursorController() relies on this method.
    // If you change this condition, make sure prepareCursorController is called anywhere
    // the value of this condition might be changed.
    if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
    return isTextEditable()
            || (isTextSelectable() && mText instanceof Spannable && isEnabled());
}

您可以在模拟器中进行调试并跟踪此代码。


1
哇,如果可以的话,我会给你100个加号。在搜索和尝试了许多小时后,这是唯一有效的解决方案。许多解决方案不起作用,或者对旧版或新版的Android有奇怪的副作用。但是这个解决了我的大型文本视图在Android 6和10中的列表视图中的问题。 - svn
1
我也可以确认这个方法适用于我,似乎根本问题仍未得到解决。https://issuetracker.google.com/issues/37095917 - wasyl
1
另一个适当且更好的解决方案是 fun TextView.fixTextSelection() { setTextIsSelectable(false) post { setTextIsSelectable(true) } } - sudoExclaimationExclaimation
1
谢谢 @sudoExclaimationExclaimation,在我对API 34进行测试时,你的答案似乎更可靠。 - Eric

6

如果在recyclerviewlistview中添加了android:descendantFocusability="blocksDescendants",请将其删除。待检查后再进行操作。


6
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
    yourTextView.fixTextSelection()
}

fun TextView.fixTextSelection() {
    setTextIsSelectable(false)
    post { setTextIsSelectable(true) }
}

1
这个救了我!比上面描述的“measure”解决方案好多了。 - sudoExclaimationExclaimation
这个是最可靠的一个。对我来说,测量解决方案在第一次和第二次文本选择时有效,但之后就不再起作用了。而这个则一直能够正常工作。 - Eric

1

看起来有很多人在这方面遇到了问题,并且有迹象表明这可能是Android代码中的一个错误,但我却没有遇到任何问题。这是对于OnClickListener()和本地选择弹出窗口对我有效的解决方法。(在KitKat 4.4、Lollipop 5.1和Nougat 7.1上测试通过)

在适配器中

class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    TextView textView;
    ImageView imageView;

    MyViewHolder(View itemView) {
        super(itemView);
        textView = (TextView) itemView.findViewById(R.id.my_text_view);
        imageView = (ImageView) itemView.findViewById(R.id.my_image_view);

        itemView.setOnClickListener(this);
        textView.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        // this shows 'my_text_view' when the text is clicked or 
        //     'my_item' if elsewhere is clicked
        Log.d(TAG, "view = " + view.toString());
        switch (view.getId()) {
            case R.id.my_item:
                break;
            case R.id.my_text_view:
                break;
        }
    }
}

和我的项目布局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/my_item"
    >

    <ImageView
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:background="@color/colorPrimary"
        android:id="@+id/my_image_view"
        />

    <!-- this works for me with either "match_parent" or "wrap_content" for width -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="20dp"
        android:text="My text view"
        android:textIsSelectable="true"
        android:id="@+id/my_text_view"
        />
</LinearLayout>

1
在您的 RecyclerView 适配器中添加以下内容:
public ViewHolder(View itemView) {
            super(itemView);
            txtDate = (TextView) itemView.findViewById(R.id.txtDate);
            txtDate.setTextIsSelectable(true);
}

它对我有用...


0
如果你的TextViewConstraintLayout内部,请确保宽度不是wrap_content。使用TextView宽度0dpmatch_parent即可正常工作。

0

我根据@Artem的答案创建了一个辅助函数

onBindViewHolder中的用法:

textView.setSelectableText("hello world!")

Extensions.kt 中的辅助函数:

/**
 * https://dev59.com/61oV5IYBdhLWcg3wA66x#61126872
 */
fun TextView.setSelectableText(text:CharSequence?)
{
    setText(text)
    setTextIsSelectable(false)
    post { setTextIsSelectable(true) }
}

0
我发现我必须在一段时间后设置TextView的文本和宽度。因此,我将这个属性(android:textIsSelectable="true")放在TextView的xml布局中,并在recyclerView适配器的onBindViewHolder方法中使用post{}来设置宽度和文本,像这样:
class ContentAdapter(): ListAdapter<Verse, ContentAdapter.ViewHolder>(DiffCallback()) {
    .
    .
    .
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
            val item = getItem(position)
            holder.bind(item)
    }

    class ViewHolder(val binding: ItemBinding): RecyclerView.ViewHolder(binding.root) {

        fun bind(item: Verse){
            binding.myTextView.apply{
                val params = layoutParams as ConstraintLayout.LayoutParams
                params.width = 100 /*any value you want for the width of your TextView*/
                post{
                    layoutParams = params
                    text = item.text
                }
            }
        }

        companion object {
            fun from(parent: ViewGroup): ViewHolder{
                val layoutInflater = LayoutInflater.from(parent.context)
                val binding = ItemBinding.inflate(layoutInflater, parent, false)
                return ViewHolder(binding)
            }
        }
    }

}

0

最终可行的解决方案

在您的onBindView中编写以下代码!

    textView.text = "the content"

    textView.setTextIsSelectable(false)
    textView.post { txtContent.setTextIsSelectable(true) }

或者更高级的版本可以在TextView上编写扩展函数

fun TextView.fixTextSelection(){
   setTextIsSelectable(false)
   post { setTextIsSelectable(true) }
}

并像这样使用它

    textView.text = "the content"

    textView.fixTextSelection()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接