יום שבת, פברואר 12

Expandable List

ועוד אחד שמתעסק עם List, הפעם מדובר ב-Expandable List: תצוגה היררכית בה הפריט ברשימה נפתח לרשימה משנית (child list).

נתחיל עם תמונת מסך להמחשה:




בתמונה ניתן להבחין בשתי רמות של רשימות נתונים: הרמה העליונה (Group), כוללת רשימה של ערים - תל אביב, חיפה. הרמה המישנית (child) כוללת רשימת כתובות, והיא נפתחת ע"י נגיעה.
מוצגת כאן דוגמא פשוטה, שמתמקדת ב-Expandable List.מתוכננות דוגמאות המשך שישכללו את המערכת שמסביב. למשל, אופן קבלת הנתונים - כרגע הנתונים מאורגנים במערך. בהמשך (אולי) זה ישוכלל לקריאת נתונים מקובץ או הכנסה אינטראקטיבית.




לגבי ה-class בו נשתמש, התצוגה וקובץ ה-Layout:
 ה-class חייב להיות מסוג extends ExpandableListActivity. באופן דיפולטיבי,התצוגה כולה תהיה של ה-ExpandableList. אין צורך בקובץ layout, ואין צורך לפרוס אותו עם הפקודה setContentView. אבל, אם מעוניינים בתצוגה שונה מזו, למשל שילוב ווידג'טים נוספים, אז יש להשתמש בקובץ layout ולפרוס אותו עם setContentView. בדוגמא המובאת כאן בחרנו האפשרות השניה, והוספנו ווידגט נוסף. היות שכך, נדרש קובץ-layout.
הגדרת ה-ExpandableListView  בשורות 17-15. ה-id חייב להיות list.

הנה הקובץ כולו:


  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.     android:orientation="vertical"
  4.     android:layout_width="fill_parent"
  5.     android:layout_height="fill_parent"
  6.     >
  7. <TextView  
  8.     android:layout_width="match_parent"
  9.     android:layout_height="wrap_content"
  10.     android:textSize="18sp"
  11.     android:gravity="center"
  12.     android:textStyle="bold"
  13.     android:text="@string/address_book_title"
  14.     />
  15.        <ExpandableListView android:id="@+id/android:list"
  16.                android:layout_width="fill_parent"
  17.                android:layout_height="fill_parent"/>
  18.  
  19.     
  20. </LinearLayout>


הנה קובץ layout נוסף בו השתמשנו. הוא מגדיר את מבנה ה-entry של ה-child. בהמשל נראה איך "מחברים" אותו במתודה שבונה את ה-adapter.
שם הקובץ: child_list_entry.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content">

    <TextView android:id="@+id/textview_name"
         android:paddingLeft="5px"
         android:textSize="15px"
         android:layout_width="120px"
         android:layout_height="wrap_content"/>

    <TextView android:id="@+id/textview_address"
         android:textSize="10px"
         android:layout_width="120px"
         android:layout_height="wrap_content"/>
        
    <TextView android:id="@+id/textview_tel"
         android:textSize="10px"
         android:layout_width="120px"
         android:layout_height="wrap_content"/>

</LinearLayout>



והנה  ה-java:



import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import android.app.ExpandableListActivity;
import android.os.Bundle;
import android.widget.ExpandableListAdapter;
import android.widget.SimpleExpandableListAdapter;
    public class MyExapandable extends ExpandableListActivity {
        private static final String CITY = "CITY";
        private static final String NAME = "NAME";
        private static final String ADDRESS = "ADDRESS";
        private static final String TEL = "TEL";
        private ExpandableListAdapter adapter;
        String[] city = {"Tel Aviv", "Haifa", "Jerusalem"};
        private static final int TEL_AVIV = 0;
        private static final int HAIFA = 1;
        private static final int JERUSALEM = 2;
        String[][] telBook ={
            {"Tel Aviv", "David Levi", "Nordau 8","052-2323232"},
            {"Haifa", "Yosi Das","Lilinblum 33","03-2323244"},
            {"Tel Aviv", "Rami Kook","Gordon 12","03-2323221"},
            {"Tel Aviv", "Sami Nof","Gordon 12","03-2323221"},
            {"Haifa", "David Levi", "Nordau 8","052-2323232"},
            {"Jerusalem","Yosi Das","Lilinblum 33","03-2323244"},
            {"Tel Aviv","Dafi Cin","Gordon 12","03-2323221"},
            {"Haifa", "Iris Hadad", "Has 8","052-2323432"},
            {"Tel Aviv","Rafi Masmus","Jabotinski 313","03-2323244"},
            {"Jerusalem","Jako Bilbo","Sokolov 32","02-2323221"},
            {"Haifa", " Itzik Solo","Sokolov 32","03-2324221"},
            {"Tel Aviv","Nahum Fantog","King George 32","03-2324221"},
            {"Tel Aviv","Irit Singer","King David 32","03-2388221"}

        };

   ArrayList<Map<String, String>> groupData = new ArrayList<Map<String, String>>();
    ArrayList<String> cityList = new ArrayList<String>();
    ArrayList<List<Map<String, String>>> childrenData = new ArrayList<List<Map<String, String>>>();
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        buildGroupData();
        buildChildrenData();
           adapter = new SimpleExpandableListAdapter(
                    this,
                    groupData,
                    android.R.layout.simple_expandable_list_item_1,
                    new String[] { CITY },
                    new int[] { android.R.id.text1},
                    childrenData,
                    R.layout.child_list_entry,
                    new String[] { NAME, ADDRESS, TEL },
                    new int[] { R.id.textview_name, R.id.textview_address, R.id.textview_tel }
                    );
        setListAdapter(adapter);
    }
    private void buildGroupData(){
        int indx = 0;
        for (String cityEntity:city) {
            Map<String, String> groupMap = new HashMap<String, String>();
            groupData.add(groupMap);
            groupMap.put(CITY, cityEntity);
            cityList.add(cityEntity);
            indx++;
        }   
    }
    private void buildChildrenData(){
        ArrayList<Map<String, String>> children0 = new ArrayList<Map<String, String>>();
        ArrayList<Map<String, String>> children1 = new ArrayList<Map<String, String>>();
        ArrayList<Map<String, String>> children2 = new ArrayList<Map<String, String>>();
           for(int index =0; index<telBook.length;index++ ){
                Map<String, String> childMap = new HashMap<String, String>();
                switch(cityList.indexOf(telBook[index][0])){
                     case TEL_AVIV:
                         children0.add(childMap);
                         break;
                     case HAIFA:
                         children1.add(childMap);
                         break;
                     case JERUSALEM:
                         children2.add(childMap);
                         break;
                     default:
                         break;
             }
            childMap.put(NAME, telBook[index][1] );
            childMap.put(ADDRESS, telBook[index][2] );
            childMap.put(TEL, telBook[index][3] );
        }
        childrenData.add(children0);
        childrenData.add(children1);
        childrenData.add(children2);
    }
}






נעבור על הקוד:
המתודה העיקרית היא ה-onCreate. מעבר לה שתי המתודות הנוספות הן מתודות עזר לבניית הרשימה הראשית -Group והרשימה המשנית - Children. רשימת ה-Children מורכבת בעצמה מ-3 רשימות child.



נסתכל לתוך ה-onCreate:

  1.     @Override
  2.     public void onCreate(Bundle savedInstanceState) {
  3.         super.onCreate(savedInstanceState);
  4.         setContentView(R.layout.main);
  5.         buildGroupData();
  6.         buildChildrenData();
  7.            adapter = new SimpleExpandableListAdapter(
  8.                     this,
  9.                     groupData,
  10.                     android.R.layout.simple_expandable_list_item_1,
  11.                     new String[] { CITY },
  12.                     new int[] { android.R.id.text1},
  13.                     childrenData,
  14.                     R.layout.child_list_entry,
  15.                     new String[] { NAME, ADDRESS, TEL },
  16.                     new int[] { R.id.textview_name, R.id.textview_address, R.id.textview_tel }
  17.                     );
  18.         setListAdapter(adapter);
  19.     }


שורה 4: sentContentView: כמו שנאמר למעלה - אם ה-ExpandableList הוא ה-view היחידי ב-UI, אין צורך בפקודה זו, כמו בקובץ ה-xml.
שורות 5 ו-6 מפעילות את המתודות שיוצרות את רשימת ה-Group וה-Children. הראשונה היא רשימת הערים, והשניה היא רשימה שבעצמה מכילה מספר רשימות: תת רשימה עבור כל פריט ברשימת ה-Group. נגיע למתודות האלה עוד מעט.
שורה 7-17: כאן מתבצע איתחול ה-Adapter. כמו ב-ListView למשל, ה-Adapter מחבר בין הנתונים אותם נציג, לבין ה-View. הפרמטרים שמועברים ל-constructor מחולקים לשתי קבוצות: 
  • הקבוצה הראשונה (שורות 9-12), שייכת ל-Group List.
  • הקבוצה השניה (שורות 13-16)  ל-Child.
נסתכל תחילה על החלק של ה-Group

שורה 9: זו הרשימה של ה-data שמיד נראה את המתודה שבונה אותה.
שורה 10: זה קובץ ה-layout לתצוגת שורה ברשימה. כאן השתמשנו ב-layout סטנדרטי של אנדרואיד להצגת רשימה עם פריט אחד :    android.R.layout.simple_expandable_list_item_1.
שורה 11: ה-key של האיבר ברשימה אותו נציג. נראה את הכנסת ה-key מיד. שורה זו מייצגת את ה-source, כלומר את הנתונים.
שורה 12: מייצגת את ה-Target: ה-view ב-layout (בתוך קובץ ה-layout הוגדר בשורה 10), אליו נמפה את הנתונים.

הפרמטרים הקשורים ל-Children List דומים שלא לאמר זהים:
שורה 13 (מקבילה לשורה 9), הרשימה של ה-Children ה-data של הרשימה. מיד נראה את המתודה שבונה את הרשימה הזו.
שורה  14 (מקבילה לשורה 10): קובץ ה-layout של השורה. כאן השתמשנו בקובץ _.child_list_entry.xml שיצרנו ולא בקובץ סטנדרטי. הקובץ נמצא בספריית -layout. תוכנו מוצג בפוסט זה למעלה.
שורה 15 (מקבילה ל11), קובעת את ה-keys לשליפת ה-data מהרשימה. כאן מדובר ב-3 שדות: שם, כתובת, טלפון.
שורה 16, מכילה את ה-views אליהם נמפה את הנתונים שבחרנו - ראה שורה 15.


שורה 18: קושרת את ה-Adapter ל-ExpandableList.

סיימנו, נשאר רק להסתכל על שתי מתודות העזר לבניית רשימת ה-Group וה-Children.

הנה הראשונה:
  1.    private void buildGroupData(){
  2.         int indx = 0;
  3.         for (String cityEntity:city) {
  4.             Map<String, String> groupMap = new HashMap<String, String>();
  5.             groupData.add(groupMap);
  6.             groupMap.put(CITY, cityEntity);
  7.             cityList.add(cityEntity);
  8.             indx++;
  9.         }   
  10.     }

מתבצע פה מעבר על כל איברי מערך ה-city.
לפי הסדר, מצרפים כל איבר במערך ל-Map עם CITY כ-Map.  (שורה 6).
כל איבר Map כנ"ל מחובר לרשימת ה-Group - שורה 5.
שורה 7: הוספת כל האיברים לרשימת עזר נוספת, שתשמש אותנו בבניית ה-Child List.



והנה מתודת העזר השניה, שבונה את ה-Children List.

  1.     private void buildChildrenData(){
  2.         ArrayList<Map<String, String>> children0 = new ArrayList<Map<String, String>>();
  3.         ArrayList<Map<String, String>> children1 = new ArrayList<Map<String, String>>();
  4.         ArrayList<Map<String, String>> children2 = new ArrayList<Map<String, String>>();
  5.            for(int index =0; index<telBook.length;index++ ){
  6.                 Map<String, String> childMap = new HashMap<String, String>();
  7.                 switch(cityList.indexOf(telBook[index][0])){
  8.                      case TEL_AVIV:                         children0.add(childMap);
  9.                          break;
  10.                      case HAIFA:
  11.                          children1.add(childMap);
  12.                          break;
  13.                      case JERUSALEM:
  14.                          children2.add(childMap);
  15.                          break;
  16.                      default:
  17.                          break;
  18.              }
  19.             childMap.put(NAME, telBook[index][1] );
  20.             childMap.put(ADDRESS, telBook[index][2] );
  21.             childMap.put(TEL, telBook[index][3] );
  22.         }
  23.         childrenData.add(children0);
  24.         childrenData.add(children1);
  25.         childrenData.add(children2);
  26.     }

מתודה זו מוסיפה את הנתונים מתוך המערך telBook אל הרשימה childrenData. 
בתהליך מעורבים 4 מבני נתונים:
המערך telBook שמכיל את הנתונים.
childMap   - מבנה נתונים מסוג   HashMap אליו מכניסים איבר מתוך המערך.
children0-children2: שלוש רשימות, שאיברי כל אחת מהן הוא מסוג Map. בונים את הרשימות האלה ע"י הוספת איברי ה-childMap. ל-children0 מוסיפים את האיברים השיייכים לת"א, children1 האיברים השייכים לחיפה ו-children3 זו הרשימה המחזיקה את האיברים השייכים לירושלים.
שלוש הרשימות הנ"ל מתווספות לרשימת העל - childrenData, שהיא רשימה של תת רשימות.
ה-switch בשורות 7-18 ממיין את האיברים ומשייכם לעיר המתאימה.





3 תגובות:

  1. בלוג מדהים! באמת שלמדתי ממנו הרבה.
    בתור חסר רקע בג'אווה לא הבנתי מה מסתתר בתוך ה<> של הרשימת מערכים...

    השבמחק
  2. תודה. בתוך ה<> מוגדר סוג ה-data. לדוגמא: <ArrayList<int מגדיר את סוג הנתונים שיכיל ה-ArrayList כ-int.

    השבמחק
  3. אם אני מעוניין להציג עמוד קונפיגורציה
    חלק מהמרכיבים הם טקסט בוקס'ס
    חלק קומבו
    וחלק צ'ק בוקס'ס
    אני רוצה לאגד אותם בקבוצות לפי נושא
    איך היית מציע לי לעשות את זה

    שאלה שניה:
    אני מנסה ליצור 4 לחצני תמונה שיהיו בתוך מיכל ממורכז לאמצע המסך
    ולא הצלחתי לבצע את זה, כי אין חלוקה לפי אחוזים, רק לפי פיקסלים, ובכל גירסת מסך זה יוצא שונה לגמרי.

    תודה על הבלוג, קידם אותי מאד

    השבמחק