android: How to get text position from touch event

2019-01-23 08:16发布

问题:

I'm wanting to implement a custom text interface, with touch+drag selecting text and the keyboard not being raised, in contrast to the default behavior of a long-click bringing up the CCP menu and the keyboard. My understanding suggests I need this approach:

onTouchEvent(event){
  case touch_down:
    get START text position

  case drag
    get END text position
    set selection range from START to END
}

I've found out all about getSelectStart() and various methods to setting a range and such, but I cannot find how to get the text position based on a touch event getX() and getY(). Is there any way to do this? I've seen the behaviour I want in other office apps.

Also, how would I stop the keyboard appearing until manually requested?

回答1:

"mText.setInputType(InputType.TYPE_NULL)" will suppress the soft keyboard but it also disables the blinking cursor in an EditText box under Android 3.0 and above. I coded an onTouchListener and returned true to disable the keyboard and then had to get the touch position from the motion event to set the cursor to the correct spot. You might be able to use this on an ACTION_MOVE motion event to select text for dragging.

Here is the code I used:

  mText = (EditText) findViewById(R.id.editText1);
  OnTouchListener otl = new OnTouchListener() {
  @Override
  public boolean onTouch(View v, MotionEvent event) {
      switch (event.getAction()) {
      case MotionEvent.ACTION_DOWN:
          Layout layout = ((EditText) v).getLayout();
          float x = event.getX() + mText.getScrollX();
          int offset = layout.getOffsetForHorizontal(0, x);
          if(offset>0)
              if(x>layout.getLineMax(0))
                  mText.setSelection(offset);     // touch was at end of text
              else
                  mText.setSelection(offset - 1);
          break;
          }
      return true;
      }
  };
  mText.setOnTouchListener(otl);


回答2:

Thanks Waine Kail for sharing the start code, but it only handled the "x" axis of the event. For multiline EditText, you also must:

1- Calculate the vertical position (y):
2- Get the line offset by the vertical position
3- Get the text offset using the line and horizontal position

EditText et = (EditText) findViewById(R.id.editText1);

long offset = -1; //text position will be corrected when touching.

et.setOnTouchListener(new OnTouchListener() {

@Override
public boolean onTouch(View v, MotionEvent event) {
    switch (event.getAction()) {
      case MotionEvent.ACTION_UP:
          Layout layout = ((EditText) v).getLayout();
          float x = event.getX() + et.getScrollX();
          float y = event.getY() + et.getScrollY();            
          int line = layout.getLineForVertical((int) y);                      

 // Here is what you wanted:

          offset = layout.getOffsetForHorizontal( line,  x);

          break;
        }
    return false;
}
});


回答3:

I'm facing the same issue too. And when I tried to use "int offset = layout.getOffsetForHorizontal(0, x);" like Wayne Kail said, also got NPE in this line. So I tried and in the end write like this:

mText = (EditText) findViewById(R.id.editText1);
OnTouchListener otl = new OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
      EditText editText = (EditText) v;
      float x = event.getX();
      float y = event.getY();
      int touchPosition = editText.getOffsetForPosition(x, y);
      if (touchPosition>0){
          editText.setSelection(touchPosition);
      }
      return true;
    }
};
mText.setOnTouchListener(otl); 


回答4:

For my application I needed to get the position within a touchlistener but the above solution appears to not give a precise location. For me it more often than not was off by 1-2 chars relative to the text cursor. The below code solves this. The key to get this to work is to use post() to postpone the code until after the cursor position has been updated. Thus the position I get is guaranteed to agree with that of EditText, and the code is also simpler.

setOnTouchListener(new OnTouchListener()
    {
    @Override
    public boolean onTouch(View view, MotionEvent event)
        {
        if(event.getAction()==MotionEvent.ACTION_UP)
            {
            //At this point the selection has not been updated. So post an
            //event at the end of the event queue to check where cursor
            //has been moved
            post(new Runnable()
                {
                public void run()
                    {
                    Editable sb=getText();
                    int currentPos=getSelectionStart();
                    //TODO
                    }
                });
            }
        return false;
        }
    });