2017年1月9日 星期一


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 sample code is to let you group 30 students (with student id, name and description) into playgroups which can be reviewed by using the Spinner’s dropdown list.
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 Class
Since we inflate our custom layout inside the getView() method, no need to pass layout for custom adapter.


Technique 2 - How to use Spinner
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"].


Technique 3 - How to receive Spinner selected item
As “Selected” item is always shown in Spinner. Therefore most of the time, OnItemSelectedListener() is used rather than OnItemClickListener().


Technique 4 - How to pass data to Custom Adapter Class
The simplest way is to create public methods inside the Adapter, then it can pass data into the Adapter.


Technique 5 - How to get data from Custom Adapter Class
The simplest way is to create public methods that have return values inside the Adapter, then it can get data from Adapter to Activity.


Technique 6 - How to make and use Custom ArrayAdapter Filter
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].


Technique 7 - How to Interface Activity from Custom Adapter Class
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>

2017年1月8日 星期日


Something about item view in Adapter code J

Item view is defined in XML file but there are something in Adapter code should be known so that it can be used efficiently and error free.



1.          View Recycling: For example, a list is scrolled forward and so the first item view is disappeared while new item view is inflated on the bottom. Android does not delete the disappeared item view, it is passed to AdapterView’s getView() method as “convertView”. Therefore in code, it is always found in the beginning of getView() like “if(convertView == null) …” which is refer as View Recycling.

Android provides View Recycling which improves efficiency, so it should be implemented. The only exceptional case that I know is: there are many different item view types. This is because even a convertView is present, it may be a wrong type and new view needs to be inflated. For this situation, you may remove the check condition “if(convertView == null)” and inflate item view directly.

2.          View Holder: View Recycling gives back the whole item view but we normally do not operate directly on it with data, e.g. the item view is a RelativeLayout contains TextViews and CheckBoxes. Therefore in the getView() method, there is a list of codes “convertView.findViewById(…)” to find the view elements and then put data into them. However findViewById() is considered as having long execution time, so to avoid doing this on every data item, we use a View Holder class. View Holder is a brief layout/framework of the item view (convertView). When item view is created for the first time, we create a View Holder and attached it to item view by setTag(). Therefore when the view is recycled, we can use getTag() to get back this brief layout/framework rather than doing a list of findViewById().

For AdapterView, View Holder is optional but Android has further developed the idea and make a new View type, RecyclerView. For this one, it is more flexible with complex layout but View Holder is necessary.

3.          AdapterView’s ChildView: Most of the time, an AdapterView only display part of the dataset on the screen. Those visible item views are referred as child view. For the first or last item view, even it may only partly displayed, is also count as a child. One common mistake on beginner: try to get a “child view” that is not visible on the screen.

4.         notifyDataSetChanged(): This is one of the method that renders all the visual views again in the AdapterView. In the beginning of development, it should be already be used after data changed. This is because the ListView may have some design changes during development. Use this method helps to avoid making mistakes. Only when the app is come to final stage and notifyDataSetChanged() is causing performance issue [since all views are rendered again and it may takes time], use editor to find out the word “notifyDataSetChanged()” and replace it with codes that modify corresponding views directly.

2016年12月28日 星期三


SAMPLE USING <SimpleAdapter> J

Let’s continue with a sample using SimpleAdapter that illustrated 5 techniques:
1. How to use Android standard built-in layout.
2. How to receive ListView item click.
3. How to use custom layout.
4. How to use custom layout and override standard adapter method.
5. How to use Filter.



Codes in the files are below.

MainActivity.java:
public class MainActivity extends Activity {
   static final int EDIT_TYPE = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
   static final int NUM_OF_ITEMS_IN_LIST = 30;
   static final String ITEM_ID = "item_id";
   static final String ITEM_NAME = "item_name";
   static final String ITEM_CHECKED ="item_checked";

   EditText mEditTextFilter;
   ListView mListView;
   SeekBar mSeekBar;

   ArrayList<HashMap<String, Object>> mDataList = new ArrayList<>();

   SimpleAdapter mSimpleAdapter;

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

      /**
       *  Setup sample DataSet.
       */
      String items[] = getResources().getStringArray(R.array.list_items_sample);
      mDataList.clear();
      for(int i=1; i<NUM_OF_ITEMS_IN_LIST; i++){
         HashMap<String, Object> newItem = new HashMap<String, Object>();
         newItem.put(ITEM_ID, String.valueOf(i));
         newItem.put(ITEM_NAME, "Sample<"+items[i-1]+">");
         newItem.put(ITEM_CHECKED, false);
         mDataList.add(newItem);
      }

      /**
       *  The different choice modes of ListView / GridView / Spinner.
       */
//    mListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
      mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
//    mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);

      sampleCodeForSimpleAdapter();

      // 4. Code for Custom Layout and override standard adapter method sample.
//    mSeekBar.setVisibility(View.VISIBLE);
//    mSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener(){
//       @Override
//       public void onStartTrackingTouch(SeekBar seekBar) {}
//       @Override
//       public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
//          if(progress < 1){
//             progress = 1;
//             seekBar.setProgress(progress);
//          }
//          mSimpleAdapter.notifyDataSetChanged();
//       }
//       @Override
//       public void onStopTrackingTouch(SeekBar seekBar) {}
//    });

      // 5. Sample code for Making Filter.
//    mEditTextFilter.setVisibility(View.VISIBLE);
//    class FilterTextWatcher implements TextWatcher{
//       @Override
//       public void afterTextChanged(Editable s) {
//          mSimpleAdapter.getFilter().filter(s.toString());
//       }
//       @Override
//       public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
//       @Override
//       public void onTextChanged(CharSequence s, int start, int before, int count) {}
//    }
//    FilterTextWatcher mTextWatcher = new FilterTextWatcher();
//    mEditTextFilter.addTextChangedListener(mTextWatcher);
   }

   /**
    * Get Views reference of main layout.
    */
   private void getViews(){
      mEditTextFilter = (EditText) findViewById(R.id.etFilter);
      mSeekBar = (SeekBar) findViewById(R.id.seekBar1);
      mListView = (ListView) findViewById(R.id.listView1);
   }

   /**
    * Sample Code for using SimpleAdapter:
    */
   public void sampleCodeForSimpleAdapter(){
      // 1. Code for Built-in Layout sample.
      String[] from = {ITEM_NAME};
      int[] to = {android.R.id.text1};
      mSimpleAdapter = new SimpleAdapter(this, mDataList, android.R.layout.simple_list_item_1, from, to);

        // 2. Code for receive ListView item click sample.
//        mListView.setOnItemClickListener(new OnItemClickListener(){
//            @SuppressWarnings("unchecked")
//            @Override
//            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//                HashMap<String, String> selectedItem = (HashMap<String, String>) parent.getItemAtPosition(position);
//                int childPosition = position - parent.getFirstVisiblePosition();
//                String msg;
//                switch(childPosition){
//                case 0:
//                    msg = "Clicked 1st position on screen.\n";
//                    break;
//                case 1:
//                    msg = "Clicked 2nd position on screen.\n";
//                    break;
//                case 2:
//                    msg = "Clicked 3rd position on screen.\n";
//                    break;
//                default:
//                    msg = "Clicked " + (childPosition + 1) +"th position on screen.\n";
//                }
//                msg += selectedItem.get(ITEM_NAME) + "\nOriginal Position: ";
//                int originalPos = mDataList.indexOf(selectedItem);
//                Toast.makeText(getApplicationContext(), msg + originalPos, Toast.LENGTH_SHORT).show();
//            }
//        });
// 3. Code for Custom Layout sample.
// String[] from = {ITEM_ID, ITEM_NAME, ITEM_CHECKED}; // int[] to = {R.id.tvId, R.id.tvDescription, R.id.cb}; // mSimpleAdapter = new SimpleAdapter(this, mDataList, R.layout.custom_list_items, from, to); // 4. Code for Custom Layout and override standard adapter method sample. // mSimpleAdapter = new SimpleAdapter(this, mDataList, R.layout.custom_list_items, from, to){ // @Override // public View getView(int position, View convertView, ViewGroup parent) { // // Prepare views ready for data // convertView = super.getView(position, convertView, parent); // RelativeLayout layout = (RelativeLayout) convertView.findViewById(R.id.rlRowView); // CheckBox cb = (CheckBox) convertView.findViewById(R.id.cb); // // cb.setOnClickListener(new OnClickListener(){ // @SuppressWarnings("unchecked") // @Override // public void onClick(View view) { // CheckBox buttonView = (CheckBox) view; // // Get view, position and DataList(position) // int pos = (int) buttonView.getTag(); // HashMap<String, Object> selectedItem = new HashMap<String, Object>(); // selectedItem = (HashMap<String, Object>) getItem(pos); // // // Update DataList // selectedItem.remove(ITEM_CHECKED); // if(buttonView.isChecked()){ // selectedItem.put(ITEM_CHECKED, true); // }else{ // selectedItem.put(ITEM_CHECKED, false); // } // mDataList.set(pos, selectedItem); // // // Update all UI views by // notifyDataSetChanged(); // } // }); // // Get data from DataList and set Views accordingly // @SuppressWarnings("unchecked") // HashMap<String, Object> currentItem = (HashMap<String, Object>) getItem(position); // int seperation = mSeekBar.getProgress() + 1; // if((position % seperation) == 0){ // layout.setBackgroundColor(Color.LTGRAY); // }else{ // layout.setBackgroundColor(Color.TRANSPARENT); // } // updateRowView(layout, (boolean)currentItem.get(ITEM_CHECKED)); // // // Save position to CheckBox, so position can be retrieved when CheckBox is clicked // cb.setTag(position); // // return convertView; // } // // private void updateRowView(RelativeLayout rowView, boolean checked){ // for(int i=0; i<rowView.getChildCount(); i++){ // View child = rowView.getChildAt(i); // if(child.getClass() == TextView.class){ // TextView tmpView = (TextView) child; // if(checked){ // rowView.setBackgroundColor(Color.CYAN); // tmpView.setTextColor(Color.WHITE); // }else{ // tmpView.setBackgroundColor(Color.TRANSPARENT); // tmpView.setTextColor(Color.BLACK); // } // } // } // } // }; // 5. Sample code for Making Filter. // String[] from = {ITEM_ID, ITEM_NAME}; // int[] to = {R.id.tvId, R.id.tvDescription}; // mSimpleAdapter = new SimpleAdapter(this, mDataList, R.layout.custom_list_items, from, to); mListView.setAdapter(mSimpleAdapter); } }

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" >

    <LinearLayout
        android:id="@+id/llHeader"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#aa7777aa"
        android:padding="7dp" >

        <EditText
            android:id="@+id/etFilter"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:ems="10"
            android:inputType="text"
            android:visibility="gone" />

        <SeekBar
            android:id="@+id/seekBar1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:max="9"
            android:progress="1"
            android:visibility="gone" />

    </LinearLayout>

    <ListView
        android:id="@+id/listView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/llHeader"
        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:id="@+id/tvDescription"
        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" />

</RelativeLayout>
strings.xml:
<?xml version="1.0" encoding="utf-8"?><resources>

    <string name="app_name">ListView Sample</string>
    <string-array name="list_items_sample">
        <item>One</item>
        <item>Two</item>
        <item>Three</item>
        <item>Four</item>
        <item>Five</item>
        <item>Six</item>
        <item>Seven</item>
        <item>Eight</item>
        <item>Nine</item>
        <item>Ten</item>
        <item>Eleven</item>
        <item>Twelve</item>
        <item>Thirteen</item>
        <item>Fourteen</item>
        <item>Fifteen</item>
        <item>Sixteen</item>
        <item>Seventeen</item>
        <item>Eighteen</item>
        <item>Nineteen</item>
        <item>Twenty</item>
        <item>Twenty-One</item>
        <item>Twenty-Two</item>
        <item>Twenty-Three</item>
        <item>Twenty-Four</item>
        <item>Twenty-Five</item>
        <item>Twenty-Six</item>
        <item>Twenty-Seven</item>
        <item>Twenty-Eight</item>
        <item>Twenty-Nine</item>
        <item>Thirty</item>
        </string-array>

</resources>



Technique 1 - How to use Android standard built-in layout
The code shown is for this. Just run it! This shows how simple to make a ListView. It is using the standard layout (android.R.layout.simple_list_item_1) which contains one TextView with Id, android.R.id.text1. Just showing a list in not that useful, actions are always needed when an item is touched/clicked.



Technique 2 - How to receive ListView item click
Inside the method, sampleCodeForSimpleAdapter(), enable the 24 commented lines after heading “// 2. Code for receive ListView item click sample”. Run the program again and click any item. There is a toast message displayed after click.
Please note that the selectedItem is a complete data item. That means the data item can be complex while the view only display minimal information, e.g. data item is a song object contains song name, song album, singer, file size, record date etc. while the view display only the song name. However when we want to show more information, a single TextView in not adequate.



Technique 3 - How to use custom layout
Inside the method, sampleCodeForSimpleAdapter(), comment out the 3 lines after heading “// 1. Code for Built-in Layout sample” and enable the 3 commented lines after heading “// 3. Code for Custom Layout sample”. Run the program again and see what is different.
Try checking any checkbox, scroll forward and then back. What do you find? The checkmark is gone. This is because dataset is not updated when the checkbox is checked. When you scroll forward and back, the rowView is drawn according to dataset and so the checkbox is unchecked.



Technique 4 - How to use custom layout and override standard adapter method
Inside the method, sampleCodeForSimpleAdapter(), comment out the last line after heading “// 3. Code for Custom Layout sample” and enable the 64 commented lines after heading “// 4. Code for Custom Layout and extends standard adapter function sample”. Also enable the 15 commented lines after heading “// 4. Code for Custom Layout and extends standard adapter function sample” in onCreate(). Then run the program again. Now when a checkbox is checked and list is scrolled, the checkmark is maintained because dataset is updated when checkbox is clicked. Please also note that there is a SeekBar on top of the screen which changes the background color of specific rows.
The standard adapter method, getView(), which responsible for filling up item views with data. Personally recommend the workflow like this:
1. Construct the rowView/convertView.
2. Get views references.
3. Set Listener to views that receive input.
4. Get data item and fill up views.
5. Set Tag to views that receive input.
6. Return the rowView/convertView.
One common mistake: 3 and 4 is reversed. Basically that don’t cause problem, but sometimes people made errors that referring to local data of getView() while the data should be obtained inside Listener. The recommend workflow just to keep that clear.



Technique 5 - How to use Filter
Inside the method, sampleCodeForSimpleAdapter(), comment out the 2 lines after heading “// 3. Code for Custom Layout sample” and the 64 lines after heading “// 4. Code for Custom Layout and extends standard adapter function sample”, then enable the 3 commented lines after heading “// 5. Code for Making Filter sample”. Also enable the 13 commented lines after heading “// 5. Code for Making Filter sample” in onCreate(). Run the program and now there is an EditText on top of screen where you can enter the filter string. Try enter “1” and see what is the result?
It can be so simple to make filtered list in SimpleAdapter but please note that there is no data bind to the checkbox now because this filter can only associate with string.

Try this, check the first checkbox and scroll the list, what do you see? Another view from the bottom of the list is checked! Why?? This is call <View Recycling> and will be explained later.