blob: 1e122a72baa128ce0f72d8cf91070c66ec03168c [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2007 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.widget;
18
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.graphics.Rect;
22import android.graphics.drawable.Drawable;
23import android.text.Editable;
24import android.text.Selection;
25import android.text.TextUtils;
26import android.text.TextWatcher;
27import android.util.AttributeSet;
28import android.util.Log;
29import android.view.KeyEvent;
30import android.view.LayoutInflater;
31import android.view.MotionEvent;
32import android.view.View;
33import android.view.ViewGroup;
34import android.view.inputmethod.CompletionInfo;
35import android.view.inputmethod.InputMethodManager;
36import android.view.inputmethod.EditorInfo;
37
38import com.android.internal.R;
39
40
41/**
42 * <p>An editable text view that shows completion suggestions automatically
43 * while the user is typing. The list of suggestions is displayed in a drop
44 * down menu from which the user can choose an item to replace the content
45 * of the edit box with.</p>
46 *
47 * <p>The drop down can be dismissed at any time by pressing the back key or,
48 * if no item is selected in the drop down, by pressing the enter/dpad center
49 * key.</p>
50 *
51 * <p>The list of suggestions is obtained from a data adapter and appears
52 * only after a given number of characters defined by
53 * {@link #getThreshold() the threshold}.</p>
54 *
55 * <p>The following code snippet shows how to create a text view which suggests
56 * various countries names while the user is typing:</p>
57 *
58 * <pre class="prettyprint">
59 * public class CountriesActivity extends Activity {
60 * protected void onCreate(Bundle icicle) {
61 * super.onCreate(icicle);
62 * setContentView(R.layout.countries);
63 *
64 * ArrayAdapter&lt;String&gt; adapter = new ArrayAdapter&lt;String&gt;(this,
65 * android.R.layout.simple_dropdown_item_1line, COUNTRIES);
66 * AutoCompleteTextView textView = (AutoCompleteTextView)
67 * findViewById(R.id.countries_list);
68 * textView.setAdapter(adapter);
69 * }
70 *
71 * private static final String[] COUNTRIES = new String[] {
72 * "Belgium", "France", "Italy", "Germany", "Spain"
73 * };
74 * }
75 * </pre>
76 *
77 * @attr ref android.R.styleable#AutoCompleteTextView_completionHint
78 * @attr ref android.R.styleable#AutoCompleteTextView_completionThreshold
79 * @attr ref android.R.styleable#AutoCompleteTextView_completionHintView
80 * @attr ref android.R.styleable#AutoCompleteTextView_dropDownSelector
81 * @attr ref android.R.styleable#AutoCompleteTextView_dropDownAnchor
82 * @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth
83 */
84public class AutoCompleteTextView extends EditText implements Filter.FilterListener {
85 static final boolean DEBUG = false;
86 static final String TAG = "AutoCompleteTextView";
87
88 private static final int HINT_VIEW_ID = 0x17;
89
90 private CharSequence mHintText;
91 private int mHintResource;
92
93 private ListAdapter mAdapter;
94 private Filter mFilter;
95 private int mThreshold;
96
97 private PopupWindow mPopup;
98 private DropDownListView mDropDownList;
99 private int mDropDownVerticalOffset;
100 private int mDropDownHorizontalOffset;
101 private int mDropDownAnchorId;
102 private View mDropDownAnchorView; // view is retrieved lazily from id once needed
103 private int mDropDownWidth;
104
105 private Drawable mDropDownListHighlight;
106
107 private AdapterView.OnItemClickListener mItemClickListener;
108 private AdapterView.OnItemSelectedListener mItemSelectedListener;
109
110 private final DropDownItemClickListener mDropDownItemClickListener =
111 new DropDownItemClickListener();
112
Karl Rosaen875d50a2009-04-23 19:00:21 -0700113 private boolean mDropDownAlwaysVisible = false;
114
115 private boolean mDropDownDismissedOnCompletion = true;
116
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800117 private int mLastKeyCode = KeyEvent.KEYCODE_UNKNOWN;
118 private boolean mOpenBefore;
119
120 private Validator mValidator = null;
121
122 private boolean mBlockCompletion;
123
124 private AutoCompleteTextView.ListSelectorHider mHideSelector;
125
126 // Indicates whether this AutoCompleteTextView is attached to a window or not
127 // The widget is attached to a window when mAttachCount > 0
128 private int mAttachCount;
129
Karl Rosaen98e333f2009-04-28 10:39:09 -0700130 private AutoCompleteTextView.PassThroughClickListener mPassThroughClickListener;
131
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800132 public AutoCompleteTextView(Context context) {
133 this(context, null);
134 }
135
136 public AutoCompleteTextView(Context context, AttributeSet attrs) {
137 this(context, attrs, com.android.internal.R.attr.autoCompleteTextViewStyle);
138 }
139
140 public AutoCompleteTextView(Context context, AttributeSet attrs, int defStyle) {
141 super(context, attrs, defStyle);
142
143 mPopup = new PopupWindow(context, attrs,
144 com.android.internal.R.attr.autoCompleteTextViewStyle);
145
146 TypedArray a =
147 context.obtainStyledAttributes(
148 attrs, com.android.internal.R.styleable.AutoCompleteTextView, defStyle, 0);
149
150 mThreshold = a.getInt(
151 R.styleable.AutoCompleteTextView_completionThreshold, 2);
152
153 mHintText = a.getText(R.styleable.AutoCompleteTextView_completionHint);
154
155 mDropDownListHighlight = a.getDrawable(
156 R.styleable.AutoCompleteTextView_dropDownSelector);
157 mDropDownVerticalOffset = (int)
158 a.getDimension(R.styleable.AutoCompleteTextView_dropDownVerticalOffset, 0.0f);
159 mDropDownHorizontalOffset = (int)
160 a.getDimension(R.styleable.AutoCompleteTextView_dropDownHorizontalOffset, 0.0f);
161
162 // Get the anchor's id now, but the view won't be ready, so wait to actually get the
163 // view and store it in mDropDownAnchorView lazily in getDropDownAnchorView later.
164 // Defaults to NO_ID, in which case the getDropDownAnchorView method will simply return
165 // this TextView, as a default anchoring point.
166 mDropDownAnchorId = a.getResourceId(R.styleable.AutoCompleteTextView_dropDownAnchor,
167 View.NO_ID);
168
169 // For dropdown width, the developer can specify a specific width, or FILL_PARENT
170 // (for full screen width) or WRAP_CONTENT (to match the width of the anchored view).
171 mDropDownWidth = a.getLayoutDimension(R.styleable.AutoCompleteTextView_dropDownWidth,
172 ViewGroup.LayoutParams.WRAP_CONTENT);
173
174 mHintResource = a.getResourceId(R.styleable.AutoCompleteTextView_completionHintView,
175 R.layout.simple_dropdown_hint);
176
177 // Always turn on the auto complete input type flag, since it
178 // makes no sense to use this widget without it.
179 int inputType = getInputType();
180 if ((inputType&EditorInfo.TYPE_MASK_CLASS)
181 == EditorInfo.TYPE_CLASS_TEXT) {
182 inputType |= EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE;
183 setRawInputType(inputType);
184 }
185
186 a.recycle();
187
188 setFocusable(true);
189
190 addTextChangedListener(new MyWatcher());
Karl Rosaen98e333f2009-04-28 10:39:09 -0700191
192 mPassThroughClickListener = new PassThroughClickListener();
193 super.setOnClickListener(mPassThroughClickListener);
194 }
195
196 @Override
197 public void setOnClickListener(OnClickListener listener) {
198 mPassThroughClickListener.mWrapped = listener;
199 }
200
201 /**
202 * Private hook into the on click event, dispatched from {@link PassThroughClickListener}
203 */
204 private void onClickImpl() {
205 // if drop down should always visible, bring it back in front of the soft
206 // keyboard when the user touches the text field
207 if (mDropDownAlwaysVisible
208 && mPopup.isShowing()
209 && mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED) {
210 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
211 mPopup.update();
212 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800213 }
214
215 /**
216 * Sets this to be single line; a separate method so
217 * MultiAutoCompleteTextView can skip this.
218 */
219 /* package */ void finishInit() {
220 setSingleLine();
221 }
222
223 /**
224 * <p>Sets the optional hint text that is displayed at the bottom of the
225 * the matching list. This can be used as a cue to the user on how to
226 * best use the list, or to provide extra information.</p>
227 *
228 * @param hint the text to be displayed to the user
229 *
230 * @attr ref android.R.styleable#AutoCompleteTextView_completionHint
231 */
232 public void setCompletionHint(CharSequence hint) {
233 mHintText = hint;
234 }
235
236 /**
237 * <p>Returns the current width for the auto-complete drop down list. This can
238 * be a fixed width, or {@link ViewGroup.LayoutParams#FILL_PARENT} to fill the screen, or
239 * {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view.</p>
240 *
241 * @return the width for the drop down list
Karl Rosaen875d50a2009-04-23 19:00:21 -0700242 *
243 * @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800244 */
245 public int getDropDownWidth() {
246 return mDropDownWidth;
247 }
248
249 /**
250 * <p>Sets the current width for the auto-complete drop down list. This can
251 * be a fixed width, or {@link ViewGroup.LayoutParams#FILL_PARENT} to fill the screen, or
252 * {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width of its anchor view.</p>
253 *
254 * @param width the width to use
Karl Rosaen875d50a2009-04-23 19:00:21 -0700255 *
256 * @attr ref android.R.styleable#AutoCompleteTextView_dropDownWidth
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800257 */
258 public void setDropDownWidth(int width) {
259 mDropDownWidth = width;
260 }
261
262 /**
263 * <p>Returns the id for the view that the auto-complete drop down list is anchored to.</p>
264 *
265 * @return the view's id, or {@link View#NO_ID} if none specified
Karl Rosaen875d50a2009-04-23 19:00:21 -0700266 *
267 * @attr ref android.R.styleable#AutoCompleteTextView_dropDownAnchor
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800268 */
269 public int getDropDownAnchor() {
270 return mDropDownAnchorId;
271 }
272
273 /**
274 * <p>Sets the view to which the auto-complete drop down list should anchor. The view
275 * corresponding to this id will not be loaded until the next time it is needed to avoid
276 * loading a view which is not yet instantiated.</p>
277 *
278 * @param id the id to anchor the drop down list view to
Karl Rosaen875d50a2009-04-23 19:00:21 -0700279 *
280 * @attr ref android.R.styleable#AutoCompleteTextView_dropDownAnchor
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800281 */
282 public void setDropDownAnchor(int id) {
283 mDropDownAnchorId = id;
284 mDropDownAnchorView = null;
285 }
Karl Rosaen875d50a2009-04-23 19:00:21 -0700286
287 /**
288 * <p>Gets the background of the auto-complete drop-down list.</p>
289 *
290 * @return the background drawable
291 *
292 * @attr ref android.R.styleable#PopupWindow_popupBackground
293 *
294 * @hide Pending API council approval
295 */
296 public Drawable getDropDownBackground() {
297 return mPopup.getBackground();
298 }
299
300 /**
301 * <p>Sets the background of the auto-complete drop-down list.</p>
302 *
303 * @param d the drawable to set as the background
304 *
305 * @attr ref android.R.styleable#PopupWindow_popupBackground
306 *
307 * @hide Pending API council approval
308 */
309 public void setDropDownBackgroundDrawable(Drawable d) {
310 mPopup.setBackgroundDrawable(d);
311 }
312
313 /**
314 * <p>Sets the background of the auto-complete drop-down list.</p>
315 *
316 * @param id the id of the drawable to set as the background
317 *
318 * @attr ref android.R.styleable#PopupWindow_popupBackground
319 *
320 * @hide Pending API council approval
321 */
322 public void setDropDownBackgroundResource(int id) {
323 mPopup.setBackgroundDrawable(getResources().getDrawable(id));
324 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800325
326 /**
Karl Rosaen875d50a2009-04-23 19:00:21 -0700327 * <p>Sets the animation style of the auto-complete drop-down list.</p>
328 *
329 * <p>If the drop-down is showing, calling this method will take effect only
330 * the next time the drop-down is shown.</p>
331 *
332 * @param animationStyle animation style to use when the drop-down appears
333 * and disappears. Set to -1 for the default animation, 0 for no
334 * animation, or a resource identifier for an explicit animation.
335 *
336 * @hide Pending API council approval
337 */
338 public void setDropDownAnimationStyle(int animationStyle) {
339 mPopup.setAnimationStyle(animationStyle);
340 }
341
342 /**
343 * <p>Returns the animation style that is used when the drop-down list appears and disappears
344 * </p>
345 *
346 * @return the animation style that is used when the drop-down list appears and disappears
347 *
348 * @hide Pending API council approval
349 */
350 public int getDropDownAnimationStyle() {
351 return mPopup.getAnimationStyle();
352 }
353
354 /**
355 * <p>Sets the vertical offset used for the auto-complete drop-down list.</p>
356 *
357 * @param offset the vertical offset
358 *
359 * @hide Pending API council approval
360 */
361 public void setDropDownVerticalOffset(int offset) {
362 mDropDownVerticalOffset = offset;
363 }
364
365 /**
366 * <p>Gets the vertical offset used for the auto-complete drop-down list.</p>
367 *
368 * @return the vertical offset
369 *
370 * @hide Pending API council approval
371 */
372 public int getDropDownVerticalOffset() {
373 return mDropDownVerticalOffset;
374 }
375
376 /**
377 * <p>Sets the horizontal offset used for the auto-complete drop-down list.</p>
378 *
379 * @param offset the horizontal offset
380 *
381 * @hide Pending API council approval
382 */
383 public void setDropDownHorizontalOffset(int offset) {
384 mDropDownHorizontalOffset = offset;
385 }
386
387 /**
388 * <p>Gets the horizontal offset used for the auto-complete drop-down list.</p>
389 *
390 * @return the horizontal offset
391 *
392 * @hide Pending API council approval
393 */
394 public int getDropDownHorizontalOffset() {
395 return mDropDownHorizontalOffset;
396 }
397
398 /**
399 * @return Whether the drop-down is visible as long as there is {@link #enoughToFilter()}
400 *
401 * @hide Pending API council approval
402 */
403 public boolean isDropDownAlwaysVisible() {
404 return mDropDownAlwaysVisible;
405 }
406
407 /**
408 * Sets whether the drop-down should remain visible as long as there is there is
409 * {@link #enoughToFilter()}. This is useful if an unknown number of results are expected
410 * to show up in the adapter sometime in the future.
411 *
412 * The drop-down will occupy the entire screen below {@link #getDropDownAnchor} regardless
413 * of the size or content of the list. {@link #getDropDownBackground()} will fill any space
414 * that is not used by the list.
415 *
416 * @param dropDownAlwaysVisible Whether to keep the drop-down visible.
417 *
418 * @hide Pending API council approval
419 */
420 public void setDropDownAlwaysVisible(boolean dropDownAlwaysVisible) {
421 mDropDownAlwaysVisible = dropDownAlwaysVisible;
422 }
423
424 /**
425 * Checks whether the drop-down is dismissed when a suggestion is clicked.
426 *
427 * @hide Pending API council approval
428 */
429 public boolean isDropDownDismissedOnCompletion() {
430 return mDropDownDismissedOnCompletion;
431 }
432
433 /**
434 * Sets whether the drop-down is dismissed when a suggestion is clicked. This is
435 * true by default.
436 *
437 * @param dropDownDismissedOnCompletion Whether to dismiss the drop-down.
438 *
439 * @hide Pending API council approval
440 */
441 public void setDropDownDismissedOnCompletion(boolean dropDownDismissedOnCompletion) {
442 mDropDownDismissedOnCompletion = dropDownDismissedOnCompletion;
443 }
444
445 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800446 * <p>Returns the number of characters the user must type before the drop
447 * down list is shown.</p>
448 *
449 * @return the minimum number of characters to type to show the drop down
450 *
451 * @see #setThreshold(int)
452 */
453 public int getThreshold() {
454 return mThreshold;
455 }
456
457 /**
458 * <p>Specifies the minimum number of characters the user has to type in the
459 * edit box before the drop down list is shown.</p>
460 *
461 * <p>When <code>threshold</code> is less than or equals 0, a threshold of
462 * 1 is applied.</p>
463 *
464 * @param threshold the number of characters to type before the drop down
465 * is shown
466 *
467 * @see #getThreshold()
468 *
469 * @attr ref android.R.styleable#AutoCompleteTextView_completionThreshold
470 */
471 public void setThreshold(int threshold) {
472 if (threshold <= 0) {
473 threshold = 1;
474 }
475
476 mThreshold = threshold;
477 }
478
479 /**
480 * <p>Sets the listener that will be notified when the user clicks an item
481 * in the drop down list.</p>
482 *
483 * @param l the item click listener
484 */
485 public void setOnItemClickListener(AdapterView.OnItemClickListener l) {
486 mItemClickListener = l;
487 }
488
489 /**
490 * <p>Sets the listener that will be notified when the user selects an item
491 * in the drop down list.</p>
492 *
493 * @param l the item selected listener
494 */
495 public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener l) {
496 mItemSelectedListener = l;
497 }
498
499 /**
500 * <p>Returns the listener that is notified whenever the user clicks an item
501 * in the drop down list.</p>
502 *
503 * @return the item click listener
504 *
505 * @deprecated Use {@link #getOnItemClickListener()} intead
506 */
507 @Deprecated
508 public AdapterView.OnItemClickListener getItemClickListener() {
509 return mItemClickListener;
510 }
511
512 /**
513 * <p>Returns the listener that is notified whenever the user selects an
514 * item in the drop down list.</p>
515 *
516 * @return the item selected listener
517 *
518 * @deprecated Use {@link #getOnItemSelectedListener()} intead
519 */
520 @Deprecated
521 public AdapterView.OnItemSelectedListener getItemSelectedListener() {
522 return mItemSelectedListener;
523 }
524
525 /**
526 * <p>Returns the listener that is notified whenever the user clicks an item
527 * in the drop down list.</p>
528 *
529 * @return the item click listener
530 */
531 public AdapterView.OnItemClickListener getOnItemClickListener() {
532 return mItemClickListener;
533 }
534
535 /**
536 * <p>Returns the listener that is notified whenever the user selects an
537 * item in the drop down list.</p>
538 *
539 * @return the item selected listener
540 */
541 public AdapterView.OnItemSelectedListener getOnItemSelectedListener() {
542 return mItemSelectedListener;
543 }
544
545 /**
546 * <p>Returns a filterable list adapter used for auto completion.</p>
547 *
548 * @return a data adapter used for auto completion
549 */
550 public ListAdapter getAdapter() {
551 return mAdapter;
552 }
553
554 /**
555 * <p>Changes the list of data used for auto completion. The provided list
556 * must be a filterable list adapter.</p>
557 *
558 * <p>The caller is still responsible for managing any resources used by the adapter.
559 * Notably, when the AutoCompleteTextView is closed or released, the adapter is not notified.
560 * A common case is the use of {@link android.widget.CursorAdapter}, which
561 * contains a {@link android.database.Cursor} that must be closed. This can be done
562 * automatically (see
563 * {@link android.app.Activity#startManagingCursor(android.database.Cursor)
564 * startManagingCursor()}),
565 * or by manually closing the cursor when the AutoCompleteTextView is dismissed.</p>
566 *
567 * @param adapter the adapter holding the auto completion data
568 *
569 * @see #getAdapter()
570 * @see android.widget.Filterable
571 * @see android.widget.ListAdapter
572 */
573 public <T extends ListAdapter & Filterable> void setAdapter(T adapter) {
574 mAdapter = adapter;
575 if (mAdapter != null) {
576 //noinspection unchecked
577 mFilter = ((Filterable) mAdapter).getFilter();
578 } else {
579 mFilter = null;
580 }
581
582 if (mDropDownList != null) {
583 mDropDownList.setAdapter(mAdapter);
584 }
585 }
586
587 @Override
588 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
589 if (isPopupShowing()) {
590 // special case for the back key, we do not even try to send it
591 // to the drop down list but instead, consume it immediately
592 if (keyCode == KeyEvent.KEYCODE_BACK) {
593 dismissDropDown();
594 return true;
595 }
596 }
597 return super.onKeyPreIme(keyCode, event);
598 }
599
600 @Override
601 public boolean onKeyUp(int keyCode, KeyEvent event) {
602 if (isPopupShowing() && mDropDownList.getSelectedItemPosition() >= 0) {
603 boolean consumed = mDropDownList.onKeyUp(keyCode, event);
604 if (consumed) {
605 switch (keyCode) {
606 // if the list accepts the key events and the key event
607 // was a click, the text view gets the selected item
608 // from the drop down as its content
609 case KeyEvent.KEYCODE_ENTER:
610 case KeyEvent.KEYCODE_DPAD_CENTER:
611 performCompletion();
612 return true;
613 }
614 }
615 }
616 return super.onKeyUp(keyCode, event);
617 }
618
619 @Override
620 public boolean onKeyDown(int keyCode, KeyEvent event) {
621 // when the drop down is shown, we drive it directly
622 if (isPopupShowing()) {
623 // the key events are forwarded to the list in the drop down view
624 // note that ListView handles space but we don't want that to happen
625 // also if selection is not currently in the drop down, then don't
626 // let center or enter presses go there since that would cause it
627 // to select one of its items
628 if (keyCode != KeyEvent.KEYCODE_SPACE
629 && (mDropDownList.getSelectedItemPosition() >= 0
630 || (keyCode != KeyEvent.KEYCODE_ENTER
631 && keyCode != KeyEvent.KEYCODE_DPAD_CENTER))) {
632 int curIndex = mDropDownList.getSelectedItemPosition();
633 boolean consumed;
634 final boolean below = !mPopup.isAboveAnchor();
635 if ((below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= 0) ||
636 (!below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN && curIndex >=
637 mDropDownList.getAdapter().getCount() - 1)) {
638 // When the selection is at the top, we block the key
639 // event to prevent focus from moving.
640 mDropDownList.hideSelector();
641 mDropDownList.requestLayout();
642 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
643 mPopup.update();
644 return true;
645 }
646 consumed = mDropDownList.onKeyDown(keyCode, event);
647 if (DEBUG) Log.v(TAG, "Key down: code=" + keyCode + " list consumed="
648 + consumed);
649 if (consumed) {
650 // If it handled the key event, then the user is
651 // navigating in the list, so we should put it in front.
652 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
653 // Here's a little trick we need to do to make sure that
654 // the list view is actually showing its focus indicator,
655 // by ensuring it has focus and getting its window out
656 // of touch mode.
657 mDropDownList.requestFocusFromTouch();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800658 mPopup.update();
659
660 switch (keyCode) {
661 // avoid passing the focus from the text view to the
662 // next component
663 case KeyEvent.KEYCODE_ENTER:
664 case KeyEvent.KEYCODE_DPAD_CENTER:
665 case KeyEvent.KEYCODE_DPAD_DOWN:
666 case KeyEvent.KEYCODE_DPAD_UP:
667 return true;
668 }
669 } else {
670 if (below && keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
671 // when the selection is at the bottom, we block the
672 // event to avoid going to the next focusable widget
673 Adapter adapter = mDropDownList.getAdapter();
674 if (adapter != null && curIndex == adapter.getCount() - 1) {
675 return true;
676 }
677 } else if (!below && keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex == 0) {
678 return true;
679 }
680 }
681 }
682 } else {
683 switch(keyCode) {
684 case KeyEvent.KEYCODE_DPAD_DOWN:
685 performValidation();
686 }
687 }
688
689 mLastKeyCode = keyCode;
690 boolean handled = super.onKeyDown(keyCode, event);
691 mLastKeyCode = KeyEvent.KEYCODE_UNKNOWN;
692
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -0700693 if (handled && isPopupShowing() && mDropDownList != null) {
694 clearListSelection();
695 }
696
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800697 return handled;
698 }
699
700 /**
701 * Returns <code>true</code> if the amount of text in the field meets
702 * or exceeds the {@link #getThreshold} requirement. You can override
703 * this to impose a different standard for when filtering will be
704 * triggered.
705 */
706 public boolean enoughToFilter() {
707 if (DEBUG) Log.v(TAG, "Enough to filter: len=" + getText().length()
708 + " threshold=" + mThreshold);
709 return getText().length() >= mThreshold;
710 }
711
712 /**
713 * This is used to watch for edits to the text view. Note that we call
714 * to methods on the auto complete text view class so that we can access
715 * private vars without going through thunks.
716 */
717 private class MyWatcher implements TextWatcher {
718 public void afterTextChanged(Editable s) {
719 doAfterTextChanged();
720 }
721 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
722 doBeforeTextChanged();
723 }
724 public void onTextChanged(CharSequence s, int start, int before, int count) {
725 }
726 }
727
728 void doBeforeTextChanged() {
729 if (mBlockCompletion) return;
730
731 // when text is changed, inserted or deleted, we attempt to show
732 // the drop down
733 mOpenBefore = isPopupShowing();
734 if (DEBUG) Log.v(TAG, "before text changed: open=" + mOpenBefore);
735 }
736
737 void doAfterTextChanged() {
738 if (mBlockCompletion) return;
739
740 // if the list was open before the keystroke, but closed afterwards,
741 // then something in the keystroke processing (an input filter perhaps)
742 // called performCompletion() and we shouldn't do any more processing.
743 if (DEBUG) Log.v(TAG, "after text changed: openBefore=" + mOpenBefore
744 + " open=" + isPopupShowing());
745 if (mOpenBefore && !isPopupShowing()) {
746 return;
747 }
748
749 // the drop down is shown only when a minimum number of characters
750 // was typed in the text view
751 if (enoughToFilter()) {
752 if (mFilter != null) {
753 performFiltering(getText(), mLastKeyCode);
754 }
755 } else {
756 // drop down is automatically dismissed when enough characters
757 // are deleted from the text view
758 dismissDropDown();
759 if (mFilter != null) {
760 mFilter.filter(null);
761 }
762 }
763 }
764
765 /**
766 * <p>Indicates whether the popup menu is showing.</p>
767 *
768 * @return true if the popup menu is showing, false otherwise
769 */
770 public boolean isPopupShowing() {
771 return mPopup.isShowing();
772 }
773
774 /**
775 * <p>Converts the selected item from the drop down list into a sequence
776 * of character that can be used in the edit box.</p>
777 *
778 * @param selectedItem the item selected by the user for completion
779 *
780 * @return a sequence of characters representing the selected suggestion
781 */
782 protected CharSequence convertSelectionToString(Object selectedItem) {
783 return mFilter.convertResultToString(selectedItem);
784 }
785
786 /**
787 * <p>Clear the list selection. This may only be temporary, as user input will often bring
788 * it back.
789 */
790 public void clearListSelection() {
791 if (mDropDownList != null) {
792 mDropDownList.hideSelector();
793 mDropDownList.requestLayout();
794 }
795 }
796
797 /**
798 * Set the position of the dropdown view selection.
799 *
800 * @param position The position to move the selector to.
801 */
802 public void setListSelection(int position) {
803 if (mPopup.isShowing() && (mDropDownList != null)) {
804 mDropDownList.setSelection(position);
805 // ListView.setSelection() will call requestLayout()
806 }
807 }
808
809 /**
810 * Get the position of the dropdown view selection, if there is one. Returns
811 * {@link ListView#INVALID_POSITION ListView.INVALID_POSITION} if there is no dropdown or if
812 * there is no selection.
813 *
814 * @return the position of the current selection, if there is one, or
815 * {@link ListView#INVALID_POSITION ListView.INVALID_POSITION} if not.
816 *
817 * @see ListView#getSelectedItemPosition()
818 */
819 public int getListSelection() {
820 if (mPopup.isShowing() && (mDropDownList != null)) {
821 return mDropDownList.getSelectedItemPosition();
822 }
823 return ListView.INVALID_POSITION;
824 }
The Android Open Source Projectba87e3e2009-03-13 13:04:22 -0700825
826 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800827 * <p>Starts filtering the content of the drop down list. The filtering
828 * pattern is the content of the edit box. Subclasses should override this
829 * method to filter with a different pattern, for instance a substring of
830 * <code>text</code>.</p>
831 *
832 * @param text the filtering pattern
833 * @param keyCode the last character inserted in the edit box; beware that
834 * this will be null when text is being added through a soft input method.
835 */
836 @SuppressWarnings({ "UnusedDeclaration" })
837 protected void performFiltering(CharSequence text, int keyCode) {
838 mFilter.filter(text, this);
839 }
840
841 /**
842 * <p>Performs the text completion by converting the selected item from
843 * the drop down list into a string, replacing the text box's content with
844 * this string and finally dismissing the drop down menu.</p>
845 */
846 public void performCompletion() {
847 performCompletion(null, -1, -1);
848 }
849
850 @Override
851 public void onCommitCompletion(CompletionInfo completion) {
852 if (isPopupShowing()) {
853 mBlockCompletion = true;
854 replaceText(completion.getText());
855 mBlockCompletion = false;
856
857 if (mItemClickListener != null) {
858 final DropDownListView list = mDropDownList;
859 // Note that we don't have a View here, so we will need to
860 // supply null. Hopefully no existing apps crash...
861 mItemClickListener.onItemClick(list, null, completion.getPosition(),
862 completion.getId());
863 }
864 }
865 }
866
867 private void performCompletion(View selectedView, int position, long id) {
868 if (isPopupShowing()) {
869 Object selectedItem;
870 if (position < 0) {
871 selectedItem = mDropDownList.getSelectedItem();
872 } else {
873 selectedItem = mAdapter.getItem(position);
874 }
875 if (selectedItem == null) {
876 Log.w(TAG, "performCompletion: no selected item");
877 return;
878 }
879
880 mBlockCompletion = true;
881 replaceText(convertSelectionToString(selectedItem));
882 mBlockCompletion = false;
883
884 if (mItemClickListener != null) {
885 final DropDownListView list = mDropDownList;
886
887 if (selectedView == null || position < 0) {
888 selectedView = list.getSelectedView();
889 position = list.getSelectedItemPosition();
890 id = list.getSelectedItemId();
891 }
892 mItemClickListener.onItemClick(list, selectedView, position, id);
893 }
894 }
895
Karl Rosaen875d50a2009-04-23 19:00:21 -0700896 if (mDropDownDismissedOnCompletion) {
897 dismissDropDown();
898 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800899 }
900
901 /**
902 * Identifies whether the view is currently performing a text completion, so subclasses
903 * can decide whether to respond to text changed events.
904 */
905 public boolean isPerformingCompletion() {
906 return mBlockCompletion;
907 }
908
909 /**
Karl Rosaen875d50a2009-04-23 19:00:21 -0700910 * Like {@link #setText(CharSequence)}, except that it can disable filtering.
911 *
912 * @param filter If <code>false</code>, no filtering will be performed
913 * as a result of this call.
914 *
915 * @hide Pending API council approval.
916 */
917 public void setText(CharSequence text, boolean filter) {
918 if (filter) {
919 setText(text);
920 } else {
921 mBlockCompletion = true;
922 setText(text);
923 mBlockCompletion = false;
924 }
925 }
926
927 /**
928 * Like {@link #setTextKeepState(CharSequence)}, except that it can disable filtering.
929 *
930 * @param filter If <code>false</code>, no filtering will be performed
931 * as a result of this call.
932 *
933 * @hide Pending API council approval.
934 */
935 public void setTextKeepState(CharSequence text, boolean filter) {
936 if (filter) {
937 setTextKeepState(text);
938 } else {
939 mBlockCompletion = true;
940 setTextKeepState(text);
941 mBlockCompletion = false;
942 }
943 }
944
945 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800946 * <p>Performs the text completion by replacing the current text by the
947 * selected item. Subclasses should override this method to avoid replacing
948 * the whole content of the edit box.</p>
949 *
950 * @param text the selected suggestion in the drop down list
951 */
952 protected void replaceText(CharSequence text) {
953 setText(text);
954 // make sure we keep the caret at the end of the text view
955 Editable spannable = getText();
956 Selection.setSelection(spannable, spannable.length());
957 }
958
Karl Rosaen875d50a2009-04-23 19:00:21 -0700959 /** {@inheritDoc} */
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800960 public void onFilterComplete(int count) {
961 if (mAttachCount <= 0) return;
962
963 /*
964 * This checks enoughToFilter() again because filtering requests
965 * are asynchronous, so the result may come back after enough text
966 * has since been deleted to make it no longer appropriate
967 * to filter.
968 */
969
Karl Rosaen875d50a2009-04-23 19:00:21 -0700970 if ((count > 0 || mDropDownAlwaysVisible) && enoughToFilter()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800971 if (hasFocus() && hasWindowFocus()) {
972 showDropDown();
973 }
974 } else {
975 dismissDropDown();
976 }
977 }
978
979 @Override
980 public void onWindowFocusChanged(boolean hasWindowFocus) {
981 super.onWindowFocusChanged(hasWindowFocus);
982 performValidation();
983 if (!hasWindowFocus) {
984 dismissDropDown();
985 }
986 }
987
988 @Override
989 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
990 super.onFocusChanged(focused, direction, previouslyFocusedRect);
991 performValidation();
992 if (!focused) {
993 dismissDropDown();
994 }
995 }
996
997 @Override
998 protected void onAttachedToWindow() {
999 super.onAttachedToWindow();
1000 mAttachCount++;
1001 }
1002
1003 @Override
1004 protected void onDetachedFromWindow() {
1005 dismissDropDown();
1006 mAttachCount--;
1007 super.onDetachedFromWindow();
1008 }
1009
1010 /**
1011 * <p>Closes the drop down if present on screen.</p>
1012 */
1013 public void dismissDropDown() {
1014 InputMethodManager imm = InputMethodManager.peekInstance();
1015 if (imm != null) {
1016 imm.displayCompletions(this, null);
1017 }
1018 mPopup.dismiss();
1019 mPopup.setContentView(null);
1020 mDropDownList = null;
1021 }
1022
1023 @Override
1024 protected boolean setFrame(int l, int t, int r, int b) {
1025 boolean result = super.setFrame(l, t, r, b);
1026
1027 if (mPopup.isShowing()) {
1028 mPopup.update(this, r - l, -1);
1029 }
1030
1031 return result;
1032 }
1033
1034 /**
1035 * <p>Used for lazy instantiation of the anchor view from the id we have. If the value of
1036 * the id is NO_ID or we can't find a view for the given id, we return this TextView as
1037 * the default anchoring point.</p>
1038 */
1039 private View getDropDownAnchorView() {
1040 if (mDropDownAnchorView == null && mDropDownAnchorId != View.NO_ID) {
1041 mDropDownAnchorView = getRootView().findViewById(mDropDownAnchorId);
1042 }
1043 return mDropDownAnchorView == null ? this : mDropDownAnchorView;
1044 }
1045
1046 /**
1047 * <p>Displays the drop down on screen.</p>
1048 */
1049 public void showDropDown() {
1050 int height = buildDropDown();
1051 if (mPopup.isShowing()) {
1052 int widthSpec;
1053 if (mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT) {
1054 // The call to PopupWindow's update method below can accept -1 for any
1055 // value you do not want to update.
1056 widthSpec = -1;
1057 } else if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
1058 widthSpec = getDropDownAnchorView().getWidth();
1059 } else {
1060 widthSpec = mDropDownWidth;
1061 }
1062 mPopup.update(getDropDownAnchorView(), mDropDownHorizontalOffset,
1063 mDropDownVerticalOffset, widthSpec, height);
1064 } else {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001065 if (mDropDownWidth == ViewGroup.LayoutParams.FILL_PARENT) {
Karl Rosaen875d50a2009-04-23 19:00:21 -07001066 mPopup.setWindowLayoutMode(ViewGroup.LayoutParams.FILL_PARENT, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001067 } else {
Karl Rosaen875d50a2009-04-23 19:00:21 -07001068 mPopup.setWindowLayoutMode(0, 0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001069 if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) {
1070 mPopup.setWidth(getDropDownAnchorView().getWidth());
1071 } else {
1072 mPopup.setWidth(mDropDownWidth);
1073 }
1074 }
1075 mPopup.setHeight(height);
1076 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
Karl Rosaen98e333f2009-04-28 10:39:09 -07001077
1078 // use outside touchable to dismiss drop down when touching outside of it, so
1079 // only set this if the dropdown is not always visible
1080 mPopup.setOutsideTouchable(!mDropDownAlwaysVisible);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001081 mPopup.setTouchInterceptor(new PopupTouchIntercepter());
1082 mPopup.showAsDropDown(getDropDownAnchorView(),
1083 mDropDownHorizontalOffset, mDropDownVerticalOffset);
1084 mDropDownList.setSelection(ListView.INVALID_POSITION);
1085 mDropDownList.hideSelector();
1086 mDropDownList.requestFocus();
1087 post(mHideSelector);
1088 }
1089 }
1090
1091 /**
1092 * <p>Builds the popup window's content and returns the height the popup
1093 * should have. Returns -1 when the content already exists.</p>
1094 *
1095 * @return the content's height or -1 if content already exists
1096 */
1097 private int buildDropDown() {
1098 ViewGroup dropDownView;
1099 int otherHeights = 0;
1100
1101 if (mAdapter != null) {
1102 InputMethodManager imm = InputMethodManager.peekInstance();
1103 if (imm != null) {
1104 int N = mAdapter.getCount();
1105 if (N > 20) N = 20;
1106 CompletionInfo[] completions = new CompletionInfo[N];
1107 for (int i = 0; i < N; i++) {
1108 Object item = mAdapter.getItem(i);
1109 long id = mAdapter.getItemId(i);
1110 completions[i] = new CompletionInfo(id, i,
1111 convertSelectionToString(item));
1112 }
1113 imm.displayCompletions(this, completions);
1114 }
1115 }
1116
1117 if (mDropDownList == null) {
1118 Context context = getContext();
1119
1120 mHideSelector = new ListSelectorHider();
1121
1122 mDropDownList = new DropDownListView(context);
1123 mDropDownList.setSelector(mDropDownListHighlight);
1124 mDropDownList.setAdapter(mAdapter);
1125 mDropDownList.setVerticalFadingEdgeEnabled(true);
1126 mDropDownList.setOnItemClickListener(mDropDownItemClickListener);
1127 mDropDownList.setFocusable(true);
1128 mDropDownList.setFocusableInTouchMode(true);
1129
1130 if (mItemSelectedListener != null) {
1131 mDropDownList.setOnItemSelectedListener(mItemSelectedListener);
1132 }
1133
1134 dropDownView = mDropDownList;
1135
1136 View hintView = getHintView(context);
1137 if (hintView != null) {
1138 // if an hint has been specified, we accomodate more space for it and
1139 // add a text view in the drop down menu, at the bottom of the list
1140 LinearLayout hintContainer = new LinearLayout(context);
1141 hintContainer.setOrientation(LinearLayout.VERTICAL);
1142
1143 LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams(
1144 ViewGroup.LayoutParams.FILL_PARENT, 0, 1.0f
1145 );
1146 hintContainer.addView(dropDownView, hintParams);
1147 hintContainer.addView(hintView);
1148
1149 // measure the hint's height to find how much more vertical space
1150 // we need to add to the drop down's height
1151 int widthSpec = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST);
1152 int heightSpec = MeasureSpec.UNSPECIFIED;
1153 hintView.measure(widthSpec, heightSpec);
1154
1155 hintParams = (LinearLayout.LayoutParams) hintView.getLayoutParams();
1156 otherHeights = hintView.getMeasuredHeight() + hintParams.topMargin
1157 + hintParams.bottomMargin;
1158
1159 dropDownView = hintContainer;
1160 }
1161
1162 mPopup.setContentView(dropDownView);
1163 } else {
1164 dropDownView = (ViewGroup) mPopup.getContentView();
1165 final View view = dropDownView.findViewById(HINT_VIEW_ID);
1166 if (view != null) {
1167 LinearLayout.LayoutParams hintParams =
1168 (LinearLayout.LayoutParams) view.getLayoutParams();
1169 otherHeights = view.getMeasuredHeight() + hintParams.topMargin
1170 + hintParams.bottomMargin;
1171 }
1172 }
1173
1174 // Max height available on the screen for a popup anchored to us
1175 final int maxHeight = mPopup.getMaxAvailableHeight(this, mDropDownVerticalOffset);
1176 //otherHeights += dropDownView.getPaddingTop() + dropDownView.getPaddingBottom();
1177
Karl Rosaen875d50a2009-04-23 19:00:21 -07001178 final int measuredHeight = mDropDownList.measureHeightOfChildren(MeasureSpec.UNSPECIFIED,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001179 0, ListView.NO_POSITION, maxHeight - otherHeights, 2) + otherHeights;
Karl Rosaen875d50a2009-04-23 19:00:21 -07001180
1181 return mDropDownAlwaysVisible ? maxHeight : measuredHeight;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001182 }
1183
1184 private View getHintView(Context context) {
1185 if (mHintText != null && mHintText.length() > 0) {
1186 final TextView hintView = (TextView) LayoutInflater.from(context).inflate(
1187 mHintResource, null).findViewById(com.android.internal.R.id.text1);
1188 hintView.setText(mHintText);
1189 hintView.setId(HINT_VIEW_ID);
1190 return hintView;
1191 } else {
1192 return null;
1193 }
1194 }
1195
1196 /**
1197 * Sets the validator used to perform text validation.
1198 *
1199 * @param validator The validator used to validate the text entered in this widget.
1200 *
1201 * @see #getValidator()
1202 * @see #performValidation()
1203 */
1204 public void setValidator(Validator validator) {
1205 mValidator = validator;
1206 }
1207
1208 /**
1209 * Returns the Validator set with {@link #setValidator},
1210 * or <code>null</code> if it was not set.
1211 *
1212 * @see #setValidator(android.widget.AutoCompleteTextView.Validator)
1213 * @see #performValidation()
1214 */
1215 public Validator getValidator() {
1216 return mValidator;
1217 }
1218
1219 /**
1220 * If a validator was set on this view and the current string is not valid,
1221 * ask the validator to fix it.
1222 *
1223 * @see #getValidator()
1224 * @see #setValidator(android.widget.AutoCompleteTextView.Validator)
1225 */
1226 public void performValidation() {
1227 if (mValidator == null) return;
1228
1229 CharSequence text = getText();
1230
1231 if (!TextUtils.isEmpty(text) && !mValidator.isValid(text)) {
1232 setText(mValidator.fixText(text));
1233 }
1234 }
1235
1236 /**
1237 * Returns the Filter obtained from {@link Filterable#getFilter},
1238 * or <code>null</code> if {@link #setAdapter} was not called with
1239 * a Filterable.
1240 */
1241 protected Filter getFilter() {
1242 return mFilter;
1243 }
1244
1245 private class ListSelectorHider implements Runnable {
1246 public void run() {
1247 if (mDropDownList != null) {
1248 mDropDownList.hideSelector();
1249 mDropDownList.requestLayout();
1250 }
1251 }
1252 }
1253
1254 private class PopupTouchIntercepter implements OnTouchListener {
1255 public boolean onTouch(View v, MotionEvent event) {
1256 if (event.getAction() == MotionEvent.ACTION_DOWN) {
1257 mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
1258 mPopup.update();
1259 }
1260 return false;
1261 }
1262 }
1263
1264 private class DropDownItemClickListener implements AdapterView.OnItemClickListener {
1265 public void onItemClick(AdapterView parent, View v, int position, long id) {
1266 performCompletion(v, position, id);
1267 }
1268 }
1269
1270 /**
1271 * <p>Wrapper class for a ListView. This wrapper hijacks the focus to
1272 * make sure the list uses the appropriate drawables and states when
1273 * displayed on screen within a drop down. The focus is never actually
1274 * passed to the drop down; the list only looks focused.</p>
1275 */
1276 private static class DropDownListView extends ListView {
1277 /**
1278 * <p>Creates a new list view wrapper.</p>
1279 *
1280 * @param context this view's context
1281 */
1282 public DropDownListView(Context context) {
1283 super(context, null, com.android.internal.R.attr.dropDownListViewStyle);
1284 }
1285
1286 /**
1287 * <p>Avoids jarring scrolling effect by ensuring that list elements
1288 * made of a text view fit on a single line.</p>
1289 *
1290 * @param position the item index in the list to get a view for
1291 * @return the view for the specified item
1292 */
1293 @Override
1294 protected View obtainView(int position) {
1295 View view = super.obtainView(position);
1296
1297 if (view instanceof TextView) {
1298 ((TextView) view).setHorizontallyScrolling(true);
1299 }
1300
1301 return view;
1302 }
1303
1304 /**
1305 * <p>Returns the top padding of the currently selected view.</p>
1306 *
1307 * @return the height of the top padding for the selection
1308 */
1309 public int getSelectionPaddingTop() {
1310 return mSelectionTopPadding;
1311 }
1312
1313 /**
1314 * <p>Returns the bottom padding of the currently selected view.</p>
1315 *
1316 * @return the height of the bottom padding for the selection
1317 */
1318 public int getSelectionPaddingBottom() {
1319 return mSelectionBottomPadding;
1320 }
1321
1322 /**
1323 * <p>Returns the focus state in the drop down.</p>
1324 *
1325 * @return true always
1326 */
1327 @Override
1328 public boolean hasWindowFocus() {
1329 return true;
1330 }
1331
1332 /**
1333 * <p>Returns the focus state in the drop down.</p>
1334 *
1335 * @return true always
1336 */
1337 @Override
1338 public boolean isFocused() {
1339 return true;
1340 }
1341
1342 /**
1343 * <p>Returns the focus state in the drop down.</p>
1344 *
1345 * @return true always
1346 */
1347 @Override
1348 public boolean hasFocus() {
1349 return true;
1350 }
1351
1352 protected int[] onCreateDrawableState(int extraSpace) {
1353 int[] res = super.onCreateDrawableState(extraSpace);
The Android Open Source Projectc39a6e02009-03-11 12:11:56 -07001354 //noinspection ConstantIfStatement
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001355 if (false) {
1356 StringBuilder sb = new StringBuilder("Created drawable state: [");
1357 for (int i=0; i<res.length; i++) {
1358 if (i > 0) sb.append(", ");
1359 sb.append("0x");
1360 sb.append(Integer.toHexString(res[i]));
1361 }
1362 sb.append("]");
1363 Log.i(TAG, sb.toString());
1364 }
1365 return res;
1366 }
1367 }
1368
1369 /**
1370 * This interface is used to make sure that the text entered in this TextView complies to
1371 * a certain format. Since there is no foolproof way to prevent the user from leaving
1372 * this View with an incorrect value in it, all we can do is try to fix it ourselves
1373 * when this happens.
1374 */
1375 public interface Validator {
1376 /**
1377 * Validates the specified text.
1378 *
1379 * @return true If the text currently in the text editor is valid.
1380 *
1381 * @see #fixText(CharSequence)
1382 */
1383 boolean isValid(CharSequence text);
1384
1385 /**
1386 * Corrects the specified text to make it valid.
1387 *
1388 * @param invalidText A string that doesn't pass validation: isValid(invalidText)
1389 * returns false
1390 *
1391 * @return A string based on invalidText such as invoking isValid() on it returns true.
1392 *
1393 * @see #isValid(CharSequence)
1394 */
1395 CharSequence fixText(CharSequence invalidText);
1396 }
Karl Rosaen98e333f2009-04-28 10:39:09 -07001397
1398 /**
1399 * Allows us a private hook into the on click event without preventing users from setting
1400 * their own click listener.
1401 */
1402 private class PassThroughClickListener implements OnClickListener {
1403
1404 private View.OnClickListener mWrapped;
1405
1406 /** {@inheritDoc} */
1407 public void onClick(View v) {
1408 onClickImpl();
1409
1410 if (mWrapped != null) mWrapped.onClick(v);
1411 }
1412 }
1413
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001414}