Touch.java revision b2beb92073cea6e472748e729ac8e265ca83a925
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 17package android.text.method; 18 19import android.text.Layout; 20import android.text.Layout.Alignment; 21import android.text.NoCopySpan; 22import android.text.Spannable; 23import android.view.KeyEvent; 24import android.view.MotionEvent; 25import android.view.ViewConfiguration; 26import android.widget.TextView; 27 28public class 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 */ 37 public static void scrollTo(TextView widget, Layout layout, int x, int y) { 38 final int verticalPadding = widget.getTotalPaddingTop() + widget.getTotalPaddingBottom(); 39 final int top = layout.getLineForVertical(y); 40 final int bottom = layout.getLineForVertical(y + widget.getHeight() - verticalPadding); 41 42 int left = Integer.MAX_VALUE; 43 int right = 0; 44 Alignment a = layout.getParagraphAlignment(top); 45 boolean ltr = layout.getParagraphDirection(top) > 0; 46 47 for (int i = top; i <= bottom; i++) { 48 left = (int) Math.min(left, layout.getLineLeft(i)); 49 right = (int) Math.max(right, layout.getLineRight(i)); 50 } 51 52 final int hoizontalPadding = widget.getTotalPaddingLeft() + widget.getTotalPaddingRight(); 53 final int availableWidth = widget.getWidth() - hoizontalPadding; 54 final int actualWidth = right - left; 55 56 if (actualWidth < availableWidth) { 57 if (a == Alignment.ALIGN_CENTER) { 58 x = left - ((availableWidth - actualWidth) / 2); 59 } else if ((ltr && (a == Alignment.ALIGN_OPPOSITE)) || (a == Alignment.ALIGN_RIGHT)) { 60 // align_opposite does NOT mean align_right, we need the paragraph 61 // direction to resolve it to left or right 62 x = left - (availableWidth - actualWidth); 63 } else { 64 x = left; 65 } 66 } else { 67 x = Math.min(x, right - availableWidth); 68 x = Math.max(x, left); 69 } 70 71 widget.scrollTo(x, y); 72 } 73 74 /** 75 * Handles touch events for dragging. You may want to do other actions 76 * like moving the cursor on touch as well. 77 */ 78 public static boolean onTouchEvent(TextView widget, Spannable buffer, 79 MotionEvent event) { 80 DragState[] ds; 81 82 switch (event.getActionMasked()) { 83 case MotionEvent.ACTION_DOWN: 84 ds = buffer.getSpans(0, buffer.length(), DragState.class); 85 86 for (int i = 0; i < ds.length; i++) { 87 buffer.removeSpan(ds[i]); 88 } 89 90 buffer.setSpan(new DragState(event.getX(), event.getY(), 91 widget.getScrollX(), widget.getScrollY()), 92 0, 0, Spannable.SPAN_MARK_MARK); 93 return true; 94 95 case MotionEvent.ACTION_UP: 96 ds = buffer.getSpans(0, buffer.length(), DragState.class); 97 98 for (int i = 0; i < ds.length; i++) { 99 buffer.removeSpan(ds[i]); 100 } 101 102 if (ds.length > 0 && ds[0].mUsed) { 103 return true; 104 } else { 105 return false; 106 } 107 108 case MotionEvent.ACTION_MOVE: 109 ds = buffer.getSpans(0, buffer.length(), DragState.class); 110 111 if (ds.length > 0) { 112 if (ds[0].mFarEnough == false) { 113 int slop = ViewConfiguration.get(widget.getContext()).getScaledTouchSlop(); 114 115 if (Math.abs(event.getX() - ds[0].mX) >= slop || 116 Math.abs(event.getY() - ds[0].mY) >= slop) { 117 ds[0].mFarEnough = true; 118 } 119 } 120 121 if (ds[0].mFarEnough) { 122 ds[0].mUsed = true; 123 boolean cap = (event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0 124 || MetaKeyKeyListener.getMetaState(buffer, 125 MetaKeyKeyListener.META_SHIFT_ON) == 1 126 || MetaKeyKeyListener.getMetaState(buffer, 127 MetaKeyKeyListener.META_SELECTING) != 0; 128 float dx; 129 float dy; 130 if (cap) { 131 // if we're selecting, we want the scroll to go in 132 // the direction of the drag 133 dx = event.getX() - ds[0].mX; 134 dy = event.getY() - ds[0].mY; 135 } else { 136 dx = ds[0].mX - event.getX(); 137 dy = ds[0].mY - event.getY(); 138 } 139 ds[0].mX = event.getX(); 140 ds[0].mY = event.getY(); 141 142 int nx = widget.getScrollX() + (int) dx; 143 int ny = widget.getScrollY() + (int) dy; 144 145 int padding = widget.getTotalPaddingTop() + widget.getTotalPaddingBottom(); 146 Layout layout = widget.getLayout(); 147 148 ny = Math.min(ny, layout.getHeight() - (widget.getHeight() - padding)); 149 ny = Math.max(ny, 0); 150 151 int oldX = widget.getScrollX(); 152 int oldY = widget.getScrollY(); 153 154 scrollTo(widget, layout, nx, ny); 155 156 // If we actually scrolled, then cancel the up action. 157 if (oldX != widget.getScrollX() || oldY != widget.getScrollY()) { 158 widget.cancelLongPress(); 159 } 160 161 return true; 162 } 163 } 164 } 165 166 return false; 167 } 168 169 public static int getInitialScrollX(TextView widget, Spannable buffer) { 170 DragState[] ds = buffer.getSpans(0, buffer.length(), DragState.class); 171 return ds.length > 0 ? ds[0].mScrollX : -1; 172 } 173 174 public static int getInitialScrollY(TextView widget, Spannable buffer) { 175 DragState[] ds = buffer.getSpans(0, buffer.length(), DragState.class); 176 return ds.length > 0 ? ds[0].mScrollY : -1; 177 } 178 179 private static class DragState implements NoCopySpan { 180 public float mX; 181 public float mY; 182 public int mScrollX; 183 public int mScrollY; 184 public boolean mFarEnough; 185 public boolean mUsed; 186 187 public DragState(float x, float y, int scrollX, int scrollY) { 188 mX = x; 189 mY = y; 190 mScrollX = scrollX; 191 mScrollY = scrollY; 192 } 193 } 194} 195