Home » Android » android – TextView selection with Spannable and LinkMovementMethod

android – TextView selection with Spannable and LinkMovementMethod

Posted by: admin May 14, 2020 Leave a comment

Questions:

What i did so far is a list view of textviews having the normal text and clickable spans:
Item untouched

Clicking the span i’m opening the URL, clicking the item View around the textView leads to the listView OnItemClickListener navigating to the item details, that’s fine:
Item touched outside the textView

Now the problem is:

Item textView normal text touched
Item textView span touched
touching the textView makes the normal text be kinda highlighted (with the same color it has when the item is selected completely), textView’s OnTouchListener touch event fires but not OnFocusChangeListener event and the item’s View does not get the selection style. Tried all the variations of FOCUS_BLOCK_DESCENDANTS for listView, item View, the textView focusable was enabled or disabled with the same result.

Fortunately, textView OnClickListener event fires this way, but that’s so ugly: the text is invisible while the touch is not released as the selected text color is the same as the item color, there’s no other indication that the user is going to the item details other than that ugly text vanishing.

I suspect that happens because the content of the textView is Spannable, and the parts which are not CliclableSpan-s behave in this strange way.

Any chance i could select the item once the normal text is touched ?


The listView item layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:descendantFocusability="blocksDescendants"
    android:orientation="horizontal" >

    <ImageView
        android:id="@+id/icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginBottom="5dp"
        android:layout_marginLeft="5dp"
        android:layout_marginRight="5dp"
        android:layout_marginTop="5dp"
        android:focusable="false" />

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >

        <TextView
            android:id="@+id/title"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="5dp"
            android:layout_marginTop="5dp"
            android:focusable="false"
            android:text=""
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:textStyle="bold" />

        <TextView
            android:id="@+id/info"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="4dp"
            android:layout_marginLeft="4dp"
            android:layout_marginRight="4dp"
            android:focusable="false"
            android:text=""
            android:textAppearance="?android:attr/textAppearanceSmall" />

        <TextView
            android:id="@+id/details"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="4dp"
            android:layout_marginLeft="4dp"
            android:layout_marginRight="4dp"
            android:focusable="false"
            android:gravity="fill_horizontal|right"
            android:text=""
            android:textAppearance="?android:attr/textAppearanceMedium"/>

    </LinearLayout>

</LinearLayout>

With the text view setClickable(false) i’m able to disable this weird selection style in the way that nothing happens while touching the text view area, not good but might be useful for solution.

Also tried to add not focusable & not clickable button to each item, when it’s touched the complete item is selected and when touch is released the item’s click event is passed, that’s exactly what i expected from the textView with Spannable content.

How to&Answers:

Did you try setting the background of your TextView to Android’s default list_selector_background?

    textView.setMovementMethod(LinkMovementMethod.getInstance());
    textView.setBackgroundResource(android.R.drawable.list_selector_background);

For me it seems to give the result that you want.

UPDATE: After seeing that the item is not just a single TextView

Well, this is not a perfect solution (since it’s probably better to fix the TextView – ListView highlighting interaction somehow), but it works well enough.

I figured out that instead of setting the movement method on the TextView (that triggers the issue), it is just simpler to check in the ListView’s onItemClick() (after a click is definitely confirmed) to see if we should launch the onClick() on our ClickableSpans:

public class MyActivity extends Activity {

    private final Rect mLastTouch = new Rect();

    private boolean spanClicked(ListView list, View view, int textViewId) {
        final TextView widget = (TextView) view.findViewById(textViewId);
        list.offsetRectIntoDescendantCoords(widget, mLastTouch);
        int x = mLastTouch.right;
        int y = mLastTouch.bottom;

        x -= widget.getTotalPaddingLeft();
        y -= widget.getTotalPaddingTop();
        x += widget.getScrollX();
        y += widget.getScrollY();

        final Layout layout = widget.getLayout();
        final int line = layout.getLineForVertical(y);
        final int off = layout.getOffsetForHorizontal(line, x);

        final Editable buffer = widget.getEditableText();
        final ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class);

        if (link.length == 0) return false;

        link[0].onClick(widget);
        return true;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // ...

        listView.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                if (spanClicked(listView, view, R.id.details)) return;

                // no span is clicked, normal onItemClick handling code here ..                    
            }
        });
        listView.setOnTouchListener(new OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (event.getAction() == MotionEvent.ACTION_UP) {
                    mLastTouch.right = (int) event.getX();
                    mLastTouch.bottom = (int) event.getY();
                }
                return false;
            }
        });

        // ...
    }

}

The spanClicked() method is basically an abbreviated version of the LinkMovementMethod’s onTouchEvent() method in the framework. To capture the last MotionEvent coordinates (to check for the click event), we simply add an OnTouchListener on our ListView.

Answer:

Use TextView‘s setHighlightColor(Color.TRANSPARENT);

Answer:

I think best way to solve this problem is using a selector. you can chan selection backgroun color so they will seen same

example selector code:

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true"
       android:color="#000000" /> <!-- pressed -->
    <item android:state_focused="true"
       android:color="#000000" /> <!-- focused -->
    <item android:color="#FFFFFF" /> <!-- default -->
</selector>

implement sth like that to your link.

let me know about result

Answer:

Quote from documentation of the setMovementMethod() of TextView

Sets the movement method (arrow key handler) to be used for this
TextView. This can be null to disallow using the arrow keys to move
the cursor or scroll the view.

Be warned that if you want a TextView
with a key listener or movement method not to be focusable, or if you
want a TextView without a key listener or movement method to be
focusable, you must call android.view.View.setFocusable(boolean) again
after calling this to get the focusability back the way you want it.

The second paragraph explains that we should explicitly call the setFocusable method after setting the LinkMovementMethod to get focusability working the way we want.

You need to do it in code after inflating the list item.
Once you are able to get the focus, you can set the selector to have a different color text.

Here is the source code of setMovementMethod.