Android中的ReplacementSpan替代方案

时间:2016-08-21 00:11:41

标签: android textview spannablestring

我有一个应用程序可以分页大文本并为每个单词或句子设置多个跨度。我正在使用ReplacementSpan为每个单词绘制背景。我不能使用BackgroundSpan,因为它太简单了,不能让我控制画布。由于ReplacementSpan扩展了MetricAffectingSpan,它影响了文本的布局,完全打破了我的分页。我使用StaticLayout来计算每个页面的文本,而StaticLayout不允许跨越,因此它可以计算跨越大小影响的先验。

是否有ReplacementSpan的替代品?如何在不影响文本本身大小和布局的情况下绘制我想要的背景?

这是我的replacementspan的代码:

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

1 个答案:

答案 0 :(得分:3)

尝试这个简单的跨度,它在所有跨度上绘制纯红色背景(即使它是多行跨度)但你可以绘制任何你喜欢的东西:

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

测试代码(将0ssb.length()设置为startend效率不高,因此您可以对其进行优化):

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