ScrollingMovementMethod.java revision 67b6ab72ae96a9f2be929de2c32c110df5390fdd
1/* 2 * Copyright (C) 2006 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 17package android.text.method; 18 19import android.view.MotionEvent; 20import android.text.*; 21import android.widget.TextView; 22import android.view.View; 23 24/** 25 * A movement method that interprets movement keys by scrolling the text buffer. 26 */ 27public class ScrollingMovementMethod extends BaseMovementMethod implements MovementMethod { 28 private int getTopLine(TextView widget) { 29 return widget.getLayout().getLineForVertical(widget.getScrollY()); 30 } 31 32 private int getBottomLine(TextView widget) { 33 return widget.getLayout().getLineForVertical(widget.getScrollY() + getInnerHeight(widget)); 34 } 35 36 private int getInnerWidth(TextView widget) { 37 return widget.getWidth() - widget.getTotalPaddingLeft() - widget.getTotalPaddingRight(); 38 } 39 40 private int getInnerHeight(TextView widget) { 41 return widget.getHeight() - widget.getTotalPaddingTop() - widget.getTotalPaddingBottom(); 42 } 43 44 private int getCharacterWidth(TextView widget) { 45 return (int) Math.ceil(widget.getPaint().getFontSpacing()); 46 } 47 48 private int getScrollBoundsLeft(TextView widget) { 49 final Layout layout = widget.getLayout(); 50 final int topLine = getTopLine(widget); 51 final int bottomLine = getBottomLine(widget); 52 if (topLine > bottomLine) { 53 return 0; 54 } 55 int left = Integer.MAX_VALUE; 56 for (int line = topLine; line <= bottomLine; line++) { 57 final int lineLeft = (int) Math.floor(layout.getLineLeft(line)); 58 if (lineLeft < left) { 59 left = lineLeft; 60 } 61 } 62 return left; 63 } 64 65 private int getScrollBoundsRight(TextView widget) { 66 final Layout layout = widget.getLayout(); 67 final int topLine = getTopLine(widget); 68 final int bottomLine = getBottomLine(widget); 69 if (topLine > bottomLine) { 70 return 0; 71 } 72 int right = Integer.MIN_VALUE; 73 for (int line = topLine; line <= bottomLine; line++) { 74 final int lineRight = (int) Math.ceil(layout.getLineRight(line)); 75 if (lineRight > right) { 76 right = lineRight; 77 } 78 } 79 return right; 80 } 81 82 @Override 83 protected boolean left(TextView widget, Spannable buffer) { 84 final int minScrollX = getScrollBoundsLeft(widget); 85 int scrollX = widget.getScrollX(); 86 if (scrollX > minScrollX) { 87 scrollX = Math.max(scrollX - getCharacterWidth(widget), minScrollX); 88 widget.scrollTo(scrollX, widget.getScrollY()); 89 return true; 90 } 91 return false; 92 } 93 94 @Override 95 protected boolean right(TextView widget, Spannable buffer) { 96 final int maxScrollX = getScrollBoundsRight(widget) - getInnerWidth(widget); 97 int scrollX = widget.getScrollX(); 98 if (scrollX < maxScrollX) { 99 scrollX = Math.min(scrollX + getCharacterWidth(widget), maxScrollX); 100 widget.scrollTo(scrollX, widget.getScrollY()); 101 return true; 102 } 103 return false; 104 } 105 106 @Override 107 protected boolean up(TextView widget, Spannable buffer) { 108 final Layout layout = widget.getLayout(); 109 final int top = widget.getScrollY(); 110 int topLine = layout.getLineForVertical(top); 111 if (layout.getLineTop(topLine) == top) { 112 // If the top line is partially visible, bring it all the way 113 // into view; otherwise, bring the previous line into view. 114 topLine -= 1; 115 } 116 if (topLine >= 0) { 117 Touch.scrollTo(widget, layout, widget.getScrollX(), layout.getLineTop(topLine)); 118 return true; 119 } 120 return false; 121 } 122 123 @Override 124 protected boolean down(TextView widget, Spannable buffer) { 125 final Layout layout = widget.getLayout(); 126 final int innerHeight = getInnerHeight(widget); 127 final int bottom = widget.getScrollY() + innerHeight; 128 int bottomLine = layout.getLineForVertical(bottom); 129 if (layout.getLineTop(bottomLine + 1) < bottom + 1) { 130 // Less than a pixel of this line is out of view, 131 // so we must have tried to make it entirely in view 132 // and now want the next line to be in view instead. 133 bottomLine += 1; 134 } 135 if (bottomLine <= layout.getLineCount() - 1) { 136 Touch.scrollTo(widget, layout, widget.getScrollX(), 137 layout.getLineTop(bottomLine + 1) - innerHeight); 138 return true; 139 } 140 return false; 141 } 142 143 @Override 144 protected boolean pageUp(TextView widget, Spannable buffer) { 145 final Layout layout = widget.getLayout(); 146 final int top = widget.getScrollY() - getInnerHeight(widget); 147 int topLine = layout.getLineForVertical(top); 148 if (topLine >= 0) { 149 Touch.scrollTo(widget, layout, widget.getScrollX(), layout.getLineTop(topLine)); 150 return true; 151 } 152 return false; 153 } 154 155 @Override 156 protected boolean pageDown(TextView widget, Spannable buffer) { 157 final Layout layout = widget.getLayout(); 158 final int innerHeight = getInnerHeight(widget); 159 final int bottom = widget.getScrollY() + innerHeight + innerHeight; 160 int bottomLine = layout.getLineForVertical(bottom); 161 if (bottomLine <= layout.getLineCount() - 1) { 162 Touch.scrollTo(widget, layout, widget.getScrollX(), 163 layout.getLineTop(bottomLine + 1) - innerHeight); 164 return true; 165 } 166 return false; 167 } 168 169 @Override 170 protected boolean top(TextView widget, Spannable buffer) { 171 final Layout layout = widget.getLayout(); 172 if (getTopLine(widget) >= 0) { 173 Touch.scrollTo(widget, layout, widget.getScrollX(), layout.getLineTop(0)); 174 return true; 175 } 176 return false; 177 } 178 179 @Override 180 protected boolean bottom(TextView widget, Spannable buffer) { 181 final Layout layout = widget.getLayout(); 182 final int lineCount = layout.getLineCount(); 183 if (getBottomLine(widget) <= lineCount - 1) { 184 Touch.scrollTo(widget, layout, widget.getScrollX(), 185 layout.getLineTop(lineCount) - getInnerHeight(widget)); 186 return true; 187 } 188 return false; 189 } 190 191 @Override 192 protected boolean lineStart(TextView widget, Spannable buffer) { 193 final int minScrollX = getScrollBoundsLeft(widget); 194 int scrollX = widget.getScrollX(); 195 if (scrollX > minScrollX) { 196 widget.scrollTo(minScrollX, widget.getScrollY()); 197 return true; 198 } 199 return false; 200 } 201 202 @Override 203 protected boolean lineEnd(TextView widget, Spannable buffer) { 204 final int maxScrollX = getScrollBoundsRight(widget) - getInnerWidth(widget); 205 int scrollX = widget.getScrollX(); 206 if (scrollX < maxScrollX) { 207 widget.scrollTo(maxScrollX, widget.getScrollY()); 208 return true; 209 } 210 return false; 211 } 212 213 @Override 214 protected boolean home(TextView widget, Spannable buffer) { 215 return top(widget, buffer); 216 } 217 218 @Override 219 protected boolean end(TextView widget, Spannable buffer) { 220 return bottom(widget, buffer); 221 } 222 223 @Override 224 public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { 225 return Touch.onTouchEvent(widget, buffer, event); 226 } 227 228 @Override 229 public void onTakeFocus(TextView widget, Spannable text, int dir) { 230 Layout layout = widget.getLayout(); 231 232 if (layout != null && (dir & View.FOCUS_FORWARD) != 0) { 233 widget.scrollTo(widget.getScrollX(), 234 layout.getLineTop(0)); 235 } 236 if (layout != null && (dir & View.FOCUS_BACKWARD) != 0) { 237 int padding = widget.getTotalPaddingTop() + 238 widget.getTotalPaddingBottom(); 239 int line = layout.getLineCount() - 1; 240 241 widget.scrollTo(widget.getScrollX(), 242 layout.getLineTop(line+1) - 243 (widget.getHeight() - padding)); 244 } 245 } 246 247 public static MovementMethod getInstance() { 248 if (sInstance == null) 249 sInstance = new ScrollingMovementMethod(); 250 251 return sInstance; 252 } 253 254 private static ScrollingMovementMethod sInstance; 255} 256