Android Spannablecontent With Rounded Corners
我正在尝试使用 Spannable String 更改我的字符串以制作中间带有数字的徽章。我可以通过设置 BackGroundColorSpan 突出显示适当的字母/数字,但需要帮助使它更漂亮一些。我希望在整个形状周围有一点填充的圆角。
这篇文章真的很接近我想要做的:Android SpannableString set background behind part of text
由于它与我的应用程序交互的方式,我确实需要将资源保留为 TextView。
有什么想法可以针对我的特定情况使用 ReplacementSpan 吗?
这是我的代码片段:
1 2 3 4 5 6 7 | if (menuItem.getMenuItemType() == SlidingMenuItem.MenuItemType.NOTIFICATIONS) { myMenuRow.setTypeface(null, Typeface.NORMAL); myMenuRow.setTextColor(getContext().getResources().getColor(R.color.BLACK)); myMenuRow.setActivated(false); SpannableString spannablecontent = new SpannableString(myMenuRow.getText()); spannablecontent.setSpan(new BackgroundColorSpan(Color.argb(150,0,0,0)), 18, myMenuRow.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); myMenuRow.setText(spannablecontent); |
实际上,在显示多行徽章时,我发现所有这些答案都存在很大问题。经过大量的测试和调整。我终于得到了上面最好的版本。
基本思想是通过设置更大的文本大小并在跨度内设置想要的大小来欺骗 TextView。此外,您可以看到我正在以不同的方式绘制徽章背景和文本。
所以,这是我的 RoundedBackgroundSpan:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | public class RoundedBackgroundSpan extends ReplacementSpan { private static final int CORNER_RADIUS = 12; private static final float PADDING_X = GeneralUtils.convertDpToPx(12); private static final float PADDING_Y = GeneralUtils.convertDpToPx(2); private static final float MAGIC_NUMBER = GeneralUtils.convertDpToPx(2); private int mBackgroundColor; private int mTextColor; private float mTextSize; /** * @param backgroundColor color value, not res id * @param textSize in pixels */ public RoundedBackgroundSpan(int backgroundColor, int textColor, float textSize) { mBackgroundColor = backgroundColor; mTextColor = textColor; mTextSize = textSize; } @Override public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) { paint = new Paint(paint); // make a copy for not editing the referenced paint paint.setTextSize(mTextSize); // Draw the rounded background paint.setColor(mBackgroundColor); float textHeightWrapping = GeneralUtils.convertDpToPx(4); float tagBottom = top + textHeightWrapping + PADDING_Y + mTextSize + PADDING_Y + textHeightWrapping; float tagRight = x + getTagWidth(text, start, end, paint); RectF rect = new RectF(x, top, tagRight, tagBottom); canvas.drawRoundRect(rect, CORNER_RADIUS, CORNER_RADIUS, paint); // Draw the text paint.setColor(mTextColor); canvas.drawText(text, start, end, x + PADDING_X, tagBottom - PADDING_Y - textHeightWrapping - MAGIC_NUMBER, paint); } private int getTagWidth(CharSequence text, int start, int end, Paint paint) { return Math.round(PADDING_X + paint.measureText(text.subSequence(start, end).toString()) + PADDING_X); } @Override public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { paint = new Paint(paint); // make a copy for not editing the referenced paint paint.setTextSize(mTextSize); return getTagWidth(text, start, end, paint); } } |
这是我使用它的方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | public void setTags(ArrayList<String> tags) { if (tags == null) { return; } mTextView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 26); // Tricking the text view for getting a bigger line height SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); String between =""; int tagStart = 0; float textSize = 13 * getResources().getDisplayMetrics().scaledDensity; // sp to px for (String tag : tags) { // Append tag and space after stringBuilder.append(tag); stringBuilder.append(between); // Set span for tag RoundedBackgroundSpan tagSpan = new RoundedBackgroundSpan(bgColor, textColor, textSize); stringBuilder.setSpan(tagSpan, tagStart, tagStart + tag.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); // Update to next tag start tagStart += tag.length() + between.length(); } mTextView.setText(stringBuilder); } |
注意:
- 您可以使用所有尺寸和常数来适应您想要的风格
- 如果您使用外部字体,请务必设置 android:includeFontPadding="false" 否则会弄乱行高
享受:)
这是基于@ericlokness 答案的改进版本,具有自定义背景和文本颜色。它也适用于同一个 TextView 上的多个跨度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | public class RoundedBackgroundSpan extends ReplacementSpan { private final int _padding = 20; private int _backgroundColor; private int _textColor; public RoundedBackgroundSpan(int backgroundColor, int textColor) { super(); _backgroundColor = backgroundColor; _textColor = textColor; } @Override public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { return (int) (_padding + paint.measureText(text.subSequence(start, end).toString()) + _padding); } @Override public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) { float width = paint.measureText(text.subSequence(start, end).toString()); RectF rect = new RectF(x - _padding, top, x + width + _padding, bottom); paint.setColor(_backgroundColor); canvas.drawRoundRect(rect, 20, 20, paint); paint.setColor(_textColor); canvas.drawText(text, start, end, x, y, paint); } } |
在阅读了关于 C# 转换器的一些帮助之后,我想出了这个。我还有一些调整要做,但如果有人也在寻找类似的答案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public class RoundedBackgroundSpan extends ReplacementSpan { @Override public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { return 0; } @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 + text.length(), bottom); paint.setColor(Color.CYAN); canvas.drawRoundRect(rect, 20, 20, paint); paint.setColor(Color.WHITE); canvas.drawText(text, start, end, x, y, paint); } } |
我进一步改进了 mvandillen 类。
这似乎工作得很好:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | public class RoundedBackgroundSpan extends ReplacementSpan { private final int mPadding = 10; private int mBackgroundColor; private int mTextColor; public RoundedBackgroundSpan(int backgroundColor, int textColor) { super(); mBackgroundColor = backgroundColor; mTextColor = textColor; } @Override public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { return (int) (mPadding + paint.measureText(text.subSequence(start, end).toString()) + mPadding); } @Override public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) { float width = paint.measureText(text.subSequence(start, end).toString()); RectF rect = new RectF(x, top+mPadding, x + width + 2*mPadding, bottom); paint.setColor(mBackgroundColor); canvas.drawRoundRect(rect, mPadding, mPadding, paint); paint.setColor(mTextColor); canvas.drawText(text, start, end, x+mPadding, y, paint); } } |
这是我基于@mvandillen 回答的版本。我还需要在 span 开始时留出一些边距。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.RectF; import android.support.annotation.NonNull; import android.text.style.ReplacementSpan; public class CoolBackgroundColorSpan extends ReplacementSpan { private final int mBackgroundColor; private final int mTextColor; private final float mCornerRadius; private final float mPaddingStart; private final float mPaddingEnd; private final float mMarginStart; public CoolBackgroundColorSpan(int backgroundColor, int textColor, float cornerRadius, float paddingStart, float paddingEnd, float marginStart) { super(); mBackgroundColor = backgroundColor; mTextColor = textColor; mCornerRadius = cornerRadius; mPaddingStart = paddingStart; mPaddingEnd = paddingEnd; mMarginStart = marginStart; } @Override public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { return (int) (mPaddingStart + paint.measureText(text.subSequence(start, end).toString()) + mPaddingEnd); } @Override public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, @NonNull Paint paint) { float width = paint.measureText(text.subSequence(start, end).toString()); RectF rect = new RectF(x - mPaddingStart + mMarginStart, top, x + width + mPaddingEnd + mMarginStart, bottom); paint.setColor(mBackgroundColor); canvas.drawRoundRect(rect, mCornerRadius, mCornerRadius, paint); paint.setColor(mTextColor); canvas.drawText(text, start, end, x + mMarginStart, y, paint); } } |
使用方法:
1 2 3 4 5 6 7 8 9 10 | int flag = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE; SpannableString staffTitleSpan = new SpannableString("staff:"); SpannableString staffNameSpan = new SpannableString("John Smith"); staffNameSpan.setSpan(new StyleSpan(Typeface.BOLD), 0, staffNameSpan.length(), flag); staffNameSpan.setSpan(new CoolBackgroundColorSpan(mStaffNameSpanBgColor, mStaffNameSpanTextColor, mStaffNameSpanBgRadius, mStaffNameSpanBgPaddingStart, mStaffNameSpanBgPaddingEnd, mStaffNameSpanMarginStart), 0, staffNameSpan.length(), flag); SpannableStringBuilder builder = new SpannableStringBuilder(); builder.append(staffTitleSpan); builder.append(staffNameSpan); staffTextView.setText(builder); |
预览:
希望这个答案可以为那些仍在寻找的人简化它......
您可以简单地使用"芯片"可绘制对象。它正确地完成了所有计算,并且代码更少。
查看独立的 ChipDrawable
为了完整性,复制到这里:
1 2 3 4 5 | <chip xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:text="@string/item_browse_database_sample" app:chipBackgroundColor="@color/blueBase" app:closeIconVisible="false" /> |
在java中:
1 2 3 4 5 6 7 8 9 | // Inflate from resources. ChipDrawable chip = ChipDrawable.createFromResource(getContext(), R.xml.standalone_chip); // Use it as a Drawable however you want. chip.setBounds(0, 0, chip.getIntrinsicWidth(), chip.getIntrinsicHeight()); ImageSpan span = new ImageSpan(chip); Editable text = editText.getText(); text.setSpan(span, 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); |
最终结果:
好的,所以问题有点乱,这是我从 DanieleB 和 mvandillen 得到的解决方案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | public class RoundedBackgroundSpan extends ReplacementSpan { private static final int CORNER_RADIUS = 8; private static final int PADDING_X = 12; private int mBackgroundColor; private int mTextColor; /** * @param backgroundColor background color * @param textColor text color */ public RoundedBackgroundSpan(int backgroundColor, int textColor) { mBackgroundColor = backgroundColor; mTextColor = textColor; } @Override public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) { return (int) (PADDING_X + paint.measureText(text.subSequence(start, end).toString()) + PADDING_X); } @Override public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) { float width = paint.measureText(text.subSequence(start, end).toString()); RectF rect = new RectF(x, top, x + width + 2 * PADDING_X, bottom); paint.setColor(mBackgroundColor); canvas.drawRoundRect(rect, CORNER_RADIUS, CORNER_RADIUS, paint); paint.setColor(mTextColor); canvas.drawText(text, start, end, x + PADDING_X, y, paint); } } |
提示:您可以删除 textColor 并使用默认的 TextView 颜色:
1 2 3 4 5 6 7 8 9 | @Override public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) { Paint paint1 = new Paint(paint); float width = paint1.measureText(text.subSequence(start, end).toString()); RectF rect = new RectF(x, top, x + width + 2 * PADDING_X, bottom); paint1.setColor(mBackgroundColor); canvas.drawRoundRect(rect, CORNER_RADIUS, CORNER_RADIUS, paint1); canvas.drawText(text, start, end, x + PADDING_X, y, paint); } |
如果您正在使用 kotlin 并针对多个密度设备,那么这将适合您
第 1 步:创建一个扩展
的类,即
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | import android.content.res.Resources import android.graphics.Canvas import android.graphics.Paint import android.graphics.RectF import android.text.style.ReplacementSpan import kotlin.math.roundToInt class RoundedBackgroundSpan( private val textColor: Int, private val backgroundColor: Int ) : ReplacementSpan() { private val additionalPadding = 4.toPx().toFloat() private val cornerRadius = 4.toPx().toFloat() override fun draw( canvas: Canvas, text: CharSequence, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint ) { val newTop = y + paint.fontMetrics.ascent val newBottom = y + paint.fontMetrics.descent val rect = RectF(x, newTop, x + measureText(paint, text, start, end) + 2 * additionalPadding, newBottom) paint.color = backgroundColor canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint) paint.color = textColor canvas.drawText(text, start, end, x + additionalPadding, y.toFloat(), paint) } override fun getSize(paint: Paint, text: CharSequence?, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int { return (paint.measureText(text, start, end) + 2 * additionalPadding).toInt() } private fun measureText(paint: Paint, text: CharSequence, start: Int, end: Int): Float { return paint.measureText(text, start, end) } private fun Int.toPx(): Int { val resources = Resources.getSystem() val metrics = resources.displayMetrics return (this * (metrics.densityDpi / 160.0f)).roundToInt() } } |
第 2 步:之后调用上面创建的类,如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | private fun updateSubjectName(textView: TextView, fullText: String, spanText: String, spanColor: String) { val flag = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE val fullTextSpan = SpannableString("$fullText") val spanTextSpan = SpannableString("$spanText") spanTextSpan.setSpan(StyleSpan(Typeface.BOLD), 0, spanTextSpan.length, flag) spanTextSpan.setSpan( RoundedBackgroundSpan(context.getColor(R.color.color_white), Color.parseColor(spanColor)), 0, spanTextSpan.length, flag ) val builder = SpannableStringBuilder() builder.append(fullTextSpan) builder.append(spanTextSpan) textView.text = builder } |
观看 Google 的视频,他们提供了以下解决方案:
很遗憾,我看到这里缺少很多东西,我找不到完整的代码,所以我无法尝试。