diff options
author | Jeff Brown <jeffbrown@google.com> | 2010-12-17 18:33:02 -0800 |
---|---|---|
committer | Jeff Brown <jeffbrown@google.com> | 2010-12-21 18:03:08 -0800 |
commit | 67b6ab72ae96a9f2be929de2c32c110df5390fdd (patch) | |
tree | 010e0fe89f0659721e24562ec2a346801222f8d0 /core/java | |
parent | d1e8e94368d8b6ac245fdcee227c6349654446ff (diff) | |
download | frameworks_base-67b6ab72ae96a9f2be929de2c32c110df5390fdd.zip frameworks_base-67b6ab72ae96a9f2be929de2c32c110df5390fdd.tar.gz frameworks_base-67b6ab72ae96a9f2be929de2c32c110df5390fdd.tar.bz2 |
Add TextView support for Home, End, PageUp, PageDown.
Change-Id: If8aa2a63b5fc33528d54eef68e695082a129acce
Diffstat (limited to 'core/java')
5 files changed, 677 insertions, 295 deletions
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java index d724320..a61ff13 100644 --- a/core/java/android/text/method/ArrowKeyMovementMethod.java +++ b/core/java/android/text/method/ArrowKeyMovementMethod.java @@ -16,6 +16,7 @@ package android.text.method; +import android.graphics.Rect; import android.text.Layout; import android.text.Selection; import android.text.Spannable; @@ -24,170 +25,185 @@ import android.view.MotionEvent; import android.view.View; import android.widget.TextView; -// XXX this doesn't extend MetaKeyKeyListener because the signatures -// don't match. Need to figure that out. Meanwhile the meta keys -// won't work in fields that don't take input. - -public class ArrowKeyMovementMethod implements MovementMethod { - private boolean isCap(Spannable buffer) { +/** + * A movement method that provides cursor movement and selection. + * Supports displaying the context menu on DPad Center. + */ +public class ArrowKeyMovementMethod extends BaseMovementMethod implements MovementMethod { + private static boolean isSelecting(Spannable buffer) { return ((MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SHIFT_ON) == 1) || (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0)); } - private boolean isAlt(Spannable buffer) { - return MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_ALT_ON) == 1; + private int getCurrentLineTop(Spannable buffer, Layout layout) { + return layout.getLineTop(layout.getLineForOffset(Selection.getSelectionEnd(buffer))); } - private boolean up(TextView widget, Spannable buffer) { - boolean cap = isCap(buffer); - boolean alt = isAlt(buffer); - Layout layout = widget.getLayout(); + private int getPageHeight(TextView widget) { + // This calculation does not take into account the view transformations that + // may have been applied to the child or its containers. In case of scaling or + // rotation, the calculated page height may be incorrect. + final Rect rect = new Rect(); + return widget.getGlobalVisibleRect(rect) ? rect.height() : 0; + } - if (cap) { - if (alt) { - Selection.extendSelection(buffer, 0); - return true; - } else { - return Selection.extendUp(buffer, layout); - } + @Override + protected boolean handleMovementKey(TextView widget, Spannable buffer, int keyCode, + int movementMetaState, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_CENTER: + if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { + if (event.getAction() == KeyEvent.ACTION_DOWN + && event.getRepeatCount() == 0 + && MetaKeyKeyListener.getMetaState(buffer, + MetaKeyKeyListener.META_SELECTING) != 0) { + return widget.showContextMenu(); + } + } + break; + } + return super.handleMovementKey(widget, buffer, keyCode, movementMetaState, event); + } + + @Override + protected boolean left(TextView widget, Spannable buffer) { + final Layout layout = widget.getLayout(); + if (isSelecting(buffer)) { + return Selection.extendLeft(buffer, layout); } else { - if (alt) { - Selection.setSelection(buffer, 0); - return true; - } else { - return Selection.moveUp(buffer, layout); - } + return Selection.moveLeft(buffer, layout); } } - private boolean down(TextView widget, Spannable buffer) { - boolean cap = isCap(buffer); - boolean alt = isAlt(buffer); - Layout layout = widget.getLayout(); + @Override + protected boolean right(TextView widget, Spannable buffer) { + final Layout layout = widget.getLayout(); + if (isSelecting(buffer)) { + return Selection.extendRight(buffer, layout); + } else { + return Selection.moveRight(buffer, layout); + } + } - if (cap) { - if (alt) { - Selection.extendSelection(buffer, buffer.length()); - return true; - } else { - return Selection.extendDown(buffer, layout); - } + @Override + protected boolean up(TextView widget, Spannable buffer) { + final Layout layout = widget.getLayout(); + if (isSelecting(buffer)) { + return Selection.extendUp(buffer, layout); } else { - if (alt) { - Selection.setSelection(buffer, buffer.length()); - return true; - } else { - return Selection.moveDown(buffer, layout); - } + return Selection.moveUp(buffer, layout); } } - private boolean left(TextView widget, Spannable buffer) { - boolean cap = isCap(buffer); - boolean alt = isAlt(buffer); - Layout layout = widget.getLayout(); + @Override + protected boolean down(TextView widget, Spannable buffer) { + final Layout layout = widget.getLayout(); + if (isSelecting(buffer)) { + return Selection.extendDown(buffer, layout); + } else { + return Selection.moveDown(buffer, layout); + } + } - if (cap) { - if (alt) { - return Selection.extendToLeftEdge(buffer, layout); + @Override + protected boolean pageUp(TextView widget, Spannable buffer) { + final Layout layout = widget.getLayout(); + final boolean selecting = isSelecting(buffer); + final int targetY = getCurrentLineTop(buffer, layout) - getPageHeight(widget); + boolean handled = false; + for (;;) { + final int previousSelectionEnd = Selection.getSelectionEnd(buffer); + if (selecting) { + Selection.extendUp(buffer, layout); } else { - return Selection.extendLeft(buffer, layout); + Selection.moveUp(buffer, layout); } - } else { - if (alt) { - return Selection.moveToLeftEdge(buffer, layout); - } else { - return Selection.moveLeft(buffer, layout); + if (Selection.getSelectionEnd(buffer) == previousSelectionEnd) { + break; + } + handled = true; + if (getCurrentLineTop(buffer, layout) <= targetY) { + break; } } + return handled; } - private boolean right(TextView widget, Spannable buffer) { - boolean cap = isCap(buffer); - boolean alt = isAlt(buffer); - Layout layout = widget.getLayout(); - - if (cap) { - if (alt) { - return Selection.extendToRightEdge(buffer, layout); + @Override + protected boolean pageDown(TextView widget, Spannable buffer) { + final Layout layout = widget.getLayout(); + final boolean selecting = isSelecting(buffer); + final int targetY = getCurrentLineTop(buffer, layout) + getPageHeight(widget); + boolean handled = false; + for (;;) { + final int previousSelectionEnd = Selection.getSelectionEnd(buffer); + if (selecting) { + Selection.extendDown(buffer, layout); } else { - return Selection.extendRight(buffer, layout); + Selection.moveDown(buffer, layout); } - } else { - if (alt) { - return Selection.moveToRightEdge(buffer, layout); - } else { - return Selection.moveRight(buffer, layout); + if (Selection.getSelectionEnd(buffer) == previousSelectionEnd) { + break; + } + handled = true; + if (getCurrentLineTop(buffer, layout) >= targetY) { + break; } } + return handled; } - public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) { - if (executeDown(widget, buffer, keyCode)) { - MetaKeyKeyListener.adjustMetaAfterKeypress(buffer); - MetaKeyKeyListener.resetLockedMeta(buffer); - return true; + @Override + protected boolean top(TextView widget, Spannable buffer) { + if (isSelecting(buffer)) { + Selection.extendSelection(buffer, 0); + } else { + Selection.setSelection(buffer, 0); } - - return false; + return true; } - private boolean executeDown(TextView widget, Spannable buffer, int keyCode) { - boolean handled = false; - - switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_UP: - handled |= up(widget, buffer); - break; - - case KeyEvent.KEYCODE_DPAD_DOWN: - handled |= down(widget, buffer); - break; - - case KeyEvent.KEYCODE_DPAD_LEFT: - handled |= left(widget, buffer); - break; - - case KeyEvent.KEYCODE_DPAD_RIGHT: - handled |= right(widget, buffer); - break; - - case KeyEvent.KEYCODE_DPAD_CENTER: - if ((MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) && - (widget.showContextMenu())) { - handled = true; - } + @Override + protected boolean bottom(TextView widget, Spannable buffer) { + if (isSelecting(buffer)) { + Selection.extendSelection(buffer, buffer.length()); + } else { + Selection.setSelection(buffer, buffer.length()); } + return true; + } - if (handled) { - MetaKeyKeyListener.adjustMetaAfterKeypress(buffer); - MetaKeyKeyListener.resetLockedMeta(buffer); + @Override + protected boolean lineStart(TextView widget, Spannable buffer) { + final Layout layout = widget.getLayout(); + if (isSelecting(buffer)) { + return Selection.extendToLeftEdge(buffer, layout); + } else { + return Selection.moveToLeftEdge(buffer, layout); } - - return handled; } - public boolean onKeyUp(TextView widget, Spannable buffer, int keyCode, KeyEvent event) { - return false; + @Override + protected boolean lineEnd(TextView widget, Spannable buffer) { + final Layout layout = widget.getLayout(); + if (isSelecting(buffer)) { + return Selection.extendToRightEdge(buffer, layout); + } else { + return Selection.moveToRightEdge(buffer, layout); + } } - public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) { - int code = event.getKeyCode(); - if (code != KeyEvent.KEYCODE_UNKNOWN && event.getAction() == KeyEvent.ACTION_MULTIPLE) { - int repeat = event.getRepeatCount(); - boolean handled = false; - while ((--repeat) > 0) { - handled |= executeDown(view, text, code); - } - return handled; - } - return false; + @Override + protected boolean home(TextView widget, Spannable buffer) { + return lineStart(widget, buffer); } - public boolean onTrackballEvent(TextView widget, Spannable text, MotionEvent event) { - return false; + @Override + protected boolean end(TextView widget, Spannable buffer) { + return lineEnd(widget, buffer); } + @Override public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { int initialScrollX = -1, initialScrollY = -1; final int action = event.getAction(); @@ -201,7 +217,7 @@ public class ArrowKeyMovementMethod implements MovementMethod { if (widget.isFocused() && !widget.didTouchFocusSelect()) { if (action == MotionEvent.ACTION_DOWN) { - boolean cap = isCap(buffer); + boolean cap = isSelecting(buffer); if (cap) { int offset = widget.getOffset((int) event.getX(), (int) event.getY()); @@ -214,7 +230,7 @@ public class ArrowKeyMovementMethod implements MovementMethod { widget.getParent().requestDisallowInterceptTouchEvent(true); } } else if (action == MotionEvent.ACTION_MOVE) { - boolean cap = isCap(buffer); + boolean cap = isSelecting(buffer); if (cap && handled) { // Before selecting, make sure we've moved out of the "slop". @@ -245,7 +261,7 @@ public class ArrowKeyMovementMethod implements MovementMethod { } int offset = widget.getOffset((int) event.getX(), (int) event.getY()); - if (isCap(buffer)) { + if (isSelecting(buffer)) { buffer.removeSpan(LAST_TAP_DOWN); Selection.extendSelection(buffer, offset); } else { @@ -262,14 +278,17 @@ public class ArrowKeyMovementMethod implements MovementMethod { return handled; } + @Override public boolean canSelectArbitrarily() { return true; } + @Override public void initialize(TextView widget, Spannable text) { Selection.setSelection(text, 0); } + @Override public void onTakeFocus(TextView view, Spannable text, int dir) { if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) { if (view.getLayout() == null) { diff --git a/core/java/android/text/method/BaseMovementMethod.java b/core/java/android/text/method/BaseMovementMethod.java new file mode 100644 index 0000000..2be18d6 --- /dev/null +++ b/core/java/android/text/method/BaseMovementMethod.java @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.text.method; + +import android.text.Layout; +import android.text.Spannable; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.widget.TextView; + +/** + * Base classes for movement methods. + */ +public class BaseMovementMethod implements MovementMethod { + @Override + public boolean canSelectArbitrarily() { + return false; + } + + @Override + public void initialize(TextView widget, Spannable text) { + } + + @Override + public boolean onKeyDown(TextView widget, Spannable text, int keyCode, KeyEvent event) { + final int movementMetaState = getMovementMetaState(text, event); + boolean handled = handleMovementKey(widget, text, keyCode, movementMetaState, event); + if (handled) { + MetaKeyKeyListener.adjustMetaAfterKeypress(text); + MetaKeyKeyListener.resetLockedMeta(text); + } + return handled; + } + + @Override + public boolean onKeyOther(TextView widget, Spannable text, KeyEvent event) { + final int movementMetaState = getMovementMetaState(text, event); + final int keyCode = event.getKeyCode(); + if (keyCode != KeyEvent.KEYCODE_UNKNOWN + && event.getAction() == KeyEvent.ACTION_MULTIPLE) { + final int repeat = event.getRepeatCount(); + boolean handled = false; + for (int i = 0; i < repeat; i++) { + if (!handleMovementKey(widget, text, keyCode, movementMetaState, event)) { + break; + } + handled = true; + } + if (handled) { + MetaKeyKeyListener.adjustMetaAfterKeypress(text); + MetaKeyKeyListener.resetLockedMeta(text); + } + return handled; + } + return false; + } + + @Override + public boolean onKeyUp(TextView widget, Spannable text, int keyCode, KeyEvent event) { + return false; + } + + @Override + public void onTakeFocus(TextView widget, Spannable text, int direction) { + } + + @Override + public boolean onTouchEvent(TextView widget, Spannable text, MotionEvent event) { + return false; + } + + @Override + public boolean onTrackballEvent(TextView widget, Spannable text, MotionEvent event) { + return false; + } + + /** + * Gets the meta state used for movement using the modifiers tracked by the text + * buffer as well as those present in the key event. + * + * The movement meta state excludes the state of locked modifiers or the SHIFT key + * since they are not used by movement actions (but they may be used for selection). + * + * @param buffer The text buffer. + * @param event The key event. + * @return The keyboard meta states used for movement. + */ + protected int getMovementMetaState(Spannable buffer, KeyEvent event) { + // We ignore locked modifiers and SHIFT. + int metaState = (event.getMetaState() | MetaKeyKeyListener.getMetaState(buffer)) + & ~(MetaKeyKeyListener.META_ALT_LOCKED | MetaKeyKeyListener.META_SYM_LOCKED); + return KeyEvent.normalizeMetaState(metaState) & ~KeyEvent.META_SHIFT_MASK; + } + + /** + * Performs a movement key action. + * The default implementation decodes the key down and invokes movement actions + * such as {@link #down} and {@link #up}. + * {@link #onKeyDown(TextView, Spannable, int, KeyEvent)} calls this method once + * to handle an {@link KeyEvent#ACTION_DOWN}. + * {@link #onKeyOther(TextView, Spannable, KeyEvent)} calls this method repeatedly + * to handle each repetition of an {@link KeyEvent#ACTION_MULTIPLE}. + * + * @param widget The text view. + * @param buffer The text buffer. + * @param event The key event. + * @param keyCode The key code. + * @param movementMetaState The keyboard meta states used for movement. + * @param event The key event. + * @return True if the event was handled. + */ + protected boolean handleMovementKey(TextView widget, Spannable buffer, + int keyCode, int movementMetaState, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_LEFT: + if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { + return left(widget, buffer); + } else if (KeyEvent.metaStateHasModifiers(movementMetaState, + KeyEvent.META_ALT_ON)) { + return lineStart(widget, buffer); + } + break; + + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { + return right(widget, buffer); + } else if (KeyEvent.metaStateHasModifiers(movementMetaState, + KeyEvent.META_ALT_ON)) { + return lineEnd(widget, buffer); + } + break; + + case KeyEvent.KEYCODE_DPAD_UP: + if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { + return up(widget, buffer); + } else if (KeyEvent.metaStateHasModifiers(movementMetaState, + KeyEvent.META_ALT_ON)) { + return top(widget, buffer); + } + break; + + case KeyEvent.KEYCODE_DPAD_DOWN: + if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { + return down(widget, buffer); + } else if (KeyEvent.metaStateHasModifiers(movementMetaState, + KeyEvent.META_ALT_ON)) { + return bottom(widget, buffer); + } + break; + + case KeyEvent.KEYCODE_PAGE_UP: + if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { + return pageUp(widget, buffer); + } else if (KeyEvent.metaStateHasModifiers(movementMetaState, + KeyEvent.META_ALT_ON)) { + return top(widget, buffer); + } + break; + + case KeyEvent.KEYCODE_PAGE_DOWN: + if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { + return pageDown(widget, buffer); + } else if (KeyEvent.metaStateHasModifiers(movementMetaState, + KeyEvent.META_ALT_ON)) { + return bottom(widget, buffer); + } + break; + + case KeyEvent.KEYCODE_MOVE_HOME: + if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { + return home(widget, buffer); + } + break; + + case KeyEvent.KEYCODE_MOVE_END: + if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { + return end(widget, buffer); + } + break; + } + return false; + } + + /** + * Performs a left movement action. + * Moves the cursor or scrolls left by one character. + * + * @param widget The text view. + * @param buffer The text buffer. + * @return True if the event was handled. + */ + protected boolean left(TextView widget, Spannable buffer) { + return false; + } + + /** + * Performs a right movement action. + * Moves the cursor or scrolls right by one character. + * + * @param widget The text view. + * @param buffer The text buffer. + * @return True if the event was handled. + */ + protected boolean right(TextView widget, Spannable buffer) { + return false; + } + + /** + * Performs an up movement action. + * Moves the cursor or scrolls up by one line. + * + * @param widget The text view. + * @param buffer The text buffer. + * @return True if the event was handled. + */ + protected boolean up(TextView widget, Spannable buffer) { + return false; + } + + /** + * Performs a down movement action. + * Moves the cursor or scrolls down by one line. + * + * @param widget The text view. + * @param buffer The text buffer. + * @return True if the event was handled. + */ + protected boolean down(TextView widget, Spannable buffer) { + return false; + } + + /** + * Performs a page-up movement action. + * Moves the cursor or scrolls up by one page. + * + * @param widget The text view. + * @param buffer The text buffer. + * @return True if the event was handled. + */ + protected boolean pageUp(TextView widget, Spannable buffer) { + return false; + } + + /** + * Performs a page-down movement action. + * Moves the cursor or scrolls down by one page. + * + * @param widget The text view. + * @param buffer The text buffer. + * @return True if the event was handled. + */ + protected boolean pageDown(TextView widget, Spannable buffer) { + return false; + } + + /** + * Performs a top movement action. + * Moves the cursor or scrolls to the top of the buffer. + * + * @param widget The text view. + * @param buffer The text buffer. + * @return True if the event was handled. + */ + protected boolean top(TextView widget, Spannable buffer) { + return false; + } + + /** + * Performs a bottom movement action. + * Moves the cursor or scrolls to the bottom of the buffer. + * + * @param widget The text view. + * @param buffer The text buffer. + * @return True if the event was handled. + */ + protected boolean bottom(TextView widget, Spannable buffer) { + return false; + } + + /** + * Performs a line-start movement action. + * Moves the cursor or scrolls to the start of the line. + * + * @param widget The text view. + * @param buffer The text buffer. + * @return True if the event was handled. + */ + protected boolean lineStart(TextView widget, Spannable buffer) { + return false; + } + + /** + * Performs an line-end movement action. + * Moves the cursor or scrolls to the end of the line. + * + * @param widget The text view. + * @param buffer The text buffer. + * @return True if the event was handled. + */ + protected boolean lineEnd(TextView widget, Spannable buffer) { + return false; + } + + /** + * Performs a home movement action. + * Moves the cursor or scrolls to the start of the line or to the top of the + * document depending on whether the insertion point is being moved or + * the document is being scrolled. + * + * @param widget The text view. + * @param buffer The text buffer. + * @return True if the event was handled. + */ + protected boolean home(TextView widget, Spannable buffer) { + return false; + } + + /** + * Performs an end movement action. + * Moves the cursor or scrolls to the start of the line or to the top of the + * document depending on whether the insertion point is being moved or + * the document is being scrolled. + * + * @param widget The text view. + * @param buffer The text buffer. + * @return True if the event was handled. + */ + protected boolean end(TextView widget, Spannable buffer) { + return false; + } +} diff --git a/core/java/android/text/method/LinkMovementMethod.java b/core/java/android/text/method/LinkMovementMethod.java index 22e9cc6..80c8169 100644 --- a/core/java/android/text/method/LinkMovementMethod.java +++ b/core/java/android/text/method/LinkMovementMethod.java @@ -16,8 +16,6 @@ package android.text.method; -import android.content.Intent; -import android.net.Uri; import android.view.KeyEvent; import android.view.MotionEvent; import android.text.*; @@ -25,28 +23,30 @@ import android.text.style.*; import android.view.View; import android.widget.TextView; -public class -LinkMovementMethod -extends ScrollingMovementMethod -{ +/** + * A movement method that traverses links in the text buffer and scrolls if necessary. + * Supports clicking on links with DPad Center or Enter. + */ +public class LinkMovementMethod extends ScrollingMovementMethod { private static final int CLICK = 1; private static final int UP = 2; private static final int DOWN = 3; @Override - public boolean onKeyDown(TextView widget, Spannable buffer, - int keyCode, KeyEvent event) { + protected boolean handleMovementKey(TextView widget, Spannable buffer, int keyCode, + int movementMetaState, KeyEvent event) { switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_CENTER: - case KeyEvent.KEYCODE_ENTER: - if (event.getRepeatCount() == 0) { - if (action(CLICK, widget, buffer)) { - return true; + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_ENTER: + if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { + if (event.getAction() == KeyEvent.ACTION_DOWN + && event.getRepeatCount() == 0) { + return action(CLICK, widget, buffer); + } } - } + break; } - - return super.onKeyDown(widget, buffer, keyCode, event); + return super.handleMovementKey(widget, buffer, keyCode, movementMetaState, event); } @Override @@ -86,8 +86,6 @@ extends ScrollingMovementMethod } private boolean action(int what, TextView widget, Spannable buffer) { - boolean handled = false; - Layout layout = widget.getLayout(); int padding = widget.getTotalPaddingTop() + @@ -184,11 +182,6 @@ extends ScrollingMovementMethod return false; } - public boolean onKeyUp(TextView widget, Spannable buffer, - int keyCode, KeyEvent event) { - return false; - } - @Override public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { @@ -229,11 +222,13 @@ extends ScrollingMovementMethod return super.onTouchEvent(widget, buffer, event); } + @Override public void initialize(TextView widget, Spannable text) { Selection.removeSelection(text); text.removeSpan(FROM_BELOW); } + @Override public void onTakeFocus(TextView view, Spannable text, int dir) { Selection.removeSelection(text); diff --git a/core/java/android/text/method/MovementMethod.java b/core/java/android/text/method/MovementMethod.java index 29f67a1..9167676 100644 --- a/core/java/android/text/method/MovementMethod.java +++ b/core/java/android/text/method/MovementMethod.java @@ -21,24 +21,32 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.text.*; -public interface MovementMethod -{ +/** + * Provides cursor positioning, scrolling and text selection functionality in a {@link TextView}. + * <p> + * The {@link TextView} delegates handling of key events, trackball motions and touches to + * the movement method for purposes of content navigation. The framework automatically + * selects an appropriate movement method based on the content of the {@link TextView}. + * </p><p> + * This interface is intended for use by the framework; it should not be implemented + * directly by applications. + * </p> + */ +public interface MovementMethod { public void initialize(TextView widget, Spannable text); public boolean onKeyDown(TextView widget, Spannable text, int keyCode, KeyEvent event); public boolean onKeyUp(TextView widget, Spannable text, int keyCode, KeyEvent event); - + /** * If the key listener wants to other kinds of key events, return true, * otherwise return false and the caller (i.e. the widget host) * will handle the key. */ public boolean onKeyOther(TextView view, Spannable text, KeyEvent event); - + public void onTakeFocus(TextView widget, Spannable text, int direction); - public boolean onTrackballEvent(TextView widget, Spannable text, - MotionEvent event); - public boolean onTouchEvent(TextView widget, Spannable text, - MotionEvent event); + public boolean onTrackballEvent(TextView widget, Spannable text, MotionEvent event); + public boolean onTouchEvent(TextView widget, Spannable text, MotionEvent event); /** * Returns true if this movement method allows arbitrary selection diff --git a/core/java/android/text/method/ScrollingMovementMethod.java b/core/java/android/text/method/ScrollingMovementMethod.java index 563ceed..194ecc1 100644 --- a/core/java/android/text/method/ScrollingMovementMethod.java +++ b/core/java/android/text/method/ScrollingMovementMethod.java @@ -16,201 +16,216 @@ package android.text.method; -import android.util.Log; -import android.view.KeyEvent; import android.view.MotionEvent; import android.text.*; import android.widget.TextView; import android.view.View; -public class -ScrollingMovementMethod -implements MovementMethod -{ - /** - * Scrolls the text to the left if possible. - */ - protected boolean left(TextView widget, Spannable buffer) { - Layout layout = widget.getLayout(); - - int scrolly = widget.getScrollY(); - int scr = widget.getScrollX(); - int em = Math.round(layout.getPaint().getFontSpacing()); - - int padding = widget.getTotalPaddingTop() + - widget.getTotalPaddingBottom(); - int top = layout.getLineForVertical(scrolly); - int bottom = layout.getLineForVertical(scrolly + widget.getHeight() - - padding); - int left = Integer.MAX_VALUE; - - for (int i = top; i <= bottom; i++) { - left = (int) Math.min(left, layout.getLineLeft(i)); - } +/** + * A movement method that interprets movement keys by scrolling the text buffer. + */ +public class ScrollingMovementMethod extends BaseMovementMethod implements MovementMethod { + private int getTopLine(TextView widget) { + return widget.getLayout().getLineForVertical(widget.getScrollY()); + } - if (scr > left) { - int s = Math.max(scr - em, left); - widget.scrollTo(s, widget.getScrollY()); - return true; - } + private int getBottomLine(TextView widget) { + return widget.getLayout().getLineForVertical(widget.getScrollY() + getInnerHeight(widget)); + } - return false; + private int getInnerWidth(TextView widget) { + return widget.getWidth() - widget.getTotalPaddingLeft() - widget.getTotalPaddingRight(); } - /** - * Scrolls the text to the right if possible. - */ - protected boolean right(TextView widget, Spannable buffer) { - Layout layout = widget.getLayout(); + private int getInnerHeight(TextView widget) { + return widget.getHeight() - widget.getTotalPaddingTop() - widget.getTotalPaddingBottom(); + } - int scrolly = widget.getScrollY(); - int scr = widget.getScrollX(); - int em = Math.round(layout.getPaint().getFontSpacing()); + private int getCharacterWidth(TextView widget) { + return (int) Math.ceil(widget.getPaint().getFontSpacing()); + } - int padding = widget.getTotalPaddingTop() + - widget.getTotalPaddingBottom(); - int top = layout.getLineForVertical(scrolly); - int bottom = layout.getLineForVertical(scrolly + widget.getHeight() - - padding); - int right = 0; + private int getScrollBoundsLeft(TextView widget) { + final Layout layout = widget.getLayout(); + final int topLine = getTopLine(widget); + final int bottomLine = getBottomLine(widget); + if (topLine > bottomLine) { + return 0; + } + int left = Integer.MAX_VALUE; + for (int line = topLine; line <= bottomLine; line++) { + final int lineLeft = (int) Math.floor(layout.getLineLeft(line)); + if (lineLeft < left) { + left = lineLeft; + } + } + return left; + } - for (int i = top; i <= bottom; i++) { - right = (int) Math.max(right, layout.getLineRight(i)); + private int getScrollBoundsRight(TextView widget) { + final Layout layout = widget.getLayout(); + final int topLine = getTopLine(widget); + final int bottomLine = getBottomLine(widget); + if (topLine > bottomLine) { + return 0; + } + int right = Integer.MIN_VALUE; + for (int line = topLine; line <= bottomLine; line++) { + final int lineRight = (int) Math.ceil(layout.getLineRight(line)); + if (lineRight > right) { + right = lineRight; + } } + return right; + } - padding = widget.getTotalPaddingLeft() + widget.getTotalPaddingRight(); - if (scr < right - (widget.getWidth() - padding)) { - int s = Math.min(scr + em, right - (widget.getWidth() - padding)); - widget.scrollTo(s, widget.getScrollY()); + @Override + protected boolean left(TextView widget, Spannable buffer) { + final int minScrollX = getScrollBoundsLeft(widget); + int scrollX = widget.getScrollX(); + if (scrollX > minScrollX) { + scrollX = Math.max(scrollX - getCharacterWidth(widget), minScrollX); + widget.scrollTo(scrollX, widget.getScrollY()); return true; } + return false; + } + @Override + protected boolean right(TextView widget, Spannable buffer) { + final int maxScrollX = getScrollBoundsRight(widget) - getInnerWidth(widget); + int scrollX = widget.getScrollX(); + if (scrollX < maxScrollX) { + scrollX = Math.min(scrollX + getCharacterWidth(widget), maxScrollX); + widget.scrollTo(scrollX, widget.getScrollY()); + return true; + } return false; } - /** - * Scrolls the text up if possible. - */ + @Override protected boolean up(TextView widget, Spannable buffer) { - Layout layout = widget.getLayout(); - - int areatop = widget.getScrollY(); - int line = layout.getLineForVertical(areatop); - int linetop = layout.getLineTop(line); - - // If the top line is partially visible, bring it all the way - // into view; otherwise, bring the previous line into view. - if (areatop == linetop) - line--; - - if (line >= 0) { - Touch.scrollTo(widget, layout, - widget.getScrollX(), layout.getLineTop(line)); + final Layout layout = widget.getLayout(); + final int top = widget.getScrollY(); + int topLine = layout.getLineForVertical(top); + if (layout.getLineTop(topLine) == top) { + // If the top line is partially visible, bring it all the way + // into view; otherwise, bring the previous line into view. + topLine -= 1; + } + if (topLine >= 0) { + Touch.scrollTo(widget, layout, widget.getScrollX(), layout.getLineTop(topLine)); return true; } - return false; } - /** - * Scrolls the text down if possible. - */ + @Override protected boolean down(TextView widget, Spannable buffer) { - Layout layout = widget.getLayout(); - - int padding = widget.getTotalPaddingTop() + - widget.getTotalPaddingBottom(); - - int areabot = widget.getScrollY() + widget.getHeight() - padding; - int line = layout.getLineForVertical(areabot); - - if (layout.getLineTop(line+1) < areabot + 1) { + final Layout layout = widget.getLayout(); + final int innerHeight = getInnerHeight(widget); + final int bottom = widget.getScrollY() + innerHeight; + int bottomLine = layout.getLineForVertical(bottom); + if (layout.getLineTop(bottomLine + 1) < bottom + 1) { // Less than a pixel of this line is out of view, // so we must have tried to make it entirely in view // and now want the next line to be in view instead. - - line++; + bottomLine += 1; } - - if (line <= layout.getLineCount() - 1) { - widget.scrollTo(widget.getScrollX(), layout.getLineTop(line+1) - - (widget.getHeight() - padding)); - Touch.scrollTo(widget, layout, - widget.getScrollX(), widget.getScrollY()); + if (bottomLine <= layout.getLineCount() - 1) { + Touch.scrollTo(widget, layout, widget.getScrollX(), + layout.getLineTop(bottomLine + 1) - innerHeight); return true; } - return false; } - public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) { - return executeDown(widget, buffer, keyCode); + @Override + protected boolean pageUp(TextView widget, Spannable buffer) { + final Layout layout = widget.getLayout(); + final int top = widget.getScrollY() - getInnerHeight(widget); + int topLine = layout.getLineForVertical(top); + if (topLine >= 0) { + Touch.scrollTo(widget, layout, widget.getScrollX(), layout.getLineTop(topLine)); + return true; + } + return false; } - private boolean executeDown(TextView widget, Spannable buffer, int keyCode) { - boolean handled = false; - - switch (keyCode) { - case KeyEvent.KEYCODE_DPAD_LEFT: - handled |= left(widget, buffer); - break; - - case KeyEvent.KEYCODE_DPAD_RIGHT: - handled |= right(widget, buffer); - break; - - case KeyEvent.KEYCODE_DPAD_UP: - handled |= up(widget, buffer); - break; - - case KeyEvent.KEYCODE_DPAD_DOWN: - handled |= down(widget, buffer); - break; + @Override + protected boolean pageDown(TextView widget, Spannable buffer) { + final Layout layout = widget.getLayout(); + final int innerHeight = getInnerHeight(widget); + final int bottom = widget.getScrollY() + innerHeight + innerHeight; + int bottomLine = layout.getLineForVertical(bottom); + if (bottomLine <= layout.getLineCount() - 1) { + Touch.scrollTo(widget, layout, widget.getScrollX(), + layout.getLineTop(bottomLine + 1) - innerHeight); + return true; } + return false; + } - return handled; + @Override + protected boolean top(TextView widget, Spannable buffer) { + final Layout layout = widget.getLayout(); + if (getTopLine(widget) >= 0) { + Touch.scrollTo(widget, layout, widget.getScrollX(), layout.getLineTop(0)); + return true; + } + return false; } - public boolean onKeyUp(TextView widget, Spannable buffer, int keyCode, KeyEvent event) { + @Override + protected boolean bottom(TextView widget, Spannable buffer) { + final Layout layout = widget.getLayout(); + final int lineCount = layout.getLineCount(); + if (getBottomLine(widget) <= lineCount - 1) { + Touch.scrollTo(widget, layout, widget.getScrollX(), + layout.getLineTop(lineCount) - getInnerHeight(widget)); + return true; + } return false; } - public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) { - int code = event.getKeyCode(); - if (code != KeyEvent.KEYCODE_UNKNOWN - && event.getAction() == KeyEvent.ACTION_MULTIPLE) { - int repeat = event.getRepeatCount(); - boolean first = true; - boolean handled = false; - while ((--repeat) > 0) { - if (first && executeDown(view, text, code)) { - handled = true; - MetaKeyKeyListener.adjustMetaAfterKeypress(text); - MetaKeyKeyListener.resetLockedMeta(text); - } - first = false; - } - return handled; + @Override + protected boolean lineStart(TextView widget, Spannable buffer) { + final int minScrollX = getScrollBoundsLeft(widget); + int scrollX = widget.getScrollX(); + if (scrollX > minScrollX) { + widget.scrollTo(minScrollX, widget.getScrollY()); + return true; } return false; } - - public boolean onTrackballEvent(TextView widget, Spannable text, - MotionEvent event) { + + @Override + protected boolean lineEnd(TextView widget, Spannable buffer) { + final int maxScrollX = getScrollBoundsRight(widget) - getInnerWidth(widget); + int scrollX = widget.getScrollX(); + if (scrollX < maxScrollX) { + widget.scrollTo(maxScrollX, widget.getScrollY()); + return true; + } return false; } - - public boolean onTouchEvent(TextView widget, Spannable buffer, - MotionEvent event) { - return Touch.onTouchEvent(widget, buffer, event); + + @Override + protected boolean home(TextView widget, Spannable buffer) { + return top(widget, buffer); } - public void initialize(TextView widget, Spannable text) { } + @Override + protected boolean end(TextView widget, Spannable buffer) { + return bottom(widget, buffer); + } - public boolean canSelectArbitrarily() { - return false; + @Override + public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { + return Touch.onTouchEvent(widget, buffer, event); } + @Override public void onTakeFocus(TextView widget, Spannable text, int dir) { Layout layout = widget.getLayout(); |