1 /* 2 * Copyright (C) 2008 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.text.Layout; 20 import android.text.Layout.Alignment; 21 import android.text.NoCopySpan; 22 import android.text.Spannable; 23 import android.view.KeyEvent; 24 import android.view.MotionEvent; 25 import android.view.ViewConfiguration; 26 import android.widget.TextView; 27 28 public class Touch { Touch()29 private Touch() { } 30 31 /** 32 * Scrolls the specified widget to the specified coordinates, except 33 * constrains the X scrolling position to the horizontal regions of 34 * the text that will be visible after scrolling to the specified 35 * Y position. 36 */ scrollTo(TextView widget, Layout layout, int x, int y)37 public static void scrollTo(TextView widget, Layout layout, int x, int y) { 38 final int horizontalPadding = widget.getTotalPaddingLeft() + widget.getTotalPaddingRight(); 39 final int availableWidth = widget.getWidth() - horizontalPadding; 40 41 final int top = layout.getLineForVertical(y); 42 Alignment a = layout.getParagraphAlignment(top); 43 boolean ltr = layout.getParagraphDirection(top) > 0; 44 45 int left, right; 46 if (widget.getHorizontallyScrolling()) { 47 final int verticalPadding = widget.getTotalPaddingTop() + widget.getTotalPaddingBottom(); 48 final int bottom = layout.getLineForVertical(y + widget.getHeight() - verticalPadding); 49 50 left = Integer.MAX_VALUE; 51 right = 0; 52 53 for (int i = top; i <= bottom; i++) { 54 left = (int) Math.min(left, layout.getLineLeft(i)); 55 right = (int) Math.max(right, layout.getLineRight(i)); 56 } 57 } else { 58 left = 0; 59 right = availableWidth; 60 } 61 62 final int actualWidth = right - left; 63 64 if (actualWidth < availableWidth) { 65 if (a == Alignment.ALIGN_CENTER) { 66 x = left - ((availableWidth - actualWidth) / 2); 67 } else if ((ltr && (a == Alignment.ALIGN_OPPOSITE)) || 68 (!ltr && (a == Alignment.ALIGN_NORMAL)) || 69 (a == Alignment.ALIGN_RIGHT)) { 70 // align_opposite does NOT mean align_right, we need the paragraph 71 // direction to resolve it to left or right 72 x = left - (availableWidth - actualWidth); 73 } else { 74 x = left; 75 } 76 } else { 77 x = Math.min(x, right - availableWidth); 78 x = Math.max(x, left); 79 } 80 81 widget.scrollTo(x, y); 82 } 83 84 /** 85 * Handles touch events for dragging. You may want to do other actions 86 * like moving the cursor on touch as well. 87 */ onTouchEvent(TextView widget, Spannable buffer, MotionEvent event)88 public static boolean onTouchEvent(TextView widget, Spannable buffer, 89 MotionEvent event) { 90 DragState[] ds; 91 92 switch (event.getActionMasked()) { 93 case MotionEvent.ACTION_DOWN: 94 ds = buffer.getSpans(0, buffer.length(), DragState.class); 95 96 for (int i = 0; i < ds.length; i++) { 97 buffer.removeSpan(ds[i]); 98 } 99 100 buffer.setSpan(new DragState(event.getX(), event.getY(), 101 widget.getScrollX(), widget.getScrollY()), 102 0, 0, Spannable.SPAN_MARK_MARK); 103 return true; 104 105 case MotionEvent.ACTION_UP: 106 ds = buffer.getSpans(0, buffer.length(), DragState.class); 107 108 for (int i = 0; i < ds.length; i++) { 109 buffer.removeSpan(ds[i]); 110 } 111 112 if (ds.length > 0 && ds[0].mUsed) { 113 return true; 114 } else { 115 return false; 116 } 117 118 case MotionEvent.ACTION_MOVE: 119 ds = buffer.getSpans(0, buffer.length(), DragState.class); 120 121 if (ds.length > 0) { 122 ds[0].mIsSelectionStarted = false; 123 124 if (ds[0].mFarEnough == false) { 125 int slop = ViewConfiguration.get(widget.getContext()).getScaledTouchSlop(); 126 127 if (Math.abs(event.getX() - ds[0].mX) >= slop || 128 Math.abs(event.getY() - ds[0].mY) >= slop) { 129 ds[0].mFarEnough = true; 130 if (event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) { 131 ds[0].mIsActivelySelecting = true; 132 ds[0].mIsSelectionStarted = true; 133 } 134 } 135 } 136 137 if (ds[0].mFarEnough) { 138 ds[0].mUsed = true; 139 boolean cap = (event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0 140 || MetaKeyKeyListener.getMetaState(buffer, 141 MetaKeyKeyListener.META_SHIFT_ON) == 1 142 || MetaKeyKeyListener.getMetaState(buffer, 143 MetaKeyKeyListener.META_SELECTING) != 0; 144 145 if (!event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) { 146 ds[0].mIsActivelySelecting = false; 147 } 148 149 float dx; 150 float dy; 151 if (cap && event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) { 152 // if we're selecting, we want the scroll to go in 153 // the direction of the drag 154 dx = event.getX() - ds[0].mX; 155 dy = event.getY() - ds[0].mY; 156 } else { 157 dx = ds[0].mX - event.getX(); 158 dy = ds[0].mY - event.getY(); 159 } 160 ds[0].mX = event.getX(); 161 ds[0].mY = event.getY(); 162 163 int nx = widget.getScrollX() + (int) dx; 164 int ny = widget.getScrollY() + (int) dy; 165 166 int padding = widget.getTotalPaddingTop() + widget.getTotalPaddingBottom(); 167 Layout layout = widget.getLayout(); 168 169 ny = Math.min(ny, layout.getHeight() - (widget.getHeight() - padding)); 170 ny = Math.max(ny, 0); 171 172 int oldX = widget.getScrollX(); 173 int oldY = widget.getScrollY(); 174 175 if (!event.isButtonPressed(MotionEvent.BUTTON_PRIMARY)) { 176 scrollTo(widget, layout, nx, ny); 177 } 178 179 // If we actually scrolled, then cancel the up action. 180 if (oldX != widget.getScrollX() || oldY != widget.getScrollY()) { 181 widget.cancelLongPress(); 182 } 183 184 return true; 185 } 186 } 187 } 188 189 return false; 190 } 191 192 /** 193 * @param widget The text view. 194 * @param buffer The text buffer. 195 */ getInitialScrollX(TextView widget, Spannable buffer)196 public static int getInitialScrollX(TextView widget, Spannable buffer) { 197 DragState[] ds = buffer.getSpans(0, buffer.length(), DragState.class); 198 return ds.length > 0 ? ds[0].mScrollX : -1; 199 } 200 201 /** 202 * @param widget The text view. 203 * @param buffer The text buffer. 204 */ getInitialScrollY(TextView widget, Spannable buffer)205 public static int getInitialScrollY(TextView widget, Spannable buffer) { 206 DragState[] ds = buffer.getSpans(0, buffer.length(), DragState.class); 207 return ds.length > 0 ? ds[0].mScrollY : -1; 208 } 209 210 /** 211 * Checks if selection is still active. 212 * This is useful for extending Selection span on buffer. 213 * @param buffer The text buffer. 214 * @return true if buffer has been marked for selection. 215 * 216 * @hide 217 */ isActivelySelecting(Spannable buffer)218 static boolean isActivelySelecting(Spannable buffer) { 219 DragState[] ds; 220 ds = buffer.getSpans(0, buffer.length(), DragState.class); 221 222 return ds.length > 0 && ds[0].mIsActivelySelecting; 223 } 224 225 /** 226 * Checks if selection has begun (are we out of slop?). 227 * Note: DragState.mIsSelectionStarted goes back to false with the very next event. 228 * This is useful for starting Selection span on buffer. 229 * @param buffer The text buffer. 230 * @return true if selection has started on the buffer. 231 * 232 * @hide 233 */ isSelectionStarted(Spannable buffer)234 static boolean isSelectionStarted(Spannable buffer) { 235 DragState[] ds; 236 ds = buffer.getSpans(0, buffer.length(), DragState.class); 237 238 return ds.length > 0 && ds[0].mIsSelectionStarted; 239 } 240 241 private static class DragState implements NoCopySpan { 242 public float mX; 243 public float mY; 244 public int mScrollX; 245 public int mScrollY; 246 public boolean mFarEnough; 247 public boolean mUsed; 248 public boolean mIsActivelySelecting; 249 public boolean mIsSelectionStarted; 250 DragState(float x, float y, int scrollX, int scrollY)251 public DragState(float x, float y, int scrollX, int scrollY) { 252 mX = x; 253 mY = y; 254 mScrollX = scrollX; 255 mScrollY = scrollY; 256 } 257 } 258 } 259