Introduction
In this tip, we will look into a common scenario where the Items in the Listview
are filtered according to the Spinner
(i.e., Dropdown
control in Android) selection. We will use Entity or Model Classes for populating our Spinner
and ListView
controls. Adapter is also needed to populate the data. For the sake of simplicity, array of objects are used to demonstrate the data population. We use City
and Address
Entities as an example.
Using the Code
Step 1
We start with creating a new sample project and add a blank activity. Select the appropriate API levels as per your choice. Now, we have a default MainActivity
class file and a corresponding layout file created. From the next step onwards, we will be only focusing on the vital parts needed to accomplish our task of populating and filtering the listview
with spinner.
Step 2
Create both the Model Classes needed, i.e., State
and Address
.
public class City {
private long id;
private String city;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
public class Address {
private long id;
private long cityId;
private String buildingName;
private String street;
private String area;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public long getCityId() {
return cityId;
}
public void setCityId(long cityId) {
this.cityId = cityId;
}
public String getBuildingName() {
return buildingName;
}
public void setBuildingName(String buildingName) {
this.buildingName = buildingName;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getArea() {
return area;
}
public void setArea(String area) {
this.area = area;
}
}
Step 3
Now let's design the Layout
which needs Android Spinner
and Listview
Controls. Here, we use a Relative Layout with some basic padding and styling. And then, we have to design a simple ListItem
layout to display our filtered Address
es inside the ListView
.
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".MainActivity"
android:background="@color/background_floating_material_light">
<Spinner
android:id="@+id/citySpinner"
android:layout_width="fill_parent"
android:layout_height="50dp"
android:spinnerMode="dropdown"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:dropDownSelector="@android:drawable/btn_dropdown"
android:background="#0fddaf"
>
</Spinner>
<ListView
android:id="@+id/address_listview"
android:layout_below="@id/citySpinner"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:background="#00ff7f"
/>
</RelativeLayout>
address_listitem.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent">
<TextView
android:id="@+id/nameTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:textSize="18sp"
android:textStyle="bold"
android:layout_gravity="center_vertical">
</TextView>
<TextView
android:id="@+id/streetTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/nameTV"
android:textSize="13sp"
android:layout_gravity="center_vertical">
</TextView>
<TextView
android:id="@+id/areaTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/streetTv"
android:textSize="13sp"
android:layout_gravity="center_vertical">
</TextView>
</RelativeLayout>
Step 4
Create the Adapters for Data population. CityAdapter
takes type of City
which means its strongly typed. (It can be altered as per your needs to make it more generic by replacing City
by a generic parent type).
public class CityAdapter extends ArrayAdapter<City> {
private Context context;
private List<City> cityList;
public CityAdapter(Context context, int textViewResourceId,
List<City> values) {
super(context, textViewResourceId, values);
this.context = context;
this.cityList = values;
}
public int getCount(){
return cityList.size();
}
public City getItem(int position){
return cityList.get(position);
}
public long getItemId(int position){
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView view = new TextView(context);
view.setTextColor(Color.BLACK);
view.setGravity(Gravity.CENTER);
view.setText(cityList.get(position).getCity());
return view;
}
@Override
public View getDropDownView(int position, View convertView,
ViewGroup parent) {
TextView view = new TextView(context);
view.setTextColor(Color.BLACK);
view.setText(cityList.get(position).getCity());
view.setHeight(60);
return view;
}
}
Similarly,
Address Adapter
In this Adapter
Class, we have used common ViewHolder
pattern for optimizing performance and reusability of View. Also, note that our Adapter
implements Filterable
class which enables us to filter as per the Spinner
Selection. It's just the same behaviour as we do autocomplete search using an EditText
in Listview
. In our scenario, it will be much useful when using JSON queries to populate and filter which helps in filtering once the Data is fetching from Json API calls.
Filterable
Interface requires us to implement the getFilter
() method which enables us to call the getFilter()
method from our MainAcitivity.java class by returning a Filter
type. AddressFilter
innerclass serves the internal implementation of actual filtering and returns the filtered items implementing performfiltering
() and publishResults()
methods.
public class AddressAdapter extends BaseAdapter implements Filterable {
private LayoutInflater mLayoutInflater;
private List<Address> addressList;
private List<Address> addressFilterList;
private AddressFilter addressFilter;
private Context context;
public AddressAdapter(Context context, List data){
addressList = data;
addressFilterList=data;
mLayoutInflater = LayoutInflater.from(context);
this.context = context;
}
@Override
public int getCount() {
return addressList.size();
}
@Override
public Address getItem(int position) {
return addressList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View view, ViewGroup parent) {
View updateView;
ViewHolder viewHolder;
if (view == null) {
updateView = mLayoutInflater.inflate(R.layout.address_listitem, null);
viewHolder = new ViewHolder();
viewHolder.tvName = (TextView) updateView.findViewById(R.id.nameTV);
viewHolder.tvArea = (TextView) updateView.findViewById(R.id.areaTV);
viewHolder.tvStreet = (TextView) updateView.findViewById(R.id.streetTv);
updateView.setTag(viewHolder);
} else {
updateView = view;
viewHolder = (ViewHolder) updateView.getTag();
}
final Address item = getItem(position);
viewHolder.tvName.setText(item.getBuildingName());
viewHolder.tvArea.setText(item.getArea());
viewHolder.tvStreet.setText(String.valueOf(item.getStreet()));
return updateView;
}
@Override
public Filter getFilter() {
if (addressFilter == null) {
addressFilter = new AddressFilter();
}
return addressFilter;
}
static class ViewHolder{
TextView tvName;
TextView tvArea;
TextView tvStreet;
}
private class AddressFilter extends Filter
{
@Override
protected FilterResults performFiltering(CharSequence constraint) {
long cityId= Long.parseLong(constraint.toString());
FilterResults results = new FilterResults();
if (cityId > 0) {
ArrayList<Address> filterList = new ArrayList<Address>();
for (int i = 0; i < addressFilterList.size(); i++) {
if ( (addressFilterList.get(i).getCityId() )== cityId) {
Address address = addressFilterList.get(i);
filterList.add(address);
}
}
results.count = filterList.size();
results.values = filterList;
} else {
results.count = addressFilterList.size();
results.values = addressFilterList;
}
return results;
}
@Override
protected void publishResults(CharSequence constraint,
FilterResults results) {
addressList = (ArrayList<Address>)results.values;
notifyDataSetChanged();
}
}
}
And finally, our MainActivity.java which contains the Spinner
and ListView
.
public class MainActivity extends Activity implements AdapterView.OnItemSelectedListener {
private List<Address> addressEntityList = new ArrayList<Address>();
private List<City> cityEntityList = new ArrayList<City>();
private ListView listView;
private AddressAdapter adapter;
private CityAdapter cityAdapter;
private Spinner citySpinner;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
citySpinner = (Spinner)findViewById(R.id.citySpinner);
listView = (ListView) findViewById(R.id.address_listview);
cityAdapter = new CityAdapter(this,android.R.layout.simple_spinner_dropdown_item,loadDummyCities());
cityAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
citySpinner.setAdapter(cityAdapter);
citySpinner.setOnItemSelectedListener(this);
loadDummyAddress();
adapter = new AddressAdapter(this, addressEntityList);
listView.setAdapter(adapter);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
private List<City> loadDummyCities(){
cityEntityList = new ArrayList<City>();
City city1 = new City();
city1.setId(1);
city1.setCity("Kochi");
cityEntityList.add(city1);
City city2 = new City();
city2.setId(2);
city2.setCity("Bangalore");
cityEntityList.add(city2);
City city3 = new City();
city3.setId(3);
city3.setCity("Delhi");
cityEntityList.add(city3);
return cityEntityList;
}
private List<Address> loadDummyAddress(){
addressEntityList = new ArrayList<Address>();
Address address1 = new Address();
address1.setId(1);
address1.setCityId(1);
address1.setArea("Kalamassery");
address1.setBuildingName("Kinfra");
address1.setStreet("2nd");
addressEntityList.add(address1);
Address address2 = new Address();
address2.setId(2);
address2.setCityId(2);
address2.setArea("Banaswadi");
address2.setBuildingName("Sharmi");
address2.setStreet("2nd Cross");
addressEntityList.add(address2);
Address address3 = new Address();
address3.setId(3);
address3.setCityId(2);
address3.setArea("MG Road");
address3.setBuildingName("Carlton");
address3.setStreet("Church Street");
addressEntityList.add(address3);
Address address4 = new Address();
address4.setId(4);
address4.setCityId(1);
address4.setArea("Thrissur");
address4.setBuildingName("New");
address4.setStreet("Vatanappilly");
addressEntityList.add(address4);
return addressEntityList;
}
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
City city = cityAdapter.getItem(position);
adapter.getFilter().filter(Long.toString(city.getId()),new Filter.FilterListener() {
@Override
public void onFilterComplete(int count) {
}
});
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
}
}
Simple dummy lists are used here for the sake of simplicity. UI needs to be tweaked for proper look and feel as I haven't put much effort for the UI side. You can download the source code which is uploaded with the tip.
Points of Interest
Usually, it's a very common requirement we all face where ListView
s are filtered using a Spinner Control. I will appreciate your comments if you have any queries or doubts regarding the post. Happy coding. :)
History
- 13th April, 2015: Initial publication with source code