1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package android.widget.espresso; 18 19 import static androidx.test.espresso.action.ViewActions.actionWithAssertions; 20 21 import android.graphics.Rect; 22 import android.text.Layout; 23 import android.view.MotionEvent; 24 import android.view.View; 25 import android.widget.Editor; 26 import android.widget.Editor.HandleView; 27 import android.widget.TextView; 28 29 import androidx.test.espresso.PerformException; 30 import androidx.test.espresso.ViewAction; 31 import androidx.test.espresso.action.CoordinatesProvider; 32 import androidx.test.espresso.action.GeneralLocation; 33 import androidx.test.espresso.action.Press; 34 import androidx.test.espresso.action.Tap; 35 import androidx.test.espresso.util.HumanReadables; 36 37 /** 38 * A collection of actions on a {@link android.widget.TextView}. 39 */ 40 public final class TextViewActions { 41 TextViewActions()42 private TextViewActions() {} 43 44 /** 45 * Returns an action that clicks on text at an index on the TextView.<br> 46 * <br> 47 * View constraints: 48 * <ul> 49 * <li>must be a TextView displayed on screen 50 * <ul> 51 * 52 * @param index The index of the TextView's text to click on. 53 */ clickOnTextAtIndex(int index)54 public static ViewAction clickOnTextAtIndex(int index) { 55 return actionWithAssertions( 56 new ViewClickAction(Tap.SINGLE, new TextCoordinates(index), Press.FINGER)); 57 } 58 59 60 /** 61 * Returns an action that single-clicks by mouse on the View.<br> 62 * <br> 63 * View constraints: 64 * <ul> 65 * <li>must be a View displayed on screen 66 * <ul> 67 */ mouseClick()68 public static ViewAction mouseClick() { 69 return actionWithAssertions(new MouseClickAction(Tap.SINGLE, GeneralLocation.VISIBLE_CENTER, 70 MotionEvent.BUTTON_PRIMARY)); 71 } 72 73 /** 74 * Returns an action that clicks by mouse on text at an index on the TextView.<br> 75 * <br> 76 * View constraints: 77 * <ul> 78 * <li>must be a TextView displayed on screen 79 * <ul> 80 * 81 * @param index The index of the TextView's text to click on. 82 */ mouseClickOnTextAtIndex(int index)83 public static ViewAction mouseClickOnTextAtIndex(int index) { 84 return mouseClickOnTextAtIndex(index, MotionEvent.BUTTON_PRIMARY); 85 } 86 87 /** 88 * Returns an action that clicks by mouse on text at an index on the TextView.<br> 89 * <br> 90 * View constraints: 91 * <ul> 92 * <li>must be a TextView displayed on screen 93 * <ul> 94 * 95 * @param index The index of the TextView's text to click on. 96 * @param button the mouse button to use. 97 */ mouseClickOnTextAtIndex(int index, @MouseUiController.MouseButton int button)98 public static ViewAction mouseClickOnTextAtIndex(int index, 99 @MouseUiController.MouseButton int button) { 100 return actionWithAssertions( 101 new MouseClickAction(Tap.SINGLE, new TextCoordinates(index), button)); 102 } 103 104 /** 105 * Returns an action that double-clicks on text at an index on the TextView.<br> 106 * <br> 107 * View constraints: 108 * <ul> 109 * <li>must be a TextView displayed on screen 110 * <ul> 111 * 112 * @param index The index of the TextView's text to double-click on. 113 */ doubleClickOnTextAtIndex(int index)114 public static ViewAction doubleClickOnTextAtIndex(int index) { 115 return actionWithAssertions( 116 new ViewClickAction(Tap.DOUBLE, new TextCoordinates(index), Press.FINGER)); 117 } 118 119 /** 120 * Returns an action that double-clicks by mouse on text at an index on the TextView.<br> 121 * <br> 122 * View constraints: 123 * <ul> 124 * <li>must be a TextView displayed on screen 125 * <ul> 126 * 127 * @param index The index of the TextView's text to double-click on. 128 */ mouseDoubleClickOnTextAtIndex(int index)129 public static ViewAction mouseDoubleClickOnTextAtIndex(int index) { 130 return actionWithAssertions( 131 new MouseClickAction(Tap.DOUBLE, new TextCoordinates(index))); 132 } 133 134 /** 135 * Returns an action that long presses on text at an index on the TextView.<br> 136 * <br> 137 * View constraints: 138 * <ul> 139 * <li>must be a TextView displayed on screen 140 * <ul> 141 * 142 * @param index The index of the TextView's text to long press on. 143 */ longPressOnTextAtIndex(int index)144 public static ViewAction longPressOnTextAtIndex(int index) { 145 return actionWithAssertions( 146 new ViewClickAction(Tap.LONG, new TextCoordinates(index), Press.FINGER)); 147 } 148 149 /** 150 * Returns an action that long click by mouse on text at an index on the TextView.<br> 151 * <br> 152 * View constraints: 153 * <ul> 154 * <li>must be a TextView displayed on screen 155 * <ul> 156 * 157 * @param index The index of the TextView's text to long click on. 158 */ mouseLongClickOnTextAtIndex(int index)159 public static ViewAction mouseLongClickOnTextAtIndex(int index) { 160 return actionWithAssertions( 161 new MouseClickAction(Tap.LONG, new TextCoordinates(index))); 162 } 163 164 /** 165 * Returns an action that triple-clicks by mouse on text at an index on the TextView.<br> 166 * <br> 167 * View constraints: 168 * <ul> 169 * <li>must be a TextView displayed on screen 170 * <ul> 171 * 172 * @param index The index of the TextView's text to triple-click on. 173 */ mouseTripleClickOnTextAtIndex(int index)174 public static ViewAction mouseTripleClickOnTextAtIndex(int index) { 175 return actionWithAssertions( 176 new MouseClickAction(MouseClickAction.CLICK.TRIPLE, new TextCoordinates(index))); 177 } 178 179 /** 180 * Returns an action that long presses then drags on text from startIndex to endIndex on the 181 * TextView.<br> 182 * <br> 183 * View constraints: 184 * <ul> 185 * <li>must be a TextView displayed on screen 186 * <ul> 187 * 188 * @param startIndex The index of the TextView's text to start a drag from 189 * @param endIndex The index of the TextView's text to end the drag at 190 */ longPressAndDragOnText(int startIndex, int endIndex)191 public static ViewAction longPressAndDragOnText(int startIndex, int endIndex) { 192 return actionWithAssertions( 193 new DragAction( 194 DragAction.Drag.LONG_PRESS, 195 new TextCoordinates(startIndex), 196 new TextCoordinates(endIndex), 197 Press.FINGER, 198 TextView.class)); 199 } 200 201 /** 202 * Returns an action that long presses then drags on handle from the current position to 203 * endIndex on the TextView.<br> 204 * <br> 205 * View constraints: 206 * <ul> 207 * <li>must be a TextView's drag-handle displayed on screen 208 * <ul> 209 * 210 * @param textView TextView the handle is on 211 * @param handleType Type of the handle 212 * @param endIndex The index of the TextView's text to end the drag at 213 */ longPressAndDragHandle(TextView textView, Handle handleType, int endIndex)214 public static ViewAction longPressAndDragHandle(TextView textView, Handle handleType, 215 int endIndex) { 216 return actionWithAssertions( 217 new DragAction( 218 DragAction.Drag.LONG_PRESS, 219 new CurrentHandleCoordinates(textView), 220 new HandleCoordinates(textView, handleType, endIndex, true), 221 Press.FINGER, 222 Editor.HandleView.class)); 223 } 224 225 /** 226 * Returns an action that long presses on the current handle.<br> 227 * <br> 228 * View constraints: 229 * <ul> 230 * <li>must be a TextView's drag-handle displayed on screen 231 * <ul> 232 * 233 * @param textView TextView the handle is on 234 */ longPressHandle(TextView textView)235 public static ViewAction longPressHandle(TextView textView) { 236 return actionWithAssertions( 237 new ViewClickAction(Tap.LONG, new CurrentHandleCoordinates(textView), 238 Press.FINGER)); 239 } 240 241 /** 242 * Returns an action that double tap then drags on handle from the current position to 243 * endIndex on the TextView.<br> 244 * <br> 245 * View constraints: 246 * <ul> 247 * <li>must be a TextView's drag-handle displayed on screen 248 * <ul> 249 * 250 * @param textView TextView the handle is on 251 * @param handleType Type of the handle 252 * @param endIndex The index of the TextView's text to end the drag at 253 */ doubleTapAndDragHandle(TextView textView, Handle handleType, int endIndex)254 public static ViewAction doubleTapAndDragHandle(TextView textView, Handle handleType, 255 int endIndex) { 256 return actionWithAssertions( 257 new DragAction( 258 DragAction.Drag.DOUBLE_TAP, 259 new CurrentHandleCoordinates(textView), 260 new HandleCoordinates(textView, handleType, endIndex, true), 261 Press.FINGER, 262 Editor.HandleView.class)); 263 } 264 265 /** 266 * Returns an action that double tap on the current handle.<br> 267 * <br> 268 * View constraints: 269 * <ul> 270 * <li>must be a TextView's drag-handle displayed on screen 271 * <ul> 272 * 273 * @param textView TextView the handle is on 274 */ doubleTapHandle(TextView textView)275 public static ViewAction doubleTapHandle(TextView textView) { 276 return actionWithAssertions( 277 new ViewClickAction(Tap.DOUBLE, new CurrentHandleCoordinates(textView), 278 Press.FINGER)); 279 } 280 281 /** 282 * Returns an action that double taps then drags on text from startIndex to endIndex on the 283 * TextView.<br> 284 * <br> 285 * View constraints: 286 * <ul> 287 * <li>must be a TextView displayed on screen 288 * <ul> 289 * 290 * @param startIndex The index of the TextView's text to start a drag from 291 * @param endIndex The index of the TextView's text to end the drag at 292 */ doubleTapAndDragOnText(int startIndex, int endIndex)293 public static ViewAction doubleTapAndDragOnText(int startIndex, int endIndex) { 294 return actionWithAssertions( 295 new DragAction( 296 DragAction.Drag.DOUBLE_TAP, 297 new TextCoordinates(startIndex), 298 new TextCoordinates(endIndex), 299 Press.FINGER, 300 TextView.class)); 301 } 302 303 /** 304 * Returns an action that click then drags by mouse on text from startIndex to endIndex on the 305 * TextView.<br> 306 * <br> 307 * View constraints: 308 * <ul> 309 * <li>must be a TextView displayed on screen 310 * <ul> 311 * 312 * @param startIndex The index of the TextView's text to start a drag from 313 * @param endIndex The index of the TextView's text to end the drag at 314 */ mouseDragOnText(int startIndex, int endIndex)315 public static ViewAction mouseDragOnText(int startIndex, int endIndex) { 316 return actionWithAssertions( 317 new DragAction( 318 DragAction.Drag.MOUSE_DOWN, 319 new TextCoordinates(startIndex), 320 new TextCoordinates(endIndex), 321 Press.PINPOINT, 322 TextView.class)); 323 } 324 325 /** 326 * Returns an action that double click then drags by mouse on text from startIndex to endIndex 327 * on the TextView.<br> 328 * <br> 329 * View constraints: 330 * <ul> 331 * <li>must be a TextView displayed on screen 332 * <ul> 333 * 334 * @param startIndex The index of the TextView's text to start a drag from 335 * @param endIndex The index of the TextView's text to end the drag at 336 */ mouseDoubleClickAndDragOnText(int startIndex, int endIndex)337 public static ViewAction mouseDoubleClickAndDragOnText(int startIndex, int endIndex) { 338 return actionWithAssertions( 339 new DragAction( 340 DragAction.Drag.MOUSE_DOUBLE_CLICK, 341 new TextCoordinates(startIndex), 342 new TextCoordinates(endIndex), 343 Press.PINPOINT, 344 TextView.class)); 345 } 346 347 /** 348 * Returns an action that long click then drags by mouse on text from startIndex to endIndex 349 * on the TextView.<br> 350 * <br> 351 * View constraints: 352 * <ul> 353 * <li>must be a TextView displayed on screen 354 * <ul> 355 * 356 * @param startIndex The index of the TextView's text to start a drag from 357 * @param endIndex The index of the TextView's text to end the drag at 358 */ mouseLongClickAndDragOnText(int startIndex, int endIndex)359 public static ViewAction mouseLongClickAndDragOnText(int startIndex, int endIndex) { 360 return actionWithAssertions( 361 new DragAction( 362 DragAction.Drag.MOUSE_LONG_CLICK, 363 new TextCoordinates(startIndex), 364 new TextCoordinates(endIndex), 365 Press.PINPOINT, 366 TextView.class)); 367 } 368 369 /** 370 * Returns an action that triple click then drags by mouse on text from startIndex to endIndex 371 * on the TextView.<br> 372 * <br> 373 * View constraints: 374 * <ul> 375 * <li>must be a TextView displayed on screen 376 * <ul> 377 * 378 * @param startIndex The index of the TextView's text to start a drag from 379 * @param endIndex The index of the TextView's text to end the drag at 380 */ mouseTripleClickAndDragOnText(int startIndex, int endIndex)381 public static ViewAction mouseTripleClickAndDragOnText(int startIndex, int endIndex) { 382 return actionWithAssertions( 383 new DragAction( 384 DragAction.Drag.MOUSE_TRIPLE_CLICK, 385 new TextCoordinates(startIndex), 386 new TextCoordinates(endIndex), 387 Press.PINPOINT, 388 TextView.class)); 389 } 390 391 public enum Handle { 392 SELECTION_START, 393 SELECTION_END, 394 INSERTION 395 }; 396 397 /** 398 * Returns an action that tap then drags on the handle from the current position to endIndex on 399 * the TextView.<br> 400 * <br> 401 * View constraints: 402 * <ul> 403 * <li>must be a TextView's drag-handle displayed on screen 404 * <ul> 405 * 406 * @param textView TextView the handle is on 407 * @param handleType Type of the handle 408 * @param endIndex The index of the TextView's text to end the drag at 409 */ dragHandle(TextView textView, Handle handleType, int endIndex)410 public static ViewAction dragHandle(TextView textView, Handle handleType, int endIndex) { 411 return dragHandle(textView, handleType, endIndex, true); 412 } 413 414 /** 415 * Returns an action that tap then drags on the handle from the current position to endIndex on 416 * the TextView.<br> 417 * <br> 418 * View constraints: 419 * <ul> 420 * <li>must be a TextView's drag-handle displayed on screen 421 * <ul> 422 * 423 * @param textView TextView the handle is on 424 * @param handleType Type of the handle 425 * @param endIndex The index of the TextView's text to end the drag at 426 * @param primary whether to use primary direction to get coordinate form index when endIndex is 427 * at a direction boundary. 428 */ dragHandle(TextView textView, Handle handleType, int endIndex, boolean primary)429 public static ViewAction dragHandle(TextView textView, Handle handleType, int endIndex, 430 boolean primary) { 431 return actionWithAssertions( 432 new DragAction( 433 DragAction.Drag.TAP, 434 new CurrentHandleCoordinates(textView), 435 new HandleCoordinates(textView, handleType, endIndex, primary), 436 Press.FINGER, 437 Editor.HandleView.class)); 438 } 439 440 /** 441 * Returns an action that drags on text from startIndex to endIndex on the TextView.<br> 442 * <br> 443 * View constraints: 444 * <ul> 445 * <li>must be a TextView displayed on screen 446 * <ul> 447 * 448 * @param startIndex The index of the TextView's text to start a drag from 449 * @param endIndex The index of the TextView's text to end the drag at 450 */ dragOnText(int startIndex, int endIndex)451 public static ViewAction dragOnText(int startIndex, int endIndex) { 452 return actionWithAssertions( 453 new DragAction( 454 DragAction.Drag.TAP, 455 new TextCoordinates(startIndex), 456 new TextCoordinates(endIndex), 457 Press.FINGER, 458 TextView.class)); 459 } 460 461 /** 462 * A provider of the x, y coordinates of the handle dragging point. 463 */ 464 private static final class CurrentHandleCoordinates implements CoordinatesProvider { 465 // Must be larger than Editor#LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS. 466 private final TextView mTextView; 467 private final String mActionDescription; 468 469 CurrentHandleCoordinates(TextView textView)470 public CurrentHandleCoordinates(TextView textView) { 471 mTextView = textView; 472 mActionDescription = "Could not locate handle."; 473 } 474 475 @Override calculateCoordinates(View view)476 public float[] calculateCoordinates(View view) { 477 try { 478 return locateHandle(view); 479 } catch (StringIndexOutOfBoundsException e) { 480 throw new PerformException.Builder() 481 .withActionDescription(mActionDescription) 482 .withViewDescription(HumanReadables.describe(view)) 483 .withCause(e) 484 .build(); 485 } 486 } 487 locateHandle(View view)488 private float[] locateHandle(View view) { 489 final Rect bounds = new Rect(); 490 view.getBoundsOnScreen(bounds); 491 final Rect visibleDisplayBounds = new Rect(); 492 mTextView.getWindowVisibleDisplayFrame(visibleDisplayBounds); 493 visibleDisplayBounds.right -= 1; 494 visibleDisplayBounds.bottom -= 1; 495 if (!visibleDisplayBounds.intersect(bounds)) { 496 throw new PerformException.Builder() 497 .withActionDescription(mActionDescription 498 + " The handle is entirely out of the visible display frame of" 499 + "the TextView's window.") 500 .withViewDescription(HumanReadables.describe(view)) 501 .build(); 502 } 503 final float dragPointX = Math.max(Math.min(bounds.centerX(), 504 visibleDisplayBounds.right), visibleDisplayBounds.left); 505 final float verticalOffset = bounds.height() * 0.7f; 506 final float dragPointY = Math.max(Math.min(bounds.top + verticalOffset, 507 visibleDisplayBounds.bottom), visibleDisplayBounds.top); 508 return new float[] {dragPointX, dragPointY}; 509 } 510 } 511 512 /** 513 * A provider of the x, y coordinates of the handle that points the specified text index in a 514 * text view. 515 */ 516 private static final class HandleCoordinates implements CoordinatesProvider { 517 // Must be larger than Editor#LINE_SLOP_MULTIPLIER_FOR_HANDLEVIEWS. 518 private final static float LINE_SLOP_MULTIPLIER = 0.6f; 519 private final TextView mTextView; 520 private final Handle mHandleType; 521 private final int mIndex; 522 private final boolean mPrimary; 523 private final String mActionDescription; 524 HandleCoordinates(TextView textView, Handle handleType, int index, boolean primary)525 public HandleCoordinates(TextView textView, Handle handleType, int index, boolean primary) { 526 mTextView = textView; 527 mHandleType = handleType; 528 mIndex = index; 529 mPrimary = primary; 530 mActionDescription = "Could not locate " + handleType.toString() 531 + " handle that points text index: " + index 532 + " (" + (primary ? "primary" : "secondary" ) + ")"; 533 } 534 535 @Override calculateCoordinates(View view)536 public float[] calculateCoordinates(View view) { 537 try { 538 return locateHandlePointsTextIndex(view); 539 } catch (StringIndexOutOfBoundsException e) { 540 throw new PerformException.Builder() 541 .withActionDescription(mActionDescription) 542 .withViewDescription(HumanReadables.describe(view)) 543 .withCause(e) 544 .build(); 545 } 546 } 547 locateHandlePointsTextIndex(View view)548 private float[] locateHandlePointsTextIndex(View view) { 549 if (!(view instanceof HandleView)) { 550 throw new PerformException.Builder() 551 .withActionDescription(mActionDescription + " The view is not a HandleView") 552 .withViewDescription(HumanReadables.describe(view)) 553 .build(); 554 } 555 final HandleView handleView = (HandleView) view; 556 final int currentOffset = mHandleType == Handle.SELECTION_START ? 557 mTextView.getSelectionStart() : mTextView.getSelectionEnd(); 558 559 final Layout layout = mTextView.getLayout(); 560 561 final int currentLine = layout.getLineForOffset(currentOffset); 562 final int targetLine = layout.getLineForOffset(mIndex); 563 final float currentX = handleView.getHorizontal(layout, currentOffset); 564 final float currentY = layout.getLineTop(currentLine); 565 final float[] currentCoordinates = 566 TextCoordinates.convertToScreenCoordinates(mTextView, currentX, currentY); 567 final float[] targetCoordinates = 568 (new TextCoordinates(mIndex, mPrimary)).calculateCoordinates(mTextView); 569 final Rect bounds = new Rect(); 570 view.getBoundsOnScreen(bounds); 571 final Rect visibleDisplayBounds = new Rect(); 572 mTextView.getWindowVisibleDisplayFrame(visibleDisplayBounds); 573 visibleDisplayBounds.right -= 1; 574 visibleDisplayBounds.bottom -= 1; 575 if (!visibleDisplayBounds.intersect(bounds)) { 576 throw new PerformException.Builder() 577 .withActionDescription(mActionDescription 578 + " The handle is entirely out of the visible display frame of" 579 + "the TextView's window.") 580 .withViewDescription(HumanReadables.describe(view)) 581 .build(); 582 } 583 final float dragPointX = Math.max(Math.min(bounds.centerX(), 584 visibleDisplayBounds.right), visibleDisplayBounds.left); 585 final float diffX = dragPointX - currentCoordinates[0]; 586 final float verticalOffset = bounds.height() * 0.7f; 587 final float dragPointY = Math.max(Math.min(bounds.top + verticalOffset, 588 visibleDisplayBounds.bottom), visibleDisplayBounds.top); 589 float diffY = dragPointY - currentCoordinates[1]; 590 if (currentLine > targetLine) { 591 diffY -= mTextView.getLineHeight() * LINE_SLOP_MULTIPLIER; 592 } else if (currentLine < targetLine) { 593 diffY += mTextView.getLineHeight() * LINE_SLOP_MULTIPLIER; 594 } 595 return new float[] {targetCoordinates[0] + diffX, targetCoordinates[1] + diffY}; 596 } 597 } 598 599 /** 600 * A provider of the x, y coordinates of the text at the specified index in a text view. 601 */ 602 private static final class TextCoordinates implements CoordinatesProvider { 603 604 private final int mIndex; 605 private final boolean mPrimary; 606 private final String mActionDescription; 607 TextCoordinates(int index)608 public TextCoordinates(int index) { 609 this(index, true); 610 } 611 TextCoordinates(int index, boolean primary)612 public TextCoordinates(int index, boolean primary) { 613 mIndex = index; 614 mPrimary = primary; 615 mActionDescription = "Could not locate text at index: " + mIndex 616 + " (" + (primary ? "primary" : "secondary" ) + ")"; 617 } 618 619 @Override calculateCoordinates(View view)620 public float[] calculateCoordinates(View view) { 621 try { 622 return locateTextAtIndex((TextView) view, mIndex, mPrimary); 623 } catch (ClassCastException e) { 624 throw new PerformException.Builder() 625 .withActionDescription(mActionDescription) 626 .withViewDescription(HumanReadables.describe(view)) 627 .withCause(e) 628 .build(); 629 } catch (StringIndexOutOfBoundsException e) { 630 throw new PerformException.Builder() 631 .withActionDescription(mActionDescription) 632 .withViewDescription(HumanReadables.describe(view)) 633 .withCause(e) 634 .build(); 635 } 636 } 637 638 /** 639 * @throws StringIndexOutOfBoundsException 640 */ locateTextAtIndex(TextView textView, int index, boolean primary)641 private float[] locateTextAtIndex(TextView textView, int index, boolean primary) { 642 if (index < 0 || index > textView.getText().length()) { 643 throw new StringIndexOutOfBoundsException(index); 644 } 645 final Layout layout = textView.getLayout(); 646 final int line = layout.getLineForOffset(index); 647 return convertToScreenCoordinates(textView, 648 (primary ? layout.getPrimaryHorizontal(index) 649 : layout.getSecondaryHorizontal(index)), 650 layout.getLineTop(line)); 651 } 652 653 /** 654 * Convert TextView's local coordinates to on screen coordinates. 655 * @param textView the TextView 656 * @param x local horizontal coordinate 657 * @param y local vertical coordinate 658 * @return 659 */ convertToScreenCoordinates(TextView textView, float x, float y)660 public static float[] convertToScreenCoordinates(TextView textView, float x, float y) { 661 final int[] xy = new int[2]; 662 textView.getLocationOnScreen(xy); 663 return new float[]{ x + textView.getTotalPaddingLeft() - textView.getScrollX() + xy[0], 664 y + textView.getTotalPaddingTop() - textView.getScrollY() + xy[1] }; 665 } 666 } 667 } 668