Java tutorial
/* * MonMa: Eine freie Android-Application fuer die Verwaltung privater Finanzen * * Copyright [2015] [Alexander Winkler, 2373 Dahme/Germany] * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU General Public License as published by the Free Software Foundation; either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along with this program; if * not, see <http://www.gnu.org/licenses/>. */ package de.aw.awlib.views; import android.content.Context; import android.database.Cursor; import android.databinding.BindingAdapter; import android.graphics.Rect; import android.support.annotation.CallSuper; import android.support.annotation.NonNull; import android.support.v4.widget.SimpleCursorAdapter; import android.util.AttributeSet; import android.view.View; import android.widget.AdapterView; import android.widget.FilterQueryProvider; import android.widget.TextView; import de.aw.awlib.activities.AWInterface; import de.aw.awlib.database.AWAbstractDBDefinition; /** * AutoCompleteTextView (siehe {@link AWAutoCompleteTextView#initialize (DBDefinition, String, * String[], boolean, int[])}.<br> Sendet eine Message nach einer TextAenderung. Threshold ist * standardmaessig 3. * * @see AWAutoCompleteTextView#onTextChanged(String newText) */ public abstract class AWAutoCompleteTextView extends android.support.v7.widget.AppCompatAutoCompleteTextView implements AWInterface, FilterQueryProvider, AdapterView.OnItemClickListener { protected OnTextChangedListener mOnTextChangeListener; private int columnIndex; private String mMainColumn; private String mOrderBy; private String[] mProjection; private String mSelection; private String cursorText = ""; private long selectionID; private AWAbstractDBDefinition tbd; private boolean initializedCalled; private boolean doValidateInput; public AWAutoCompleteTextView(Context context) { super(context); } public AWAutoCompleteTextView(Context context, AttributeSet attrs) { super(context, attrs); } public AWAutoCompleteTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @BindingAdapter({ "onTextChanged" }) public static void onTextChanged(AWAutoCompleteTextView view, OnTextChangedListener listener) { view.setOnTextChangedListener(listener); } /** * @return Liefert die ID des selektierten Textes. Wenn * {@link AWAutoCompleteTextView#validateInput(boolean * doValidateInput)} mit true gerufen wurde, die erste ID aus dem Cursor,ansonsten NOID. */ public final long getSelectionID() { if (!doValidateInput) { return selectionID; } else { String text = getText().toString(); if (text.equals(cursorText)) { return selectionID; } } return NOID; } /** * Initialisiert AutoCompleteTextView. * * @param tbd * DBDefinition. Aus dieser Tabelle wird das Feld gelesen * @param selection * selection * @param selectionArgs * Argumente zur Selection * @param column * Feld, welches fuer die Selection benutzt werden soll. * @param orderBy * * @throws NullPointerException, * wenn LoaderManager null ist. */ public final void initialize(AWAbstractDBDefinition tbd, String selection, String[] selectionArgs, @NonNull String column, String orderBy) { if (!isInEditMode()) { initializedCalled = true; this.tbd = tbd; mMainColumn = column; mProjection = new String[] { column, _id }; mSelection = column + " Like ? "; mOrderBy = orderBy; if (mOrderBy == null) { mOrderBy = "LENGTH(" + mMainColumn + ")"; } if (selection != null) { if (selectionArgs != null) { for (String sel : selectionArgs) { selection = selection.replaceFirst("\\?", "'" + sel + "'"); } } mSelection = mSelection + " AND (" + selection + ")"; } mSelection = mSelection + " GROUP BY " + mMainColumn; mOrderBy = mOrderBy + ", " + mMainColumn; } } @Override protected void onDetachedFromWindow() { SimpleCursorAdapter adapter = (SimpleCursorAdapter) getAdapter(); if (adapter != null) { Cursor c = adapter.swapCursor(null); if (c != null && !c.isClosed()) { c.close(); } } super.onDetachedFromWindow(); } /** * Wenn die View den Fokus verliert, wird geprueft, ob neue Eintraeg zugelassen sind. Ist dies * nicht der Fall, wird der Text auf den zuletzt gueltigen Text zurueckgesetzt und dieser Text * versendet. */ @Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { super.onFocusChanged(focused, direction, previouslyFocusedRect); if (!focused) { dismissDropDown(); if (doValidateInput) { setText(cursorText); onTextChanged(cursorText); } } else { if (!isInEditMode()) { performFiltering(getText(), 0); } } } /** * Wenn ein List-Item ausgewaehlt wird, wird eine Message mit dem ausgewaehlten Text gesendet. */ @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { selectionID = id; cursorText = ((TextView) view).getText().toString().trim(); onTextChanged(cursorText); } @Override protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { if (doValidateInput) { onTextChanged(cursorText); } else { onTextChanged(text.toString()); } } /** * Wird bei Textaenderungen gerufen. * * @param currentText * Text */ @CallSuper protected void onTextChanged(String currentText) { if (mOnTextChangeListener != null) { mOnTextChangeListener.onTextChanged(this, currentText, cursorText, selectionID); } } /** * Nach tippen wird hier nachgelesen. Es wird mit 'LIKE %constraint%' ausgewaehlt. Hat der * Cursor Daten und validierung ist eingeschaltet (es ist kein neuer Wert zugelassen), wird die * erste ID aus dem Cursor geholt und der Text auf den entsprechenden Wert des Cursors gesetzt. * Gibt es nur einen oder gar keinen Wert, wird Dropdown ausgeblendet * * @param constraint * Text * * @return den neuen Cursor */ @Override public Cursor runQuery(final CharSequence constraint) { selectionID = NOID; final String mConstraint = constraint == null ? "" : constraint.toString().trim(); String[] mSelectionArgs = new String[] { "%" + constraint + "%" }; final Cursor data = getContext().getContentResolver().query(tbd.getUri(), mProjection, mSelection, mSelectionArgs, mOrderBy); if (data.moveToFirst()) { selectionID = data.getLong(1); cursorText = data.getString(0).trim(); if (data.getCount() == 1) { onTextChanged(cursorText); } } post(new Runnable() { @Override public void run() { if (data.getCount() == 1 && mConstraint.equals(cursorText)) { dismissDropDown(); } else { if (hasFocus()) { showDropDown(); } } } }); columnIndex = data.getColumnIndexOrThrow(mMainColumn); return data; } public void setOnTextChangedListener(OnTextChangedListener onTextChangedListener) { mOnTextChangeListener = onTextChangedListener; } /** * Wenn diese Methode true zurueckliefert, sind nur Werte aus dem Cursor erlaubt. Wir ein Wert * erfasst, der nicht im Cursor vorhanden ist, wird der eingegebene Wert mit dem ersten Wert aus * dem Cursor ersetzt. * * @return default false. Neue Werte sind immer erlaubt. */ protected void validateInput(boolean doValidating) { this.doValidateInput = doValidating; } /** * Interface fuer Listener auf Textaenderungen */ public interface OnTextChangedListener { /** * Wird gerufen, wenn sich der Text einer View geaendert hat * * @param view * view, deren Text sich geaendert hat * @param currentText * Neuer Text. * @param newID * ID aus der DB, wenn Nutzer ein Item aus dem Pulldown selektiert hat oder wenn * der */ void onTextChanged(View view, String currentText, String cursorText, long newID); } }