【问题标题】:Android Spannablecontent With Rounded Corners带有圆角的 Android Spanablecontent
【发布时间】:2014-09-02 07:36:34
【问题描述】:

我正在尝试使用 Spannable String 更改我的字符串以制作中间带有数字的徽章。我可以通过设置 BackGroundColorSpan 突出显示适当的字母/数字,但需要帮助使其更漂亮一些。我希望在整个形状周围有一点填充的圆角。

这篇文章真的很接近我想要做的事情:Android SpannableString set background behind part of text

由于它与我的应用程序交互的方式,我确实需要将资源保留为 TextView。

有什么想法可以针对我的特定情况使用 ReplacementSpan 吗?

这是我的代码 sn-p:

            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);

【问题讨论】:

  • 张贴你想做的图片
  • 我的名声不够高...
  • 好吧,将它添加到我的原始帖子中。像这样的东西:i59.tinypic.com/16h7srr.png
  • 好的,那么现在那个蓝色圆形矩形有什么问题?
  • 你问我想做什么。这就是我想要的样子。现在它只是一个方形 BackgroundColorSpan 项目,我的项目周围没有圆角。

标签: java android icons


【解决方案1】:

实际上,在显示多行徽章时,我发现所有这些答案都存在很大问题。经过大量的测试和调整。我终于得到了上面最好的版本。

基本思想是通过设置更大的文本大小并在跨度内设置想要的大小来欺骗 TextView。此外,您可以看到我正在以不同的方式绘制徽章背景和文本。

所以,这是我的 RoundedBackgroundSpan:

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);
    }
}

这是我的使用方法:

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" 否则会弄乱行高

享受:)

【讨论】:

  • 我添加了 MAGIC_NUMBER,因为由于某种原因,我无法让文本垂直居中,我不知道为什么,但它被绘制在 2 dp 下面。这已在多种分辨率上进行了测试。
  • 这个解决方案给了我一个很好的起点。我使用了额外的行距来避免传递字体大小。
  • 据我记得,如果超过 2 行,额外的行间距会使第一行或最后一行的高度不同。这就是我这样做的原因。
  • 感谢提醒 - 刚刚确认在我这边看起来不错,有任意数量的行。但你是对的,额外的间距只在行之间,而不是在最后一行之下,所以我最终画在TextView 边界之外。为了解决这个问题,我在视图底部添加了填充 - 一种黑客或另一种方式。
  • 太好了,很高兴我的回答对您有所帮助:)
【解决方案2】:

这是基于@ericlokness 答案的改进版本,具有自定义背景和文本颜色。它也适用于同一个 TextView 上的多个跨度。

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);
  }
}

【讨论】:

    【解决方案3】:

    在阅读了关于 C# 转换器的一些帮助之后,我想出了这个。我还有一些调整要做,但如果有人也在寻找类似的答案。

    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);
        }
    }
    

    【讨论】:

    • 太棒了!我是寻找它的人之一:-)
    【解决方案4】:

    我进一步改进了 mvandillen 类。

    这似乎工作得很好:

    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);
            }
        }
    

    【讨论】:

      【解决方案5】:

      这是我基于@mvandillen 回答的版本。我还需要在 span 开始时留出一些边距。

      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);
          }
      }
      

      使用方法:

      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);
      

      预览:

      【讨论】:

      • 不错。但是,我注意到当尝试设置填充顶部/底部时,圆角消失了。你知道为什么吗?
      • @anticafe 不完全确定您是如何添加填充的。我建议您使用代码和屏幕截图创建一个新问题。
      【解决方案6】:

      希望这个答案可以为那些仍在寻找的人简化它......

      您可以简单地使用“芯片”可绘制对象。它正确地完成了所有计算,并且代码更少。

      Standalone ChipDrawable

      为了完整性,复制到这里:

      res/xml/standalone_chip.xml:

      <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中:

      // 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);
      

      最终结果:

      【讨论】:

        【解决方案7】:

        好的,所以问题有点乱,这是我的 DanieleB 和 mvandillen 的解决方案。

        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 颜色:

        @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);
        }
        

        【讨论】:

          【解决方案8】:

          如果您使用 kotlin 并针对多个密度设备,那么这对您有用

          第 1 步:创建一个扩展 ReplacementSpan 的类,即 RoundedBackgroundSpan.kt

          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 步:之后调用上面创建的类,如下所示

             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
              }
          

          【讨论】:

            【解决方案9】:

            看着Google's video,他们提供了这个解决方案:

            很遗憾,我在这里看到很多东西丢失了,我找不到完整的代码,所以我无法尝试。

            【讨论】:

              猜你喜欢
              • 2012-09-12
              • 2020-01-15
              • 1970-01-01
              • 2020-05-11
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-08-06
              • 2015-10-07
              相关资源
              最近更新 更多