Alternative to replacing Span with Android

I have an application that contains large text and sets multiple intervals for each word or sentence. I use ReplacementSpan to paint a background for each word. I cannot use BackgroundSpan because it is too simple and does not give me control over the canvas. Due to the fact that ReplacementSpan extends MetricAffectingSpan, which affects the layout of the text, completely breaking my paging. I use StaticLayout to calculate the text for each page, and StaticLayout does not allow coverage so that it can calculate a priori affecting spanning size.

Is there a replacement for ResplacementSpan? How can I draw the background that I want without affecting the size and layout of the text itself?

This is the code to replace:

public class BackgroundColorWithoutLineHeightSpan extends ReplacementSpan {

  private static final float DP_ACTIVE = ViewsUtils.dpToPx(4);
  private static final int DP_OUTSIDE_PADDING = (int) ViewsUtils.dpToPx(6);
  private static final float DP_PHRASE = ViewsUtils.dpToPx(4);
  private static final float DP_ROUNDED = ViewsUtils.dpToPx(3);

  private final int mColor;
  private final int mTextHeight;
  private int mBorderColor;
  private boolean mIsSelected;
  private boolean mIsPhrase;

  public BackgroundColorWithoutLineHeightSpan(int color, int textHeight, boolean isPhrase) {
    mColor = color;
    mTextHeight = textHeight;
    mIsPhrase = isPhrase;
  }

  public BackgroundColorWithoutLineHeightSpan(int color, int textHeight, boolean isSelected, int borderColor, boolean isPhrase) {
    mColor = color;
    mTextHeight = textHeight;
    mIsSelected = isSelected;
    mBorderColor = borderColor;
    mIsPhrase = isPhrase;
  }

  @Override
  public int getSize(@NonNull Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
    return Math.round(measureText(paint, text, start, end));
  }

  @Override
  public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {

    canvas.save();

    Rect newRect = canvas.getClipBounds();
    newRect.inset(-DP_OUTSIDE_PADDING, -DP_OUTSIDE_PADDING);

    canvas.clipRect(newRect, Region.Op.REPLACE);

    float measuredText = measureText(paint, text, start, end);

    int paintColor = paint.getColor();

    if (!mIsSelected) {
      RectF rect;
      rect = new RectF(x, top, x + measuredText, top + mTextHeight);

      paint.setStrokeWidth(0.0f);
      paint.setColor(mColor);
      paint.setStyle(Paint.Style.FILL);

      canvas.drawRoundRect(rect, DP_ROUNDED, DP_ROUNDED, paint);

    } else {

      RectF rect;
      if (mIsPhrase) {
        rect = new RectF(x - DP_PHRASE, top - DP_PHRASE, x + measuredText + DP_PHRASE, top + mTextHeight + DP_PHRASE);
      } else {
        rect = new RectF(x - DP_ACTIVE, top - DP_ACTIVE, x + measuredText + DP_ACTIVE, top + mTextHeight + DP_ACTIVE);
      }
      paint.setStrokeWidth(0.0f);
      paint.setColor(mColor);
      paint.setStyle(Paint.Style.FILL);

      canvas.drawRoundRect(rect, DP_ROUNDED, DP_ROUNDED, paint);

      RectF border;
      if (mIsPhrase) {
        border = new RectF(x - DP_PHRASE, top - DP_PHRASE, x + measuredText + DP_PHRASE, top + mTextHeight + DP_PHRASE);
      } else {
        border = new RectF(x - DP_ACTIVE, top - DP_ACTIVE, x + measuredText + DP_ACTIVE, top + mTextHeight + DP_ACTIVE);
      }

      paint.setColor(mBorderColor);
      paint.setStrokeWidth(4.0f);
      paint.setStyle(Paint.Style.STROKE);

      canvas.drawRoundRect(border, DP_ROUNDED, DP_ROUNDED, paint);
    }

    paint.setStyle(Paint.Style.FILL);
    paint.setColor(paintColor);
    canvas.drawText(text, start, end, x, y, paint);

    canvas.restore();
  }

  private float measureText(Paint paint, CharSequence text, int start, int end) {
    return paint.measureText(text, start, end);
  }
}
+4
1

, ( ), , :

class LBS implements LineBackgroundSpan {
    private final TextView tv;
    private int start;
    private int end;

    public LBS(TextView tv, int start, int end) {
        this.tv = tv;
        this.start = start;
        this.end = end;
    }

    @Override
    public void drawBackground(Canvas c, Paint p, int left, int right, int top, int baseline, int bottom, CharSequence text, int start, int end, int lnum) {
        Layout layout = tv.getLayout();
        int startLine = layout.getLineForOffset(this.start);
        int endLine = layout.getLineForOffset(this.end);
        if (startLine <= lnum && lnum <= endLine) {
            if (startLine == lnum) {
                left = (int) layout.getPrimaryHorizontal(this.start);
            }
            if (endLine == lnum) {
                right = (int) layout.getPrimaryHorizontal(this.end);
            }
            int origColor = p.getColor();
            p.setColor(Color.RED);
            c.drawRect(left, top, right, bottom, p);
            p.setColor(origColor);
        }
    }
}

( 0 ssb.length() start end , ):

TextView tv = new TextView(this);
setContentView(tv);
tv.setTextSize(32);
SpannableStringBuilder ssb = new SpannableStringBuilder("Chop a handfull spinach, pork shoulder, and dill in a large cooker over medium heat, cook for six minutes and varnish with some bok choy.");
LBS span = new LBS(tv, 30, 100);
ssb.setSpan(span, 0, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
tv.setText(ssb);

Log.d(TAG, "onCreate text [" + ssb.subSequence(30, 100) + "]");

/, ​​ :

class LBS implements LineBackgroundSpan {
    TextView tv;
    List<Pair<Integer, Integer>> ranges;

    public LBS(TextView tv) {
        this.tv = tv;
        ranges = new ArrayList<>();
    }

    public void add(int start, int end) {
        ranges.add(new Pair<>(start, end));
    }

    @Override
    public void drawBackground(Canvas c, Paint p, int left, int right, int top, int baseline, int bottom, CharSequence text, int start, int end, int lnum) {
        Layout layout = tv.getLayout();
        for (Pair<Integer, Integer> range : ranges) {
            int startLine = layout.getLineForOffset(range.first);
            int endLine = layout.getLineForOffset(range.second);
            if (startLine <= lnum && lnum <= endLine) {
                if (startLine == lnum) {
                    left = (int) layout.getPrimaryHorizontal(range.first);
                }
                if (endLine == lnum) {
                    right = (int) layout.getPrimaryHorizontal(range.second);
                }
                int origColor = p.getColor();
                p.setColor(Color.RED);
                c.drawRect(left, top, right, bottom, p);
                p.setColor(origColor);
            }
        }
    }
}

:

    TextView tv = new TextView(this);
    setContentView(tv);
    tv.setTextSize(32);
    String text = "Chop a handfull spinach, pork shoulder, and dill in a large cooker over medium heat, cook for six minutes and varnish with some bok choy.";
    SpannableStringBuilder ssb = new SpannableStringBuilder(text);
    LBS span = new LBS(tv);

    String[] words = {
            "spinach, pork shoulder", "cooker", "with some bok choy",
    };
    for (String word : words) {
        int idx = text.indexOf(word);
        span.add(idx, idx + word.length());
    }

    ssb.setSpan(span, 0, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
    tv.setText(ssb);
+1

Source: https://habr.com/ru/post/1651913/


All Articles