How to set Filter to ListView with CursorAdapter?

2.5k views Asked by At

I have a ListView. which of it's items are from custom CursorAdapter. I have created separate layout for ListView items which is a CardView. I have six TextViews on each card they are Database values. Now i have to put a EditText to filter the ListView items according to the text entered in EditText. My Code is layout with ListView is

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#1d4563">

<ListView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/listView2"
    android:layout_marginTop="40dp"
    android:layout_centerHorizontal="true" />

<EditText
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:inputType="number"
    android:ems="10"
    android:id="@+id/editText5"
    android:layout_alignParentTop="true"
    android:layout_alignParentLeft="true"
    android:layout_alignParentStart="true"
    android:layout_alignParentRight="true"
    android:layout_alignParentEnd="true"
    android:background="@drawable/bg_edit"
    android:textColorHint="#d3d3d3"
    android:hint="enter Serial Number to search" />

ListView items layout is

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
xmlns:android.support.v7.cardview="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android.support.v7.cardview:cardBackgroundColor="@color/primary_dark"
android:id="@+id/cardItems">

<android.support.v7.widget.CardView
    android:id="@+id/card_view"
    android:layout_gravity="center"
    android:layout_width="fill_parent"
    android:layout_height="150dp"
    android:layout_margin="1dp"
    card_view:cardCornerRadius="0dp"
    card_view:contentPadding="2dp">

    <RelativeLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:background="@drawable/card_style">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Username"
            android:id="@+id/textView24"
            android:textColor="#FFFFFF"
            android:layout_alignParentTop="true"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true"
            android:layout_marginLeft="25dp"
            android:layout_marginStart="25dp"
            android:layout_marginTop="8dp"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Mobile No."
            android:id="@+id/textView25"
            android:textColor="#FFFFFF"
            android:layout_centerVertical="true"
            android:layout_marginLeft="25dp"
            android:layout_marginStart="25dp"
            android:layout_alignRight="@+id/textView24"
            android:layout_alignEnd="@+id/textView24" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Serial No."
            android:id="@+id/textView26"
            android:textColor="#FFFFFF"
            android:layout_marginLeft="25dp"
            android:layout_marginStart="25dp"
            android:layout_alignParentBottom="true"
            android:layout_marginBottom="8dp"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="New Text"
            android:id="@+id/textView27"
            android:textColor="#FFFFFF"
            android:layout_marginTop="8dp"
            android:layout_centerHorizontal="true" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="New Text"
            android:id="@+id/textView28"
            android:textColor="#FFFFFF"
            android:layout_centerVertical="true"
            android:layout_alignLeft="@+id/textView27"
            android:layout_alignStart="@+id/textView27" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="New Text"
            android:id="@+id/textView29"
            android:layout_alignTop="@+id/textView26"
            android:layout_alignLeft="@+id/textView28"
            android:layout_alignStart="@+id/textView28"
            android:layout_marginBottom="8dp"
            android:textColor="#FFFFFF"/>

    </RelativeLayout>

</android.support.v7.widget.CardView>

My DisplayAdapter is

public class DisplayAdapter extends CursorAdapter{

@SuppressWarnings("deprecation")
public DisplayAdapter(Context context,Cursor c){
    super(context,c);
}

@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
    LayoutInflater inflater = LayoutInflater.from(parent.getContext());
    View retView = inflater.inflate(R.layout.card_items, parent, false);

    return retView;
}

@Override
public void bindView(View view, Context context, Cursor cursor) {
    TextView tvUserName=(TextView)view.findViewById(R.id.textView27);
    tvUserName.setText(cursor.getString(2));

    TextView tvMobile=(TextView)view.findViewById(R.id.textView28);
    tvMobile.setText(cursor.getString(3));

    TextView tvSerail=(TextView)view.findViewById(R.id.textView29);
    tvSerail.setText(cursor.getString(5));
}

}

My Activity is public class TableBikeActivity extends ActionBarActivity {

private DisplayAdapter adapter;
ListView bikeList;
DatabaseHelper databaseHelper;
EditText filterText;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_table_bike);

    databaseHelper=new DatabaseHelper(this);
    bikeList=(ListView)findViewById(R.id.listView2);

    filterText=(EditText)findViewById(R.id.editText5);
    filterText.addTextChangedListener(filterTextWatcher);
    new Handler().post(new Runnable() {
        @Override
        public void run() {
            adapter = new DisplayAdapter(TableBikeActivity.this, databaseHelper.getAllDataBike());
            bikeList.setAdapter(adapter);
        }
    });
}

private TextWatcher filterTextWatcher=new TextWatcher() {
    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {

    }

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

    @Override
    public void afterTextChanged(Editable s) {

    }
};

@Override
protected void onDestroy(){
    super.onDestroy();
    filterText.removeTextChangedListener(filterTextWatcher);
}

}

I have tried TextChangedListener but it's not working. Help me.

3

There are 3 answers

0
Kartheek On BEST ANSWER

The setTextFilterEnabled() method doesn't automatically implement filtering, as it doesn't know what in your Cursor the text should be filtered against.

For a CursorAdapter cursor, you only need to use the setFilterQueryProvider, to run another query for your cursor, based on the constraint:

m_Adapter.setFilterQueryProvider(new FilterQueryProvider() {

  public Cursor runQuery(CharSequence constraint) {
    Log.d(LOG_TAG, "runQuery constraint:"+constraint);
    //uri, projection, and sortOrder might be the same as previous
    //but you might want a new selection, based on your filter content (constraint)
    Cursor cur = managedQuery(uri, projection, selection, selectionArgs, sortOrder);
    return cur; //now your adapter will have the new filtered content
  }

});

When a constraint is added (eg. by using a TextView) the adapter must be filtered:

public void onTextChanged(CharSequence s, int start, int before, int count) {
  Log.d(LOG_TAG, "Filter:"+s);
  if (m_slvAdapter!=null) {
    m_Adapter.getFilter().filter(s);
  }
}
1
Chaudhary Amar On
       inputSearch.addTextChangedListener(new TextWatcher() {

        @Override
        public void onTextChanged(CharSequence s, int start, int before,
                int count) {
            // TODO Auto-generated method stub
            adapter_members.getFilter().filter(s.toString());
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count,
                int after) {
            // TODO Auto-generated method stub

        }

        @Override
        public void afterTextChanged(Editable s) {
            // TODO Auto-generated method stub

        }
0
Emad Easa On

here is my approach since I could not find anything that helped me. I don´t know if there is a better solution, but at least it is a working one.

  1. First you need an EditText element in the layout where you have your listview, let´s call it inputSearch.

  2. Declare a global variable from type MatrixCursor in your activity, which will be used later for filteres results - this special cursor is a cursor which makes it possible to handles elements by simple functions like addRow():

    MatrixCursor lastValidSearchResult;

  3. Somewhere in your code, where you initialize your cursor, with which you will feed your cursor adapter, save the result of the cursor also in lastValidSearchResult:

    Cursor myCursor = myDbObj.doMyQuery();
    lastValidSearchResult = myCursor;
  1. For the EditText element you implement the addTextChangedListener method like this:
inputSearch.addTextChangedListener(new TextWatcher() {
        @Override
        public void onTextChanged(CharSequence cs, int arg1, int arg2, int arg3) {
            // yourCustomCursorAdapter is the adapter for which you implemented a custom class - so in your case of type display adapter             
            YourCurrentActivity.this.yourCustomCursorAdapter.getFilter().filter(cs.toString());
            YourCurrentActivity.this.yourCustomCursorAdapter.notifyDataSetChanged();
        }
        
        @Override
        public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { }
        @Override
        public void afterTextChanged(Editable arg0) {}
    });
  1. Last but not least the code snippet which preforms the filter. For this you need to implement the code for the function setFilterQueryProvider of your custom adapter (in this case the object from DisplayAdapter):
    yourCustomCursorAdapter.setFilterQueryProvider(new FilterQueryProvider() {

        // constraint is the variable which is passed from your text change listener
        public Cursor runQuery(CharSequence constraint) {
            // if the search string is empty, then return the original cursor with all results from the original query
            if(constraint.equals(""))
            {
                return myCursor;
            }
            MatrixCursor filteredValues = new MatrixCursor(new String[]{/*Same columns as your original cursor myCursor*/);

            while(myCursor.moveToNext()) {
                String smth = myCursor.getString(myCursor.getColumnIndex(validColumnIndex);
                String smth2 = ....
                //Some condition to check previous data is not matched and only then add row
                if (smth.toLowerCase().contains(constraint.toString().toLowerCase())) {
                    filteredValues.addRow(new String[]{smth, ...});
                }
            }
            // do not forget next line since you manipulated your original cursor with moveToNext
            yourCursor.moveToFirst();

            // If your search value was not found at all return the last valid search result
            if(filteredValues.getCount() == 0)
            {                   
                return lastValidSearchResult;
            }
            // else you can save the new correct search result and return the results
            lastValidSearchResult = filteredValues;
            return filteredValues; //now your adapter will have the new filtered content
        }});