Blog para desarrollo de aplicaciones en Android, aprende paso a paso como crear aplicaciones.

Usamos cookies propias y de terceros que entre otras cosas recogen datos sobre sus hábitos de navegación para mostrarle publicidad personalizada y realizar análisis de uso de nuestro sitio.
Si continúa navegando consideramos que acepta su uso. OK Más información | Y más

Como crear un Spinner buscador de palabras en Android

Como crear un Spinner buscador de palabras en Android

Android Spinner Search Example

Hola amigos vamos a crear un tutorial sobre un Spinner Search en nuestra aplicaciones. Para ello deben seguir paso a paso como crear un Spinner Search en Android.

Crear un Proyecto Android (Aquí)

Vamos a crear nuestro proyecto para desarrollar este ejemplo sobre el Spinner Search para tener una idea mas clara de como utilizarlo en nuestras aplicaciones Android.

Crear Diseño Spinner utilizando Buscador

Vamos a crear el diseño de nuestra aplicación para ver como va a quedar nuestro Spinner utilizando un buscador de palabras.


<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.hn.universoandroid.example_spinnersearch.SearchableSpinner
            android:id="@+id/spinner"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:entries="@array/animes"
            android:layout_margin="10dp"
            android:layout_centerInParent="true"
            app:hintText="Elige tu anime favorito!"/>
    </RelativeLayout>
</android.support.constraint.ConstraintLayout>

El diseño nos quedara de la siguiente manera.

Ahora vamos a continuar con otros detalles del diseño y agregaremos una lista de palabras en nuestro archivo String.xml crearemos un String de tipo Array para contener nuestras palabras y las que estarán asignadas a nuestro Spinner Search.


<resources>
    <string name="app_name">Spinner Search Android</string>

    <string-array name="animes">
        <item>Black Jack</item>
        <item>Naruto Shippuden</item>
        <item>Dragon Ball</item>
        <item>Pokemon</item>
        <item>Digimon</item>
        <item>Samurai X</item>
        <item>Berserk</item>
    </string-array>

</resources>

Y esta seria nuestra lista que mostrara nuestro Spinner y con el cual podremos buscar las palabras claves. Ahora añadiremos un archivo xml a nuestra carpeta Values. Y le pondremos attrs.xml.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="SearchableSpinner">
        <attr name="hintText" format="string"/>
    </declare-styleable>
</resources>
Y con esto quedaría listo nuestro diseño.

Crear el código de nuestro Spinner buscador en Android

Ahora vamos a crear el código para nuestro ejemplo utilizando el Spinner con un buscador de palabras y nos dirigiremos a nuestros paquetes donde estan las clases Java.

Añadiremos una clase y le pondremos SearchableListDialog ha esta clase le vamos a dar un Extends de tipo DialogFragment y añadiremos las siguientes implementaciones de clases SearchView.OnQueryTextListener, SearchView.OnCloseListener que seran las encargadas de ejecutar los eventos del SearchView en nuestro Spinner.


import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.SearchManager;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.SearchView;

import java.io.Serializable;
import java.util.List;

public class SearchableListDialog extends DialogFragment implements
        SearchView.OnQueryTextListener, SearchView.OnCloseListener {

    private static final String ITEMS = "items";
    private ArrayAdapter listAdapter;
    private ListView _listViewItems;
    private SearchableItem _searchableItem;
    private OnSearchTextChanged _onSearchTextChanged;
    private SearchView _searchView;
    private String _strTitle;
    private String _strPositiveButtonText;
    private DialogInterface.OnClickListener _onClickListener;

    public SearchableListDialog() {

    }

    public static SearchableListDialog newInstance(List items) {
        SearchableListDialog multiSelectExpandableFragment = new
                SearchableListDialog();

        Bundle args = new Bundle();
        args.putSerializable(ITEMS, (Serializable) items);

        multiSelectExpandableFragment.setArguments(args);

        return multiSelectExpandableFragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams
                .SOFT_INPUT_STATE_HIDDEN);
        return super.onCreateView(inflater, container, savedInstanceState);
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {

        // Getting the layout inflater to inflate the view in an alert dialog.
        LayoutInflater inflater = LayoutInflater.from(getActivity());

        // Crash on orientation change #7
        // Change Start
        // Description: As the instance was re initializing to null on rotating the device,
        // getting the instance from the saved instance
        if (null != savedInstanceState) {
            _searchableItem = (SearchableItem) savedInstanceState.getSerializable("item");
        }
        // Change End

        View rootView = inflater.inflate(R.layout.searchable_list_dialog, null);
        setData(rootView);

        AlertDialog.Builder alertDialog = new AlertDialog.Builder(getActivity());
        alertDialog.setView(rootView);

        String strPositiveButton = _strPositiveButtonText == null ? "CLOSE" : _strPositiveButtonText;
        alertDialog.setPositiveButton(strPositiveButton, _onClickListener);

        String strTitle = _strTitle == null ? "Select Item" : _strTitle;
        alertDialog.setTitle(strTitle);

        final AlertDialog dialog = alertDialog.create();
        dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams
                .SOFT_INPUT_STATE_HIDDEN);
        return dialog;
    }

    // Crash on orientation change #7
    // Change Start
    // Description: Saving the instance of searchable item instance.
    @Override
    public void onSaveInstanceState(Bundle outState) {
        outState.putSerializable("item", _searchableItem);
        super.onSaveInstanceState(outState);
    }
    // Change End

    public void setTitle(String strTitle) {
        _strTitle = strTitle;
    }

    public void setPositiveButton(String strPositiveButtonText) {
        _strPositiveButtonText = strPositiveButtonText;
    }

    public void setPositiveButton(String strPositiveButtonText, DialogInterface.OnClickListener onClickListener) {
        _strPositiveButtonText = strPositiveButtonText;
        _onClickListener = onClickListener;
    }

    public void setOnSearchableItemClickListener(SearchableItem searchableItem) {
        this._searchableItem = searchableItem;
    }

    public void setOnSearchTextChangedListener(OnSearchTextChanged onSearchTextChanged) {
        this._onSearchTextChanged = onSearchTextChanged;
    }

    private void setData(View rootView) {
        SearchManager searchManager = (SearchManager) getActivity().getSystemService(Context
                .SEARCH_SERVICE);

        _searchView = (SearchView) rootView.findViewById(R.id.search);
        _searchView.setSearchableInfo(searchManager.getSearchableInfo(getActivity().getComponentName
                ()));
        _searchView.setIconifiedByDefault(false);
        _searchView.setOnQueryTextListener(this);
        _searchView.setOnCloseListener(this);
        _searchView.clearFocus();
        InputMethodManager mgr = (InputMethodManager) getActivity().getSystemService(Context
                .INPUT_METHOD_SERVICE);
        mgr.hideSoftInputFromWindow(_searchView.getWindowToken(), 0);

        List items = (List) getArguments().getSerializable(ITEMS);
        _listViewItems = (ListView) rootView.findViewById(R.id.listItems);

        //create the adapter by passing your ArrayList data
        listAdapter = new ArrayAdapter(getActivity(), android.R.layout.simple_list_item_1,items);        //attach the adapter to the list

        _listViewItems.setAdapter(listAdapter);
        _listViewItems.setTextFilterEnabled(true);

        _listViewItems.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                _searchableItem.onSearchableItemClicked(listAdapter.getItem(position), position);
                getDialog().dismiss();
            }
        });
    }

    @Override
    public boolean onClose() {
        return false;
    }

    @Override
    public boolean onQueryTextSubmit(String s) {
        _searchView.clearFocus();
        return true;
    }

    @Override
    public boolean onQueryTextChange(String s) {
        //listAdapter.filterData(s);
        if (TextUtils.isEmpty(s)) {
        //listViewItems.clearTextFilter();
            ((ArrayAdapter) _listViewItems.getAdapter()).getFilter().filter(null);
        } else {
            ((ArrayAdapter) _listViewItems.getAdapter()).getFilter().filter(s);
        }
        if (null != _onSearchTextChanged) {
            _onSearchTextChanged.onSearchTextChanged(s);
        }
        return true;
    }

    public interface SearchableItem<T> extends Serializable {
        void onSearchableItemClicked(T item, int position);
    }
    public interface OnSearchTextChanged {
        void onSearchTextChanged(String strText);
    }
}

Ahora continuamos con la clase principal de nuestro Spinner utilizando un buscador de palabras. Le pondremos SearchableSpinner a nuestra clase y le vamos a dar un Extends de la clase Spinner y le implementaremos la clase View.OnTouchListener.


import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.TypedArray;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.SpinnerAdapter;
import android.widget.Toast
import java.util.ArrayList;
import java.util.List;

public class SearchableSpinner extends android.support.v7.widget.AppCompatSpinner implements View.OnTouchListener,
    SearchableListDialog.SearchableItem {

    String selectedItem;
    //this string above will store the value of selected item.

    public static final int NO_ITEM_SELECTED = -1;
    private Context _context;
    private List _items;
    private SearchableListDialog _searchableListDialog;

    private boolean _isDirty;
    private ArrayAdapter _arrayAdapter;
    private String _strHintText;
    private boolean _isFromInit;

    public SearchableSpinner(Context context) {
        super(context);
        this._context = context;
        init();
    }

    public SearchableSpinner(Context context, AttributeSet attrs) {
        super(context, attrs);
        this._context = context;
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SearchableSpinner);
        final int N = a.getIndexCount();
        for (int i = 0; i < N; ++i) {
            int attr = a.getIndex(i);
            if (attr == R.styleable.SearchableSpinner_hintText) {
                _strHintText = a.getString(attr);
            }
        }
        a.recycle();
        init();
    }

    public SearchableSpinner(Context context, AttributeSet attrs, int defStyleAttr) 
        super(context, attrs, defStyleAttr);
        this._context = context;
        init();
    }

    private void init() {
        _items = new ArrayList();
        _searchableListDialog = SearchableListDialog.newInstance
                (_items);
        _searchableListDialog.setOnSearchableItemClickListener(this);
        setOnTouchListener(this);

        _arrayAdapter = (ArrayAdapter) getAdapter();
        if (!TextUtils.isEmpty(_strHintText)) {
            ArrayAdapter arrayAdapter = new ArrayAdapter(_context, android.R.layout
                    .simple_list_item_1, new String[]{_strHintText});
            _isFromInit = true;
            setAdapter(arrayAdapter);
        }
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_UP) {
            if (null != _arrayAdapter) {

                // Refresh content #6
                // Change Start
                // Description: The items were only set initially, not reloading the data in the
                // spinner every time it is loaded with items in the adapter.
                _items.clear();
                for (int i = 0; i < _arrayAdapter.getCount(); i++) {
                    _items.add(_arrayAdapter.getItem(i));
                }
                // Change end.

                _searchableListDialog.show(scanForActivity(_context).getFragmentManager(), "TAG");
            }
        }
        return true;
    }

    @Override
    public void setAdapter(SpinnerAdapter adapter) {

        if (!_isFromInit) {
            _arrayAdapter = (ArrayAdapter) adapter;
            if (!TextUtils.isEmpty(_strHintText) && !_isDirty) {
                ArrayAdapter arrayAdapter = new ArrayAdapter(_context, android.R.layout
                        .simple_list_item_1, new String[]{_strHintText});
                super.setAdapter(arrayAdapter)
            } else {
                super.setAdapter(adapter);
            }

        } else {
            _isFromInit = false;
            super.setAdapter(adapter);
        }
    }
    //The method just below is executed  when an item in the searchlist is tapped.This is where we store the value int string called selectedItem.
    @Override
    public void onSearchableItemClicked(Object item, int position) {
        setSelection(_items.indexOf(item));

        if (!_isDirty) {
            _isDirty = true;
            setAdapter(_arrayAdapter);
            setSelection(_items.indexOf(item));
        }
        selectedItem= getItemAtPosition(position).toString();

        Toast.makeText(getContext(),"You selected "+selectedItem,Toast.LENGTH_LONG).show();
    }

    private Activity scanForActivity(Context cont) {
        if (cont == null)
            return null;
        else if (cont instanceof Activity)
            return (Activity) cont;
        else if (cont instanceof ContextWrapper)
            return scanForActivity(((ContextWrapper) cont).getBaseContext());

        return null;
    }

    @Override
    public int getSelectedItemPosition() {
        if (!TextUtils.isEmpty(_strHintText) && !_isDirty) {
            return NO_ITEM_SELECTED;
        } else {
            return super.getSelectedItemPosition();
        }
    }

    @Override
    public Object getSelectedItem() {
        if (!TextUtils.isEmpty(_strHintText) && !_isDirty) {
            return null;
        } else {
            return super.getSelectedItem();
        }
    }
}

Y con esto tendríamos listo nuestro ejemplo utilizando un Spinner con buscador de palabras.

2 comentarios:

  1. Genial, gracias por el tutorial.. pero hay unas cosas que agregar y corregir;

    1.- Te faltó agregar el layout de searchable_list_dialog que me imagino sería:

    //searchable_list_dialog.xml

    LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"

    SearchView
    android:id="@+id/search"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"

    /SearchView

    ListView
    android:id="@+id/listItems"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    /ListView

    /LinearLayout

    2.- En la clase SearchableSpinner, en el método sobrescrito onSearchableItemClicked, en la línea:
    selectedItem = getItemAtPosition(position).toString();
    no deberías enviar la variable position porque es la posición del item seleccionado de la lista filtrada, por lo tanto tendrías que cambiar por _items.indexOf(item) y quedaría así:
    selectedItem = getItemAtPosition(_items.indexOf(item)).toString();

    Con eso sale tu ejemplo.. gracias por el tuto (y);

    ResponderEliminar
  2. No utiliza librería amigo pero puedes estar pendiente subiremos mas contenido.
    Y agregaremos un control nuevo.
    Saludos.

    ResponderEliminar