如何在 TextView 中的部分文本周围设置 rectangular-dashed/dotted-line-outline?
How to set a rectangular-dashed/dotted-line-outline around partial text in TextView?
背景
有很多方法可以设置 TextView 中显示的部分文本的样式,例如设置其前景色 (here), and others (here)。
问题
我不知道是否有办法在 TextView 的 部分 文本上设置 rectangular-dashed/dotted-line-outline。像这样:
我试过的
我试图寻找这样的解决方案,也试图阅读 CharacterStyle 的文档。不过,我认为没有任何可用的 span 适合这种风格。
问题
是否有针对此问题的内置解决方案,或者我是否需要使用自定义实现?
我使用了建议 的修改版本,它在 POC 上运行良好,但出于某种原因,在实际项目中,文本两侧的垂直虚线是粗体:
这是当前代码:
要使用的字符串
<string name="text_to_format">test   %1$s test</string>
使用代码
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/drawable/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>
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"/>
我什至为该视图的多个父级设置了 android:clipChildren="false"、android:clipToPadding="false"(认为它在尝试时不会绘制到)。没有任何帮助。
怎么会这样,我应该怎么做才能解决?
解决方案 1
1 - 为破折号创建一个可绘制对象。像这样:
<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 - 将其设置为文本视图的背景,可以只是一个单词。
<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>
结果:
重要说明:此解决方案仅适用于小文本,例如在游戏中显示分数或小消息。它不会适应大文本。
解决方案 2
如果您需要适用于大文本的更复杂的解决方案,您可以使用 Spannable。
1 -> 创建自定义 ReplacementSpan
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 -> 应用 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);
此解决方案适用于所有情况。这是一个更好的解决方案,虽然它更复杂。
例子
如果您想将它与占位符一起使用,请像这样使用它:
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);
结果:
这样跨度将只设置在您的占位符上。如果您有更复杂的持有人,请使用相同的逻辑来实现您的需要。
编辑
方案2有点小问题,不过有解决方案。
您必须注意虚线边框可绘制对象的填充。如果在虚线边框中使用填充,则需要在使用 Span 的 TextView 中设置填充。在问题作者提供的图像中,您可以看到上部和底部的线条被切断(如果增加填充,线条将完全消失),以避免在文本视图中使用填充。像这样:
<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"/>
这将解决问题 =]
编码愉快!
背景
有很多方法可以设置 TextView 中显示的部分文本的样式,例如设置其前景色 (here), and others (here)。
问题
我不知道是否有办法在 TextView 的 部分 文本上设置 rectangular-dashed/dotted-line-outline。像这样:
我试过的
我试图寻找这样的解决方案,也试图阅读 CharacterStyle 的文档。不过,我认为没有任何可用的 span 适合这种风格。
问题
是否有针对此问题的内置解决方案,或者我是否需要使用自定义实现?
我使用了建议
这是当前代码:
要使用的字符串
<string name="text_to_format">test   %1$s test</string>
使用代码
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/drawable/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>
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"/>
我什至为该视图的多个父级设置了 android:clipChildren="false"、android:clipToPadding="false"(认为它在尝试时不会绘制到)。没有任何帮助。
怎么会这样,我应该怎么做才能解决?
解决方案 1
1 - 为破折号创建一个可绘制对象。像这样:
<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 - 将其设置为文本视图的背景,可以只是一个单词。
<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>
结果:
重要说明:此解决方案仅适用于小文本,例如在游戏中显示分数或小消息。它不会适应大文本。
解决方案 2
如果您需要适用于大文本的更复杂的解决方案,您可以使用 Spannable。
1 -> 创建自定义 ReplacementSpan
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 -> 应用 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);
此解决方案适用于所有情况。这是一个更好的解决方案,虽然它更复杂。
例子 如果您想将它与占位符一起使用,请像这样使用它:
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);
结果:
这样跨度将只设置在您的占位符上。如果您有更复杂的持有人,请使用相同的逻辑来实现您的需要。
编辑
方案2有点小问题,不过有解决方案。
您必须注意虚线边框可绘制对象的填充。如果在虚线边框中使用填充,则需要在使用 Span 的 TextView 中设置填充。在问题作者提供的图像中,您可以看到上部和底部的线条被切断(如果增加填充,线条将完全消失),以避免在文本视图中使用填充。像这样:
<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"/>
这将解决问题 =] 编码愉快!