יום רביעי, דצמבר 8

טאבים \ Tabs

טאבים (Tabs) הם אמצאי נוח לאירגון חלונות תצוגה. בפוסט זה נדגים את יצירתם.
נראה שתי דוגמאות:
האחת מגדירה את תוכן הטאבים בקובץ ה-layout בעוד השניהמאפשרת יצירה דינאמית של טאבים, גם בזמן ריצה (פיירפוקס עשו את זה קודם?).
נעבור מיד לדוגמא הראשונה.
כאן ממומשים 4 טאבים כפי שמוצג בצילום המסך הבא:

תמונה 1: מסך הטאבים, שימוש ב-xml:

התצוגה הנ"ל כוללת 4 טאבים (ראה בתמונה):
  1. שעון אנלוגי - מוצג כעת.
  2. כפתור - שלחיצה עליו תוביל לפתיחת webview ל-Gmail.
  3. שעון דיגיטאלי.
  4. חלון תצוגת טקסט.
נצלול פנימה, ונסקור את שני הקבצים העיקריים:
1. קובץ ה-layout.
2. cla ה-ג'אווה.

נסתכל תחילה על קובץ ה-layout:
הקובץ הנ"ל מגדיר טאב קונטיינר ואת תוכנם של 4 הטאבים השייכים לו.

הנה תוכן הקובץ בשלמותו:
main.xml:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <TabHost xmlns:android="http://schemas.android.com/apk/res/android"
  3.     android:id="@+id/tabhost"
  4.     android:layout_width="fill_parent"
  5.     android:layout_height="fill_parent">
  6.     <LinearLayout
  7.         android:orientation="vertical"
  8.         android:layout_width="fill_parent"
  9.         android:layout_height="fill_parent">
  10.         <TabWidget android:id="@android:id/tabs"
  11.             android:layout_width="fill_parent"
  12.             android:layout_height="wrap_content"
  13.         />
  14.         <FrameLayout android:id="@android:id/tabcontent"
  15.             android:layout_width="fill_parent"
  16.             android:layout_height="fill_parent">
  17.             <AnalogClock android:id="@+id/tab_analog_clock"
  18.                 android:layout_width="fill_parent"
  19.                 android:layout_height="fill_parent"
  20.             />
  21.             <Button android:id="@+id/tab_button"
  22.                 android:layout_width="wrap_content"
  23.                 android:layout_height="wrap_content"
  24.                 android:text="Gmail"
  25.                 android:gravity="center"
  26.             />
  27.             <DigitalClock android:id="@+id/tab_digital_clock"
  28.                 android:layout_width="fill_parent"
  29.                 android:layout_height="fill_parent"
  30.             />
  31.             <TextView android:id="@+id/tab_text_view"
  32.                 android:layout_width="fill_parent"
  33.                 android:layout_height="fill_parent"
  34.                 android:text="@string/tab_text_view"
  35.             />
  36.         </FrameLayout>
  37.     </LinearLayout>
  38. </TabHost>


הרכיבים הבונים את  ה-tab הם:
  • TabHost (שורה 2) - זהו ה-container שמכיל את כל המרכיבים: את הכפתורים ואת תוכנם. הוא עוטף את כל ה-layout. ה-id שלו כאן הוא tabhost. שני הרכיבים הבאים הם הבנים שלו.
  • TabWidget (שורה 10) - זהו ה-container שמכיל את פס כפתורי הטאב.
  • FrameLayout (שורה 14) - ה-container שמכיל את התוכן של הטאבים. 

 בקובץ הנ"ל התוכן של 4 הטאבים מוגדר בשורות 15 עד 35:
שורה 15: שעון אנלוגי
שורה 21: כפתור
שורה 27: שעון דיגיטאלי
שורה 31: חלון טקסט.

קונטיינר נוסף, LinearLayout (שורה 6)- מגדיר, כרגיל, את הסידור בתוך המסגרת של ה-TabHost. כאן הוא מסדר את ה- Tabwidget וה-FrameLayout  בסדר אנכי (שורה 7).


הג'אווה תפרוס את ה-layout, כרגיל, כפי שנראה בקובץ MyTabsXml.java:

  1. public class MyTabsXml extends Activity {
  2.     WebView webview;
  3.         @Override
  4.         public void onCreate(Bundle icicle) {
  5.             super.onCreate(icicle);
  6.             setContentView(R.layout.main);
  7.             TabHost tabs=(TabHost)findViewById(R.id.tabhost);
  8.             tabs.setup();
  9.             TabHost.TabSpec spec;
  10.            
  11.             spec=tabs.newTabSpec("tag1");
  12.             spec.setContent(R.id.tab_analog_clock);
  13.             spec.setIndicator("Analog Clock",  resources.getDrawable(R.drawable.my_halfmoon_icon));
  14.             tabs.addTab(spec);
  15.            
  16.             spec=tabs.newTabSpec("tag2");
  17.             spec.setContent(R.id.tab_button);
  18.         spec.setIndicator("Button", resources.getDrawable(R.drawable.my_email));
  19.             tabs.addTab(spec);
  20.            
  21.             spec=tabs.newTabSpec("tag3");
  22.             spec.setContent(R.id.tab_digital_clock);
  23.             spec.setIndicator("Digital Clock", resources.getDrawable(R.drawable.my_small_android));
  24.             tabs.addTab(spec);
  25.            
  26.            
  27.             spec=tabs.newTabSpec("tag4");
  28.             spec.setContent(R.id.tab_text_view);
  29.             spec.setIndicator("Read Text", resources.getDrawable(R.drawable.my_halfmoon_icon));
  30.             tabs.addTab(spec);
  31.                 
  32.             Button button=(Button)findViewById(R.id.tab_button);
  33.             webview=new WebView(this);
  34.             button.setOnClickListener(new View.OnClickListener() {
  35.                 public void onClick(View view) {
  36.                     setContentView(webview);
  37.                     webview.loadUrl("http://mail.google.com");
  38.                  }
  39.             });
  40.            
  41.             tabs.setCurrentTab(0);
  42.     }
  43. }

המימוש נעשה במתודה onCreate.

עיקר המתודה הוא בניית הטאבים. פעולה זו חוזרת על עצמה 4 פעמים - בפעם הראשונה בשורות 11-14, עבור בניית הטאב לשעון האנלוגי.

נתחיל קצת לפני כן, בשורה 7: כאן נשלף ה-TabHost מקובץ ה-layout.
שורה 8 - הפעלת setup. חובה לפני הוספת הטאבים. לא לשאול למה ומה נעשה ב-setup כי לא מצאתי לכך הסבר.
שורה 9: יצירת ה-אובייקט spec. בניית הטאבים נעשית ע"י אובייקט זה כפי שנראה בשורות הבאות:
שורות 11-14 כאמור, בונות את הטאב הראשון: השעון האנלוגי.
נעתיק שורות אלה שוב:
  1.   spec=tabs.newTabSpec("tag1");
  2.             spec.setContent(R.id.tab_analog_clock);
  3. spec.setIndicator("Analog Clock",  resources.getDrawable(R.drawable.my_halfmoon_icon));
  4.             tabs.addTab(spec);
מתבצעת כאן הכנסה של פרמטרים לאובייקט העזר spec, ואחכ הוספה spec לטאבים.
התהליך מתבצע ב-4 שלבים\4 שורות:
שורה 1 יוצרת אובייקט TabSpec חדש. מוצמד לו תג (שאין לו שימוש!).
שורה 2 מטפלת בתוכן של הטאב.האינפורמציה נשלפת מה-xml לפי ה-id.
שורה 3 מצמידה לכפתור הטאב תווית טקסט ואייקון. האייקון (אופציונלי) נשלף מתוך ה-drawable. הכנו את התמונות מראש...
שורה 4 הוספת טאב. האובייקט spec הוא הפרמטר של המתודה addTabb.

התהליך הנ"ל חוזר על עצמו באופן דומה 4 פעמים, עבור כל אחד מהטאבים: ראה שורות 16, 21,27.

שורות 32 ואילך יוצרות את ה-callback של הכפתור שמסתתר בטאב השני. ה-callback של הכפתור יפתח חלון web של gmail.
הנה קטע הקוד:
  1.         Button button=(Button)findViewById(R.id.tab_button);
  2.             webview=new WebView(this);
  3.             button.setOnClickListener(new View.OnClickListener() {
  4.                 public void onClick(View view) {
  5.                     setContentView(webview);
  6.                     webview.loadUrl("http://mail.google.com");
  7.                  }
  8.             });
שורה 2 מכינה את ה-webview בו נשתמש בתוך-callback.
שורות 3-8 יוצרות את ה-callback באופן אנונימי. 
בתוך onClick יוצרים את ה-webview ומפעילים את הגישה ל-url: שורות 5,6.

עד כאן הדוגמה הראשונה. הקישור לקבצים לדוגמא זו בתחתית הדף - כרגיל.

נעבור לדוגמא השניה.
בדוגמא זו שילבנו מספר תהליכים השונים  מאלה שהיו בדוגמא הקודמת:
1.  שניים מהטאבים מפעילים Activity אחר ע"י intent.
2. הטאבים לא מוגדרים בקובץ ה-layout, כך שאפשר להוסיף טאבים באופן דינאמי בזמן ריצה.

כך נראית התצוגה:




הערות מקדימות
  • בדוגמא זו נעשה שימוש ב-  TabActivity שהיא subclass של Activity עם התאמות ל-Tab. זה קצת מקל על העבודה.
  • קובץ ה-layout זהה לזה של הדוגמא הקודמת, למעט העובדה שאין צורך להוסיף את הרכיבים בתוך ה-FrameLayout - שורה 17-35 בקובץ ה-xml כאן למעלה מיותרות.
  • ה-id של TabHost חייב להיות tabhost.

הנה  ה-class בשלמותו, מתוך הקובץ MyTabs.java:
  1.  
  2. public class MyTabs extends TabActivity {
  3.     WebView webview;
  4.     @Override
  5.     public void onCreate(Bundle icicle) {
  6.         super.onCreate(icicle);
  7.          webview=new WebView(this);
  8.         TabHost tabs=getTabHost();
  9.         TabHost.TabSpec spec;
  10.        
  11.         spec=tabs.newTabSpec("tag1");
  12.         spec.setIndicator("Gmail");
  13.         spec.setContent(new Intent(this, MyGmail.class));
  14.         tabs.addTab(spec);
  15.        
  16.         tabs.addTab( tabs.newTabSpec("tag2")
  17.         .setIndicator("Yahoo Mail")
  18.         .setContent(new Intent(this, MyYmail.class)));
  19.        
  20.         spec=tabs.newTabSpec("tag3");
  21.         spec.setIndicator("Clock");
  22.         spec.setContent(tabContentFactoryClock);
  23.         tabs.addTab(spec);
  24.        
  25.         spec=tabs.newTabSpec("tag4");
  26.         spec.setIndicator("Text");
  27.         spec.setContent(tabContentFactoryText);
  28.         tabs.addTab(spec);
  29.         tabs.setCurrentTab(0);
  30.     }
  31.     private TabHost.TabContentFactory tabContentFactoryClock = new TabHost.TabContentFactory() {
  32.         public View createTabContent(String tag) {
  33.             return(new AnalogClock(MyTabs.this));
  34.     }};
  35.     private TabHost.TabContentFactory tabContentFactoryText = new TabHost.TabContentFactory() {
  36.         public View createTabContent(String tag) {
  37.             TextView textView = new TextView(MyTabs.this);
  38.             textView.setText("הטאב הזה מדגים סוג נוסף של" +
  39.                     " תצוגה המכילה טקסט קבוע.");
  40.             return(textView);
  41.     }};
  42. }

דגשים:
  •  4 הכפתורים מטופלים בשורות 11,16,20 ו-25.
  • ההבדלים מהמימוש הקודם בגלל השימוש ב-TabActivity:
    • ה-TabHost נשלף כאן ע"י המתודה getTabHost - שורה 8.
    • אין צורך במתודה setUp
    • קריאה ל-intent נעשית בקלות כפי  שנראה מיד.

  • שני הטאבים הראשונים, עם האינדיקטורים Gmail ו-Yahoo Mail יוצרים intent שגורם להפעלה של Activity אחר כפי שנפרט מיד.
  • שני הטאבים האחרים, clock ו-text, מפעילים אלמנטים של view, בדומה לדוגמא הקודמת, אולם ללא שימוש בקובץ xml. במקום זאת נשתמש ב- TabContentFactory כפי שנראה בהמשך.

נפרט תחילה את הטיפול בשני הטאבים הראשונים - הפעלת intent.
נקח לדוגמא את הטיפול בטאב הראשון,שורה 11 למעלה (הטיפול ב-tab השני נעשה באותו אופן):

  1. spec=tabs.newTabSpec("tag1");
  2.         spec.setIndicator("Gmail");
  3.         spec.setContent(new Intent(this, MyGmail.class));
  4.         tabs.addTab(spec);
התהליך דומה לזה שראינו בדוגמא הראשונה. ההבדל בשורה 3: אפשר להכניס ל-setContent גם intent שמפעיל Activity אחר. (ניתן לבצע זאת גם עם class מסוג-Activity ולא TabActivity , אך זה מורכב יותר).
הטאב הנ"ל יפעיל את MyGmail.
זהו class קטן ופשוט שיפתח webview עם הכתובת של gmail:

  1. public class MyGmail extends Activity {
  2.     WebView webview;
  3.     @Override
  4.     public void onCreate(Bundle icicle) {
  5.         super.onCreate(icicle);
  6.         webview=new WebView(this);
  7.         setContentView(webview);
  8.         webview.loadUrl("http://mail.google.com");
  9.        
  10.     }           
  11. }
כעת נסתכל על הטיפול בשני הטאבים האחרונים. נתרכז בטיפול בטאב clock. הטיפול בטאב text נעשה באותו אופן. הנה הקוד מועתק שוב:


  1.     spec=tabs.newTabSpec("tag3");
  2.         spec.setIndicator("Clock");
  3.         spec.setContent(tabContentFactoryClock);
  4.         tabs.addTab(spec);

4 המתודות הנ"ל זהות לאלה שראינו בדוגמאות הקודמות. ההבדל הוא במתודה setContent. כאן יש שימוש ב-class מסוג ContentFactory. זהו interface עם מתודה אבסטרקטית אחת, ואותו צריך לממש, שורות 31-34 מובאות  כאן שוב:

  1.     private TabHost.TabContentFactory tabContentFactoryClock = new TabHost.TabContentFactory() {
  2.         public View createTabContent(String tag) {
  3.             return(new AnalogClock(MyTabs.this));
  4.     }};

 המתודה אותה צריך לממש ב-interface  הזה היא createTabContent - שורה 2.
 בתוך מתודה זו, נייצר את האובייקט מסוג AnalogClock.

הטיפול בטאב השני, ה-text, נעשה כאמור באופן דומה. גם שם מומש ה-interface מסוג TabContentFactory - שורות 36-41 למעלה.


בשיטה שתוארה בדוגמא השניה, אפשר לייצר טאבים תוך כדי ריצה אופן דינאמי, בלי צורך להגדירם מראש בקובץ ה-layout. בכל זאת ישנה בעיה קטנה: אין מתודה למחיקת טאב יחיד. יש אמנם את public void clearAllTabs אך היא מוחקת את כולם. הפיתרון שנראה לי כיותר פשוט הוא למחוק את כולם ולהחזיר את את הטאבים אחד אחד.


ונזכיר גם את המתודה setCurrentTab שקובעת את הטאב הפעיל. במקרה שלנו, נבחר הטאב הראשון - הפרמטר 0.




4 תגובות: