【问题标题】:Get absolute position for a given offset on TextView (Android)在 TextView (Android) 上获取给定偏移量的绝对位置
【发布时间】:2012-11-03 08:26:07
【问题描述】:

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

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

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

基本上,我正在寻找与getOffsetForPosition(float x, float y) method完全相反的东西

【问题讨论】:

  • 是单行文字还是多行文字?

标签: android textview position character offset


【解决方案1】:

根据这篇文章: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);
 }

【讨论】:

  • 我没有得到这个,你怎么用这个..??我的意思是如果我选择文本,这段代码将由它自己执行??还是我们需要做点别的?我已经创建了自定义文本视图并将此方法放入其中。将如何执行?
  • @NirPatel 此代码在targetWord 上绘制了一个矩形,这是算法输入。如果您在没有任何更改的情况下使用代码并在自定义文本视图上写入targetWord(在本例中为“WORD”),则会在其顶部绘制一个矩形。如果您想将矩形放置在用户选择的文本之上,那么startOffsetOfClickedText endXCoordinatesOfClickedText 必须根据用户所做的任何选择范围来计算,但是我相信这不会适用于多个目标词,因为它只绘制一个矩形。
【解决方案2】:

您可以为此使用跨度。 首先你为你的文本创建一个spannable,像这样:

Spannable span = new SpannableString(text);

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

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

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

【讨论】:

    【解决方案3】:

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

    所以你会得到

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

    如果这足够了。例如,请参阅 Wikipedia 以获取 unicode 符号的浪费列表。

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

    按照here 的说明计算“WORD”之前的文本部分的宽度以找到框的左边缘并使用相同的方法计算“WORD”的宽度以找到框的宽度。

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

    【讨论】:

    • 首先感谢您的回复。由于它是多行的,也许我可以使用一种技术来扫描每一行以查找 WORD,但我不知道这是否非常有效
    • 不,这不会很好。您必须重建 TextView 的文本换行过程才能找出哪一行中的文本。
    【解决方案4】:

    使用getLayout().getLineBottomtextpaint.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());
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-27
      • 1970-01-01
      • 1970-01-01
      • 2021-04-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多