Bearded-Hen / Android-Bootstrap

Bootstrap style widgets for Android, with Glyph Icons
MIT License
7.29k stars 1.43k forks source link

How to inject BootstrapLabel inside TextView? #189

Open StNekroman opened 7 years ago

StNekroman commented 7 years ago

Is this possible at all?

Use case: I need next content to be inserted as text content of text view:

[#hashtag] blah blah blah.... second line, blah blah... <-- second line is inside the same textView (text view is multilined)

StNekroman commented 7 years ago

I'm looking on https://github.com/binaryfork/Spanny at the moment. But that would be great to have spannable elements inside this Bootstrap project.

jamie-beardedhen commented 7 years ago

Is it possible to just use the newline character? BootstrapLabel ultimately extends from TextView, and TextView definitely supports this

StNekroman commented 7 years ago

I forgot to add clarification:

[#hashtag] - it's BootstrapLabel.

So whole text [#hashtag] blah blah blah....\nsecond line, blah blah... is content of some TextView.

But I cannot insert BootstrapLabel as content of TextView, because BootstrapLabel is not Spannable. And, as I know, your project doesn't contain bootstrap-like styled Spannable elements. While I need them.

StNekroman commented 7 years ago

In Spanny (https://github.com/binaryfork/Spanny) take a look on CustomBackgroundSpan. That's what I need, but with bootstrap background colors.

StNekroman commented 7 years ago

I ended up with my own impl Span (like CustomBackgroundSpan) which uses BootstrapBrand styling. I'll post here my solution later.

jamie-beardedhen commented 7 years ago

FYI BootstrapText extends SpannableString, so that might help achieve the same effect?

https://github.com/Bearded-Hen/Android-Bootstrap/blob/master/AndroidBootstrap/src/main/java/com/beardedhen/androidbootstrap/BootstrapText.java

StNekroman commented 7 years ago

That's what I need:

device-2017-01-03-212645

StNekroman commented 7 years ago

I found two solutions:

Solution I:

public class BootstrapSpan
        extends CharacterStyle
        implements LineBackgroundSpan, UpdateAppearance {
    private DefaultBootstrapSize bootstrapSize;
    private final BootstrapBrand bootstrapBrand;
    private final Context context;

    private final float baselineCornerRadius;

    private boolean rounded;

    public BootstrapSpan(@NotNull final Context context,
                         @NotNull final BootstrapBrand bootstrapBrand,
                         @NotNull final DefaultBootstrapSize bootstrapSize) {
        super();
        this.context = context;
        this.bootstrapBrand = bootstrapBrand;
        this.bootstrapSize = bootstrapSize;

        baselineCornerRadius = DimenUtils.pixelsFromDpResource(context, com.beardedhen.androidbootstrap.R.dimen.bootstrap_button_default_corner_radius);

        setRounded(false);
    }

    public BootstrapSpan(@NotNull final Context context,
                         @NotNull final BootstrapBrand bootstrapBrand,
                         @NotNull final DefaultBootstrapSize bootstrapSize,
                         final boolean rounded) {
        this(context, bootstrapBrand, bootstrapSize);
        setRounded(rounded);
    }

    public void setRounded(boolean rounded) {
        this.rounded = rounded;
    }

    @Override
    public void drawBackground(Canvas canvas, Paint paint, int left, int right, int top,
                               int baseline, int bottom, CharSequence text, int start, int end, int lnum) {
        float width = paint.measureText(text, start, end) * bootstrapSize.scaleFactor();
        float height = (bottom - top) * bootstrapSize.scaleFactor();
        final int cornerRadius = rounded ? (int) (baselineCornerRadius * bootstrapSize.scaleFactor()) : 0;
        //final RectF rect = new RectF(left, top, left + width, top + height);
        final RectF rect = new RectF(left, bottom - height, left + width, bottom);
        paint.setColor(bootstrapBrand.defaultFill(context));
        canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint);
    }

    @Override
    public void updateDrawState(final TextPaint paint) {
        paint.setTextSize(paint.getTextSize() * bootstrapSize.scaleFactor());
        paint.setColor(bootstrapBrand.defaultTextColor(context));
    }
}

Solution II

public class BootstrapSpan extends ReplacementSpan {
    private DefaultBootstrapSize bootstrapSize;
    private final BootstrapBrand bootstrapBrand;
    private final Context context;

    private final float baselineCornerRadius;

    private boolean rounded;

    public BootstrapSpan(@NotNull final Context context,
                         @NotNull final BootstrapBrand bootstrapBrand,
                         @NotNull final DefaultBootstrapSize bootstrapSize) {
        super();
        this.context = context;
        this.bootstrapBrand = bootstrapBrand;
        this.bootstrapSize = bootstrapSize;

        baselineCornerRadius = DimenUtils.pixelsFromDpResource(context, com.beardedhen.androidbootstrap.R.dimen.bootstrap_button_default_corner_radius);

        setRounded(false);
    }

    public BootstrapSpan(@NotNull final Context context,
                         @NotNull final BootstrapBrand bootstrapBrand,
                         @NotNull final DefaultBootstrapSize bootstrapSize,
                         final boolean rounded) {
        this(context, bootstrapBrand, bootstrapSize);
        setRounded(rounded);
    }

    @Override
    public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
        paint.setTextSize(paint.getTextSize() * bootstrapSize.scaleFactor());

        paint.setColor(bootstrapBrand.defaultFill(context));
        float width = paint.measureText(text, start, end) * bootstrapSize.scaleFactor();
        float height = (bottom - top) * bootstrapSize.scaleFactor();
        Log.i("DEBUG_TAG", "Height=" + height);
        int cornerRadius = rounded ? (int) (baselineCornerRadius * bootstrapSize.scaleFactor()) : 0;

        RectF rect = new RectF(x, top, x + width + cornerRadius, top + height);
        canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint);
        paint.setColor(bootstrapBrand.defaultTextColor(context));
        canvas.drawText(text, start, end, x + cornerRadius/2, y * bootstrapSize.scaleFactor(), paint);
    }

    public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
        paint.setTextSize(paint.getTextSize() * bootstrapSize.scaleFactor());

        float width = paint.measureText(text, start, end) * bootstrapSize.scaleFactor();
        int cornerRadius = rounded ? (int) (baselineCornerRadius * bootstrapSize.scaleFactor()) : 0;
        return Math.round(width + cornerRadius);
    }

    public void setRounded(boolean rounded) {
        this.rounded = rounded;
    }
}
StNekroman commented 7 years ago

Solution 2 is based on ReplacementSpan. It's good in all cases becuase provides full controll over drawing and measing the size of span. But I faced later with strange bug: If TextView contains two lines of text, and if second line is tagged only by BootstrapSpan, then spannable string (which was tagged by this BootstrapSpan) becomes invisible. Acts like visibility is set to GONE (completely gone, without allocated space) But if span tag includes only part of string - no bug. If spanned string is prepeded (or appended to) by anythin else - no bug. bug happens only if this tag includes whole string in the line. I believe it's related somehow to ReplacementSpan's internal impl.

Solution 1 is based on LineBackgroundSpan which created to write background. The only one difference is - in order to write text over the background without introducing another tag I modify text color of TextPaint and it writes the text automatically. It is not very good, because this solution doesn't provide manual control of text rendering. Everything you can do here - modify TextPaint's text color and textSize. But you cannot modify text's measured width. So you cannot include additional space before and after string (like additional paddingStart/End) - LineBackgroundSpan doesn't provide way to override text measurement.

I choose solution 1 temporarily, because I dont like solutions, where I cannot explain something, even if there is a workaround.