1 /* 2 * Copyright (C) 2010 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.text.method; 18 19 import android.annotation.NonNull; 20 import android.text.Layout; 21 import android.text.Spannable; 22 import android.view.InputDevice; 23 import android.view.KeyEvent; 24 import android.view.MotionEvent; 25 import android.widget.TextView; 26 27 /** 28 * Base classes for movement methods. 29 */ 30 public class BaseMovementMethod implements MovementMethod { 31 @Override canSelectArbitrarily()32 public boolean canSelectArbitrarily() { 33 return false; 34 } 35 36 @Override initialize(TextView widget, Spannable text)37 public void initialize(TextView widget, Spannable text) { 38 } 39 40 @Override onKeyDown(TextView widget, Spannable text, int keyCode, KeyEvent event)41 public boolean onKeyDown(TextView widget, Spannable text, int keyCode, KeyEvent event) { 42 final int movementMetaState = getMovementMetaState(text, event); 43 boolean handled = handleMovementKey(widget, text, keyCode, movementMetaState, event); 44 if (handled) { 45 MetaKeyKeyListener.adjustMetaAfterKeypress(text); 46 MetaKeyKeyListener.resetLockedMeta(text); 47 } 48 return handled; 49 } 50 51 @Override onKeyOther(TextView widget, Spannable text, KeyEvent event)52 public boolean onKeyOther(TextView widget, Spannable text, KeyEvent event) { 53 final int movementMetaState = getMovementMetaState(text, event); 54 final int keyCode = event.getKeyCode(); 55 if (keyCode != KeyEvent.KEYCODE_UNKNOWN 56 && event.getAction() == KeyEvent.ACTION_MULTIPLE) { 57 final int repeat = event.getRepeatCount(); 58 boolean handled = false; 59 for (int i = 0; i < repeat; i++) { 60 if (!handleMovementKey(widget, text, keyCode, movementMetaState, event)) { 61 break; 62 } 63 handled = true; 64 } 65 if (handled) { 66 MetaKeyKeyListener.adjustMetaAfterKeypress(text); 67 MetaKeyKeyListener.resetLockedMeta(text); 68 } 69 return handled; 70 } 71 return false; 72 } 73 74 @Override onKeyUp(TextView widget, Spannable text, int keyCode, KeyEvent event)75 public boolean onKeyUp(TextView widget, Spannable text, int keyCode, KeyEvent event) { 76 return false; 77 } 78 79 @Override onTakeFocus(TextView widget, Spannable text, int direction)80 public void onTakeFocus(TextView widget, Spannable text, int direction) { 81 } 82 83 @Override onTouchEvent(TextView widget, Spannable text, MotionEvent event)84 public boolean onTouchEvent(TextView widget, Spannable text, MotionEvent event) { 85 return false; 86 } 87 88 @Override onTrackballEvent(TextView widget, Spannable text, MotionEvent event)89 public boolean onTrackballEvent(TextView widget, Spannable text, MotionEvent event) { 90 return false; 91 } 92 93 @Override onGenericMotionEvent(TextView widget, Spannable text, MotionEvent event)94 public boolean onGenericMotionEvent(TextView widget, Spannable text, MotionEvent event) { 95 if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { 96 switch (event.getAction()) { 97 case MotionEvent.ACTION_SCROLL: { 98 final float vscroll; 99 final float hscroll; 100 if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) { 101 vscroll = 0; 102 hscroll = event.getAxisValue(MotionEvent.AXIS_VSCROLL); 103 } else { 104 vscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL); 105 hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL); 106 } 107 108 boolean handled = false; 109 if (hscroll < 0) { 110 handled |= scrollLeft(widget, text, (int)Math.ceil(-hscroll)); 111 } else if (hscroll > 0) { 112 handled |= scrollRight(widget, text, (int)Math.ceil(hscroll)); 113 } 114 if (vscroll < 0) { 115 handled |= scrollUp(widget, text, (int)Math.ceil(-vscroll)); 116 } else if (vscroll > 0) { 117 handled |= scrollDown(widget, text, (int)Math.ceil(vscroll)); 118 } 119 return handled; 120 } 121 } 122 } 123 return false; 124 } 125 126 /** 127 * Gets the meta state used for movement using the modifiers tracked by the text 128 * buffer as well as those present in the key event. 129 * 130 * The movement meta state excludes the state of locked modifiers or the SHIFT key 131 * since they are not used by movement actions (but they may be used for selection). 132 * 133 * @param buffer The text buffer. 134 * @param event The key event. 135 * @return The keyboard meta states used for movement. 136 */ getMovementMetaState(Spannable buffer, KeyEvent event)137 protected int getMovementMetaState(Spannable buffer, KeyEvent event) { 138 // We ignore locked modifiers and SHIFT. 139 int metaState = MetaKeyKeyListener.getMetaState(buffer, event) 140 & ~(MetaKeyKeyListener.META_ALT_LOCKED | MetaKeyKeyListener.META_SYM_LOCKED); 141 return KeyEvent.normalizeMetaState(metaState) & ~KeyEvent.META_SHIFT_MASK; 142 } 143 144 /** 145 * Performs a movement key action. 146 * The default implementation decodes the key down and invokes movement actions 147 * such as {@link #down} and {@link #up}. 148 * {@link #onKeyDown(TextView, Spannable, int, KeyEvent)} calls this method once 149 * to handle an {@link KeyEvent#ACTION_DOWN}. 150 * {@link #onKeyOther(TextView, Spannable, KeyEvent)} calls this method repeatedly 151 * to handle each repetition of an {@link KeyEvent#ACTION_MULTIPLE}. 152 * 153 * @param widget The text view. 154 * @param buffer The text buffer. 155 * @param event The key event. 156 * @param keyCode The key code. 157 * @param movementMetaState The keyboard meta states used for movement. 158 * @param event The key event. 159 * @return True if the event was handled. 160 */ handleMovementKey(TextView widget, Spannable buffer, int keyCode, int movementMetaState, KeyEvent event)161 protected boolean handleMovementKey(TextView widget, Spannable buffer, 162 int keyCode, int movementMetaState, KeyEvent event) { 163 switch (keyCode) { 164 case KeyEvent.KEYCODE_DPAD_LEFT: 165 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { 166 return left(widget, buffer); 167 } else if (KeyEvent.metaStateHasModifiers(movementMetaState, 168 KeyEvent.META_CTRL_ON)) { 169 return leftWord(widget, buffer); 170 } else if (KeyEvent.metaStateHasModifiers(movementMetaState, 171 KeyEvent.META_ALT_ON)) { 172 return lineStart(widget, buffer); 173 } 174 break; 175 176 case KeyEvent.KEYCODE_DPAD_RIGHT: 177 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { 178 return right(widget, buffer); 179 } else if (KeyEvent.metaStateHasModifiers(movementMetaState, 180 KeyEvent.META_CTRL_ON)) { 181 return rightWord(widget, buffer); 182 } else if (KeyEvent.metaStateHasModifiers(movementMetaState, 183 KeyEvent.META_ALT_ON)) { 184 return lineEnd(widget, buffer); 185 } 186 break; 187 188 case KeyEvent.KEYCODE_DPAD_UP: 189 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { 190 return up(widget, buffer); 191 } else if (KeyEvent.metaStateHasModifiers(movementMetaState, 192 KeyEvent.META_ALT_ON)) { 193 return top(widget, buffer); 194 } else if (KeyEvent.metaStateHasModifiers(movementMetaState, 195 KeyEvent.META_CTRL_ON)) { 196 return previousParagraph(widget, buffer); 197 } 198 break; 199 200 case KeyEvent.KEYCODE_DPAD_DOWN: 201 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { 202 return down(widget, buffer); 203 } else if (KeyEvent.metaStateHasModifiers(movementMetaState, 204 KeyEvent.META_ALT_ON)) { 205 return bottom(widget, buffer); 206 } else if (KeyEvent.metaStateHasModifiers(movementMetaState, 207 KeyEvent.META_CTRL_ON)) { 208 return nextParagraph(widget, buffer); 209 } 210 break; 211 212 case KeyEvent.KEYCODE_PAGE_UP: 213 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { 214 return pageUp(widget, buffer); 215 } else if (KeyEvent.metaStateHasModifiers(movementMetaState, 216 KeyEvent.META_ALT_ON)) { 217 return top(widget, buffer); 218 } 219 break; 220 221 case KeyEvent.KEYCODE_PAGE_DOWN: 222 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { 223 return pageDown(widget, buffer); 224 } else if (KeyEvent.metaStateHasModifiers(movementMetaState, 225 KeyEvent.META_ALT_ON)) { 226 return bottom(widget, buffer); 227 } 228 break; 229 230 case KeyEvent.KEYCODE_MOVE_HOME: 231 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { 232 return home(widget, buffer); 233 } else if (KeyEvent.metaStateHasModifiers(movementMetaState, 234 KeyEvent.META_CTRL_ON)) { 235 return top(widget, buffer); 236 } 237 break; 238 239 case KeyEvent.KEYCODE_MOVE_END: 240 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { 241 return end(widget, buffer); 242 } else if (KeyEvent.metaStateHasModifiers(movementMetaState, 243 KeyEvent.META_CTRL_ON)) { 244 return bottom(widget, buffer); 245 } 246 break; 247 } 248 return false; 249 } 250 251 /** 252 * Performs a left movement action. 253 * Moves the cursor or scrolls left by one character. 254 * 255 * @param widget The text view. 256 * @param buffer The text buffer. 257 * @return True if the event was handled. 258 */ left(TextView widget, Spannable buffer)259 protected boolean left(TextView widget, Spannable buffer) { 260 return false; 261 } 262 263 /** 264 * Performs a right movement action. 265 * Moves the cursor or scrolls right by one character. 266 * 267 * @param widget The text view. 268 * @param buffer The text buffer. 269 * @return True if the event was handled. 270 */ right(TextView widget, Spannable buffer)271 protected boolean right(TextView widget, Spannable buffer) { 272 return false; 273 } 274 275 /** 276 * Performs an up movement action. 277 * Moves the cursor or scrolls up by one line. 278 * 279 * @param widget The text view. 280 * @param buffer The text buffer. 281 * @return True if the event was handled. 282 */ up(TextView widget, Spannable buffer)283 protected boolean up(TextView widget, Spannable buffer) { 284 return false; 285 } 286 287 /** 288 * Performs a down movement action. 289 * Moves the cursor or scrolls down by one line. 290 * 291 * @param widget The text view. 292 * @param buffer The text buffer. 293 * @return True if the event was handled. 294 */ down(TextView widget, Spannable buffer)295 protected boolean down(TextView widget, Spannable buffer) { 296 return false; 297 } 298 299 /** 300 * Performs a page-up movement action. 301 * Moves the cursor or scrolls up by one page. 302 * 303 * @param widget The text view. 304 * @param buffer The text buffer. 305 * @return True if the event was handled. 306 */ pageUp(TextView widget, Spannable buffer)307 protected boolean pageUp(TextView widget, Spannable buffer) { 308 return false; 309 } 310 311 /** 312 * Performs a page-down movement action. 313 * Moves the cursor or scrolls down by one page. 314 * 315 * @param widget The text view. 316 * @param buffer The text buffer. 317 * @return True if the event was handled. 318 */ pageDown(TextView widget, Spannable buffer)319 protected boolean pageDown(TextView widget, Spannable buffer) { 320 return false; 321 } 322 323 /** 324 * Performs a top movement action. 325 * Moves the cursor or scrolls to the top of the buffer. 326 * 327 * @param widget The text view. 328 * @param buffer The text buffer. 329 * @return True if the event was handled. 330 */ top(TextView widget, Spannable buffer)331 protected boolean top(TextView widget, Spannable buffer) { 332 return false; 333 } 334 335 /** 336 * Performs a bottom movement action. 337 * Moves the cursor or scrolls to the bottom of the buffer. 338 * 339 * @param widget The text view. 340 * @param buffer The text buffer. 341 * @return True if the event was handled. 342 */ bottom(TextView widget, Spannable buffer)343 protected boolean bottom(TextView widget, Spannable buffer) { 344 return false; 345 } 346 347 /** 348 * Performs a line-start movement action. 349 * Moves the cursor or scrolls to the start of the line. 350 * 351 * @param widget The text view. 352 * @param buffer The text buffer. 353 * @return True if the event was handled. 354 */ lineStart(TextView widget, Spannable buffer)355 protected boolean lineStart(TextView widget, Spannable buffer) { 356 return false; 357 } 358 359 /** 360 * Performs a line-end movement action. 361 * Moves the cursor or scrolls to the end of the line. 362 * 363 * @param widget The text view. 364 * @param buffer The text buffer. 365 * @return True if the event was handled. 366 */ lineEnd(TextView widget, Spannable buffer)367 protected boolean lineEnd(TextView widget, Spannable buffer) { 368 return false; 369 } 370 371 /** {@hide} */ leftWord(TextView widget, Spannable buffer)372 protected boolean leftWord(TextView widget, Spannable buffer) { 373 return false; 374 } 375 376 /** {@hide} */ rightWord(TextView widget, Spannable buffer)377 protected boolean rightWord(TextView widget, Spannable buffer) { 378 return false; 379 } 380 381 /** 382 * Performs a home movement action. 383 * Moves the cursor or scrolls to the start of the line or to the top of the 384 * document depending on whether the insertion point is being moved or 385 * the document is being scrolled. 386 * 387 * @param widget The text view. 388 * @param buffer The text buffer. 389 * @return True if the event was handled. 390 */ home(TextView widget, Spannable buffer)391 protected boolean home(TextView widget, Spannable buffer) { 392 return false; 393 } 394 395 /** 396 * Performs an end movement action. 397 * Moves the cursor or scrolls to the start of the line or to the top of the 398 * document depending on whether the insertion point is being moved or 399 * the document is being scrolled. 400 * 401 * @param widget The text view. 402 * @param buffer The text buffer. 403 * @return True if the event was handled. 404 */ end(TextView widget, Spannable buffer)405 protected boolean end(TextView widget, Spannable buffer) { 406 return false; 407 } 408 409 /** 410 * Performs a previous paragraph movement action. 411 * 412 * @param widget the text view 413 * @param buffer the text buffer 414 * @return true if the event was handled, otherwise false. 415 */ previousParagraph(@onNull TextView widget, @NonNull Spannable buffer)416 public boolean previousParagraph(@NonNull TextView widget, @NonNull Spannable buffer) { 417 return false; 418 } 419 420 /** 421 * Performs a next paragraph movement action. 422 * 423 * @param widget the text view 424 * @param buffer the text buffer 425 * @return true if the event was handled, otherwise false. 426 */ nextParagraph(@onNull TextView widget, @NonNull Spannable buffer)427 public boolean nextParagraph(@NonNull TextView widget, @NonNull Spannable buffer) { 428 return false; 429 } 430 getTopLine(TextView widget)431 private int getTopLine(TextView widget) { 432 return widget.getLayout().getLineForVertical(widget.getScrollY()); 433 } 434 getBottomLine(TextView widget)435 private int getBottomLine(TextView widget) { 436 return widget.getLayout().getLineForVertical(widget.getScrollY() + getInnerHeight(widget)); 437 } 438 getInnerWidth(TextView widget)439 private int getInnerWidth(TextView widget) { 440 return widget.getWidth() - widget.getTotalPaddingLeft() - widget.getTotalPaddingRight(); 441 } 442 getInnerHeight(TextView widget)443 private int getInnerHeight(TextView widget) { 444 return widget.getHeight() - widget.getTotalPaddingTop() - widget.getTotalPaddingBottom(); 445 } 446 getCharacterWidth(TextView widget)447 private int getCharacterWidth(TextView widget) { 448 return (int) Math.ceil(widget.getPaint().getFontSpacing()); 449 } 450 getScrollBoundsLeft(TextView widget)451 private int getScrollBoundsLeft(TextView widget) { 452 final Layout layout = widget.getLayout(); 453 final int topLine = getTopLine(widget); 454 final int bottomLine = getBottomLine(widget); 455 if (topLine > bottomLine) { 456 return 0; 457 } 458 int left = Integer.MAX_VALUE; 459 for (int line = topLine; line <= bottomLine; line++) { 460 final int lineLeft = (int) Math.floor(layout.getLineLeft(line)); 461 if (lineLeft < left) { 462 left = lineLeft; 463 } 464 } 465 return left; 466 } 467 getScrollBoundsRight(TextView widget)468 private int getScrollBoundsRight(TextView widget) { 469 final Layout layout = widget.getLayout(); 470 final int topLine = getTopLine(widget); 471 final int bottomLine = getBottomLine(widget); 472 if (topLine > bottomLine) { 473 return 0; 474 } 475 int right = Integer.MIN_VALUE; 476 for (int line = topLine; line <= bottomLine; line++) { 477 final int lineRight = (int) Math.ceil(layout.getLineRight(line)); 478 if (lineRight > right) { 479 right = lineRight; 480 } 481 } 482 return right; 483 } 484 485 /** 486 * Performs a scroll left action. 487 * Scrolls left by the specified number of characters. 488 * 489 * @param widget The text view. 490 * @param buffer The text buffer. 491 * @param amount The number of characters to scroll by. Must be at least 1. 492 * @return True if the event was handled. 493 * @hide 494 */ scrollLeft(TextView widget, Spannable buffer, int amount)495 protected boolean scrollLeft(TextView widget, Spannable buffer, int amount) { 496 final int minScrollX = getScrollBoundsLeft(widget); 497 int scrollX = widget.getScrollX(); 498 if (scrollX > minScrollX) { 499 scrollX = Math.max(scrollX - getCharacterWidth(widget) * amount, minScrollX); 500 widget.scrollTo(scrollX, widget.getScrollY()); 501 return true; 502 } 503 return false; 504 } 505 506 /** 507 * Performs a scroll right action. 508 * Scrolls right by the specified number of characters. 509 * 510 * @param widget The text view. 511 * @param buffer The text buffer. 512 * @param amount The number of characters to scroll by. Must be at least 1. 513 * @return True if the event was handled. 514 * @hide 515 */ scrollRight(TextView widget, Spannable buffer, int amount)516 protected boolean scrollRight(TextView widget, Spannable buffer, int amount) { 517 final int maxScrollX = getScrollBoundsRight(widget) - getInnerWidth(widget); 518 int scrollX = widget.getScrollX(); 519 if (scrollX < maxScrollX) { 520 scrollX = Math.min(scrollX + getCharacterWidth(widget) * amount, maxScrollX); 521 widget.scrollTo(scrollX, widget.getScrollY()); 522 return true; 523 } 524 return false; 525 } 526 527 /** 528 * Performs a scroll up action. 529 * Scrolls up by the specified number of lines. 530 * 531 * @param widget The text view. 532 * @param buffer The text buffer. 533 * @param amount The number of lines to scroll by. Must be at least 1. 534 * @return True if the event was handled. 535 * @hide 536 */ scrollUp(TextView widget, Spannable buffer, int amount)537 protected boolean scrollUp(TextView widget, Spannable buffer, int amount) { 538 final Layout layout = widget.getLayout(); 539 final int top = widget.getScrollY(); 540 int topLine = layout.getLineForVertical(top); 541 if (layout.getLineTop(topLine) == top) { 542 // If the top line is partially visible, bring it all the way 543 // into view; otherwise, bring the previous line into view. 544 topLine -= 1; 545 } 546 if (topLine >= 0) { 547 topLine = Math.max(topLine - amount + 1, 0); 548 Touch.scrollTo(widget, layout, widget.getScrollX(), layout.getLineTop(topLine)); 549 return true; 550 } 551 return false; 552 } 553 554 /** 555 * Performs a scroll down action. 556 * Scrolls down by the specified number of lines. 557 * 558 * @param widget The text view. 559 * @param buffer The text buffer. 560 * @param amount The number of lines to scroll by. Must be at least 1. 561 * @return True if the event was handled. 562 * @hide 563 */ scrollDown(TextView widget, Spannable buffer, int amount)564 protected boolean scrollDown(TextView widget, Spannable buffer, int amount) { 565 final Layout layout = widget.getLayout(); 566 final int innerHeight = getInnerHeight(widget); 567 final int bottom = widget.getScrollY() + innerHeight; 568 int bottomLine = layout.getLineForVertical(bottom); 569 if (layout.getLineTop(bottomLine + 1) < bottom + 1) { 570 // Less than a pixel of this line is out of view, 571 // so we must have tried to make it entirely in view 572 // and now want the next line to be in view instead. 573 bottomLine += 1; 574 } 575 final int limit = layout.getLineCount() - 1; 576 if (bottomLine <= limit) { 577 bottomLine = Math.min(bottomLine + amount - 1, limit); 578 Touch.scrollTo(widget, layout, widget.getScrollX(), 579 layout.getLineTop(bottomLine + 1) - innerHeight); 580 return true; 581 } 582 return false; 583 } 584 585 /** 586 * Performs a scroll page up action. 587 * Scrolls up by one page. 588 * 589 * @param widget The text view. 590 * @param buffer The text buffer. 591 * @return True if the event was handled. 592 * @hide 593 */ scrollPageUp(TextView widget, Spannable buffer)594 protected boolean scrollPageUp(TextView widget, Spannable buffer) { 595 final Layout layout = widget.getLayout(); 596 final int top = widget.getScrollY() - getInnerHeight(widget); 597 int topLine = layout.getLineForVertical(top); 598 if (topLine >= 0) { 599 Touch.scrollTo(widget, layout, widget.getScrollX(), layout.getLineTop(topLine)); 600 return true; 601 } 602 return false; 603 } 604 605 /** 606 * Performs a scroll page up action. 607 * Scrolls down by one page. 608 * 609 * @param widget The text view. 610 * @param buffer The text buffer. 611 * @return True if the event was handled. 612 * @hide 613 */ scrollPageDown(TextView widget, Spannable buffer)614 protected boolean scrollPageDown(TextView widget, Spannable buffer) { 615 final Layout layout = widget.getLayout(); 616 final int innerHeight = getInnerHeight(widget); 617 final int bottom = widget.getScrollY() + innerHeight + innerHeight; 618 int bottomLine = layout.getLineForVertical(bottom); 619 if (bottomLine <= layout.getLineCount() - 1) { 620 Touch.scrollTo(widget, layout, widget.getScrollX(), 621 layout.getLineTop(bottomLine + 1) - innerHeight); 622 return true; 623 } 624 return false; 625 } 626 627 /** 628 * Performs a scroll to top action. 629 * Scrolls to the top of the document. 630 * 631 * @param widget The text view. 632 * @param buffer The text buffer. 633 * @return True if the event was handled. 634 * @hide 635 */ scrollTop(TextView widget, Spannable buffer)636 protected boolean scrollTop(TextView widget, Spannable buffer) { 637 final Layout layout = widget.getLayout(); 638 if (getTopLine(widget) >= 0) { 639 Touch.scrollTo(widget, layout, widget.getScrollX(), layout.getLineTop(0)); 640 return true; 641 } 642 return false; 643 } 644 645 /** 646 * Performs a scroll to bottom action. 647 * Scrolls to the bottom of the document. 648 * 649 * @param widget The text view. 650 * @param buffer The text buffer. 651 * @return True if the event was handled. 652 * @hide 653 */ scrollBottom(TextView widget, Spannable buffer)654 protected boolean scrollBottom(TextView widget, Spannable buffer) { 655 final Layout layout = widget.getLayout(); 656 final int lineCount = layout.getLineCount(); 657 if (getBottomLine(widget) <= lineCount - 1) { 658 Touch.scrollTo(widget, layout, widget.getScrollX(), 659 layout.getLineTop(lineCount) - getInnerHeight(widget)); 660 return true; 661 } 662 return false; 663 } 664 665 /** 666 * Performs a scroll to line start action. 667 * Scrolls to the start of the line. 668 * 669 * @param widget The text view. 670 * @param buffer The text buffer. 671 * @return True if the event was handled. 672 * @hide 673 */ scrollLineStart(TextView widget, Spannable buffer)674 protected boolean scrollLineStart(TextView widget, Spannable buffer) { 675 final int minScrollX = getScrollBoundsLeft(widget); 676 int scrollX = widget.getScrollX(); 677 if (scrollX > minScrollX) { 678 widget.scrollTo(minScrollX, widget.getScrollY()); 679 return true; 680 } 681 return false; 682 } 683 684 /** 685 * Performs a scroll to line end action. 686 * Scrolls to the end of the line. 687 * 688 * @param widget The text view. 689 * @param buffer The text buffer. 690 * @return True if the event was handled. 691 * @hide 692 */ scrollLineEnd(TextView widget, Spannable buffer)693 protected boolean scrollLineEnd(TextView widget, Spannable buffer) { 694 final int maxScrollX = getScrollBoundsRight(widget) - getInnerWidth(widget); 695 int scrollX = widget.getScrollX(); 696 if (scrollX < maxScrollX) { 697 widget.scrollTo(maxScrollX, widget.getScrollY()); 698 return true; 699 } 700 return false; 701 } 702 } 703