2016-08-21 28 views
4

我有一個應用程序,頁面大文本,並設置多個跨度到每個單詞或句子。我正在使用ReplacementSpan爲每個單詞繪製背景。我不能使用BackgroundSpan,因爲它太簡單了,不能控制畫布。由於ReplacementSpan擴展了影響文本佈局的MetricAffectingSpan,因此完全打破了我的分頁。我使用StaticLayout來計算每個頁面的文本,並且StaticLayout不允許跨越,因此它可以事先計算跨越大小的影響。替代Android的ReplacementSpan

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

試試這個簡單的跨度,它吸引固體紅色的背景在所有的跨度(即使它是多線跨度),但你可以畫任何你喜歡:

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

感謝你這麼多爲你的幫助!有了這個解決方案,我可以爲每個單詞設置背景,並且文本仍然包裝,而不會影響其佈局。 – mobilepotato7

+0

還有另外一件事情,我在幾分鐘前發現:'Layout#getSelectionPath',檢查它,也許它會有幫助 – pskink

+0

佈局非常強大。我還有其他幾個與文本有關的功能,肯定會檢查我可以使用Layout做些什麼。謝謝。 – mobilepotato7