获取TextView(Android)上给定偏移的绝对位置

时间:2012-11-14 19:12:13

标签: android textview position character offset

我有一个TextView,我想在TextView的给定单词上放置一个纯色块,例如:

“这是一个文本字符串,我想在这个WORD上放一个矩形” - 所以,“WORD”会有一个带有纯色的矩形。

为此,我正在考虑重写onDraw(Canvas canvas)方法,以便在文本上绘制一个块。我唯一的问题是找到一种有效的方法来获得给定单词或字符的绝对位置。

基本上,我正在寻找与getOffsetForPosition(float x, float y) method

完全相反的东西

4 个答案:

答案 0 :(得分:9)

根据这篇文章:How get coordinate of a ClickableSpan inside a TextView?,我设法使用此代码,以便在文本的顶部放置一个矩形:

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

答案 1 :(得分:2)

您可以使用跨度。 首先,为文本创建一个spannable,如下所示:

Spannable span = new SpannableString(text);

然后你在要突出显示的单词周围加上一个跨度,有点像这样:

span.setSpan(new UnderlineSpan(), start, end, 
                     Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);

不幸的是,我不知道现有的跨度会在一个单词周围加上边框。我发现了UnderlineSpan,还有BackgroundColorSpan,也许这些也对你有用,或者你可以查看代码,看看你是否可以根据其中一个创建一个BorderSpan。

答案 2 :(得分:0)

不是在WORD上绘制一个矩形,而是简单地用适当的unicode符号替换它的字符,如U + 25AE(▮黑色垂直矩形)。

所以你得到了

  

“这是一个文本字符串,我想在这个上面放一个矩形”

如果这就足够了。有关unicode符号的列表,请参阅Wikipedia

如果您确实需要绘制黑匣子,只要您的文字在一行中就可以执行以下操作:

按照here解释'WORD'之前的文本部分的宽度,找到框的左边缘,并使用相同的方法计算'WORD'的宽度,以找到框的宽度。 / p>

对于多行文本,解释的方法也可能有用,但我认为你必须在这里做很多工作。

答案 3 :(得分:0)

使用getLayout().getLineBottomtextpaint.measureText手动进行getOffsetForPosition的反向计算。

下面是一个示例,该示例对某些textOffset使用计算的x,y来单击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());
    }

}
相关问题