Android Customs

Custom Array Adapter for Drop-Down, AutoCompleteTextView and for Spinner

Introduction :

In Android development, any time we want to show a vertical list of scrollable items we will use a AutoCompleteTextView, MultiAutoCompleteTextView, Spinners and Any other Dropdown which has data populated using an Adapter. The simplest adapter to use is called an ArrayAdapter because the adapter converts an ArrayList of objects into View items loaded into the AutoCompleteTextView, MultiAutoCompleteTextView, Spinners and Any other Dropdown container.

The ArrayAdapter fits in between an ArrayList (data source of String) and the AutoCompleteTextView, MultiAutoCompleteTextView, Spinners and Any other Dropdown (visual representation) and configures two aspects:

  • Which array to use as the data source for the list
  • How to convert any given item in the array into a corresponding View object

CustomArrayAdapter is a single but All-In-one-Setup for all type of AutoCompleteTextView, MultiAutoCompleteTextView, Spinners and Any other Dropdown.

AutoCompleteTextView Control : A AutoCompleteTextView is a view that is similar to EditText, except that it shows a list of completion suggestions automatically while the user is typing. The list of suggestions is displayed in drop down menu.

MultiAutoCompleteTextView Control : A MultiAutoCompleteTextView is a view that is similar to AutoCompleteTextView, except that with a list of completion suggestions automatically while the user is typing. The list of suggestions is displayed in drop down menu offers suggestion for every token in the sentence. We can specify what is the delimiter between tokens like (“,”).

Using a Basic ArrayAdapter :

To use a basic ArrayAdapter, you just need to initialize the adapter and attach the adapter to the ListView. First, we initialize the adapter:


How to Implement?

Simple Steps and Points explain you how can you implement Original and Customize this Array Adapter by your own Good hand, and let you know there Basic Ideology behind this creation :

Like as shown Below :

ArrayAdapter<String> adapter = new ArrayAdapter<>(activity, R.layout.simple_list_item_1, data);
adapter.setDropDownViewResource(R.layout.simple_spinner_dropdown);

The ArrayAdapter requires a declaration of the type of the item to be converted to a View (a String in this case) and then accepts three arguments: context (activity instance), XML item layout,and the array of data. Note that we’ve chosen simple_list_item_1.xml. which is a simple TextView as the layout for each of the items.
Now, we just need to connect this adapter to a AutoCompleteTextView to be populated:

Like as shown Below :

AutoCompleteTextView autoCompleteTextView=view.findViewById(R.id.id_auto_complete_text_view);
autoCompleteTextView.setAdapter(arrayAdapter);
autoCompleteTextView.setThreshold(1);

By default, this will now convert each item in the data array into a view by calling toString on the item and then assigning the result as the value of a TextView (simple_list_item_1.xml) that is displayed as the row for that data item. If the app requires a more complex translation between item and View then we need to create a custom ArrayAdapter instead.

Using a Custom ArrayAdapter :

1. Class Implementation:

Now this will help you in creating a custom class which extend ArrayAdapter Class to get there basic methods and function calls and code to put your custom views :

Step 1 : By Extending Array Adapter Class : This is a basic need you extending there class because it will provide you all basic needs:

public class CustomArrayAdapter extends ArrayAdapter {
    public CustomArrayAdapter(Context context, int resource, ArrayList<String> storeDataLst) {
        super(context, resource, storeDataLst);
    }
}

You just need to Extend and need only one Constructor to Override you can leave rest of them for auto use.

Step 2 : Need to override basic methods :

These will help you handling customs clicks and custom item selection feature with FilterList and also getView() Method for your views customization :

@Override
public int getCount() {
    return super.getCount();
}
@Nullable
@Override
public Object getItem(int position) {
    return super.getItem(position);
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
    return super.getView(position, convertView, parent);
}
@NonNull
@Override
public Filter getFilter() {
    return super.getFilter();
}

Step 3 : Create ListFilter Class and Override its Method :

A filter constrains data with a filtering pattern. Filtering operations performed by calling CharSequence performed asynchronously. When these methods are called, a filtering request is posted in a request queue and processed later.

public class ListFilter extends Filter {
    @Override
    protected FilterResults performFiltering(CharSequence constraint) {
        return null;
    }
    @Override
    protected void publishResults(CharSequence constraint, FilterResults results) {
    }
}

This will be enough with ArrayAdapter, now lets deep in Customization and make your Custom View like shown in screens :

Now our CustomArrayAdapter Class will be look like :

public class CustomArrayAdapter extends ArrayAdapter {
   
    public CustomArrayAdapter(Context context, int resource, ArrayList<String> storeDataLst) {
        super(context, resource, storeDataLst);
    }
    @Override
    public int getCount() {
        return super.getCount();
    }
    @Nullable
    @Override
    public Object getItem(int position) {
        return super.getItem(position);
    }
    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
        return super.getView(position, convertView, parent);
    }
    @NonNull
    @Override
    public Filter getFilter() {
        return super.getFilter();
    }
    public class ListFilter extends Filter {
        @Override
        protected FilterResults performFiltering(CharSequence constraint) {
            return null;
        }

        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {

        }
    }
}

Step 4 : Basic variable Initialization to handle data at your Own :

Start with basic Global Variable Initialization with Class Private Specifiers :

public class CustomArrayAdapter extends ArrayAdapter {
    /**
     * Your Data list of String which have a list of entryies shown in View
     */
    private ArrayList<String> dataList;
    /**
     * Your Activity Context need for basic feature use
     */
    private Context mContext;
    /**
     * Your Single Item XML Layout Id will be in used for making Your Custom Array Adapter View
     */
    private int itemLayout;

    /**
     * Filter List for handling data changing, range changes and managing data globally
     */
    private ListFilter listFilter = new ListFilter();
    /**
     * Another array used to perform some in-module operations
     */
    private List<String> dataListAllItems;
    /**
     * @param mContext Context of your Activity
     * @param itemLayout Single Item Layout Id
     * @param dataList    Data List
     */
    public CustomArrayAdapter(Context mContext, int itemLayout, ArrayList<String> dataList) {
        super(context, itemLayout, dataList);
        this.dataList = dataList;
        this.mContext= mContext;
        this.itemLayout = itemLayout;
    }
.......
}

Next lets complete some routine calls and changes those are basic in use like :

@Override
public int getCount() {
    return dataList.size();
}
@Override
public String getItem(int position) {
    return dataList.get(position);
}
@NonNull
@Override
public Filter getFilter() {
    return listFilter;
}

Step 5 : ItemLayout.xml File for Single Adapter Item View :

This will provide you your own custom xml creation for making your view what it look like :

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <android.support.v7.widget.CardView
        style="@style/CardStyleDark"
        android:layout_width="@dimen/dimen_dropdown_view_width"
        android:layout_height="@dimen/dimen_dropdown_view_height"
        android:layout_marginBottom="@dimen/dimen_common_margin_very_small"
        android:layout_marginLeft="@dimen/dimen_common_margin_very_small"
        android:layout_marginRight="@dimen/dimen_common_margin_very_small"
        android:layout_marginTop="@dimen/dimen_common_margin_very_small"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/id_text"
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_marginLeft="@dimen/dimen_common_margin"
                android:layout_marginStart="@dimen/dimen_common_margin"
                android:layout_weight="1"
                android:ellipsize="end"
                android:gravity="center_vertical"
                android:maxLines="2"
                android:textColor="@color/colorTextBlack"
                android:textSize="@dimen/dimen_dropdown_text_size_title" />

            <android.support.v7.widget.AppCompatImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:tint="@color/colorIconLight"
                app:srcCompat="@drawable/icon_vd_right" />
        </LinearLayout>

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

Let me tell you my own custom Dimens and Colors implementation, you can change them as your own :

dimen_common_margin_very_small = "2dp"
@dimen/dimen_dropdown_view_width="250dp"
dimen_common_margin_very_small = "2dp"
@dimen/dimen_dropdown_view_radious = "21dp"
@color/colorIconLight =#666666
@dimen/dimen_dropdown_text_size_title ="15dp"
@color/colorTextBlack =#262626

 

CardStyleDark file look like :

<style name="CardStyleDark" parent="CardStyleDark.Parent" />
<style name="CardStyleDark.Parent" parent="Base.CardView">
    <item name="cardCornerRadius">@dimen/dimen_dropdown_view_radious</item>
    <item name="cardBackgroundColor">@android:color/white</item>
    <item name="cardPreventCornerOverlap">false</item>
    <item name="cardElevation">2.5dp</item>
</style>

Step 6 : Implement view into ArrayAdpater List Item :

@NonNull
@Override
public View getView(int position, View view, @NonNull ViewGroup parent) {
    if (view == null) {
        view = LayoutInflater.from(parent.getContext()).inflate(itemLayout, parent, false);
       view.setBackgroundColor(mContext.getResources().getColor(android.R.color.transparent));
       TextView itemTextView = view.findViewById(R.id.id_text);
      itemTextView.setText(getItem(position)); 
} 
return view;
}

Now in this code : you can find how your custom view resource ItemLayout inflate and TextView itemTextView can get data from getItem(position), this will call again and again for new item upto array size of DataListItem just like onBindListener and OnCreateView of Recycler View and add your Item for View making.

Step 7 : Set ListFIter for Array Adapter basic operation :

Just like operation of matching your entry with list items, sort and filter your list according to AutoCompleteTextView and MultiAutoCompleteTextView.

public class ListFilter extends Filter {
    private Object lock = new Object();
    @Override
    protected FilterResults performFiltering(CharSequence prefix) {
        FilterResults results = new FilterResults();
        if (dataListAllItems == null) {
            synchronized (lock) {
                dataListAllItems = new ArrayList<>(dataList);
            }
        }
        if (prefix == null || prefix.length() == 0) {
            synchronized (lock) {
                results.values = dataListAllItems;
                results.count = dataListAllItems.size();
            }
        } else {
            final String searchStrLowerCase = prefix.toString().toLowerCase();
            ArrayList<String> matchValues = new ArrayList<String>();
            for (String dataItem : dataListAllItems) {
                if (dataItem.toLowerCase().startsWith(searchStrLowerCase)) {
                    matchValues.add(dataItem);
                }
            }
            results.values = matchValues;
            results.count = matchValues.size();
        }
        return results;
    }
    @Override
    protected void publishResults(CharSequence constraint, FilterResults results) {
        if (results.values != null) {
            dataList = (ArrayList<String>) results.values;
        } else {
            dataList = null;
        }
        if (results.count > 0) {
            notifyDataSetChanged();
        } else {
            notifyDataSetInvalidated();
        }
    }
}

This will be end of your complete implementation with Overall  calls and code customization.

Compelete Code Look Like :

public class CustomArrayAdapter extends ArrayAdapter {

    /**
     * Your Data list of String which have a list of entryies shown in View
     */
    private ArrayList<String> dataList;
    /**
     * Your Activity Context need for basic feature use
     */
    private Context mContext;
    /**
     * Your Single Item XML Layout Id will be in used for making Your Custom Array Adapter View
     */
    private int itemLayout;

    /**
     * Filter List for handling data changing, range changes and managing data globally
     */
    private ListFilter listFilter = new ListFilter();
    /**
     * Another array used to perform some in-module operations
     */
    private List<String> dataListAllItems;


    /**
     * @param context Context of your Activity
     * @param resource Single Item Layout Id
     * @param storeDataLst Data List
     */
    public CustomArrayAdapter(Context context, int resource, ArrayList<String> storeDataLst) {
        super(context, resource, storeDataLst);
        dataList = storeDataLst;
        mContext = context;
        itemLayout = resource;
    }

    /**
     * Handle your view counting or size based of list size
     * @return return total item size
     */
    @Override
    public int getCount() {
        return dataList.size();
    }

    /**
     * return item based on click position
     * @param position item position in view
     * @return item
     */
    @Override
    public String getItem(int position) {
        return dataList.get(position);
    }

    /**
     * Used in filter operation
     * @return listFilter
     */
    @NonNull
    @Override
    public Filter getFilter() {
        return listFilter;
    }

    /**
     * View customization perform here with your custom view and there elements
     * @param position Position of current item
     * @param view View object
     * @param parent ViewGroup
     * @return View Object
     */
    @NonNull
    @Override
    public View getView(int position, View view, @NonNull ViewGroup parent) {
        if (view == null) {
            view = LayoutInflater.from(parent.getContext())
                    .inflate(itemLayout, parent, false);
        }
        view.setBackgroundColor(mContext.getResources().getColor(android.R.color.transparent));
        TextView strName = view.findViewById(R.id.id_text);
        strName.setText(getItem(position));
        return view;
    }


    public class ListFilter extends Filter {
        private Object lock = new Object();

        @Override
        protected FilterResults performFiltering(CharSequence prefix) {
            FilterResults results = new FilterResults();
            if (dataListAllItems == null) {
                synchronized (lock) {
                    dataListAllItems = new ArrayList<>(dataList);
                }
            }

            if (prefix == null || prefix.length() == 0) {
                synchronized (lock) {
                    results.values = dataListAllItems;
                    results.count = dataListAllItems.size();
                }
            } else {
                final String searchStrLowerCase = prefix.toString().toLowerCase();

                ArrayList<String> matchValues = new ArrayList<String>();

                for (String dataItem : dataListAllItems) {
                    if (dataItem.toLowerCase().startsWith(searchStrLowerCase)) {
                        matchValues.add(dataItem);
                    }
                }

                results.values = matchValues;
                results.count = matchValues.size();
            }

            return results;
        }

        @Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
            if (results.values != null) {
                dataList = (ArrayList<String>) results.values;
            } else {
                dataList = null;
            }
            if (results.count > 0) {
                notifyDataSetChanged();
            } else {
                notifyDataSetInvalidated();
            }
        }
    }
}

Now lets use this into your Own view :

Step 8: Now Lets call this into your Activity of Fragment with AutoCompleteTextView :

AutoCompleteTextView autoCompleteTextView=view.findViewById(R.id.id_auto_complete_text_view);
CustomArrayAdapter adapter = new CustomArrayAdapter(getContext(),
        R.layout.ItemLayout, dataArrayList);
autoCompleteTextView.setAdapter(adapter);
autoCompleteTextView.setThreshold(1);

Step 9: Now Lets call this into your Activity of Fragment with MuliAutoCompleteTextView :

MultiAutoCompleteTextView multiAutoCompleteTextView=view.findViewById(R.id.id_auto_complete_text_view);
CustomArrayAdapter adapter = new CustomArrayAdapter(getContext(),
R.layout.ItemLayout, dataArrayList);
multiAutoCompleteTextView.setAdapter(adapter);
multiAutoCompleteTextView.setThreshold(1);
multiAutoCompleteTextView.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());

this will be the End with Coding now you can check your Code by Running this into Device or Emulator.

In this way, we have learnt how we can customize and code like a charm for own quality work into Each Element for making app like yours CustomArrayAdapter is a single but all in on setup for all type of AutoCompleteTextView, MultiAutoCompleteTextView, Spinners and Any other Dropdown.

If you have any problem than you can refer this video

Thank you,

For any queries please feel free to comment…!

About the author

Jatin Sahgal

Jamun is a collection of cool android Libraries, Elements classes and more over Utils who help other developer to build a different quality android product by putting material design themes in there mind.

Leave a Comment