我有一个 TextView,我想在 TextView 的给定单词上放置一个纯色块,例如:
“这是一个文本字符串,我想在这个 WORD 上放置一个矩形” - 因此,“WORD”将有一个带有纯色的矩形。
为此,我正在考虑重写 onDraw(Canvas canvas) 方法,以便在文本上绘制一个块。我唯一的问题是找到一种有效的方法来获取给定单词或字符的绝对位置。
基于这篇文章:如何在 TextView 中获取 ClickableSpan 的坐标?,我设法使用此代码在文本顶部放置一个矩形:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.WHITE);
// Initialize global value
TextView parentTextView = this;
Rect parentTextViewRect = new Rect();
// Find where the WORD is
String targetWord = "WORD";
int startOffsetOfClickedText = this.getText().toString().indexOf(targetWord);
int endOffsetOfClickedText = startOffsetOfClickedText + targetWord.length();
// Initialize values for the computing of clickedText position
Layout textViewLayout = parentTextView.getLayout();
double startXCoordinatesOfClickedText = textViewLayout.getPrimaryHorizontal((int)startOffsetOfClickedText);
double endXCoordinatesOfClickedText = textViewLayout.getPrimaryHorizontal((int)endOffsetOfClickedText);
// Get the rectangle of the clicked text
int currentLineStartOffset = textViewLayout.getLineForOffset((int)startOffsetOfClickedText);
int currentLineEndOffset = textViewLayout.getLineForOffset((int)endOffsetOfClickedText);
boolean keywordIsInMultiLine = currentLineStartOffset != currentLineEndOffset;
textViewLayout.getLineBounds(currentLineStartOffset, parentTextViewRect);
// Update the rectangle position to his real position on screen
int[] parentTextViewLocation = {0,0};
parentTextView.getLocationOnScreen(parentTextViewLocation);
double parentTextViewTopAndBottomOffset = (
//parentTextViewLocation[1] -
parentTextView.getScrollY() +
parentTextView.getCompoundPaddingTop()
);
parentTextViewRect.top += parentTextViewTopAndBottomOffset;
parentTextViewRect.bottom += parentTextViewTopAndBottomOffset;
// In the case of multi line text, we have to choose what rectangle take
if (keywordIsInMultiLine){
WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
int screenHeight = display.getHeight();
int dyTop = parentTextViewRect.top;
int dyBottom = screenHeight - parentTextViewRect.bottom;
boolean onTop = dyTop > dyBottom;
if (onTop){
endXCoordinatesOfClickedText = textViewLayout.getLineRight(currentLineStartOffset);
}
else{
parentTextViewRect = new Rect();
textViewLayout.getLineBounds(currentLineEndOffset, parentTextViewRect);
parentTextViewRect.top += parentTextViewTopAndBottomOffset;
parentTextViewRect.bottom += parentTextViewTopAndBottomOffset;
startXCoordinatesOfClickedText = textViewLayout.getLineLeft(currentLineEndOffset);
}
}
parentTextViewRect.left += (
parentTextViewLocation[0] +
startXCoordinatesOfClickedText +
parentTextView.getCompoundPaddingLeft() -
parentTextView.getScrollX()
);
parentTextViewRect.right = (int) (
parentTextViewRect.left +
endXCoordinatesOfClickedText -
startXCoordinatesOfClickedText
);
canvas.drawRect(parentTextViewRect, paint);
}
您可以为此使用跨度。首先,您为文本创建一个 spannable,如下所示:
Spannable span = new SpannableString(text);
然后在要突出显示的单词周围加上一个跨度,有点像这样:
span.setSpan(new UnderlineSpan(), start, end,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
不幸的是,我不知道现有的跨度会在单词周围加上边框。我找到了 UnderlineSpan 和BackgroundColorSpan,也许这些对您也有用,或者您可以查看代码,看看您是否可以基于其中之一创建 BorderSpan。
您可以简单地将其字符替换为适当的 unicode 符号,例如 U+25AE(▮ 黑色垂直矩形),而不是在 WORD 上绘制一个矩形。
所以你会得到
“这是一个文本字符串,我想在它上面放一个矩形▮▮▮▮”
如果这足够了。例如,请参阅Wikipedia以获取 unicode 符号的浪费列表。
如果您确实需要绘制该黑框,只要您的文本在一行中,您就可以执行以下操作:
按照此处的说明计算“WORD”之前的文本部分的宽度以找到框的左边缘并使用相同的方法计算“WORD”的宽度以找到框的宽度。
对于多行文本,解释的方法也可能有效,但我认为您必须在这里做很多工作。
使用getLayout().getLineBottom
和textpaint.measureText
手动进行逆向计算getOffsetForPosition
。
下面是一个使用计算出的 x,y 为一些 textOffset 来定位可绘制的句柄的示例,当点击 textview 时。
class TextViewCustom extends TextView{
float lastX,lastY;
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean ret = super.onTouchEvent(event);
lastX=event.getX();
lastY=event.getY();
return ret;
}
BreakIterator boundary;
Drawable handleLeft;
private void init() {// call it in constructors
boundary = BreakIterator.getWordInstance();
handleLeft=getResources().getDrawable(R.drawable.abc_text_select_handle_left_mtrl_dark);
setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
int line = getLayout().getLineForVertical((int) lastY);
int offset = getLayout().getOffsetForHorizontal(line, lastX);
int wordEnd = boundary.following(offset);
int wordStart = boundary.previous();
CMN.Log(getText().subSequence(wordStart, wordEnd));
int y = getLayout().getLineBottom(line);
int trimA = getLayout().getLineStart(line);
float x = getPaddingLeft()+getPaint().measureText(getText(), trimA, wordStart);
x-=handleLeft.getIntrinsicWidth()*1.f*9/12;
handleLeft.setBounds((int)x,y,(int)(x+handleLeft.getIntrinsicWidth()),y+handleLeft.getIntrinsicHeight());
invalidate();
}
});
}
@Override
public void setText(CharSequence text, BufferType type) {
super.setText(text, type);
if(boundary!=null)
boundary.setText(text.toString());
}
}