Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TextView maxLines, movement method and ellipsize

I have a TextView with maxLines:5 and ellipsize:end applied, I'm also using setMovementMethod(LinkMovementMethod.getInstance()) on the TextView to make links clickable (HTML content).

The combination of all of the above disables the text being truncated and the '...' suffix to be appended.

Any idea what goes wrong and how to work around it?

Without setting the movement method, everything works as expected.

Update regarding bounty: looking for solution other than manually setting ellipses

like image 952
Aviran Avatar asked Sep 05 '17 13:09

Aviran


3 Answers

Sorry I'm late on this one.

Here is little work around for this

MainActivity

public class MainActivity extends AppCompatActivity {


    TextView htmlTextView;

    CustomEllipsizeTextView  customEllipsizeTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        htmlTextView = findViewById(R.id.htmlTextView1);

        customEllipsizeTextView = findViewById(R.id.customEllipsizeTextView);

        String value = "Hello this is a dummy textview";

        String myText = "You can visit my  Profile in <a href=\"https://stackoverflow.com/users/7666442/nilesh-rathod?tab=profile\">stackoverflow</a> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis bibendum mattis risus eget pulvinar. Praesenttingd" +
                " commodo erat enim, id564654 congue sem tristique vitae. Proin vitae accumsan justo, ut imperdiet Mauris neque nibh, hendrerit id tortor vel, congue sagittis odio. Morbi elementum lobortis maximus. Etiam sit amet porttitor massa. Fusce sed magna quis arcu tincidunt finibus vitae id erat. " +
                "commodo erat enim, id54654 congue sem tristique vitae. Proin vitae accumsan commodo erat enim, id congue sem tristique vitae. Proin vitae accumsan Pellentesque massa mi, imperdiet eget accums ";


        SpannableString spanText2 = new SpannableString(myText);
        htmlTextView.setText(value);

        if (Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
            customEllipsizeTextView.setText(Html.fromHtml(spanText2.toString(), Html.FROM_HTML_MODE_LEGACY));
        } else {
            customEllipsizeTextView.setText(Html.fromHtml(spanText2.toString()));
        }

        htmlTextView.setMovementMethod(LinkMovementMethod.getInstance());
        customEllipsizeTextView.setMovementMethod(LinkMovementMethod.getInstance());
        customEllipsizeTextView.setOnTouchListener(new TouchTextView(spanText2));

    }

}

layout.activity_main

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".FirstFragment">



    <TextView
        android:id="@+id/htmlTextView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:maxLines="1"
        android:text="@string/link_text" />

    <neel.com.demo.CustomEllipsizeTextView
        android:id="@+id/customEllipsizeTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ellipsize="end"
        android:linksClickable="true"
        android:maxLines="5"
        android:padding="5dp"
        android:visibility="visible" />


</LinearLayout>

CustomEllipsizeTextView

public class CustomEllipsizeTextView extends android.support.v7.widget.AppCompatTextView {
    public CustomEllipsizeTextView(Context context) {
        super(context);
    }

    public CustomEllipsizeTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomEllipsizeTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        StaticLayout layout = null;
        Field field = null;
        try {
            Field staticField = DynamicLayout.class.getDeclaredField("sStaticLayout");
            staticField.setAccessible(true);
            layout = (StaticLayout) staticField.get(DynamicLayout.class);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        if (layout != null) {
            try {
                field = StaticLayout.class.getDeclaredField("mMaximumVisibleLineCount");
                field.setAccessible(true);
                field.setInt(layout, getMaxLines());
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (layout != null && field != null) {
            try {
                field.setInt(layout, Integer.MAX_VALUE);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
}

TouchTextView

public class TouchTextView implements View.OnTouchListener {
    Spannable spannable;

    public TouchTextView (Spannable spannable){
        this.spannable = spannable;
    }
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        int action = event.getAction();
        if(!(v instanceof TextView)){
            return false;
        }
        TextView textView  = (TextView) v;
        if (action == MotionEvent.ACTION_UP ||
                action == MotionEvent.ACTION_DOWN) {
            int x = (int) event.getX();
            int y = (int) event.getY();

            x -= textView.getTotalPaddingLeft();
            y -= textView.getTotalPaddingTop();

            x += textView.getScrollX();
            y += textView.getScrollY();

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

            ClickableSpan[] link = spannable.getSpans(off, off, ClickableSpan.class);

            if (link.length != 0) {
                if (action == MotionEvent.ACTION_UP) {
                    link[0].onClick(textView);
                } else if (action == MotionEvent.ACTION_DOWN) {
                    Selection.setSelection(spannable,
                            spannable.getSpanStart(link[0]),
                            spannable.getSpanEnd(link[0]));
                }

                return true;
            } else {
                Selection.removeSelection(spannable);
            }
        }

        return false;
    }
}

OUTPUT

enter image description here

Here is the explanation :

I have debugged TextView and found out the following :

So when you use LinkMovementMethod() actually text is acting as Spannable. In other case it is String.

There is one following condition inside TextView

if (mText instanceof Spannable) {
//executes incase of LinkMovementMethod
            result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
                    alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad,
                    mBreakStrategy, mHyphenationFrequency, mJustificationMode,
                    getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth);
        } else { 
   //executes without any movementmethod
..create StaticLayout
}

So DynamicLayout internally calls StaticLayout to render text, but it is not setting mMaximumVisibleLineCountinside StaticLayout when coming from DynamicLayout so it is default Integer.MAX_VALUE. But when creating StaticLayout from String, it is actually setting mMaximumVisibleLineCount as maxLines. This mMaximumVisibleLineCount is used for displaying ellipsize. That's why "..." is not displaying.

For displaying the number of lines, the following code works

if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
            unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
        }

mMaximum will be set to maxLines in both case, but mLayout.getLineCount() will be maxLines for one without MovementMethod and for with MovementMethod it will be number of lines of original string

like image 139
AskNilesh Avatar answered Oct 18 '22 01:10

AskNilesh


Try this code.

           <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/white"
            android:textStyle="bold"
            android:textSize="20dp"
            android:id="@+id/txtTitle"
            android:text="" />

           String message="<font color='gray'>"+"YOUR CONTENT"+ "<br>" +"<font color='cyan'>"+"<font size='5'>"+" "+"</font>";
           txtTitle.setBackgroundColor(Color.TRANSPARENT);
           txtTitle.setText(message);
like image 40
Atman Bhatt Avatar answered Oct 18 '22 01:10

Atman Bhatt


Try this code.

XML

<TextView
 android:id="@+id/tvcondition1"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:textColor="@color/white"
 android:textStyle="bold"
 android:ellipsize="end"
 android:maxLines="5"
 android:textSize="15sp" />

Java file Code

    String strTeamsCondition = "<b><u> Terms &amp; Condition </u></b>";
    String conditionlimit = "Terms & Condition";
    String strConditionFirstLine = getString(R.string.condition_1);
    String condition_1_1 = getString(R.string.condition_1_1);
    String htmlAsString2 = strConditionFirstLine + strTeamsCondition + condition_1_1;
    Spanned htmlAsSpannedCondition = Html.fromHtml(htmlAsString2);


    tvcondition1.setText(htmlAsSpannedCondition);
    Spannable spanText = new SpannableString(htmlAsSpannedCondition);
    spanText.setSpan(new MyClickableSpan(htmlAsSpannedCondition), strConditionFirstLine.length(), strConditionFirstLine.length() + conditionlimit.length() + 1, 0);
    tvcondition1.setText(spanText);
    tvcondition1.setMovementMethod(LinkMovementMethod.getInstance());

Touch Selected Text

 class MyClickableSpan extends ClickableSpan {
        public MyClickableSpan(Spanned string) {
            super();
        }

        public void onClick(View tv) {
            Toast.makeText(getApplicationContext(), "Thanks for the click!",
                Toast.LENGTH_SHORT).show();

        }

        public void updateDrawState(TextPaint ds) {

            ds.setColor(getResources().getColor(R.color.black));
            ds.setUnderlineText(true); // set to false to remove underline
        }

    }
like image 31
Paresh Godhani Avatar answered Oct 18 '22 02:10

Paresh Godhani