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.



2016年12月27日 星期二


In Android, ListView / GridView / Spinner are commonly used for listing items and making selection. RecyclerView has similar function but its structure is a bit different, so not included here. They can be simple and easy to code when you understand their operations, otherwise they are troublesome.

Let’s start the talk:



INTRODUCTION J

They are all << AdapterView >>!!!

ListView / GridView / Spinner are subclasses of AdapterView. From the name, it is clear that it is a view with adapter. For this type of View, there are 3 building elements:

1.      Dataset: Your data should be something that can be put in a table. Every data item has same number of data fields.

Temporary states should also be considered. One common mistake: when item can be selected with no immediate action [e.g. allow selecting multiple items for Delete], data field - checked/selected is not added.

2.      Adapter: This is the linkage from dataset to user interface. It matches up data fields to corresponding display elements and takes care which item should be displayed. The appropriate adapter is mainly relied on the nature of the dataset but sometime also need to consider about UI actions. There are several different types of adapter. Some basic adapters are:

ArrayAdapter - TextView is referenced, it will be filled with the toString() of each object in the array. You can add lists or arrays of custom objects. Override the toString() method of your objects to determine what text will be displayed for the item in the list. To use something other than TextViews for the array display, for instance, ImageViews, or to have some of data besides toString() results fill the views, override getView(int, View, ViewGroup) to return the type of view you want.

BaseAdapter - Common base class of common implementation for an Adapter that can be used in both ListView (by implementing the specialized ListAdapter interface) and Spinner (by implementing the specialized SpinnerAdapter interface).

CursorAdapter - The Cursor must include a column named "_id" or this class will not work. Additionally, using MergeCursor with this class will not work if the merged Cursors have overlapping values in their "_id" columns.

SimpleAdapter - An easy adapter to map static data to views defined in an XML file. You can specify the data backing the list as an ArrayList of Maps. Each entry in the ArrayList corresponds to one row in the list. The Maps contain the data for each row. You also specify an XML file that defines the views used to display the row, and a mapping from keys in the Map to specific views. Binding data to views occurs in two phases. First, if a SimpleAdapter.ViewBinder is available, setViewValue(android.view.View, Object, String) is invoked. If the returned value is true, binding has occurred. If the returned value is false, the following views are then tried in order:

·     A view that implements Checkable (e.g. CheckBox). The expected bind value is a boolean.

·     TextView. The expected bind value is a string and setViewText(TextView, String) is invoked.

·     ImageView. The expected bind value is a resource id or a string and setViewImage(ImageView, int) or setViewImage(ImageView, String) is invoked.

SimpleCursorAdapter - An easy adapter to map columns from a cursor to TextViews or ImageViews defined in an XML file. You can specify which columns you want, which views you want to display the columns, and the XML file that defines the appearance of these views. Binding occurs in two phases. First, if a SimpleCursorAdapter.ViewBinder is available, setViewValue(android.view.View, android.database.Cursor, int) is invoked. If the returned value is true, binding has occured. If the returned value is false and the view to bind is a TextView, setViewText(TextView, String) is invoked. If the returned value is false and the view to bind is an ImageView, setViewImage(ImageView, String) is invoked. If this adapter is used with filtering, for instance in an AutoCompleteTextView, you can use the SimpleCursorAdapter.CursorToStringConverter and the FilterQueryProvider interfaces to get control over the filtering process.

3.      View: This is the User Interface/Layout XML files, presenting the data on the screen. 2 layout XML files is related. The first one contains the AdapterView. The second one defines the layout of single item/row [Android has some standard built-in layouts on this]. In Java code, we always refer the whole ListView / GridView / Spinner as parent/container/adapterView and item view as rowView/convertView/childView.





The above diagram is always drawn without the red solid arrows. However I would like to emphasis this because of one common mistake: when item view contains temporary states [e.g. CheckBox, Switch, ToggleButton, EditText/TextView as shopping cart counter etc.], view is changed/updated but forgot dataset. Therefore personally recommend the workflow like this:


UPDATE DATASET FIRST, THEN CALL ADAPTER [notifyDataSetChanged(), this method will be explained in more detail later] TO REDRAW VIEW. If that caused performance issue, then just replace notifyDataSetChanged() with code that update views directly. This makes sure that dataset is updated.