How to set a rectangular dotted / dashed line around partial text in a TextView?

Background

There are many ways to style part of the text displayed in the TextView, for example, setting the foreground color ( here ) and others ( here ).

Problem

I cannot figure out if there is a way to set a rectangular dashed / dashed line on partial TextView text. Something like that:

enter image description here

What i tried

I tried to find such a solution, and also tried to read the CharacterStyle documentation. However, I do not see any of the gaps available as good candidates for this style.

Question

Is there a built-in solution for this or do I need to use an individual implementation?




I used the modified version of the one below , and it worked fine on POC, but for some reason the real project, the vertical dashed lines on the sides of the text are in bold:

enter image description here

Here's the current code:

for use

<string name="text_to_format">test &#160;&#160;%1$s test</string> 

usage code

  final String textToDash="DASHED"; String formattedStr = getString(R.string.text_to_format, textToDash+ "<bc/>"); Spanned textToShow = Html.fromHtml(formattedStr, null, new TagHandler() { int start; @Override public void handleTag(final boolean opening, final String tag, Editable output, final XMLReader xmlReader) { switch (tag) { case "bc": if (!opening) start = output.length() - textToDash.length(); break; case "html": if (!opening) output.setSpan( new DrawableSpan(ResourcesCompat.getDrawable(getResources(), R.drawable.dashed_border_shape, null)), start, start + textToDash.length(), 0); } } }); textView.setText(textToShow); 

DrawableSpan

 public class DrawableSpan extends ReplacementSpan { private Drawable mDrawable; private final Rect mPadding; public DrawableSpan(Drawable drawable) { super(); mDrawable = drawable; mPadding = new Rect(); mDrawable.getPadding(mPadding); } @Override public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) { RectF rect = new RectF(x, top, x + measureText(paint, text, start, end), bottom); mDrawable.setBounds((int) rect.left - mPadding.left, (int) rect.top - mPadding.top, (int) rect.right + mPadding.right, (int) rect.bottom + mPadding.bottom); canvas.drawText(text, start, end, x, y, paint); mDrawable.draw(canvas); } @Override public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { return Math.round(paint.measureText(text, start, end)); } private float measureText(Paint paint, CharSequence text, int start, int end) { return paint.measureText(text, start, end); } } 

Res / Hood / dashed_border_shape.xml

 <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <padding android:bottom="1dp" android:left="4dp" android:right="4dp" android:top="1dp"/> <solid android:color="@android:color/transparent"/> <stroke android:width="2dp" android:color="#ff474747" android:dashGap="10px" android:dashWidth="10px"/> </shape> 

There is nothing special about TextView:

  <TextView android:id="@+id/..." android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginBottom="20dp" android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:layout_marginTop="30dp" android:gravity="center_horizontal" android:textSize="20sp"/> 

I even set android: clipChildren = "false", android: clipToPadding = "false" for several parents of this view (thinking that it does not draw when it tries). Nothing helped.

Why, and what should I do to fix this?

+2
android android-textview spannablestring
Jan 30 '17 at 14:30
source share
1 answer

Solution 1

1 - Create a selection for the dash. Like this:

 <shape android:shape="rectangle" xmlns:android="http://schemas.android.com/apk/res/android"> <corners android:radius="5dp" /> <solid android:color="@android:color/transparent" /> <stroke android:color="@android:color/black" android:dashWidth="20px" android:dashGap="10px" android:width="3dp"/> </shape> 

2 - set it as the background of your text view, it can be just a word.

 <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="8dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello!"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/button_shape" android:padding="4dp" android:text="world!"/> </LinearLayout> 

Result:

enter image description here

Important Note. . This solution will work only for small text, for example, to show points in the game or small messengers. He will not adapt to large texts.

Decision 2

If you need a more complex solution that works for large texts, you can use Spannable.

1 → Create Custom Span Replacement

  public class DashedBorderSpan extends ReplacementSpan { private Drawable mDrawable; private int mPadding; public DashedBorderSpan(Drawable drawable, int padding) { super(); mDrawable = drawable; mPadding = padding; } @Override public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) { RectF rect = new RectF(x - mPadding, top - mPadding, x + measureText(paint, text, start, end) + mPadding, bottom + mPadding); mDrawable.setBounds((int) rect.left, (int)rect.top, (int)rect.right, (int)rect.bottom); canvas.drawText(text, start, end, x, y, paint); mDrawable.draw(canvas); } @Override public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { return Math.round(paint.measureText(text, start, end)); } private float measureText(Paint paint, CharSequence text, int start, int end) { return paint.measureText(text, start, end); } } 

2 → Apply Spannable

 TextView textView = (TextView) findViewById(R.id.textasd); String hello = "Dashed!"; SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); stringBuilder.append(hello); stringBuilder.setSpan(new DrawableSpan(getDrawable(R.drawable.dashed_border_shape)), 0, stringBuilder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); stringBuilder.append("not dashed... boring"); textView.setText(stringBuilder); 

This solution will work for all cases. This is the best solution, although it is more complicated.

Example If you want to use it with a place owner, use it like this:

  String someText = "Some Text!"; //R.string.placeholder = Hello: %s String formatedText = String.format(getString(R.string.placeholder), someText); SpannableStringBuilder stringBuilderPlaceHolder = new SpannableStringBuilder(); stringBuilderPlaceHolder.append(formatedText); stringBuilderPlaceHolder.setSpan(new DashedBorderSpan(getDrawable(R.drawable.dashed_border_shape), 10), formatedText.length() - someText.length(), formatedText.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); textViewPlaceHolder.setText(stringBuilderPlaceHolder); 

Result: enter image description here

Thus, the range will be set only in your place. If you have a more complex holder, use the same logic to achieve what you need.

Edit

There is a small problem with solution 2, but there is a solution .

You must take care to fill out the dotted box. If you use registration in a dotted frame, you will need to install an add-on in TextView that uses Span. In the image provided by the author of the question, you can see that the top and bottom lines were cropped (if you increase the filling, the lines will be completely deleted) to avoid using this add-on in your text form. Like this:

 <TextView android:id="@+id/text" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginBottom="20dp" android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:layout_marginTop="30dp" android:paddingTop="3dp" <!-- This will fix the problem! --> android:paddingBottom="3dp" <!-- This will fix the problem! --> android:gravity="center_horizontal" android:text="blabla" android:textSize="20sp"/> 

This will fix the problem =] Happy coding!

+6
Jan 30 '17 at 16:18
source share



All Articles