21 March 2014

Android type ahead filter


Any time I find a self filtering search box that works properly, my life is improved! Good UI and all that :) I find auto complete a great way to search and thought I'd share. There is no rocket science here, I just pieced this together from a few various examples.

First a simple activity main:

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

    <!-- Pretty hint text, and maxLines -->
    <EditText 
        android:id="@+id/searchbox1" 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:hint="@string/filter_hint"
        android:inputType="text"
        android:maxLines="1"/>

    <!-- Set height to 0, and let the weight param expand it -->
    <!-- Note the use of the default ID! This lets us use a ListActivity still! -->
    <ListView android:id="@+id/listview1"
        android:layout_width="fill_parent"
        android:layout_height="0dip"
        android:layout_weight="1" /> 
</LinearLayout>

Next in my ActivityMain I create a helper method getNames() to load in the data. Obviously you'll do this in your own unique way. Also note the TextWatcher, this is pretty simple.

    private TextWatcher filterTextWatcher = new TextWatcher() {

        public void afterTextChanged(Editable s) {
        }

        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }

        public void onTextChanged(CharSequence s, int start, int before, int count) {
            arrayAdapter.getFilter().filter(s);
        }

    };

   
    private ArrayList<String> getNames(){
        String[] names = getResources().getStringArray(R.array.planets_array);
        ArrayList<String> listNames = new ArrayList<String>();
        
        for(String name: names){
            listNames.add(name);
        }
        
        return listNames;
    }

Then in onCreate we put it all together:

        mFriendsList = (ListView) findViewById(R.id.listview1);
        
        //Apply Filter
        filterText = (EditText) findViewById(R.id.searchbox1);
        filterText.addTextChangedListener(filterTextWatcher);
        
        arrayAdapter = new newNameAdapter(this, getNames());
        arrayAdapter.sort();
        
        mFriendsList.setAdapter(arrayAdapter);

Finally the complicated (ish) bit is the Adapter.

package com.example.testleaguefinder;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Locale;


import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.TextView;

public class newNameAdapter extends BaseAdapter implements Filterable{

    Context context;
    ArrayList<String> names;
    private LayoutInflater mInflater;
    private GameFilter filter;
    ArrayList<String> orig;
    
    public newNameAdapter(Context _context, ArrayList<String> _names) {
        this.context = _context;
        this.names = _names;
        filter = new GameFilter();
        
        mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }
    
    @Override
    public int getCount() {
        return names.size();
    }

    @Override
    public String getItem(int position) {
        return names.get(position);
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.listview_names, null);
        }
        
        TextView txtName = (TextView) convertView.findViewById(R.id.listview_names_name);
        txtName.setText(getItem(position));
            
        return convertView;
    }
    
    public void sort(){
        Collections.sort(names, new Comparator<String>() {
            @Override
            public int compare(String fruite1, String fruite2)
            {
                return  fruite1.compareTo(fruite2);
            }
        });
    }
    
    @Override
    public Filter getFilter() {
        return filter;
    }
    
    private class GameFilter extends Filter {
        
        public GameFilter() {
        }
        
        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            String lowerConstraint = constraint.toString();
            FilterResults oReturn = new FilterResults();
            ArrayList<String> results = new ArrayList<String>();
            String league;
            
            if (orig == null){
                orig = names;
            }
            
            if (lowerConstraint != null){
                lowerConstraint = lowerConstraint.toLowerCase(Locale.UK);
                if (orig != null && orig.size() > 0) {
                    for (String g : orig) {
                        league = g.toLowerCase(Locale.UK);
                        if (league.contains(lowerConstraint)){
                            results.add(g);
                        }
                    }
                }
                oReturn.values = results;
            }
            return oReturn;
        }
        
        @SuppressWarnings("unchecked")
        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
            names = (ArrayList<String>)results.values;
            notifyDataSetChanged();
        }
    }
    
    
}

Most of it should be fairly standard, the only interesting bit should be the Filter. All we're doing really is taking the entered text and lower casing it. Then we loop through the list of entries and find any that contain the filter string.

Simples!



No comments: