Search code examples
androidtextviewpositioncharacteroffset

Get absolute position for a given offset on TextView (Android)


I have a TextView in which I want to place a solid color block over given words of the TextView, for example:

"This is a text string, I want to put a rectangle over this WORD" - so, "WORD" would have a rectangle with a solid color over it.

To do this, I am thinking about overriding the onDraw(Canvas canvas) method, in order to draw a block over the text. My only problem is to find an efficient way to get the absolute position of a given word or character.

Basically, I am looking for something that does the exact opposite of the getOffsetForPosition(float x, float y) method


Solution

  • Based on this post: How get coordinate of a ClickableSpan inside a TextView?, I managed to use this code in order to put a rectangle on top of the text:

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