默认的选择背景绘制流程
TextView.java
@Override
protected void onDraw(Canvas canvas) {
…
Path highlight = getUpdatedHighlightPath();
if (mEditor != null) {
mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
} else {
layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
}
…
}
背景其实就是是绘制Path,path由getUpdatedHightlightPath方法获取
private Path getUpdatedHighlightPath() {
Path highlight = null;
Paint highlightPaint = mHighlightPaint;
final int selStart = getSelectionStart();
final int selEnd = getSelectionEnd();
if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
if (selStart == selEnd) {
…
} else {
if (mHighlightPathBogus) {
if (mHighlightPath == null) mHighlightPath = new Path();
mHighlightPath.reset();
mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
mHighlightPathBogus = false;
}
// XXX should pass to skin instead of drawing directly
highlightPaint.setColor(mHighlightColor);
highlightPaint.setStyle(Paint.Style.FILL);
highlight = mHighlightPath;
}
}
return highlight;
}
核心是利用Layout的getSelectionPath生成Path,其余就是设置Path的填充色和绘制模式
Layout.java
public void getCursorPath(int point, Path dest,
CharSequence editingBuffer) {
dest.reset();
int line = getLineForOffset(point);
int top = getLineTop(line);
int bottom = getLineTop(line+1);
…
if (Float.compare(h1, h2) == 0) {
dest.moveTo(h1, top);
dest.lineTo(h1, bottom);
} else {
…
}
if (caps == 2) {
dest.moveTo(h2, bottom);
dest.lineTo(h2 - dist, bottom + dist);
dest.lineTo(h2, bottom);
dest.lineTo(h2 + dist, bottom + dist);
} else if (caps == 1) {
…
}
if (fn == 2) {
dest.moveTo(h1, top);
dest.lineTo(h1 - dist, top - dist);
dest.lineTo(h1, top);
dest.lineTo(h1 + dist, top - dist);
} else if (fn == 1) {
…
}
}
绘制就是一堆lineTo组成选择区域的不规则形状
自定义方法
自定义原理
源码中预留了自定义的扩展
Layout.java
public void drawBackground(Canvas canvas, Path highlight, Paint highlightPaint,
int cursorOffsetVertical, int firstLine, int lastLine) {
// First, draw LineBackgroundSpans.
// LineBackgroundSpans know nothing about the alignment, margins, or
// direction of the layout or line. XXX: Should they?
// They are evaluated at each line.
if (mSpannedText) {
if (mLineBackgroundSpans == null) {
mLineBackgroundSpans = new SpanSet<LineBackgroundSpan>(LineBackgroundSpan.class);
}
Spanned buffer = (Spanned) mText;
int textLength = buffer.length();
mLineBackgroundSpans.init(buffer, 0, textLength);
if (mLineBackgroundSpans.numberOfSpans > 0) {
…
for (int i = firstLine; i <= lastLine; i++) {
…
for (int n = 0; n < spansLength; n++) {
LineBackgroundSpan lineBackgroundSpan = (LineBackgroundSpan) spans[n];
lineBackgroundSpan.drawBackground(canvas, paint, 0, width,
ltop, lbaseline, lbottom,
buffer, start, end, i);
}
}
}
mLineBackgroundSpans.recycle();
}
// There can be a highlight even without spans if we are drawing
// a non-spanned transformation of a spanned editing buffer.
if (highlight != null) {
if (cursorOffsetVertical != 0) canvas.translate(0, cursorOffsetVertical);
canvas.drawPath(highlight, highlightPaint);
if (cursorOffsetVertical != 0) canvas.translate(0, -cursorOffsetVertical);
}
}
看出背景绘制分为两步,先绘制LineBackgroundSpan,然后绘制之前提及的Path。Path是默认的绘制,LineBackgroundSpan就是自定义的入口了
public interface LineBackgroundSpan
extends ParagraphStyle
{
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);
}
实现LineBackgroundSpan,自由的绘制吧。
注意点
1.使用自定义后,防止背景重叠,要把Path的颜色改为透明
mEditText.setHighlightColor(Color.TRANSPARENT);
2.设置span的参数问题。setSpan要一行行的设置,开始和结束必须是不同值(不然不会绘制),这里行的计算和位置的计算都是Layout中的方法。
Layout layout = mEditText.getLayout();
mEditText.getEditableText().setSpan(tempSpan, layout.getLineStart(line), layout.getLineEnd(line), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);