2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android.widget;
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.graphics.Rect;
22 import android.graphics.drawable.Drawable;
23 import android.text.Editable;
24 import android.text.Selection;
25 import android.text.Spanned;
26 import android.text.Spannable;
27 import android.text.SpannableString;
28 import android.text.TextUtils;
29 import android.text.method.QwertyKeyListener;
30 import android.util.AttributeSet;
31 import android.util.Log;
32 import android.view.KeyEvent;
33 import android.view.LayoutInflater;
34 import android.view.View;
35 import android.view.ViewGroup;
37 import com.android.internal.R;
40 * An editable text view, extending {@link AutoCompleteTextView}, that
41 * can show completion suggestions for the substring of the text where
42 * the user is typing instead of necessarily for the entire thing.
44 * You must must provide a {@link Tokenizer} to distinguish the
47 * <p>The following code snippet shows how to create a text view which suggests
48 * various countries names while the user is typing:</p>
50 * <pre class="prettyprint">
51 * public class CountriesActivity extends Activity {
52 * protected void onCreate(Bundle savedInstanceState) {
53 * super.onCreate(savedInstanceState);
54 * setContentView(R.layout.autocomplete_7);
56 * ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
57 * android.R.layout.simple_dropdown_item_1line, COUNTRIES);
58 * MultiAutoCompleteTextView textView = (MultiAutoCompleteTextView) findViewById(R.id.edit);
59 * textView.setAdapter(adapter);
60 * textView.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
63 * private static final String[] COUNTRIES = new String[] {
64 * "Belgium", "France", "Italy", "Germany", "Spain"
69 public class MultiAutoCompleteTextView extends AutoCompleteTextView {
70 private Tokenizer mTokenizer;
72 public MultiAutoCompleteTextView(Context context) {
76 public MultiAutoCompleteTextView(Context context, AttributeSet attrs) {
77 this(context, attrs, com.android.internal.R.attr.autoCompleteTextViewStyle);
80 public MultiAutoCompleteTextView(Context context, AttributeSet attrs, int defStyle) {
81 super(context, attrs, defStyle);
84 /* package */ void finishInit() { }
87 * Sets the Tokenizer that will be used to determine the relevant
88 * range of the text where the user is typing.
90 public void setTokenizer(Tokenizer t) {
95 * Instead of filtering on the entire contents of the edit box,
96 * this subclass method filters on the range from
97 * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
98 * if the length of that range meets or exceeds {@link #getThreshold}.
101 protected void performFiltering(CharSequence text, int keyCode) {
102 if (enoughToFilter()) {
103 int end = getSelectionEnd();
104 int start = mTokenizer.findTokenStart(text, end);
106 performFiltering(text, start, end, keyCode);
110 Filter f = getFilter();
118 * Instead of filtering whenever the total length of the text
119 * exceeds the threshhold, this subclass filters only when the
120 * length of the range from
121 * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd}
122 * meets or exceeds {@link #getThreshold}.
125 public boolean enoughToFilter() {
126 Editable text = getText();
128 int end = getSelectionEnd();
129 if (end < 0 || mTokenizer == null) {
133 int start = mTokenizer.findTokenStart(text, end);
135 if (end - start >= getThreshold()) {
143 * Instead of validating the entire text, this subclass method validates
144 * each token of the text individually. Empty tokens are removed.
147 public void performValidation() {
148 Validator v = getValidator();
150 if (v == null || mTokenizer == null) {
154 Editable e = getText();
155 int i = getText().length();
157 int start = mTokenizer.findTokenStart(e, i);
158 int end = mTokenizer.findTokenEnd(e, start);
160 CharSequence sub = e.subSequence(start, end);
161 if (TextUtils.isEmpty(sub)) {
162 e.replace(start, i, "");
163 } else if (!v.isValid(sub)) {
165 mTokenizer.terminateToken(v.fixText(sub)));
173 * <p>Starts filtering the content of the drop down list. The filtering
174 * pattern is the specified range of text from the edit box. Subclasses may
175 * override this method to filter with a different pattern, for
176 * instance a smaller substring of <code>text</code>.</p>
178 protected void performFiltering(CharSequence text, int start, int end,
180 getFilter().filter(text.subSequence(start, end), this);
184 * <p>Performs the text completion by replacing the range from
185 * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd} by the
186 * the result of passing <code>text</code> through
187 * {@link Tokenizer#terminateToken}.
188 * In addition, the replaced region will be marked as an AutoText
189 * substition so that if the user immediately presses DEL, the
190 * completion will be undone.
191 * Subclasses may override this method to do some different
192 * insertion of the content into the edit box.</p>
194 * @param text the selected suggestion in the drop down list
197 protected void replaceText(CharSequence text) {
198 clearComposingText();
200 int end = getSelectionEnd();
201 int start = mTokenizer.findTokenStart(getText(), end);
203 Editable editable = getText();
204 String original = TextUtils.substring(editable, start, end);
206 QwertyKeyListener.markAsReplaced(editable, start, end, original);
207 editable.replace(start, end, mTokenizer.terminateToken(text));
210 public static interface Tokenizer {
212 * Returns the start of the token that ends at offset
213 * <code>cursor</code> within <code>text</code>.
215 public int findTokenStart(CharSequence text, int cursor);
218 * Returns the end of the token (minus trailing punctuation)
219 * that begins at offset <code>cursor</code> within <code>text</code>.
221 public int findTokenEnd(CharSequence text, int cursor);
224 * Returns <code>text</code>, modified, if necessary, to ensure that
225 * it ends with a token terminator (for example a space or comma).
227 public CharSequence terminateToken(CharSequence text);
231 * This simple Tokenizer can be used for lists where the items are
232 * separated by a comma and one or more spaces.
234 public static class CommaTokenizer implements Tokenizer {
235 public int findTokenStart(CharSequence text, int cursor) {
238 while (i > 0 && text.charAt(i - 1) != ',') {
241 while (i < cursor && text.charAt(i) == ' ') {
248 public int findTokenEnd(CharSequence text, int cursor) {
250 int len = text.length();
253 if (text.charAt(i) == ',') {
263 public CharSequence terminateToken(CharSequence text) {
264 int i = text.length();
266 while (i > 0 && text.charAt(i - 1) == ' ') {
270 if (i > 0 && text.charAt(i - 1) == ',') {
273 if (text instanceof Spanned) {
274 SpannableString sp = new SpannableString(text + ", ");
275 TextUtils.copySpansFrom((Spanned) text, 0, text.length(),
276 Object.class, sp, 0);