├── .gitignore
├── README.md
├── art
└── logo.png
├── library
├── AndroidManifest.xml
├── libs
│ ├── CWAC-MergeAdapter.jar
│ ├── CWAC-SackOfViewsAdapter.jar
│ ├── android-support-v4.jar
│ ├── gson-2.2.2.jar
│ └── resty-0.3.2.jar
├── pom.xml
├── proguard-project.txt
├── project.properties
├── res
│ ├── color
│ │ ├── het__primary_text_dark.xml
│ │ └── het__primary_text_light.xml
│ ├── drawable-hdpi
│ │ └── het__ic_device_access_time.png
│ ├── drawable-mdpi
│ │ └── het__ic_device_access_time.png
│ ├── drawable-xhdpi
│ │ └── het__ic_device_access_time.png
│ ├── layout
│ │ ├── het__dropdown_history_item.xml
│ │ ├── het__simple_dropdown_hint.xml
│ │ └── het__simple_dropdown_item_1line.xml
│ └── values
│ │ ├── het__attrs.xml
│ │ ├── het__colors.xml
│ │ └── het__styles.xml
└── src
│ └── com
│ └── zenlibs
│ └── historyedittext
│ ├── AbsHistoryEditText.java
│ ├── FroyoListView.java
│ ├── FroyoPopupWindow.java
│ ├── HistoryDb.java
│ ├── HistoryEditText.java
│ └── Zen.java
├── pom.xml
└── sample
├── AndroidManifest.xml
├── ic_launcher-web.png
├── libs
└── android-support-v4.jar
├── pom.xml
├── proguard-project.txt
├── project.properties
├── res
├── drawable-hdpi
│ └── ic_launcher.png
├── drawable-mdpi
│ └── ic_launcher.png
├── drawable-xhdpi
│ ├── ic_action_search.png
│ ├── ic_content_import_export.png
│ ├── ic_launcher.png
│ └── ic_location_directions.png
├── drawable-xxhdpi
│ └── ic_launcher.png
├── layout
│ ├── activity_home.xml
│ ├── activity_no_user_adapter.xml
│ └── activity_simple.xml
├── menu
│ └── activity_simple.xml
├── raw
│ └── countries
└── values
│ ├── strings.xml
│ └── styles.xml
└── src
└── com
└── zenlibs
└── historyedittext
└── demo
├── ActivityInfo.java
├── HomeActivity.java
├── nouseradaptersample
└── NoUserAdapterActivity.java
└── simplehistorysample
└── SimpleHistoryActivity.java
/.gitignore:
--------------------------------------------------------------------------------
1 | # built application files
2 | *.apk
3 | *.ap_
4 |
5 | # files for the dex VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # generated files
12 | bin/
13 | gen/
14 |
15 | # Local configuration file (sdk path, etc)
16 | local.properties
17 |
18 | # Eclipse project files
19 | .classpath
20 | .project
21 |
22 | #Maven
23 | target
24 | release.properties
25 | pom.xml.*
26 |
27 | # Folder containing all the Google Play APKs
28 | apks
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ![Logo][1]
2 |
3 | HistoryEditText
4 | ===============
5 | Android EditText that auto-saves previous values and displays them in a dropdown when used again.
6 | [1]: http://png-5.findicons.com/files/icons/1757/isabi/128/time_machine_shaped.png
7 |
8 |
9 | *This is a work in progress*
10 |
--------------------------------------------------------------------------------
/art/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zenlibs/HistoryEditText/311294b7d84323758d7bf729dd27ad8ce7c4e2fb/art/logo.png
--------------------------------------------------------------------------------
/library/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
52 | * An editable text view that shows completion suggestions automatically while 53 | * the user is typing. The list of suggestions is displayed in a drop down menu 54 | * from which the user can choose an item to replace the content of the edit box 55 | * with. 56 | *
57 | * 58 | *59 | * The drop down can be dismissed at any time by pressing the back key or, if no 60 | * item is selected in the drop down, by pressing the enter/dpad center key. 61 | *
62 | * 63 | *64 | * The list of suggestions is obtained from a data adapter and appears only 65 | * after a given number of characters defined by {@link #getThreshold() the 66 | * threshold}. 67 | *
68 | * 69 | *70 | * The following code snippet shows how to create a text view which suggests 71 | * various countries names while the user is typing: 72 | *
73 | * 74 | *75 | * public class CountriesActivity extends Activity { 76 | * protected void onCreate(Bundle icicle) { 77 | * super.onCreate(icicle); 78 | * setContentView(R.layout.countries); 79 | * 80 | * ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, 81 | * COUNTRIES); 82 | * AutoCompleteTextView textView = (AutoCompleteTextView) findViewById(R.id.countries_list); 83 | * textView.setAdapter(adapter); 84 | * } 85 | * 86 | * private static final String[] COUNTRIES = new String[] { "Belgium", "France", "Italy", "Germany", "Spain" }; 87 | * } 88 | *89 | * 90 | * @attr ref android.android.R.styleable#AutoCompleteTextView_completionHint 91 | * @attr ref 92 | * android.android.R.styleable#AutoCompleteTextView_completionThreshold 93 | * @attr ref android.android.R.styleable#AutoCompleteTextView_completionHintView 94 | * @attr ref android.android.R.styleable#AutoCompleteTextView_dropDownSelector 95 | * @attr ref android.android.R.styleable#AutoCompleteTextView_dropDownAnchor 96 | * @attr ref android.android.R.styleable#AutoCompleteTextView_dropDownWidth 97 | * @attr ref android.android.R.styleable#AutoCompleteTextView_dropDownHeight 98 | * @attr ref 99 | * android.android.R.styleable#AutoCompleteTextView_dropDownVerticalOffset 100 | * @attr ref 101 | * android.android.R.styleable#AutoCompleteTextView_dropDownHorizontalOffset 102 | */ 103 | abstract class AbsHistoryEditText extends EditText { 104 | static final boolean DEBUG = false; 105 | static final String TAG = "AutoCompleteTextView"; 106 | 107 | private static final int HINT_VIEW_ID = 0x17; 108 | 109 | /** 110 | * This value controls the length of time that the user must leave a pointer 111 | * down without scrolling to expand the autocomplete dropdown list to cover 112 | * the IME. 113 | */ 114 | private static final int EXPAND_LIST_TIMEOUT = 250; 115 | 116 | private CharSequence mHintText; 117 | private int mHintResource; 118 | 119 | // Zenlibs: this was mAdapter, renamed to mUserAdapter 120 | private ListAdapter mUserAdapter; 121 | private Filter mFilter; 122 | 123 | private FroyoPopupWindow mPopup; 124 | private DropDownListView mDropDownList; 125 | private int mDropDownVerticalOffset; 126 | private int mDropDownHorizontalOffset; 127 | private int mDropDownAnchorId; 128 | private View mDropDownAnchorView; // view is retrieved lazily from id once needed 129 | private int mDropDownWidth; 130 | private int mDropDownHeight; 131 | private final Rect mTempRect = new Rect(); 132 | 133 | private Drawable mDropDownListHighlight; 134 | 135 | private AdapterView.OnItemClickListener mItemClickListener; 136 | private AdapterView.OnItemSelectedListener mItemSelectedListener; 137 | 138 | private final DropDownItemClickListener mDropDownItemClickListener = new DropDownItemClickListener(); 139 | 140 | private boolean mDropDownDismissedOnCompletion = true; 141 | 142 | private boolean mForceIgnoreOutsideTouch = false; 143 | 144 | private int mLastKeyCode = KeyEvent.KEYCODE_UNKNOWN; 145 | private boolean mOpenBefore; 146 | 147 | private Validator mValidator = null; 148 | 149 | private boolean mBlockCompletion; 150 | 151 | private ListSelectorHider mHideSelector; 152 | private Runnable mShowDropDownRunnable; 153 | private Runnable mResizePopupRunnable = new ResizePopupRunnable(); 154 | 155 | private PassThroughClickListener mPassThroughClickListener; 156 | private PopupDataSetObserver mObserver; 157 | private InputMethodManager mImm; 158 | // Zenlibs 159 | private ListAdapter mCombinedAdapter; 160 | private int mThreshold = 1; 161 | 162 | public AbsHistoryEditText(Context context) { 163 | this(context, null); 164 | } 165 | 166 | public AbsHistoryEditText(Context context, AttributeSet attrs) { 167 | this(context, attrs, android.R.attr.autoCompleteTextViewStyle); 168 | } 169 | 170 | public AbsHistoryEditText(Context context, AttributeSet attrs, int defStyle) { 171 | super(context, attrs, defStyle); 172 | 173 | mImm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 174 | 175 | mPopup = new FroyoPopupWindow(context, attrs, android.R.attr.autoCompleteTextViewStyle); 176 | mPopup.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); 177 | 178 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HistoryEditText, defStyle, 179 | R.style.Widget_HistoryEditText); 180 | 181 | mThreshold = a.getInt(R.styleable.HistoryEditText_android_completionThreshold, 2); 182 | 183 | mHintText = a.getText(R.styleable.HistoryEditText_android_completionHint); 184 | 185 | mDropDownListHighlight = a.getDrawable(R.styleable.HistoryEditText_android_dropDownSelector); 186 | mDropDownVerticalOffset = (int) a 187 | .getDimension(R.styleable.HistoryEditText_android_dropDownVerticalOffset, 0.0f); 188 | mDropDownHorizontalOffset = (int) a.getDimension(R.styleable.HistoryEditText_android_dropDownHorizontalOffset, 189 | 0.0f); 190 | 191 | // Get the anchor's id now, but the view won't be ready, so wait to actually get the 192 | // view and store it in mDropDownAnchorView lazily in getDropDownAnchorView later. 193 | // Defaults to NO_ID, in which case the getDropDownAnchorView method will simply return 194 | // this TextView, as a default anchoring point. 195 | mDropDownAnchorId = a.getResourceId(R.styleable.HistoryEditText_android_dropDownAnchor, View.NO_ID); 196 | 197 | // For dropdown width, the developer can specify a specific width, or MATCH_PARENT 198 | // (for full screen width) or WRAP_CONTENT (to match the width of the anchored view). 199 | mDropDownWidth = a.getLayoutDimension(R.styleable.HistoryEditText_android_dropDownWidth, 200 | ViewGroup.LayoutParams.WRAP_CONTENT); 201 | mDropDownHeight = a.getLayoutDimension(R.styleable.HistoryEditText_android_dropDownHeight, 202 | ViewGroup.LayoutParams.WRAP_CONTENT); 203 | 204 | mHintResource = a.getResourceId(R.styleable.HistoryEditText_android_completionHintView, 205 | R.layout.het__simple_dropdown_hint); 206 | 207 | // Always turn on the auto complete input type flag, since it 208 | // makes no sense to use this widget without it. 209 | int inputType = getInputType(); 210 | if ((inputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) { 211 | inputType |= EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE; 212 | setRawInputType(inputType); 213 | } 214 | 215 | a.recycle(); 216 | 217 | setFocusable(true); 218 | 219 | addTextChangedListener(new MyWatcher()); 220 | 221 | mPassThroughClickListener = new PassThroughClickListener(); 222 | super.setOnClickListener(mPassThroughClickListener); 223 | } 224 | 225 | @Override 226 | public void setOnClickListener(OnClickListener listener) { 227 | mPassThroughClickListener.mWrapped = listener; 228 | } 229 | 230 | /** 231 | * Private hook into the on click event, dispatched from 232 | * {@link PassThroughClickListener} 233 | */ 234 | private void onClickImpl() { 235 | // If the dropdown is showing, bring the keyboard to the front 236 | // when the user touches the text field. 237 | if (mPopup.isShowing()) { 238 | ensureImeVisible(true); 239 | } 240 | } 241 | 242 | /** 243 | *
244 | * Sets the optional hint text that is displayed at the bottom of the the 245 | * matching list. This can be used as a cue to the user on how to best use 246 | * the list, or to provide extra information. 247 | *
248 | * 249 | * @param hint 250 | * the text to be displayed to the user 251 | * 252 | * @attr ref android.android.R.styleable#AutoCompleteTextView_completionHint 253 | */ 254 | public void setCompletionHint(CharSequence hint) { 255 | mHintText = hint; 256 | } 257 | 258 | /** 259 | *260 | * Returns the current width for the auto-complete drop down list. This can 261 | * be a fixed width, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill 262 | * the screen, or {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the 263 | * width of its anchor view. 264 | *
265 | * 266 | * @return the width for the drop down list 267 | * 268 | * @attr ref android.android.R.styleable#AutoCompleteTextView_dropDownWidth 269 | */ 270 | public int getDropDownWidth() { 271 | return mDropDownWidth; 272 | } 273 | 274 | /** 275 | *276 | * Sets the current width for the auto-complete drop down list. This can be 277 | * a fixed width, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill the 278 | * screen, or {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the width 279 | * of its anchor view. 280 | *
281 | * 282 | * @param width 283 | * the width to use 284 | * 285 | * @attr ref android.android.R.styleable#AutoCompleteTextView_dropDownWidth 286 | */ 287 | public void setDropDownWidth(int width) { 288 | mDropDownWidth = width; 289 | } 290 | 291 | /** 292 | *293 | * Returns the current height for the auto-complete drop down list. This can 294 | * be a fixed height, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill 295 | * the screen, or {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the 296 | * height of the drop down's content. 297 | *
298 | * 299 | * @return the height for the drop down list 300 | * 301 | * @attr ref android.android.R.styleable#AutoCompleteTextView_dropDownHeight 302 | */ 303 | public int getDropDownHeight() { 304 | return mDropDownHeight; 305 | } 306 | 307 | /** 308 | *309 | * Sets the current height for the auto-complete drop down list. This can be 310 | * a fixed height, or {@link ViewGroup.LayoutParams#MATCH_PARENT} to fill 311 | * the screen, or {@link ViewGroup.LayoutParams#WRAP_CONTENT} to fit the 312 | * height of the drop down's content. 313 | *
314 | * 315 | * @param height 316 | * the height to use 317 | * 318 | * @attr ref android.android.R.styleable#AutoCompleteTextView_dropDownHeight 319 | */ 320 | public void setDropDownHeight(int height) { 321 | mDropDownHeight = height; 322 | } 323 | 324 | /** 325 | *326 | * Returns the id for the view that the auto-complete drop down list is 327 | * anchored to. 328 | *
329 | * 330 | * @return the view's id, or {@link View#NO_ID} if none specified 331 | * 332 | * @attr ref android.android.R.styleable#AutoCompleteTextView_dropDownAnchor 333 | */ 334 | public int getDropDownAnchor() { 335 | return mDropDownAnchorId; 336 | } 337 | 338 | /** 339 | *340 | * Sets the view to which the auto-complete drop down list should anchor. 341 | * The view corresponding to this id will not be loaded until the next time 342 | * it is needed to avoid loading a view which is not yet instantiated. 343 | *
344 | * 345 | * @param id 346 | * the id to anchor the drop down list view to 347 | * 348 | * @attr ref android.android.R.styleable#AutoCompleteTextView_dropDownAnchor 349 | */ 350 | public void setDropDownAnchor(int id) { 351 | mDropDownAnchorId = id; 352 | mDropDownAnchorView = null; 353 | } 354 | 355 | /** 356 | *357 | * Gets the background of the auto-complete drop-down list. 358 | *
359 | * 360 | * @return the background drawable 361 | * 362 | * @attr ref android.android.R.styleable#PopupWindow_popupBackground 363 | */ 364 | public Drawable getDropDownBackground() { 365 | return mPopup.getBackground(); 366 | } 367 | 368 | /** 369 | *370 | * Sets the background of the auto-complete drop-down list. 371 | *
372 | * 373 | * @param d 374 | * the drawable to set as the background 375 | * 376 | * @attr ref android.android.R.styleable#PopupWindow_popupBackground 377 | */ 378 | public void setDropDownBackgroundDrawable(Drawable d) { 379 | mPopup.setBackgroundDrawable(d); 380 | } 381 | 382 | /** 383 | *384 | * Sets the background of the auto-complete drop-down list. 385 | *
386 | * 387 | * @param id 388 | * the id of the drawable to set as the background 389 | * 390 | * @attr ref android.android.R.styleable#PopupWindow_popupBackground 391 | */ 392 | public void setDropDownBackgroundResource(int id) { 393 | mPopup.setBackgroundDrawable(getResources().getDrawable(id)); 394 | } 395 | 396 | /** 397 | *398 | * Sets the vertical offset used for the auto-complete drop-down list. 399 | *
400 | * 401 | * @param offset 402 | * the vertical offset 403 | */ 404 | public void setDropDownVerticalOffset(int offset) { 405 | mDropDownVerticalOffset = offset; 406 | } 407 | 408 | /** 409 | *410 | * Gets the vertical offset used for the auto-complete drop-down list. 411 | *
412 | * 413 | * @return the vertical offset 414 | */ 415 | public int getDropDownVerticalOffset() { 416 | return mDropDownVerticalOffset; 417 | } 418 | 419 | /** 420 | *421 | * Sets the horizontal offset used for the auto-complete drop-down list. 422 | *
423 | * 424 | * @param offset 425 | * the horizontal offset 426 | */ 427 | public void setDropDownHorizontalOffset(int offset) { 428 | mDropDownHorizontalOffset = offset; 429 | } 430 | 431 | /** 432 | *433 | * Gets the horizontal offset used for the auto-complete drop-down list. 434 | *
435 | * 436 | * @return the horizontal offset 437 | */ 438 | public int getDropDownHorizontalOffset() { 439 | return mDropDownHorizontalOffset; 440 | } 441 | 442 | /** 443 | *444 | * Sets the animation style of the auto-complete drop-down list. 445 | *
446 | * 447 | *448 | * If the drop-down is showing, calling this method will take effect only 449 | * the next time the drop-down is shown. 450 | *
451 | * 452 | * @param animationStyle 453 | * animation style to use when the drop-down appears and 454 | * disappears. Set to -1 for the default animation, 0 for no 455 | * animation, or a resource identifier for an explicit animation. 456 | * 457 | * @hide Pending API council approval 458 | */ 459 | public void setDropDownAnimationStyle(int animationStyle) { 460 | mPopup.setAnimationStyle(animationStyle); 461 | } 462 | 463 | /** 464 | *465 | * Returns the animation style that is used when the drop-down list appears 466 | * and disappears 467 | *
468 | * 469 | * @return the animation style that is used when the drop-down list appears 470 | * and disappears 471 | * 472 | * @hide Pending API council approval 473 | */ 474 | public int getDropDownAnimationStyle() { 475 | return mPopup.getAnimationStyle(); 476 | } 477 | 478 | /** 479 | * Checks whether the drop-down is dismissed when a suggestion is clicked. 480 | * 481 | * @hide Pending API council approval 482 | */ 483 | public boolean isDropDownDismissedOnCompletion() { 484 | return mDropDownDismissedOnCompletion; 485 | } 486 | 487 | /** 488 | * Sets whether the drop-down is dismissed when a suggestion is clicked. 489 | * This is true by default. 490 | * 491 | * @param dropDownDismissedOnCompletion 492 | * Whether to dismiss the drop-down. 493 | * 494 | * @hide Pending API council approval 495 | */ 496 | public void setDropDownDismissedOnCompletion(boolean dropDownDismissedOnCompletion) { 497 | mDropDownDismissedOnCompletion = dropDownDismissedOnCompletion; 498 | } 499 | 500 | /** 501 | *502 | * Returns the number of characters the user must type before the drop down 503 | * list is shown. 504 | *
505 | * 506 | * @return the minimum number of characters to type to show the drop down 507 | * 508 | * @see #setThreshold(int) 509 | */ 510 | public int getThreshold() { 511 | return mThreshold; 512 | } 513 | 514 | /** 515 | *516 | * Specifies the minimum number of characters the user has to type in the 517 | * edit box before the drop down list is shown. 518 | *
519 | * 520 | *
521 | * When threshold
is less than or equals 0, a threshold of 1 is
522 | * applied.
523 | *
541 | * Sets the listener that will be notified when the user clicks an item in 542 | * the drop down list. 543 | *
544 | * 545 | * @param l 546 | * the item click listener 547 | */ 548 | public void setOnItemClickListener(AdapterView.OnItemClickListener l) { 549 | mItemClickListener = l; 550 | } 551 | 552 | /** 553 | *554 | * Sets the listener that will be notified when the user selects an item in 555 | * the drop down list. 556 | *
557 | * 558 | * @param l 559 | * the item selected listener 560 | */ 561 | public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener l) { 562 | mItemSelectedListener = l; 563 | } 564 | 565 | /** 566 | *567 | * Returns the listener that is notified whenever the user clicks an item in 568 | * the drop down list. 569 | *
570 | * 571 | * @return the item click listener 572 | * 573 | * @deprecated Use {@link #getOnItemClickListener()} intead 574 | */ 575 | @Deprecated 576 | public AdapterView.OnItemClickListener getItemClickListener() { 577 | return mItemClickListener; 578 | } 579 | 580 | /** 581 | *582 | * Returns the listener that is notified whenever the user selects an item 583 | * in the drop down list. 584 | *
585 | * 586 | * @return the item selected listener 587 | * 588 | * @deprecated Use {@link #getOnItemSelectedListener()} intead 589 | */ 590 | @Deprecated 591 | public AdapterView.OnItemSelectedListener getItemSelectedListener() { 592 | return mItemSelectedListener; 593 | } 594 | 595 | /** 596 | *597 | * Returns the listener that is notified whenever the user clicks an item in 598 | * the drop down list. 599 | *
600 | * 601 | * @return the item click listener 602 | */ 603 | public AdapterView.OnItemClickListener getOnItemClickListener() { 604 | return mItemClickListener; 605 | } 606 | 607 | /** 608 | *609 | * Returns the listener that is notified whenever the user selects an item 610 | * in the drop down list. 611 | *
612 | * 613 | * @return the item selected listener 614 | */ 615 | public AdapterView.OnItemSelectedListener getOnItemSelectedListener() { 616 | return mItemSelectedListener; 617 | } 618 | 619 | /** 620 | *621 | * Returns a filterable list adapter used for auto completion. 622 | *
623 | * 624 | * @return a data adapter used for auto completion 625 | */ 626 | public ListAdapter getAdapter() { 627 | return mUserAdapter; 628 | } 629 | 630 | /** 631 | *632 | * Changes the list of data used for auto completion. The provided list must 633 | * be a filterable list adapter. 634 | *
635 | * 636 | *637 | * The caller is still responsible for managing any resources used by the 638 | * adapter. Notably, when the AutoCompleteTextView is closed or released, 639 | * the adapter is not notified. A common case is the use of 640 | * {@link android.widget.CursorAdapter}, which contains a 641 | * {@link android.database.Cursor} that must be closed. This can be done 642 | * automatically (see 643 | * {@link android.app.Activity#startManagingCursor(android.database.Cursor) 644 | * startManagingCursor()}), or by manually closing the cursor when the 645 | * AutoCompleteTextView is dismissed. 646 | *
647 | * 648 | * @param adapter 649 | * the adapter holding the auto completion data 650 | * 651 | * @see #getAdapter() 652 | * @see android.widget.Filterable 653 | * @see android.widget.ListAdapter 654 | */ 655 | public873 | * Indicates whether the popup menu is showing. 874 | *
875 | * 876 | * @return true if the popup menu is showing, false otherwise 877 | */ 878 | public boolean isPopupShowing() { 879 | return mPopup.isShowing(); 880 | } 881 | 882 | /** 883 | *884 | * Converts the selected item from the drop down list into a sequence of 885 | * character that can be used in the edit box. 886 | *
887 | * 888 | * @param selectedItem 889 | * the item selected by the user for completion 890 | * 891 | * @return a sequence of characters representing the selected suggestion 892 | */ 893 | protected CharSequence convertSelectionToString(Object selectedItem) { 894 | //905 | * Clear the list selection. This may only be temporary, as user input will 906 | * often bring it back. 907 | */ 908 | public void clearListSelection() { 909 | final DropDownListView list = mDropDownList; 910 | if (list != null) { 911 | // WARNING: Please read the comment where mListSelectionHidden is declared 912 | list.mListSelectionHidden = true; 913 | list.hideSelectorCompat(); 914 | list.requestLayout(); 915 | } 916 | } 917 | 918 | /** 919 | * Set the position of the dropdown view selection. 920 | * 921 | * @param position 922 | * The position to move the selector to. 923 | */ 924 | public void setListSelection(int position) { 925 | if (mPopup.isShowing() && (mDropDownList != null)) { 926 | mDropDownList.mListSelectionHidden = false; 927 | mDropDownList.setSelection(position); 928 | // ListView.setSelection() will call requestLayout() 929 | } 930 | } 931 | 932 | /** 933 | * Get the position of the dropdown view selection, if there is one. Returns 934 | * {@link ListView#INVALID_POSITION ListView.INVALID_POSITION} if there is 935 | * no dropdown or if there is no selection. 936 | * 937 | * @return the position of the current selection, if there is one, or 938 | * {@link ListView#INVALID_POSITION ListView.INVALID_POSITION} if 939 | * not. 940 | * 941 | * @see ListView#getSelectedItemPosition() 942 | */ 943 | public int getListSelection() { 944 | if (mPopup.isShowing() && (mDropDownList != null)) { 945 | return mDropDownList.getSelectedItemPosition(); 946 | } 947 | return ListView.INVALID_POSITION; 948 | } 949 | 950 | /** 951 | *
952 | * Starts filtering the content of the drop down list. The filtering pattern
953 | * is the content of the edit box. Subclasses should override this method to
954 | * filter with a different pattern, for instance a substring of
955 | * text
.
956 | *
984 | * Performs the text completion by converting the selected item from the 985 | * drop down list into a string, replacing the text box's content with this 986 | * string and finally dismissing the drop down menu. 987 | *
988 | */ 989 | public void performCompletion() { 990 | performCompletion(null, -1, -1); 991 | } 992 | 993 | @Override 994 | public void onCommitCompletion(CompletionInfo completion) { 995 | if (isPopupShowing()) { 996 | mBlockCompletion = true; 997 | replaceText(completion.getText()); 998 | mBlockCompletion = false; 999 | 1000 | if (mItemClickListener != null) { 1001 | final DropDownListView list = mDropDownList; 1002 | // Note that we don't have a View here, so we will need to 1003 | // supply null. Hopefully no existing apps crash... 1004 | mItemClickListener.onItemClick(list, null, completion.getPosition(), completion.getId()); 1005 | } 1006 | } 1007 | } 1008 | 1009 | private void performCompletion(View selectedView, int position, long id) { 1010 | if (isPopupShowing()) { 1011 | Object selectedItem; 1012 | if (position < 0) { 1013 | selectedItem = mDropDownList.getSelectedItem(); 1014 | } else { 1015 | selectedItem = mCombinedAdapter.getItem(position); 1016 | } 1017 | if (selectedItem == null) { 1018 | Log.w(TAG, "performCompletion: no selected item"); 1019 | return; 1020 | } 1021 | 1022 | mBlockCompletion = true; 1023 | replaceText(convertSelectionToString(selectedItem)); 1024 | mBlockCompletion = false; 1025 | 1026 | if (mItemClickListener != null) { 1027 | final DropDownListView list = mDropDownList; 1028 | 1029 | if (selectedView == null || position < 0) { 1030 | selectedView = list.getSelectedView(); 1031 | position = list.getSelectedItemPosition(); 1032 | id = list.getSelectedItemId(); 1033 | } 1034 | mItemClickListener.onItemClick(list, selectedView, position, id); 1035 | } 1036 | } 1037 | 1038 | if (mDropDownDismissedOnCompletion && true) { 1039 | dismissDropDown(); 1040 | } 1041 | } 1042 | 1043 | /** 1044 | * Identifies whether the view is currently performing a text completion, so 1045 | * subclasses can decide whether to respond to text changed events. 1046 | */ 1047 | public boolean isPerformingCompletion() { 1048 | return mBlockCompletion; 1049 | } 1050 | 1051 | /** 1052 | * Like {@link #setText(CharSequence)}, except that it can disable 1053 | * filtering. 1054 | * 1055 | * @param filter 1056 | * Iffalse
, no filtering will be performed as a
1057 | * result of this call.
1058 | *
1059 | * @hide Pending API council approval.
1060 | */
1061 | public void setText(CharSequence text, boolean filter) {
1062 | if (filter) {
1063 | setText(text);
1064 | } else {
1065 | mBlockCompletion = true;
1066 | setText(text);
1067 | mBlockCompletion = false;
1068 | }
1069 | }
1070 |
1071 | /**
1072 | * 1073 | * Performs the text completion by replacing the current text by the 1074 | * selected item. Subclasses should override this method to avoid replacing 1075 | * the whole content of the edit box. 1076 | *
1077 | * 1078 | * @param text 1079 | * the selected suggestion in the drop down list 1080 | */ 1081 | protected void replaceText(CharSequence text) { 1082 | clearComposingText(); 1083 | 1084 | setText(text); 1085 | // make sure we keep the caret at the end of the text view 1086 | Editable spannable = getText(); 1087 | Selection.setSelection(spannable, spannable.length()); 1088 | } 1089 | 1090 | private void updateDropDownForFilter(int count, boolean force) { 1091 | // Not attached to window, don't update drop-down 1092 | if (getWindowVisibility() == View.GONE) 1093 | return; 1094 | 1095 | /* 1096 | * This checks enoughToFilter() again because filtering requests 1097 | * are asynchronous, so the result may come back after enough text 1098 | * has since been deleted to make it no longer appropriate 1099 | * to filter. 1100 | */ 1101 | 1102 | if (count > 0 || force) { 1103 | if (hasFocus() && hasWindowFocus()) { 1104 | showDropDown(); 1105 | } 1106 | } else { 1107 | dismissDropDown(); 1108 | } 1109 | } 1110 | 1111 | @Override 1112 | public void onWindowFocusChanged(boolean hasWindowFocus) { 1113 | super.onWindowFocusChanged(hasWindowFocus); 1114 | if (!hasWindowFocus) { 1115 | dismissDropDown(); 1116 | } 1117 | } 1118 | 1119 | @Override 1120 | protected void onDisplayHint(int hint) { 1121 | super.onDisplayHint(hint); 1122 | switch (hint) { 1123 | case INVISIBLE: 1124 | if (true) { 1125 | dismissDropDown(); 1126 | } 1127 | break; 1128 | } 1129 | } 1130 | 1131 | @Override 1132 | protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { 1133 | super.onFocusChanged(focused, direction, previouslyFocusedRect); 1134 | // Perform validation if the view is losing focus. 1135 | if (!focused) { 1136 | performValidation(); 1137 | dismissDropDown(); 1138 | } else { 1139 | // Zenlibs 1140 | showDropDown(); 1141 | } 1142 | } 1143 | 1144 | @Override 1145 | protected void onAttachedToWindow() { 1146 | super.onAttachedToWindow(); 1147 | } 1148 | 1149 | @Override 1150 | protected void onDetachedFromWindow() { 1151 | dismissDropDown(); 1152 | super.onDetachedFromWindow(); 1153 | } 1154 | 1155 | /** 1156 | *1157 | * Closes the drop down if present on screen. 1158 | *
1159 | */ 1160 | public void dismissDropDown() { 1161 | if (mImm != null) { 1162 | mImm.displayCompletions(this, null); 1163 | } 1164 | mPopup.dismiss(); 1165 | mPopup.setContentView(null); 1166 | mDropDownList = null; 1167 | } 1168 | 1169 | @Override 1170 | protected boolean setFrame(final int l, int t, final int r, int b) { 1171 | boolean result = super.setFrame(l, t, r, b); 1172 | 1173 | if (mPopup.isShowing()) { 1174 | showDropDown(); 1175 | } 1176 | 1177 | return result; 1178 | } 1179 | 1180 | /** 1181 | *1182 | * Used for lazy instantiation of the anchor view from the id we have. If 1183 | * the value of the id is NO_ID or we can't find a view for the given id, we 1184 | * return this TextView as the default anchoring point. 1185 | *
1186 | */ 1187 | private View getDropDownAnchorView() { 1188 | if (mDropDownAnchorView == null && mDropDownAnchorId != View.NO_ID) { 1189 | mDropDownAnchorView = getRootView().findViewById(mDropDownAnchorId); 1190 | } 1191 | return mDropDownAnchorView == null ? this : mDropDownAnchorView; 1192 | } 1193 | 1194 | /** 1195 | * Issues a runnable to show the dropdown as soon as possible. 1196 | * 1197 | * @hide internal used only by SearchDialog 1198 | */ 1199 | public void showDropDownAfterLayout() { 1200 | post(mShowDropDownRunnable); 1201 | } 1202 | 1203 | /** 1204 | * Ensures that the drop down is not obscuring the IME. 1205 | * 1206 | * @param visible 1207 | * whether the ime should be in front. If false, the ime is 1208 | * pushed to the background. 1209 | * @hide internal used only here and SearchDialog 1210 | */ 1211 | public void ensureImeVisible(boolean visible) { 1212 | mPopup.setInputMethodMode(visible ? PopupWindow.INPUT_METHOD_NEEDED : PopupWindow.INPUT_METHOD_NOT_NEEDED); 1213 | showDropDown(); 1214 | } 1215 | 1216 | /** 1217 | * @hide internal used only here and SearchDialog 1218 | */ 1219 | public boolean isInputMethodNotNeeded() { 1220 | return mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED; 1221 | } 1222 | 1223 | /** 1224 | *1225 | * Displays the drop down on screen. 1226 | *
1227 | */ 1228 | public void showDropDown() { 1229 | int height = buildDropDown(); 1230 | 1231 | int widthSpec = 0; 1232 | int heightSpec = 0; 1233 | 1234 | boolean noInputMethod = isInputMethodNotNeeded(); 1235 | 1236 | if (mPopup.isShowing()) { 1237 | if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) { 1238 | // The call to PopupWindow's update method below can accept -1 for any 1239 | // value you do not want to update. 1240 | widthSpec = -1; 1241 | } else if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) { 1242 | widthSpec = getDropDownAnchorView().getWidth(); 1243 | } else { 1244 | widthSpec = mDropDownWidth; 1245 | } 1246 | 1247 | if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) { 1248 | // The call to PopupWindow's update method below can accept -1 for any 1249 | // value you do not want to update. 1250 | heightSpec = noInputMethod ? height : ViewGroup.LayoutParams.MATCH_PARENT; 1251 | if (noInputMethod) { 1252 | mPopup.setWindowLayoutMode( 1253 | mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ? ViewGroup.LayoutParams.MATCH_PARENT 1254 | : 0, 0); 1255 | } else { 1256 | mPopup.setWindowLayoutMode( 1257 | mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT ? ViewGroup.LayoutParams.MATCH_PARENT 1258 | : 0, ViewGroup.LayoutParams.MATCH_PARENT); 1259 | } 1260 | } else if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) { 1261 | heightSpec = height; 1262 | } else { 1263 | heightSpec = mDropDownHeight; 1264 | } 1265 | 1266 | mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && true); 1267 | 1268 | mPopup.update(getDropDownAnchorView(), mDropDownHorizontalOffset, mDropDownVerticalOffset, widthSpec, 1269 | heightSpec); 1270 | } else { 1271 | if (mDropDownWidth == ViewGroup.LayoutParams.MATCH_PARENT) { 1272 | widthSpec = ViewGroup.LayoutParams.MATCH_PARENT; 1273 | } else { 1274 | if (mDropDownWidth == ViewGroup.LayoutParams.WRAP_CONTENT) { 1275 | mPopup.setWidth(getDropDownAnchorView().getWidth()); 1276 | } else { 1277 | mPopup.setWidth(mDropDownWidth); 1278 | } 1279 | } 1280 | 1281 | if (mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) { 1282 | heightSpec = ViewGroup.LayoutParams.MATCH_PARENT; 1283 | } else { 1284 | if (mDropDownHeight == ViewGroup.LayoutParams.WRAP_CONTENT) { 1285 | mPopup.setHeight(height); 1286 | } else { 1287 | mPopup.setHeight(mDropDownHeight); 1288 | } 1289 | } 1290 | 1291 | mPopup.setWindowLayoutMode(widthSpec, heightSpec); 1292 | mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); 1293 | 1294 | // use outside touchable to dismiss drop down when touching outside of it, so 1295 | // only set this if the dropdown is not always visible 1296 | mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && true); 1297 | mPopup.setTouchInterceptor(new PopupTouchInterceptor()); 1298 | mPopup.showAsDropDown(getDropDownAnchorView(), mDropDownHorizontalOffset, mDropDownVerticalOffset); 1299 | mDropDownList.setSelection(ListView.INVALID_POSITION); 1300 | clearListSelection(); 1301 | post(mHideSelector); 1302 | } 1303 | } 1304 | 1305 | /** 1306 | * Forces outside touches to be ignored. Normally if 1307 | * {@link #isDropDownAlwaysVisible()} is false, we allow outside touch to 1308 | * dismiss the dropdown. If this is set to true, then we ignore outside 1309 | * touch even when the drop down is not set to always visible. 1310 | * 1311 | * @hide used only by SearchDialog 1312 | */ 1313 | public void setForceIgnoreOutsideTouch(boolean forceIgnoreOutsideTouch) { 1314 | mForceIgnoreOutsideTouch = forceIgnoreOutsideTouch; 1315 | } 1316 | 1317 | /** 1318 | *1319 | * Builds the popup window's content and returns the height the popup should 1320 | * have. Returns -1 when the content already exists. 1321 | *
1322 | * 1323 | * @return the content's height or -1 if content already exists 1324 | */ 1325 | private int buildDropDown() { 1326 | ViewGroup dropDownView; 1327 | int otherHeights = 0; 1328 | 1329 | rebuildCombinedAdapter(); 1330 | 1331 | if (mCombinedAdapter != null) { 1332 | if (mImm != null) { 1333 | final int count = Math.min(mCombinedAdapter.getCount(), 20); 1334 | CompletionInfo[] completions = new CompletionInfo[count]; 1335 | int realCount = 0; 1336 | 1337 | for (int i = 0; i < count; i++) { 1338 | if (mCombinedAdapter.isEnabled(i)) { 1339 | realCount++; 1340 | Object item = mCombinedAdapter.getItem(i); 1341 | long id = mCombinedAdapter.getItemId(i); 1342 | completions[i] = new CompletionInfo(id, i, convertSelectionToString(item)); 1343 | } 1344 | } 1345 | 1346 | if (realCount != count) { 1347 | CompletionInfo[] tmp = new CompletionInfo[realCount]; 1348 | System.arraycopy(completions, 0, tmp, 0, realCount); 1349 | completions = tmp; 1350 | } 1351 | 1352 | mImm.displayCompletions(this, completions); 1353 | } 1354 | } 1355 | 1356 | if (mDropDownList == null) { 1357 | Context context = getContext(); 1358 | 1359 | mHideSelector = new ListSelectorHider(); 1360 | 1361 | /** 1362 | * This Runnable exists for the sole purpose of checking if the view 1363 | * layout has got completed and if so call showDropDown to display 1364 | * the drop down. This is used to show the drop down as soon as 1365 | * possible after user opens up the search dialog, without waiting 1366 | * for the normal UI pipeline to do it's job which is slower than 1367 | * this method. 1368 | */ 1369 | mShowDropDownRunnable = new Runnable() { 1370 | public void run() { 1371 | // View layout should be all done before displaying the drop down. 1372 | View view = getDropDownAnchorView(); 1373 | if (view != null && view.getWindowToken() != null) { 1374 | showDropDown(); 1375 | } 1376 | } 1377 | }; 1378 | 1379 | mDropDownList = new DropDownListView(context); 1380 | mDropDownList.setSelector(mDropDownListHighlight); 1381 | mDropDownList.setAdapter(mCombinedAdapter); 1382 | mDropDownList.setVerticalFadingEdgeEnabled(true); 1383 | mDropDownList.setOnItemClickListener(mDropDownItemClickListener); 1384 | mDropDownList.setFocusable(true); 1385 | mDropDownList.setFocusableInTouchMode(true); 1386 | mDropDownList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { 1387 | public void onItemSelected(AdapterView> parent, View view, int position, long id) { 1388 | 1389 | if (position != -1) { 1390 | DropDownListView dropDownList = mDropDownList; 1391 | 1392 | if (dropDownList != null) { 1393 | dropDownList.mListSelectionHidden = false; 1394 | } 1395 | } 1396 | } 1397 | 1398 | public void onNothingSelected(AdapterView> parent) { 1399 | } 1400 | }); 1401 | mDropDownList.setOnScrollListener(new PopupScrollListener()); 1402 | 1403 | if (mItemSelectedListener != null) { 1404 | mDropDownList.setOnItemSelectedListener(mItemSelectedListener); 1405 | } 1406 | 1407 | dropDownView = mDropDownList; 1408 | 1409 | View hintView = getHintView(context); 1410 | if (hintView != null) { 1411 | // if an hint has been specified, we accomodate more space for it and 1412 | // add a text view in the drop down menu, at the bottom of the list 1413 | LinearLayout hintContainer = new LinearLayout(context); 1414 | hintContainer.setOrientation(LinearLayout.VERTICAL); 1415 | 1416 | LinearLayout.LayoutParams hintParams = new LinearLayout.LayoutParams( 1417 | ViewGroup.LayoutParams.MATCH_PARENT, 0, 1.0f); 1418 | hintContainer.addView(dropDownView, hintParams); 1419 | hintContainer.addView(hintView); 1420 | 1421 | // measure the hint's height to find how much more vertical space 1422 | // we need to add to the drop down's height 1423 | int widthSpec = MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST); 1424 | int heightSpec = MeasureSpec.UNSPECIFIED; 1425 | hintView.measure(widthSpec, heightSpec); 1426 | 1427 | hintParams = (LinearLayout.LayoutParams) hintView.getLayoutParams(); 1428 | otherHeights = hintView.getMeasuredHeight() + hintParams.topMargin + hintParams.bottomMargin; 1429 | 1430 | dropDownView = hintContainer; 1431 | } 1432 | 1433 | mPopup.setContentView(dropDownView); 1434 | } else { 1435 | dropDownView = (ViewGroup) mPopup.getContentView(); 1436 | final View view = dropDownView.findViewById(HINT_VIEW_ID); 1437 | if (view != null) { 1438 | LinearLayout.LayoutParams hintParams = (LinearLayout.LayoutParams) view.getLayoutParams(); 1439 | otherHeights = view.getMeasuredHeight() + hintParams.topMargin + hintParams.bottomMargin; 1440 | } 1441 | } 1442 | 1443 | // Max height available on the screen for a popup. 1444 | boolean ignoreBottomDecorations = mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED; 1445 | final int maxHeight = mPopup.getMaxAvailableHeightCompat(getDropDownAnchorView(), mDropDownVerticalOffset, 1446 | ignoreBottomDecorations); 1447 | 1448 | // getMaxAvailableHeight() subtracts the padding, so we put it back, 1449 | // to get the available height for the whole window 1450 | int padding = 0; 1451 | Drawable background = mPopup.getBackground(); 1452 | if (background != null) { 1453 | background.getPadding(mTempRect); 1454 | padding = mTempRect.top + mTempRect.bottom; 1455 | } 1456 | 1457 | if (false || mDropDownHeight == ViewGroup.LayoutParams.MATCH_PARENT) { 1458 | return maxHeight + padding; 1459 | } 1460 | 1461 | final int listContent = mDropDownList.measureHeightOfChildrenCompat(MeasureSpec.UNSPECIFIED, 0, 1462 | FroyoListView.NO_POSITION, maxHeight - otherHeights, 2); 1463 | // add padding only if the list has items in it, that way we don't show 1464 | // the popup if it is not needed 1465 | if (listContent > 0) 1466 | otherHeights += padding; 1467 | 1468 | return listContent + otherHeights; 1469 | } 1470 | 1471 | protected void rebuildCombinedAdapter() { 1472 | ListAdapter adapter = getCombinedAdapter(mUserAdapter); 1473 | setCombinedAdapter(adapter); 1474 | } 1475 | 1476 | /** 1477 | * Returnstrue
if the amount of text in the field meets or
1478 | * exceeds the {@link #getThreshold} requirement. You can override this to
1479 | * impose a different standard for when filtering will be triggered.
1480 | */
1481 | protected boolean enoughToFilter() {
1482 | if (DEBUG)
1483 | Log.v(TAG, "Enough to filter: len=" + getText().length() + " threshold=" + mThreshold);
1484 | return getText().length() >= mThreshold;
1485 | }
1486 |
1487 | // Zenlibs
1488 | protected abstract ListAdapter getCombinedAdapter(ListAdapter userAdapter);
1489 |
1490 | private View getHintView(Context context) {
1491 | if (mHintText != null && mHintText.length() > 0) {
1492 | final TextView hintView = (TextView) LayoutInflater.from(context).inflate(mHintResource, null)
1493 | .findViewById(android.R.id.text1);
1494 | hintView.setText(mHintText);
1495 | hintView.setId(HINT_VIEW_ID);
1496 | return hintView;
1497 | } else {
1498 | return null;
1499 | }
1500 | }
1501 |
1502 | /**
1503 | * Sets the validator used to perform text validation.
1504 | *
1505 | * @param validator
1506 | * The validator used to validate the text entered in this
1507 | * widget.
1508 | *
1509 | * @see #getValidator()
1510 | * @see #performValidation()
1511 | */
1512 | public void setValidator(Validator validator) {
1513 | mValidator = validator;
1514 | }
1515 |
1516 | /**
1517 | * Returns the Validator set with {@link #setValidator}, or
1518 | * null
if it was not set.
1519 | *
1520 | * @see #setValidator(AbsHistoryEditText.Validator)
1521 | * @see #performValidation()
1522 | */
1523 | public Validator getValidator() {
1524 | return mValidator;
1525 | }
1526 |
1527 | /**
1528 | * If a validator was set on this view and the current string is not valid,
1529 | * ask the validator to fix it.
1530 | *
1531 | * @see #getValidator()
1532 | * @see #setValidator(AbsHistoryEditText.Validator)
1533 | */
1534 | public void performValidation() {
1535 | if (mValidator == null)
1536 | return;
1537 |
1538 | CharSequence text = getText();
1539 |
1540 | if (!TextUtils.isEmpty(text) && !mValidator.isValid(text)) {
1541 | setText(mValidator.fixText(text));
1542 | }
1543 | }
1544 |
1545 | /**
1546 | * Returns the Filter obtained from {@link Filterable#getFilter}, or
1547 | * null
if {@link #setAdapter} was not called with a
1548 | * Filterable.
1549 | */
1550 | protected Filter getFilter() {
1551 | return mFilter;
1552 | }
1553 |
1554 | private class ListSelectorHider implements Runnable {
1555 | public void run() {
1556 | clearListSelection();
1557 | }
1558 | }
1559 |
1560 | private class ResizePopupRunnable implements Runnable {
1561 | public void run() {
1562 | mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
1563 | showDropDown();
1564 | }
1565 | }
1566 |
1567 | private class PopupTouchInterceptor implements OnTouchListener {
1568 | public boolean onTouch(View v, MotionEvent event) {
1569 | final int action = event.getAction();
1570 | if (action == MotionEvent.ACTION_DOWN && mPopup != null && mPopup.isShowing()) {
1571 | postDelayed(mResizePopupRunnable, EXPAND_LIST_TIMEOUT);
1572 | } else if (action == MotionEvent.ACTION_UP) {
1573 | removeCallbacks(mResizePopupRunnable);
1574 | }
1575 | return false;
1576 | }
1577 | }
1578 |
1579 | private class PopupScrollListener implements ListView.OnScrollListener {
1580 | public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
1581 |
1582 | }
1583 |
1584 | public void onScrollStateChanged(AbsListView view, int scrollState) {
1585 | if (scrollState == SCROLL_STATE_TOUCH_SCROLL && !isInputMethodNotNeeded()
1586 | && mPopup.getContentView() != null) {
1587 | removeCallbacks(mResizePopupRunnable);
1588 | mResizePopupRunnable.run();
1589 | }
1590 | }
1591 | }
1592 |
1593 | private class DropDownItemClickListener implements AdapterView.OnItemClickListener {
1594 | public void onItemClick(AdapterView parent, View v, int position, long id) {
1595 | performCompletion(v, position, id);
1596 | }
1597 | }
1598 |
1599 | /**
1600 | * 1601 | * Wrapper class for a ListView. This wrapper hijacks the focus to make sure 1602 | * the list uses the appropriate drawables and states when displayed on 1603 | * screen within a drop down. The focus is never actually passed to the drop 1604 | * down; the list only looks focused. 1605 | *
1606 | */ 1607 | private static class DropDownListView extends FroyoListView { 1608 | /* 1609 | * WARNING: This is a workaround for a touch mode issue. 1610 | * 1611 | * Touch mode is propagated lazily to windows. This causes problems in 1612 | * the following scenario: 1613 | * - Type something in the AutoCompleteTextView and get some results 1614 | * - Move down with the d-pad to select an item in the list 1615 | * - Move up with the d-pad until the selection disappears 1616 | * - Type more text in the AutoCompleteTextView *using the soft keyboard* 1617 | * and get new results; you are now in touch mode 1618 | * - The selection comes back on the first item in the list, even though 1619 | * the list is supposed to be in touch mode 1620 | * 1621 | * Using the soft keyboard triggers the touch mode change but that change 1622 | * is propagated to our window only after the first list layout, therefore 1623 | * after the list attempts to resurrect the selection. 1624 | * 1625 | * The trick to work around this issue is to pretend the list is in touch 1626 | * mode when we know that the selection should not appear, that is when 1627 | * we know the user moved the selection away from the list. 1628 | * 1629 | * This boolean is set to true whenever we explicitely hide the list's 1630 | * selection and reset to false whenver we know the user moved the 1631 | * selection back to the list. 1632 | * 1633 | * When this boolean is true, isInTouchMode() returns true, otherwise it 1634 | * returns super.isInTouchMode(). 1635 | */ 1636 | private boolean mListSelectionHidden; 1637 | 1638 | /** 1639 | *1640 | * Creates a new list view wrapper. 1641 | *
1642 | * 1643 | * @param context 1644 | * this view's context 1645 | */ 1646 | public DropDownListView(Context context) { 1647 | super(context, null, android.R.attr.dropDownListViewStyle); 1648 | } 1649 | 1650 | /** 1651 | *1652 | * Avoids jarring scrolling effect by ensuring that list elements made 1653 | * of a text view fit on a single line. 1654 | *
1655 | * 1656 | * @param position 1657 | * the item index in the list to get a view for 1658 | * @return the view for the specified item 1659 | */ 1660 | @Override 1661 | View obtainView(int position, boolean[] isScrap) { 1662 | View view = super.obtainView(position, isScrap); 1663 | 1664 | if (view instanceof TextView) { 1665 | ((TextView) view).setHorizontallyScrolling(true); 1666 | } 1667 | 1668 | return view; 1669 | } 1670 | 1671 | @Override 1672 | public boolean isInTouchMode() { 1673 | // WARNING: Please read the comment where mListSelectionHidden is declared 1674 | return mListSelectionHidden || super.isInTouchMode(); 1675 | } 1676 | 1677 | /** 1678 | *1679 | * Returns the focus state in the drop down. 1680 | *
1681 | * 1682 | * @return true always 1683 | */ 1684 | @Override 1685 | public boolean hasWindowFocus() { 1686 | return true; 1687 | } 1688 | 1689 | /** 1690 | *1691 | * Returns the focus state in the drop down. 1692 | *
1693 | * 1694 | * @return true always 1695 | */ 1696 | @Override 1697 | public boolean isFocused() { 1698 | return true; 1699 | } 1700 | 1701 | /** 1702 | *1703 | * Returns the focus state in the drop down. 1704 | *
1705 | * 1706 | * @return true always 1707 | */ 1708 | @Override 1709 | public boolean hasFocus() { 1710 | return true; 1711 | } 1712 | 1713 | protected int[] onCreateDrawableState(int extraSpace) { 1714 | int[] res = super.onCreateDrawableState(extraSpace); 1715 | //noinspection ConstantIfStatement 1716 | if (false) { 1717 | StringBuilder sb = new StringBuilder("Created drawable state: ["); 1718 | for (int i = 0; i < res.length; i++) { 1719 | if (i > 0) 1720 | sb.append(", "); 1721 | sb.append("0x"); 1722 | sb.append(Integer.toHexString(res[i])); 1723 | } 1724 | sb.append("]"); 1725 | Log.i(TAG, sb.toString()); 1726 | } 1727 | return res; 1728 | } 1729 | } 1730 | 1731 | /** 1732 | * This interface is used to make sure that the text entered in this 1733 | * TextView complies to a certain format. Since there is no foolproof way to 1734 | * prevent the user from leaving this View with an incorrect value in it, 1735 | * all we can do is try to fix it ourselves when this happens. 1736 | */ 1737 | public interface Validator { 1738 | /** 1739 | * Validates the specified text. 1740 | * 1741 | * @return true If the text currently in the text editor is valid. 1742 | * 1743 | * @see #fixText(CharSequence) 1744 | */ 1745 | boolean isValid(CharSequence text); 1746 | 1747 | /** 1748 | * Corrects the specified text to make it valid. 1749 | * 1750 | * @param invalidText 1751 | * A string that doesn't pass validation: 1752 | * isValid(invalidText) returns false 1753 | * 1754 | * @return A string based on invalidText such as invoking isValid() on 1755 | * it returns true. 1756 | * 1757 | * @see #isValid(CharSequence) 1758 | */ 1759 | CharSequence fixText(CharSequence invalidText); 1760 | } 1761 | 1762 | /** 1763 | * Allows us a private hook into the on click event without preventing users 1764 | * from setting their own click listener. 1765 | */ 1766 | private class PassThroughClickListener implements OnClickListener { 1767 | 1768 | private View.OnClickListener mWrapped; 1769 | 1770 | /** {@inheritDoc} */ 1771 | public void onClick(View v) { 1772 | onClickImpl(); 1773 | 1774 | if (mWrapped != null) 1775 | mWrapped.onClick(v); 1776 | } 1777 | } 1778 | 1779 | private class PopupDataSetObserver extends DataSetObserver { 1780 | @Override 1781 | public void onChanged() { 1782 | if (isPopupShowing()) { 1783 | // This will resize the popup to fit the new adapter's content 1784 | showDropDown(); 1785 | } else if (mCombinedAdapter != null) { 1786 | // If the popup is not showing already, showing it will cause 1787 | // the list of data set observers attached to the adapter to 1788 | // change. We can't do it from here, because we are in the middle 1789 | // of iterating throught he list of observers. 1790 | post(new Runnable() { 1791 | public void run() { 1792 | final ListAdapter adapter = mCombinedAdapter; 1793 | if (adapter != null) { 1794 | updateDropDownForFilter(adapter.getCount(), false); 1795 | } 1796 | } 1797 | }); 1798 | } 1799 | } 1800 | 1801 | @Override 1802 | public void onInvalidated() { 1803 | if (true) { 1804 | // There's no data to display so make sure we're not showing 1805 | // the drop down and its list 1806 | dismissDropDown(); 1807 | } 1808 | } 1809 | } 1810 | } -------------------------------------------------------------------------------- /library/src/com/zenlibs/historyedittext/FroyoListView.java: -------------------------------------------------------------------------------- 1 | package com.zenlibs.historyedittext; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.View; 6 | import android.view.ViewDebug; 7 | import android.widget.AbsListView; 8 | import android.widget.ListAdapter; 9 | import android.widget.ListView; 10 | 11 | import java.lang.reflect.Field; 12 | import java.lang.reflect.InvocationTargetException; 13 | import java.lang.reflect.Method; 14 | 15 | public class FroyoListView extends ListView { 16 | /** 17 | * Used to indicate a no preference for a position type. 18 | */ 19 | public static final int NO_POSITION = -1; 20 | private boolean mAreAllItemsSelectable; 21 | private Method mHideSelectorMethod; 22 | private Object mRecycler; 23 | private Method mGetScrapViewMethod; 24 | private Method mAddScrapViewMethod; 25 | private Method mDispatchFinishTemporaryDispatchMethod; 26 | private Method mMeasureHeightOfChildren; 27 | 28 | public FroyoListView(Context context) { 29 | super(context); 30 | init(); 31 | } 32 | 33 | public FroyoListView(Context context, AttributeSet attrs) { 34 | super(context, attrs); 35 | init(); 36 | } 37 | 38 | public FroyoListView(Context context, AttributeSet attrs, int defStyle) { 39 | super(context, attrs, defStyle); 40 | init(); 41 | } 42 | 43 | private void init() { 44 | try { 45 | // This is dangerous, whenever a new version of Android is released we should 46 | // check that this method still exists and that it hasn't 47 | mHideSelectorMethod = AbsListView.class.getDeclaredMethod("hideSelector"); 48 | mHideSelectorMethod.setAccessible(true); 49 | mDispatchFinishTemporaryDispatchMethod = View.class.getDeclaredMethod("dispatchFinishTemporaryDetach"); 50 | mDispatchFinishTemporaryDispatchMethod.setAccessible(true); 51 | mMeasureHeightOfChildren = ListView.class.getDeclaredMethod("measureHeightOfChildren", int.class, int.class, int.class, int.class, int.class); 52 | mMeasureHeightOfChildren.setAccessible(true); 53 | 54 | Field recyclerField = AbsListView.class.getDeclaredField("mRecycler"); 55 | recyclerField.setAccessible(true); 56 | mRecycler = recyclerField.get(this); 57 | Class>[] classes = AbsListView.class.getDeclaredClasses(); 58 | for (Class> class_ : classes) { 59 | if (class_.getSimpleName().equals("RecycleBin")) { 60 | Class> recycleBinClass = class_; 61 | mGetScrapViewMethod = recycleBinClass.getDeclaredMethod("getScrapView", int.class); 62 | mGetScrapViewMethod.setAccessible(true); 63 | mAddScrapViewMethod = recycleBinClass.getDeclaredMethod("addScrapView", View.class, int.class); 64 | mAddScrapViewMethod.setAccessible(true); 65 | break; 66 | } 67 | } 68 | } catch (Exception e) { 69 | e.printStackTrace(); 70 | } 71 | } 72 | 73 | @Override 74 | public void setAdapter(ListAdapter adapter) { 75 | super.setAdapter(adapter); 76 | if (adapter != null) { 77 | mAreAllItemsSelectable = adapter.areAllItemsEnabled(); 78 | } else { 79 | mAreAllItemsSelectable = true; 80 | } 81 | } 82 | 83 | int lookForSelectablePositionCompat(int position, boolean lookDown) { 84 | final ListAdapter adapter = getAdapter(); 85 | if (adapter == null || isInTouchMode()) { 86 | return INVALID_POSITION; 87 | } 88 | 89 | final int count = adapter.getCount(); 90 | if (!mAreAllItemsSelectable) { 91 | if (lookDown) { 92 | position = Math.max(0, position); 93 | while (position < count && !adapter.isEnabled(position)) { 94 | position++; 95 | } 96 | } else { 97 | position = Math.min(position, count - 1); 98 | while (position >= 0 && !adapter.isEnabled(position)) { 99 | position--; 100 | } 101 | } 102 | 103 | if (position < 0 || position >= count) { 104 | return INVALID_POSITION; 105 | } 106 | return position; 107 | } else { 108 | if (position < 0 || position >= count) { 109 | return INVALID_POSITION; 110 | } 111 | return position; 112 | } 113 | } 114 | 115 | void hideSelectorCompat() { 116 | try { 117 | mHideSelectorMethod.invoke(this); 118 | } catch (Exception e) { 119 | e.printStackTrace(); 120 | } 121 | } 122 | 123 | /** 124 | * Get a view and have it show the data associated with the specified 125 | * position. This is called when we have already discovered that the view is 126 | * not available for reuse in the recycle bin. The only choices left are 127 | * converting an old view or making a new one. 128 | * 129 | * @param position The position to display 130 | * @param isScrap Array of at least 1 boolean, the first entry will become true if 131 | * the returned view was taken from the scrap heap, false if otherwise. 132 | * 133 | * @return A view displaying the data associated with the specified position 134 | */ 135 | View obtainView(int position, boolean[] isScrap) { 136 | try { 137 | isScrap[0] = false; 138 | View scrapView; 139 | scrapView = (View) mGetScrapViewMethod.invoke(mRecycler, position); 140 | 141 | View child; 142 | ListAdapter adapter = getAdapter(); 143 | int cacheColorHint = getCacheColorHint(); 144 | if (scrapView != null) { 145 | if (ViewDebug.TRACE_RECYCLER) { 146 | ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.RECYCLE_FROM_SCRAP_HEAP, 147 | position, -1); 148 | } 149 | 150 | child = adapter.getView(position, scrapView, this); 151 | 152 | if (ViewDebug.TRACE_RECYCLER) { 153 | ViewDebug.trace(child, ViewDebug.RecyclerTraceType.BIND_VIEW, 154 | position, getChildCount()); 155 | } 156 | if (child != scrapView) { 157 | mAddScrapViewMethod.invoke(mRecycler, scrapView); 158 | if (cacheColorHint != 0) { 159 | child.setDrawingCacheBackgroundColor(cacheColorHint); 160 | } 161 | if (ViewDebug.TRACE_RECYCLER) { 162 | ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, 163 | position, -1); 164 | } 165 | } else { 166 | isScrap[0] = true; 167 | mDispatchFinishTemporaryDispatchMethod.invoke(child); 168 | } 169 | } else { 170 | child = adapter.getView(position, null, this); 171 | if (cacheColorHint != 0) { 172 | child.setDrawingCacheBackgroundColor(cacheColorHint); 173 | } 174 | if (ViewDebug.TRACE_RECYCLER) { 175 | ViewDebug.trace(child, ViewDebug.RecyclerTraceType.NEW_VIEW, 176 | position, getChildCount()); 177 | } 178 | } 179 | return child; 180 | } catch (InvocationTargetException e) { 181 | e.printStackTrace(); 182 | } catch (IllegalAccessException e) { 183 | e.printStackTrace(); 184 | } 185 | return null; 186 | } 187 | 188 | public final int measureHeightOfChildrenCompat(int widthMeasureSpec, int startPosition, int endPosition, 189 | final int maxHeight, int disallowPartialChildPosition) { 190 | try { 191 | Object result = mMeasureHeightOfChildren.invoke(this, widthMeasureSpec, startPosition, endPosition, 192 | maxHeight, disallowPartialChildPosition); 193 | return (Integer) result; 194 | } catch (IllegalArgumentException e) { 195 | e.printStackTrace(); 196 | } catch (IllegalAccessException e) { 197 | e.printStackTrace(); 198 | } catch (InvocationTargetException e) { 199 | e.printStackTrace(); 200 | } catch (Exception e) { 201 | e.printStackTrace(); 202 | } 203 | 204 | return 0; 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /library/src/com/zenlibs/historyedittext/FroyoPopupWindow.java: -------------------------------------------------------------------------------- 1 | package com.zenlibs.historyedittext; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | import java.lang.reflect.Method; 5 | 6 | import android.content.Context; 7 | import android.util.AttributeSet; 8 | import android.view.View; 9 | import android.widget.PopupWindow; 10 | 11 | public class FroyoPopupWindow extends PopupWindow { 12 | 13 | 14 | 15 | private Method mGetMaxAvailableHeightMethod; 16 | 17 | public FroyoPopupWindow() { 18 | super(); 19 | init(); 20 | } 21 | 22 | public FroyoPopupWindow(Context context, AttributeSet attrs, int defStyle) { 23 | super(context, attrs, defStyle); 24 | init(); 25 | } 26 | 27 | public FroyoPopupWindow(Context context, AttributeSet attrs) { 28 | super(context, attrs); 29 | init(); 30 | } 31 | 32 | public FroyoPopupWindow(Context context) { 33 | super(context); 34 | init(); 35 | } 36 | 37 | public FroyoPopupWindow(int width, int height) { 38 | super(width, height); 39 | init(); 40 | } 41 | 42 | public FroyoPopupWindow(View contentView, int width, int height, boolean focusable) { 43 | super(contentView, width, height, focusable); 44 | init(); 45 | } 46 | 47 | public FroyoPopupWindow(View contentView, int width, int height) { 48 | super(contentView, width, height); 49 | init(); 50 | } 51 | 52 | public FroyoPopupWindow(View contentView) { 53 | super(contentView); 54 | init(); 55 | } 56 | 57 | private void init() { 58 | try { 59 | mGetMaxAvailableHeightMethod = getClass().getMethod("getMaxAvailableHeight", View.class, int.class, boolean.class); 60 | } catch (NoSuchMethodException e) { 61 | e.printStackTrace(); 62 | } 63 | } 64 | 65 | public int getMaxAvailableHeightCompat(View anchor, int yOffset, boolean ignoreBottomDecorations) { 66 | try { 67 | return (Integer) mGetMaxAvailableHeightMethod.invoke(this, anchor, yOffset, ignoreBottomDecorations); 68 | } catch (IllegalArgumentException e) { 69 | e.printStackTrace(); 70 | } catch (IllegalAccessException e) { 71 | e.printStackTrace(); 72 | } catch (InvocationTargetException e) { 73 | e.printStackTrace(); 74 | } catch (Exception e) { 75 | e.printStackTrace(); 76 | } 77 | 78 | return 0; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /library/src/com/zenlibs/historyedittext/HistoryDb.java: -------------------------------------------------------------------------------- 1 | package com.zenlibs.historyedittext; 2 | 3 | import android.content.ContentValues; 4 | import android.content.Context; 5 | import android.database.Cursor; 6 | import android.database.sqlite.SQLiteDatabase; 7 | import android.database.sqlite.SQLiteOpenHelper; 8 | 9 | class HistoryDb { 10 | private static final String HISTORY_TABLE = "history"; 11 | private static final String COLUMN_ID = "_id"; 12 | private static final String COLUMN_TAG = "tag"; 13 | private static final String COLUMN_TEXT = "text"; 14 | private static final String COLUMN_TIME = "time"; 15 | private static final String[] QUERY_BY_TAG_COLUMNS = new String[] { COLUMN_TEXT }; 16 | private static final String QUERY_BY_TAG_SELECTION = COLUMN_TAG + "=?"; 17 | private static final String QUERY_BY_TAG_ORDER = COLUMN_TIME + " DESC"; 18 | private static final String QUERY_BY_TAG_TEXT_SELECTION = COLUMN_TAG + "=? AND " + COLUMN_TEXT + "=?"; 19 | private static HistoryDbSQLiteHelper mHelper; 20 | 21 | public static void clear(SQLiteDatabase db) { 22 | db.delete(HISTORY_TABLE, null, null); 23 | } 24 | 25 | public static void insertEntry(SQLiteDatabase db, String tag, String text) { 26 | if (text != null) { 27 | text = text.trim(); 28 | 29 | ContentValues values = new ContentValues(); 30 | values.put(COLUMN_TAG, tag); 31 | values.put(COLUMN_TEXT, text); 32 | values.put(COLUMN_TIME, System.currentTimeMillis()); 33 | 34 | // Test if it already exists 35 | Cursor c = queryByTagText(db, tag, text); 36 | if (c.getCount() > 0) { 37 | // It's an update 38 | db.update(HISTORY_TABLE, values, QUERY_BY_TAG_TEXT_SELECTION, new String[] { tag, text }); 39 | } else { 40 | // It's an insert 41 | db.insert(HISTORY_TABLE, null, values); 42 | } 43 | } 44 | } 45 | 46 | public static Cursor queryByTag(SQLiteDatabase db, String tag) { 47 | return db.query(HISTORY_TABLE, QUERY_BY_TAG_COLUMNS, QUERY_BY_TAG_SELECTION, new String[] { tag }, null, null, 48 | QUERY_BY_TAG_ORDER); 49 | } 50 | 51 | private static Cursor queryByTagText(SQLiteDatabase db, String tag, String text) { 52 | return db.query(HISTORY_TABLE, QUERY_BY_TAG_COLUMNS, QUERY_BY_TAG_TEXT_SELECTION, new String[] { tag, text }, 53 | null, null, QUERY_BY_TAG_ORDER); 54 | } 55 | 56 | static SQLiteDatabase getReadable(Context context) { 57 | HistoryDbSQLiteHelper helper = getHelper(context); 58 | return helper.getReadableDatabase(); 59 | } 60 | 61 | static SQLiteDatabase getWritable(Context context) { 62 | HistoryDbSQLiteHelper helper = getHelper(context); 63 | return helper.getWritableDatabase(); 64 | } 65 | 66 | private static HistoryDbSQLiteHelper getHelper(Context context) { 67 | if (mHelper == null) { 68 | mHelper = new HistoryDbSQLiteHelper(context); 69 | } 70 | return mHelper; 71 | } 72 | 73 | static class HistoryDbSQLiteHelper extends SQLiteOpenHelper { 74 | 75 | private static final String DATABASE_NAME = "historyedittext.db"; 76 | private static final int DATABASE_VERSION = 1; 77 | 78 | public HistoryDbSQLiteHelper(Context context) { 79 | super(context, DATABASE_NAME, null, DATABASE_VERSION); 80 | } 81 | 82 | @Override 83 | public void onCreate(SQLiteDatabase db) { 84 | db.execSQL("create table " + HISTORY_TABLE + "(" + COLUMN_ID + " integer primary key autoincrement, " 85 | + COLUMN_TAG + " text not null, " + COLUMN_TEXT + " text not null, " + COLUMN_TIME + " long);"); 86 | } 87 | 88 | @Override 89 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 90 | db.execSQL("drop table if exists " + HISTORY_TABLE); 91 | onCreate(db); 92 | } 93 | } 94 | 95 | public static String getText(Cursor c) { 96 | return Zen.getTextColumn(c, COLUMN_TEXT); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /library/src/com/zenlibs/historyedittext/HistoryEditText.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2013 Zenlibs 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 com.zenlibs.historyedittext; 18 | 19 | import android.content.Context; 20 | import android.content.res.TypedArray; 21 | import android.database.Cursor; 22 | import android.database.sqlite.SQLiteDatabase; 23 | import android.graphics.Rect; 24 | import android.text.TextUtils; 25 | import android.util.AttributeSet; 26 | import android.widget.ArrayAdapter; 27 | import android.widget.Filter; 28 | import android.widget.Filter.FilterListener; 29 | import android.widget.Filterable; 30 | import android.widget.ListAdapter; 31 | import com.commonsware.cwac.merge.MergeAdapter; 32 | 33 | public class HistoryEditText extends AbsHistoryEditText { 34 | 35 | private boolean mFirstFiltering = true; 36 | private ListAdapter mHistoryAdapter; 37 | private Filter mHistoryFilter; 38 | private int mMaxHistoryValues; 39 | 40 | public HistoryEditText(Context context, AttributeSet attrs, int defStyle) { 41 | super(context, attrs, defStyle); 42 | parseAttrs(context, attrs, defStyle); 43 | } 44 | 45 | private void parseAttrs(Context context, AttributeSet attrs, int defStyle) { 46 | 47 | // Getting max history values 48 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HistoryEditText, defStyle, 49 | R.style.Widget_HistoryEditText); 50 | 51 | mMaxHistoryValues = a.getInt(R.styleable.HistoryEditText_maxHistoryValues, 5); 52 | 53 | a.recycle(); 54 | } 55 | 56 | public HistoryEditText(Context context, AttributeSet attrs) { 57 | super(context, attrs); 58 | parseAttrs(context, attrs, android.R.attr.autoCompleteTextViewStyle); 59 | } 60 | 61 | public HistoryEditText(Context context) { 62 | super(context); 63 | } 64 | 65 | public void clearHistory() { 66 | boolean wasShowing = isPopupShowing(); 67 | SQLiteDatabase db = HistoryDb.getWritable(getContext()); 68 | HistoryDb.clear(db); 69 | db.close(); 70 | rebuildHistoryAdapter(); 71 | if (wasShowing) { 72 | showDropDown(); 73 | } 74 | } 75 | 76 | @Override 77 | protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { 78 | super.onFocusChanged(focused, direction, previouslyFocusedRect); 79 | if (focused) { 80 | performFiltering(null, -1); 81 | } 82 | } 83 | 84 | @Override 85 | public void onEditorAction(int actionCode) { 86 | super.onEditorAction(actionCode); 87 | if (actionCode == getImeOptions()) { 88 | addCurrentTextToHistory(); 89 | rebuildHistoryAdapter(); 90 | } 91 | } 92 | 93 | private