LinkMovementMethod.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.KeyEvent; 20import android.view.MotionEvent; 21import android.text.*; 22import android.text.style.*; 23import android.view.View; 24import android.widget.TextView; 25 26/** 27 * A movement method that traverses links in the text buffer and scrolls if necessary. 28 * Supports clicking on links with DPad Center or Enter. 29 */ 30public class LinkMovementMethod extends ScrollingMovementMethod { 31 private static final int CLICK = 1; 32 private static final int UP = 2; 33 private static final int DOWN = 3; 34 35 @Override 36 protected boolean handleMovementKey(TextView widget, Spannable buffer, int keyCode, 37 int movementMetaState, KeyEvent event) { 38 switch (keyCode) { 39 case KeyEvent.KEYCODE_DPAD_CENTER: 40 case KeyEvent.KEYCODE_ENTER: 41 if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) { 42 if (event.getAction() == KeyEvent.ACTION_DOWN 43 && event.getRepeatCount() == 0) { 44 return action(CLICK, widget, buffer); 45 } 46 } 47 break; 48 } 49 return super.handleMovementKey(widget, buffer, keyCode, movementMetaState, event); 50 } 51 52 @Override 53 protected boolean up(TextView widget, Spannable buffer) { 54 if (action(UP, widget, buffer)) { 55 return true; 56 } 57 58 return super.up(widget, buffer); 59 } 60 61 @Override 62 protected boolean down(TextView widget, Spannable buffer) { 63 if (action(DOWN, widget, buffer)) { 64 return true; 65 } 66 67 return super.down(widget, buffer); 68 } 69 70 @Override 71 protected boolean left(TextView widget, Spannable buffer) { 72 if (action(UP, widget, buffer)) { 73 return true; 74 } 75 76 return super.left(widget, buffer); 77 } 78 79 @Override 80 protected boolean right(TextView widget, Spannable buffer) { 81 if (action(DOWN, widget, buffer)) { 82 return true; 83 } 84 85 return super.right(widget, buffer); 86 } 87 88 private boolean action(int what, TextView widget, Spannable buffer) { 89 Layout layout = widget.getLayout(); 90 91 int padding = widget.getTotalPaddingTop() + 92 widget.getTotalPaddingBottom(); 93 int areatop = widget.getScrollY(); 94 int areabot = areatop + widget.getHeight() - padding; 95 96 int linetop = layout.getLineForVertical(areatop); 97 int linebot = layout.getLineForVertical(areabot); 98 99 int first = layout.getLineStart(linetop); 100 int last = layout.getLineEnd(linebot); 101 102 ClickableSpan[] candidates = buffer.getSpans(first, last, ClickableSpan.class); 103 104 int a = Selection.getSelectionStart(buffer); 105 int b = Selection.getSelectionEnd(buffer); 106 107 int selStart = Math.min(a, b); 108 int selEnd = Math.max(a, b); 109 110 if (selStart < 0) { 111 if (buffer.getSpanStart(FROM_BELOW) >= 0) { 112 selStart = selEnd = buffer.length(); 113 } 114 } 115 116 if (selStart > last) 117 selStart = selEnd = Integer.MAX_VALUE; 118 if (selEnd < first) 119 selStart = selEnd = -1; 120 121 switch (what) { 122 case CLICK: 123 if (selStart == selEnd) { 124 return false; 125 } 126 127 ClickableSpan[] link = buffer.getSpans(selStart, selEnd, ClickableSpan.class); 128 129 if (link.length != 1) 130 return false; 131 132 link[0].onClick(widget); 133 break; 134 135 case UP: 136 int beststart, bestend; 137 138 beststart = -1; 139 bestend = -1; 140 141 for (int i = 0; i < candidates.length; i++) { 142 int end = buffer.getSpanEnd(candidates[i]); 143 144 if (end < selEnd || selStart == selEnd) { 145 if (end > bestend) { 146 beststart = buffer.getSpanStart(candidates[i]); 147 bestend = end; 148 } 149 } 150 } 151 152 if (beststart >= 0) { 153 Selection.setSelection(buffer, bestend, beststart); 154 return true; 155 } 156 157 break; 158 159 case DOWN: 160 beststart = Integer.MAX_VALUE; 161 bestend = Integer.MAX_VALUE; 162 163 for (int i = 0; i < candidates.length; i++) { 164 int start = buffer.getSpanStart(candidates[i]); 165 166 if (start > selStart || selStart == selEnd) { 167 if (start < beststart) { 168 beststart = start; 169 bestend = buffer.getSpanEnd(candidates[i]); 170 } 171 } 172 } 173 174 if (bestend < Integer.MAX_VALUE) { 175 Selection.setSelection(buffer, beststart, bestend); 176 return true; 177 } 178 179 break; 180 } 181 182 return false; 183 } 184 185 @Override 186 public boolean onTouchEvent(TextView widget, Spannable buffer, 187 MotionEvent event) { 188 int action = event.getAction(); 189 190 if (action == MotionEvent.ACTION_UP || 191 action == MotionEvent.ACTION_DOWN) { 192 int x = (int) event.getX(); 193 int y = (int) event.getY(); 194 195 x -= widget.getTotalPaddingLeft(); 196 y -= widget.getTotalPaddingTop(); 197 198 x += widget.getScrollX(); 199 y += widget.getScrollY(); 200 201 Layout layout = widget.getLayout(); 202 int line = layout.getLineForVertical(y); 203 int off = layout.getOffsetForHorizontal(line, x); 204 205 ClickableSpan[] link = buffer.getSpans(off, off, ClickableSpan.class); 206 207 if (link.length != 0) { 208 if (action == MotionEvent.ACTION_UP) { 209 link[0].onClick(widget); 210 } else if (action == MotionEvent.ACTION_DOWN) { 211 Selection.setSelection(buffer, 212 buffer.getSpanStart(link[0]), 213 buffer.getSpanEnd(link[0])); 214 } 215 216 return true; 217 } else { 218 Selection.removeSelection(buffer); 219 } 220 } 221 222 return super.onTouchEvent(widget, buffer, event); 223 } 224 225 @Override 226 public void initialize(TextView widget, Spannable text) { 227 Selection.removeSelection(text); 228 text.removeSpan(FROM_BELOW); 229 } 230 231 @Override 232 public void onTakeFocus(TextView view, Spannable text, int dir) { 233 Selection.removeSelection(text); 234 235 if ((dir & View.FOCUS_BACKWARD) != 0) { 236 text.setSpan(FROM_BELOW, 0, 0, Spannable.SPAN_POINT_POINT); 237 } else { 238 text.removeSpan(FROM_BELOW); 239 } 240 } 241 242 public static MovementMethod getInstance() { 243 if (sInstance == null) 244 sInstance = new LinkMovementMethod(); 245 246 return sInstance; 247 } 248 249 private static LinkMovementMethod sInstance; 250 private static Object FROM_BELOW = new NoCopySpan.Concrete(); 251} 252