Android - Add Margin for SpannableStringBuilder using ReplacementSpan

前端 未结 3 1371
面向向阳花
面向向阳花 2021-02-09 19:51

I\'m trying to format Hashtags inside a TextView/EditText (Say like Chips mentioned in the Material Design Specs). I\'m able to format the background using ReplacementSpan

3条回答
  •  一个人的身影
    2021-02-09 20:21

    I had a similar problem a while ago and this is the solution I've come up with:

    The hosting TextView in xml:

    
    

    A custom version of ReplacementSpan

    public class TagBadgeSpannable extends ReplacementSpan implements LineHeightSpan {
    
        private static int CORNER_RADIUS = 30;
        private final int textColor;
        private final int backgroundColor;
        private final int lineHeight;
    
        public TagBadgeSpannable(int lineHeight, int textColor, int backgroundColor) {
            super();
            this.textColor = textColor;
            this.backgroundColor = backgroundColor;
            this.lineHeight = lineHeight;
        }
    
        @Override
        public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
            final float textSize = paint.getTextSize();
            final float textLength = x + measureText(paint, text, start, end);
            final float badgeHeight = textSize * 2.25f;
            final float textOffsetVertical = textSize * 1.45f;
    
            RectF badge = new RectF(x, y, textLength, y + badgeHeight);
            paint.setColor(backgroundColor);
            canvas.drawRoundRect(badge, CORNER_RADIUS, CORNER_RADIUS, paint);
    
            paint.setColor(textColor);
            canvas.drawText(text, start, end, x, y + textOffsetVertical, paint);
        }
    
        @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);
        }
    
        @Override
        public void chooseHeight(CharSequence charSequence, int i, int i1, int i2, int i3, Paint.FontMetricsInt fontMetricsInt) {
            fontMetricsInt.bottom += lineHeight;
            fontMetricsInt.descent += lineHeight;
        }
    }
    

    And finally a builder that creates the Spannable

    public class AndroidTagBadgeBuilder implements TagBadgeBuilder {
    
        private final SpannableStringBuilder stringBuilder;
        private final String textColor;
        private final int lineHeight;
    
        public AndroidTagBadgeBuilder(SpannableStringBuilder stringBuilder, int lineHeight, String textColor) {
            this.stringBuilder = stringBuilder;
            this.lineHeight = lineHeight;
            this.textColor = textColor;
        }
    
        @Override
        public void appendTag(String tagName, String badgeColor) {
            final String nbspSpacing = "\u202F\u202F"; // none-breaking spaces
    
            String badgeText = nbspSpacing + tagName + nbspSpacing;
            stringBuilder.append(badgeText);
            stringBuilder.setSpan(
                new TagBadgeSpannable(lineHeight, Color.parseColor(textColor), Color.parseColor(badgeColor)),
                stringBuilder.length() - badgeText.length(),
                stringBuilder.length()- badgeText.length() + badgeText.length(),
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
            );
            stringBuilder.append("  ");
        }
    
        @Override
        public CharSequence getTags() {
            return stringBuilder;
        }
    
        @Override
        public void clear() {
            stringBuilder.clear();
            stringBuilder.clearSpans();
        }
    }
    

    The outcome will look something like this:

    Tweak the measures in TagBadgeSpannable to your liking.

    I've uploaded a very minimal sample project using this code to github so feel free to check it out.

    NOTE: The sample uses Android Databinding and is written MVVM style

提交回复
热议问题