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