SAMPLE USING
<ArrayAdapter> J
Let’s try a sample using ArrayAdapter that
illustrated 7 techniques:
1. How to use Custom Adapter Class.2. How to use Spinner.
3. How to receive Spinner selected item.
4. How to pass data to Custom Adapter Class.
5. How to get data from Custom Adapter Class.
6. How to make and use Custom ArrayAdapter Filter
7. How to Interface Activity from Custom Adapter Class
The maximum number of students in a playgroup can be changed by the SeekBar on top of the screen. You can select any student from the ListView with the CheckBox. When there is students selected, a Button on the right of the Spinner is shown. The Button shows the group number and number of students selected. Click that Button will put all selected students into the corresponding playgroup and add a new item in the Spinner dropdown list. Note that the first selected student is assigned as leader of that group. Click on the Spinner and its dropdown list comes up. It shows the number of students in the group, group number and leader. Choose any playgroup, the ListView will display all the members of the playgroup with leader highlighted. Choose “Not grouped” from Spinner to continue setting the other playgroups. When all students have set, “Not grouped” is deleted from the Spinner.
In the sample code, comments are added for
corresponding technique and here are additional explanations:
Technique 1 - How to use Custom Adapter ClassSince we inflate our custom layout inside the getView() method, no need to pass layout for custom adapter.
For Spinner, it should be noted that that are two type of views. The “Selected” item is always shown which controlled by getView() method. When the Spinner is expanded, the item view in the dropdown list is controlled by getDropDownView() method. If dataset is a simple String array [or class with toString() method)], the dropdown list can be simply set with setDropDownViewResource() using a layout of single TextView [android:id="@android:id/text1"].
As “Selected” item is always shown in Spinner. Therefore most of the time, OnItemSelectedListener() is used rather than OnItemClickListener().
The
simplest way is to create public methods inside the Adapter, then it can pass
data into the Adapter.
The simplest way is to create public methods that have return values inside the Adapter, then it can get data from Adapter to Activity.
Remember to create a backup list of the original dataset and filter data out from there. The other important thing is that Filter is an async operation. The method, performFiltering(), is on another thread and after it is completed, publishResults() is called on UI thread to display result. Therefore when filter a large dataset that takes long time, you may show a progress bar in Activity before filtering and in publishResults(), call the Activity to dismiss the progress bar [Technique 7].
There are a few different ways to communicate between Activity and Adapter, the one shown here is using Interface.
Sample is tested with Android Studio. Codes
in the files (3 Java and 2 XML) are below.
SampleDataItem.java:
class SampleDataItem { private int playgroup; private int num; private String name; private String description; private boolean selected; SampleDataItem(int playgroup, int num, String name, String description){ this.playgroup = playgroup; this.num = num; this.name = name; this.description = description; selected = false; } int getPlaygroup() {return playgroup;} int getNum() {return num;} String getName() {return name;} String getDescription() {return description;} boolean isSelected() {return selected;} void setPlaygroup(int groupNum) {this.playgroup = groupNum;} void setNum(int newNum) {this.num = newNum;} void setSelected(boolean flag) { this.selected = flag;} @Override public String toString() {return getNum() + " " + getName();} }
MainActivity.java:
@SuppressWarnings({"NullableProblems", "ConstantConditions"}) // 7. Code for Interface Activity from Custom Adapter Class sample. public class MainActivity extends Activity implements MyArrayAdapter.MyArrayAdapterCallbacks{ static final int NUM_OF_ITEMS_IN_LIST = 30; Button mbtAddGroup; ListView mListView; SeekBar mSeekBar; Spinner mSpinner; TextView mtvPlaygroupSize; ArrayList<SampleDataItem> mStudentList = new ArrayList<>(); ArrayList<SampleDataItem> mPlaygroupList = new ArrayList<>(); int mNewGroupNum = 1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); getViews(); // Setup sample DataSet. for(int i=1; i<=NUM_OF_ITEMS_IN_LIST; i++){ SampleDataItem student = new SampleDataItem(0, i, "Student<"+i+">", "Description<"+i+">"); mStudentList.add(student); } mPlaygroupList.add(new SampleDataItem(0, NUM_OF_ITEMS_IN_LIST, "Not Grouped", "")); mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); // Spinner do not have ChoiceMode. mSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener(){ @Override public void onStartTrackingTouch(SeekBar seekBar) {} @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { // 5. Code for getting data from Custom Adapter Class sample. MyArrayAdapter tmpAdapter = (MyArrayAdapter) mListView.getAdapter(); if(progress < tmpAdapter.getSelectedItemsCount()){ progress = tmpAdapter.getSelectedItemsCount(); }else if(progress < 2){ progress = 2; } seekBar.setProgress(progress); mtvPlaygroupSize.setText(String.valueOf(progress)); } @Override public void onStopTrackingTouch(SeekBar seekBar) { // 4. Code for passing data to Custom Adapter Class sample. MyArrayAdapter tmpAdapter = (MyArrayAdapter) mListView.getAdapter(); tmpAdapter.setPlaygroupSize(seekBar.getProgress()); } }); mbtAddGroup.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // 5. Code for getting data from Custom Adapter Class sample. MyArrayAdapter tmpAdapter = (MyArrayAdapter) mListView.getAdapter(); ArrayList<SampleDataItem> selectedList = tmpAdapter.updateLists(mNewGroupNum); int groupSize = selectedList.size(); tmpAdapter.getFilter().filter("0"); mPlaygroupList.add(new SampleDataItem(mNewGroupNum, groupSize, "in Group " + mNewGroupNum, "Leader: " + selectedList.get(0).getName())); SampleDataItem notGrouped = mPlaygroupList.get(0); groupSize = notGrouped.getNum() - groupSize; notGrouped.setNum(groupSize); if(groupSize == 0){ mPlaygroupList.remove(0); tmpAdapter.getFilter().filter("1"); } ArrayAdapter tmpAdapter1 = (ArrayAdapter) mSpinner.getAdapter(); tmpAdapter1.notifyDataSetChanged(); mNewGroupNum++; mbtAddGroup.setVisibility(View.INVISIBLE); } }); sampleCodeForArrayAdapter(); } // Get Views reference of main layout. private void getViews(){ mtvPlaygroupSize = (TextView) findViewById(R.id.tvGroupSize); mSeekBar = (SeekBar) findViewById(R.id.seekBar1); mSpinner = (Spinner) findViewById(R.id.spinner); mbtAddGroup = (Button) findViewById(R.id.bt_group); mListView = (ListView) findViewById(R.id.listView1); mtvPlaygroupSize.setText(String.valueOf(mSeekBar.getProgress())); mbtAddGroup.setText(String.valueOf(mNewGroupNum)); mbtAddGroup.setVisibility(View.INVISIBLE); } /** * Sample Code for using ArrayAdapter: */ public void sampleCodeForArrayAdapter(){ // 1. Code for Custom Adapter Class sample. MyArrayAdapter mStudentAdapter = new MyArrayAdapter(this, mStudentList); // 2. Code for Spinner sample. ArrayAdapter mPlaygroupAdapter = new ArrayAdapter<SampleDataItem>(this, android.R.layout.simple_list_item_1, mPlaygroupList){ @Override public View getDropDownView(int position, View convertView, ViewGroup parent) { if(convertView == null) convertView = getLayoutInflater() .inflate(R.layout.custom_list_items, parent, false); TextView tvId = (TextView) convertView.findViewById(R.id.tvId); TextView tvName = (TextView) convertView.findViewById(R.id.tvName); TextView tvDescription = (TextView) convertView.findViewById(R.id.tvDescription); CheckBox cb = (CheckBox) convertView.findViewById(R.id.cb); SampleDataItem currentItem = getItem(position); if(currentItem != null) { tvId.setText(String.valueOf(currentItem.getNum())); tvName.setText(currentItem.getName()); tvDescription.setText(currentItem.getDescription()); cb.setVisibility(View.INVISIBLE); } if(currentItem.getPlaygroup() == 0) convertView.setBackgroundColor(Color.TRANSPARENT); else if((position % 2) == 0) convertView.setBackgroundColor(0xff777700); else convertView.setBackgroundColor(0xff007777); return convertView; } }; // 3. Code for Spinner item select sample. mSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener(){ @Override public void onItemSelected(AdapterView<?> adapterView, View view, int position, long id) { SampleDataItem selectedItem = (SampleDataItem) adapterView.getItemAtPosition(position); MyArrayAdapter tmpAdapter = (MyArrayAdapter) mListView.getAdapter(); // 6. Code for Custom ArrayAdapter Filter sample. tmpAdapter.getFilter().filter(String.valueOf(selectedItem.getPlaygroup())); if((selectedItem.getPlaygroup() == 0)&&(tmpAdapter.getSelectedItemsCount() > 0)) mbtAddGroup.setVisibility(View.VISIBLE); else mbtAddGroup.setVisibility(View.INVISIBLE); } @Override public void onNothingSelected(AdapterView<?> adapterView) {} }); mSpinner.setAdapter(mPlaygroupAdapter); mListView.setAdapter(mStudentAdapter); } // 7. Code for Interface Activity from Custom Adapter Class sample. @Override public void adapterCheckBoxClicked(int selectedCount) { if(selectedCount == 0) mbtAddGroup.setVisibility(View.INVISIBLE); else{ String msg = mNewGroupNum + "-" + selectedCount; mbtAddGroup.setText(msg); mbtAddGroup.setVisibility(View.VISIBLE); } } @Override public void getLeaderName() { SampleDataItem selectedItem = (SampleDataItem) mSpinner.getSelectedItem(); MyArrayAdapter tmpAdapter = (MyArrayAdapter) mListView.getAdapter(); tmpAdapter.setLeader(selectedItem.getDescription().substring(8)); } }
MyArrayAdapter.java:
@SuppressWarnings({"ConstantConditions", "SuspiciousMethodCalls", "NullableProblems"}) class MyArrayAdapter extends ArrayAdapter<SampleDataItem> { private LayoutInflater mInflater; private int mPlaygroupSize = 5; private List<Integer> mSelectedList = new ArrayList<>(); // 6. Code for Custom ArrayAdapter Filter sample. private List<SampleDataItem> mBackupList = new ArrayList<>(); // 7. Code for Interface Activity from Custom Adapter Class sample. private MyArrayAdapterCallbacks mCallbacks; MyArrayAdapter(Context context, List<SampleDataItem> objects) { super(context, 0, objects); mInflater = LayoutInflater.from(context); // 6. Code for Custom ArrayAdapter Filter sample. mBackupList.addAll(objects); // 7. Code for Interface Activity from Custom Adapter Class sample. mCallbacks = (MyArrayAdapterCallbacks) context; } private class ViewHolder{ TextView tvId; TextView tvName; TextView tvDescription; CheckBox cb; ViewHolder(View convertView){ this.tvId = (TextView) convertView.findViewById(R.id.tvId); this.tvName = (TextView) convertView.findViewById(R.id.tvName); this.tvDescription = (TextView) convertView.findViewById(R.id.tvDescription); this.cb = (CheckBox) convertView.findViewById(R.id.cb); } } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; // Prepare views ready for data if(convertView == null){ convertView = mInflater.inflate(R.layout.custom_list_items, parent, false); viewHolder = new ViewHolder(convertView); convertView.setTag(viewHolder); }else{ viewHolder = (ViewHolder) convertView.getTag(); } viewHolder.cb.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View view) { // Get view, position and DataList(position) CheckBox buttonView = (CheckBox) view; int pos = (int) buttonView.getTag(); SampleDataItem selectedItem = getItem(pos); // Update DataLists selectedItem.setSelected(buttonView.isChecked()); if(!buttonView.isChecked()){ mSelectedList.remove((Object)pos); }else{ if(mSelectedList.size() < mPlaygroupSize){ mSelectedList.add(pos); }else{ Toast.makeText(getContext(), "Group already Full!!", Toast.LENGTH_SHORT).show(); selectedItem.setSelected(false); buttonView.setChecked(false); } } // Update all UI views by notifyDataSetChanged(); // 7. Code for Interface Activity from Custom Adapter Class sample. mCallbacks.adapterCheckBoxClicked(mSelectedList.size()); } }); // Get data from DataList and set Views accordingly SampleDataItem currentItem = getItem(position); if(currentItem != null) { viewHolder.tvId.setText(String.valueOf(currentItem.getNum())); viewHolder.tvName.setText(currentItem.getName()); viewHolder.tvDescription.setText(currentItem.getDescription()); viewHolder.cb.setChecked(currentItem.isSelected()); if(currentItem.getPlaygroup() != 0) viewHolder.cb.setVisibility(View.INVISIBLE); else viewHolder.cb.setVisibility(View.VISIBLE); if(currentItem.isSelected()) convertView.setBackgroundColor(Color.CYAN); else convertView.setBackgroundColor(Color.TRANSPARENT); } // Save position to CheckBox, so position can be retrieved when CheckBox is clicked viewHolder.cb.setTag(position); return convertView; } // 6. Code for Custom ArrayAdapter Filter sample. @Override public Filter getFilter() {return new SampleFilter();} @SuppressWarnings("unchecked") private class SampleFilter extends Filter{ @Override protected FilterResults performFiltering(CharSequence charSequence) { int groupNum = Integer.parseInt(charSequence.toString()); List<SampleDataItem> filteredList = new ArrayList<>(); SampleDataItem tmpItem; for(int i=0; i<mBackupList.size(); i++){ tmpItem = mBackupList.get(i); if(tmpItem.getPlaygroup() == groupNum) filteredList.add(tmpItem); } FilterResults filterResults = new FilterResults(); filterResults.count = filteredList.size(); filterResults.values = filteredList; return filterResults; } @Override protected void publishResults(CharSequence charSequence, FilterResults filterResults) { clear(); addAll((List<SampleDataItem>) filterResults.values); if(!charSequence.toString().equals("0")){ // 7. Code for Interface Activity from Custom Adapter Class sample. mCallbacks.getLeaderName(); } } } // 4. Code for passing data to Custom Adapter Class sample. void setPlaygroupSize(int newSize) {mPlaygroupSize = newSize;} void setLeader(String leaderName){ SampleDataItem tmpItem; for(int i=0; i<getCount(); i++){ tmpItem = getItem(i); if(tmpItem.getName().equals(leaderName)) tmpItem.setSelected(true); } } // 5. Code for getting data from Custom Adapter Class sample. int getSelectedItemsCount() {return mSelectedList.size();} ArrayList<SampleDataItem> updateLists(int groupNum) { ArrayList<SampleDataItem> outList = new ArrayList<>(); SampleDataItem tmpItem; for(int i=0; i<mSelectedList.size(); i++){ tmpItem = getItem(mSelectedList.get(i)); tmpItem.setPlaygroup(groupNum); outList.add(tmpItem); tmpItem.setSelected(false); } mSelectedList.clear(); return outList; } // 7. Code for Interface Activity from Custom Adapter Class sample. interface MyArrayAdapterCallbacks { void adapterCheckBoxClicked(int selectedCount); void getLeaderName(); } }
activity_main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <LinearLayout android:id="@+id/llHeader" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#aa7777aa" android:padding="7dp" > <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/tvGroupSize" android:layout_weight="5" android:layout_marginLeft="10dp" android:layout_marginStart="10dp" android:textSize="20sp" android:gravity="center" /> <SeekBar android:id="@+id/seekBar1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:max="9" android:progress="5" android:layout_gravity="center_vertical" /> </LinearLayout> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#ff00ff00" android:layout_below="@+id/llHeader" android:id="@+id/rlSpinner"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/bt_group" android:layout_alignParentEnd="true" android:layout_alignParentRight="true" android:gravity="center" android:layout_centerVertical="true" /> <Spinner android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/spinner" android:layout_margin="7dp" android:layout_toLeftOf="@+id/bt_group" android:layout_toStartOf="@+id/bt_group" /> </RelativeLayout> <ListView android:id="@+id/listView1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/rlSpinner" android:layout_margin="7dp" > </ListView> </RelativeLayout>
custom_list_items.xml:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/rlRowView" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:paddingBottom="15dp" android:paddingTop="15dp" > <CheckBox android:id="@+id/cb" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentEnd="true" android:layout_alignParentRight="true" android:focusable="false" /> <TextView android:id="@+id/tvId" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/cb" android:layout_alignParentLeft="true" android:layout_alignParentStart="true" android:layout_alignParentTop="true" android:layout_marginBottom="5dp" android:layout_marginTop="5dp" android:gravity="center" android:minWidth="70dp" android:textSize="25sp" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/cb" android:layout_alignParentTop="true" android:layout_toEndOf="@id/tvId" android:layout_toLeftOf="@+id/cb" android:layout_toRightOf="@id/tvId" android:layout_toStartOf="@id/cb" android:id="@+id/tvName" /> <TextView android:id="@+id/tvDescription" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/tvId" android:gravity="center" android:textSize="20sp" /> </RelativeLayout>