38

I know that setting android:textIsSelectable="true" in xml for the TextView will show the native text selection popup and I've been using that in my application. But what I found that it is not working any more when I try to set the same attribute in a view attached to the RecyclerView. Whenever I try to select the text the following log appears -

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

And I don't know why? Why it works on other screens and not with the RecyclerView. I read multiple posts -

TextView with android:textIsSelectable="true" not working in listview

textview textIsSelectable="true" not working in Listview

android:textIsSelectable="true" for TextView inside Listview does not work

But then I encountered this post -

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

And the reply by @hungkk worked for me. His solution suggested the TextView width to change to wrap_content from match_parent.

I know I can do this but my question is how this fixed the issue because it looks weird to me. And also, what is the solution if I want to keep the width to match_parent.

Any inputs are welcome.

Community
  • 1
  • 1
Shadab Ansari
  • 7,022
  • 2
  • 27
  • 45
  • 1
    What are you doing with the selection? (Marty or Shadab). I don't have any problems using `View.OnClickListener()` for either `match_parent` or `wrap_content`. – Gary99 Jun 18 '17 at 14:59
  • 1
    Wierd but I've read in a few posts that when the recycler's view reuses the cell, it disables the selectable text feature if the TextView is set to match_parent. Have you tried setting `android:inputType="textMultiLine"'` instead of `android:textIsSelectable="true"` as a workaround? – fmaccaroni Jun 19 '17 at 01:22
  • Did you find any solution ? This question was asked 6 years back but I am still facing the same issue. – Khushbu Shah Jun 10 '22 at 07:12

10 Answers10

14

In the main-parent layout of recyclerview add attribute

android:descendantFocusability="beforeDescendants"

and then in TextView of rowitem layout add

android:textIsSelectable="true"
Nitin Patel
  • 1,605
  • 13
  • 31
13

I found TextView in RecyclerView can select first time ,but when ViewHolder was recycled or adapter notifyDataSetChanged,all text view will can't be selected. And I found this solution was working for me.

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

Why do this? because I have debugged and found some logic in android source code:

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());
}

you can debug in Emulator and trace this code.

Shoad
  • 192
  • 1
  • 6
  • 1
    Wow, I would give you 100 plusses if I could. This is the only solution that worked for me after many hours of searching and trying. Many solutions do not work or have strange side effects on either older or newer android versions. But this solved the problem for my large textviews in a listview in android 6 and 10. – svn Sep 16 '20 at 14:53
  • 1
    Can confirm this works for me as well, seems like the underlying issue is still not fixed https://issuetracker.google.com/issues/37095917 – wasyl Oct 01 '20 at 11:46
  • 1
    Another proper and better solution is `fun TextView.fixTextSelection() { setTextIsSelectable(false) post { setTextIsSelectable(true) } }` – sudoExclaimationExclaimation Aug 06 '21 at 04:19
  • 1
    thank you @sudoExclaimationExclaimation, in my testing on API 34, your answer seems to work more reliably – Eric Aug 15 '23 at 08:07
6

If you add android:descendantFocusability="blocksDescendants"​ in the recyclerview or listview, then remove it. And after check this

AskNilesh
  • 67,701
  • 16
  • 123
  • 163
Bhavin Soni
  • 94
  • 1
  • 3
5
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
    yourTextView.fixTextSelection()
}

fun TextView.fixTextSelection() {
    setTextIsSelectable(false)
    post { setTextIsSelectable(true) }
}
Artem Odnovolov
  • 101
  • 1
  • 4
1

There seems to be many that have problems with this and indications that it may be a bug in the Android code but I don't have a problem. This is what works for me both for an OnClickListener() and the native selection popup. (Tested on KitKat 4.4, Lollipop 5.1 and Nougat 7.1)

In the adapter

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;
        }
    }
}

And my item layout

<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>
Gary99
  • 1,750
  • 1
  • 19
  • 33
1

Add In Your RecyclerView Adapter:

public ViewHolder(View itemView) {
            super(itemView);
            txtDate = (TextView) itemView.findViewById(R.id.txtDate);
            txtDate.setTextIsSelectable(true);
}

its worked for me..

husen
  • 180
  • 1
  • 2
  • 11
0

If your TextView is inside ConstraintLayout, make sure the width is not wrap_content. With TextView width 0dp or match_parent this works fine.

Mikhail
  • 800
  • 8
  • 21
0

I found I have to set the TextView text and its width after a while. So I put this attribute (android:textIsSelectable="true") in xml layout of TextView and post{} the width and the text in onBindViewHolder method of the recyclerView adapter like this:

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)
            }
        }
    }

}
mharam
  • 29
  • 1
  • 3
0

Final And Working Solution

In Your onBindView write your code like this!

    textView.text = "the content"

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

or advanced version could be writing an extension function on TextView

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

and use it like this

    textView.text = "the content"

    textView.fixTextSelection()
AmirahmadAdibi
  • 358
  • 3
  • 9
0

I created a helper function based on @Artem's answer

usage in onBindViewHolder:

textView.setSelectableText("hello world!")

helper function in Extensions.kt:

/**
 * https://stackoverflow.com/a/61126872/2898715
 */
fun TextView.setSelectableText(text:CharSequence?)
{
    setText(text)
    setTextIsSelectable(false)
    post { setTextIsSelectable(true) }
}
Eric
  • 16,397
  • 8
  • 68
  • 76