יום שישי, דצמבר 3

Dialogs


ה-Dialog הוא חלון המשמש לתצוגה של סטטוס ואינטראקציה עם המשתמש. אנדרואיד מספק classes המאפשרים טיפול פשוט בדיאלוגים. נציג דוגמא הכוללת מגוון סוגים ואפשרויות של דיאלוג.
תמונה של חלון דיאלוג לדוגמא:

כמה מילות רקע על אופן הפעולה
  • הטיפול ב-Dialog נעשה באופן אסינכרוני: שלא כמו בהרבה מערכות אחרות, עם הצגת הדיאלוג המערכת לא עוצרת, אלא ממשיכה לבצע את הפקודות הבאות. 
  • למרות הפעולה האסינכרונית,  כשהדיאלוג מופיע,  הוא "משתלט" על אמצעי הקלט, כך שהמקלדת, המגע וכו" עוברים להיות "שייכים" לדיאלוג ולא ל-view הרגיל של ה-Activity.

קיימים מספר סוגים של Dialog
באנדרואיד מוגדר class מסוג Dialog. מעבר לכך, class ה-Dialog הבסיסי הורחב למספר sub classes המפשטים את המימוש עבור סוגי דיאלוג שימושיים.
נדגים את השימוש בסוגי דיאלוגים הבאים:
  1. Alert Dialog - זה הדיאלוג הנפוץ והשימושי ביותר.
  2. Progress Dialog - כפי ששמו מרמז, נועד לתת אינדיקציה על מצב ההתקדמות של פעילות. נציג שני סוגי Progress Dialog:
    1. spinner - גרפיקה בצורת עיגול.
    2. horizontal  -סרגל התקדמות אופקי.
  3. Time Picker Dialog - משמש לקליטת נתוני זמן.
  4. Date Picker Dialog- משמש לקליטת נתוני תאריך.




 תאור האפליקציה שמודגמת כאן:
  • האפליקציה מדגימה 5 סוגים של Dialogs.
  • הפעלת כל אחת מהדוגמאות נעשית ע"י כפתור ב-Options Menu.
הנה צילומי המסך של כל אחד מה-Dialogs אותם נייצר, ולפניהם ה-Menu הראשי:
1. מסך התפריט הראשי שממנו מופעלים 5 סוגי ה-Dialogs:

2. Alert Dialog
ראה תמונה בראש הדף.



    3.   Progress Horizontal




    4. Progress Spinner


    5. בחירת שעה - Time Pick


    6. בחירת תאריך






    מימוש כל הדיאלוגים הנ"ל, וגם התפריט הראשי נעשה ב-class אחד. אנחנו נסקור כל דיאלוג בנפרד ואת הקוד הרלוונטי אליו.

    מתודות חשובות למימוש הדיאלוג
    לפני שנצלול לתוך הקוד, נציין מספר מתודות חשובות בהקשר של ה-dialog. 


    • (onCreateDialog(int id, Bundle args - זהו callback שמופעל בדיוק לפני שה-dialog מוצג בפעם הראשונה. בתוך ה-callback הזה אפשר כמובן לצור את אובייקט הדיאלוג, כפי שנראה בדוגמא. פרמטר ה-id הוא האינדקס שמזהה את ה-Dialog. 
    • (onPrepareDialog(int id, Dialog dialog, Bundle args - זהו callback שמופעל בכל פעם לפני שה-Dialog מוצג (בשונה מה-callback הקודם שמופעל פעם אחת). זה נותן אפשרות לאפיין מחדש את ה-dialog בכל פעם מחדש. בדוגמאות שיוצגו כאן לא נעשה override ל-callback זה.
    • (showDialog(id - זו המתודה שמפעילה את הדיאלוג. ה-id הוא מספר שנקבע ע"י המתכנת כמייצג את ה-Dialog. כפועל יוצא מהפעלת מתודה זו, יופעלו שני ה-callbacks שמוזכרים כאן למעלה
    • (dismissDialog(id  - כשמו כן הוא. סוגר את הדיאלוג. בדוגמא שלפנינו, הוא יופעל עבור ה-Horizontal Progressing Dialog.
     סקירת מרכיבי התוכנית
    נסקור את מרכיבי התוכנית. נתחיל עם התפריט הראשי (שכשלעצמו לא קשור ישירות לנושא הדיאלוג), ונעבור לדיאלוגים אחד אחרי השני.




     הסבר על התפריט
    ההפעלה של הדיאלוגים נשלטת ע"י optionsMenu. ראה הסבר מפורט על Menu ב פוסט שעסק ב-בנושא.
    ה-Menu ממומש ע"י  שתי מתודות: 
    1. onCreateOptionsMenu
    2.  onOptionsItemSelected
    • המתודה הראשונה מייצרת את התפריט ע"י פריסת קובץ ה-xml.
    • השניה היא ה-callback שמופעל עם לחיצה על לחצן של התפריט.
    מייד נציג את התוכן של שתי המתודות הנ"ל אך תחילה נראה את ה-xml שמגדיר את מבנה התפריט. הקובץ ונמצא ב-layout/menu:

    Menu Layout

    1. <?xml version="1.0" encoding="utf-8"?>
    2. <menu xmlns:android="http://schemas.android.com/apk/res/android">
    3.     <item android:id="@+id/alert_dialog"
    4.           android:icon="@drawable/my_monster1"
    5.           android:title="@string/alert_dialog" />
    6.          
    7.              <item android:id="@+id/progress_dialog_horizontal"
    8.           android:icon="@drawable/my_monster1"
    9.           android:title="@string/progress_dialog_horizontal" />
    10.          
    11.              <item android:id="@+id/progress_dialog_spinner"
    12.           android:icon="@drawable/my_monster1"
    13.           android:title="@string/progress_dialog_spinner" />
    14.          
    15.              <item android:id="@+id/time_pick_dialog"
    16.           android:icon="@drawable/my_monster1"
    17.           android:title="@string/time_pick_dialog" />
    18.           
    19.     <item android:id="@+id/date_pick_dialog"
    20.           android:icon="@drawable/my_monster2"
    21.           android:title="@string/date_pick_dialog" />
    22.        
    23.          
    24. </menu>

    אפשר להבחין ב-items של התפריט (שורות 3,7,11 וכ"), אשר כל אחד מהם מייצג כפתור בתפריט. כל כפתור יפעיל dialog מסוג אחר.

    כעת נראה את שתי המתודות של ה-menu:
    onCreateMenu המתודה ליצירת (פריסת) התפריט
    onOptionsItemSelected ה-callback של לחצני התפריט


    final int ALERT_DIALOG_ID = 0;
    final int PROGRESS_HORIZONTAL_DIALOG_ID = 1;
    final int PROGRESS_SPINNER_DIALOG_ID = 2;
    final int TIME_PICK_DIALOG_ID = 3;
    final int DATE_PICK_DIALOG_ID = 4;


    1.         @Override
    2.         public boolean onCreateOptionsMenu(Menu menu) {
    3.             MenuInflater inflater = getMenuInflater();
    4.             inflater.inflate(R.menu.my_options_menu, menu);
    5.             return(super.onCreateOptionsMenu(menu));
    6.         }
           
    1.         @Override
    2.         public boolean onOptionsItemSelected(MenuItem item) {
    3.             super.onOptionsItemSelected(item);
    4.             switch (item.getItemId()) {
    5.                 case R.id.alert_dialog:
    6.                     showDialog(ALERT_DIALOG_ID);
    7.                     return(true);
    8.                 case R.id.progress_dialog_horizontal:
    9.                     showDialog(PROGRESS_HORIZONTAL_DIALOG_ID);
    10.                        progressThread = new ProgressThread(handler);
    11.                         progressThread.start();
    12.                     return(true);
    13.                 case R.id.progress_dialog_spinner:
    14.                     ProgressDialog.show(this, "","טוען...נא להמתין", false,true,new DialogInterface.OnCancelListener(){
    15.                         public void onCancel(DialogInterface dialog){
    16.                             msgDisplay("Action is cancelled");
    17.                            }
    18.                        });
    19.                     return(true);
    20.                 case R.id.time_pick_dialog:
    21.                     showDialog(TIME_PICK_DIALOG_ID);
    22.                     return(true);
    23.                 case R.id.date_pick_dialog:
    24.                     showDialog(DATE_PICK_DIALOG_ID);
    25.                     return(true);
    26.             }
    27.             return(false);
    28.         }
    התיחסות לשתי המתודות הנ"ל:
    • במתודה onCreateOptionsMenu, שורות 3-4: פריסת ה-xml.
    • במתודה onOptionsItemSelected: החל משורה 4, switch על כפתורי ה-menu.
    • הפעלת ה-dialog תתבצע בד"כ ע"י המתודה showDialog - ראה שורות 6, 9 וכו". 
      • הטיפול ב- progress_dialog_spinner - שורה 13 - קצת שונה. נדון בפרטים בהמשך.
    עד כאן לגבי ה-menu.
    מכאן נסקור את חמשת הדיאלוגים אחד אחרי השני.


    Alert Dialog

    הטיפול ב- Alert Dialog מתבצע בשלושה שלבים לפי הסדר הבא:
    1. הפעלת המתודה להצגת הדיאלוג - Dialog Show.
    2. יצירת ה-Dialog ב-Dialog Create callback.
    3. הפעלת ה-on click callback  שמתבצע עם לחיצה על כפתור(י) הדיאלוג.
    נפרט את שלושת השלבים:

    שלב 1: פעלת המתודה להצגת הדיאלוג. למעלה שורה 6:
      showDialog(ALERT_DIALOG_ID);

    בעקבות הפעלת showDialog יופעל callback  - ראה שלב 2.
    שלב 2: יצירת ה-Dialog ב-Dialog Create callback.
    יצירת הדיאלוג כוללת אפשרות אפיון של מספר פרמטרים:

    • כותרת\title - שורה 7 למטה.
    • גוף ההודעה - שורה 6 למטה.
    • כפתורים - עד שלושה - שורה 10 למטה.
    • הוספת icon - שורה 8 למטה.
    • קביעת יכולת ביטול חלון הדיאלוג ע"י  back - שורה 9 למטה.
     כל הנ"ל יתבצעו ב- onCreateDialog שלפנינו. הנה התוכנית, ואחריה נכנס לפרטים:

    1.         @Override
    2.         protected  Dialog onCreateDialog(int id, Bundle args){
    3.             switch(id){
    4.             case ALERT_DIALOG_ID:
    5.                 AlertDialog.Builder builder = new AlertDialog.Builder(this);
    6.                 builder.setMessage("האם לפרמט את הדיסק?");
    7.                 builder.setTitle("הודעה חשובה");
    8.                 builder.setIcon(R.drawable.my_monster1);
    9.                 builder.setCancelable(false);
    10.                 builder.setPositiveButton("כן", new DialogInterface.OnClickListener() {
    11.                        public void onClick(DialogInterface dialog, int id) {
    12.                            msgDisplay("You answered Yes, but it is not accepted ");
    13.                        }
    14.                    });
    15.                 builder.setNegativeButton("לא", new DialogInterface.OnClickListener() {
    16.                        public void onClick(DialogInterface dialog, int id) {
    17.                            msgDisplay("Answer was No");
    18.                        }
    19.                    });
    20.                 AlertDialog alert = builder.create();
    21.                 return(alert);
    22.             case PROGRESS_HORIZONTAL_DIALOG_ID:


    -----> החל משורה 22 התוכנית ממשיכה לטפל בדיאלוגים האחרים - PROGRESS_HORIZONTAL וכו" עליהם נדון בהמשך.
    שורה 3: ביצוע switch להפרדת הטיפול בבניית ה-dialog לפי ה-id.
    שורות 4-21: מטפלות ביצירת ה-Alert Dialog. היצירה מתבצעת בשני שלבים:
    1. יצירת builder ואפיון כל הפרמטרים דרכו  - שורה 5-19.
    2. יצירת אוביקט AlertDialog - שהוא subclass של Dialog - שורה 20. ה-builder משמש רק כלי עזר כשהמטרה היא יצירת אובייקט ה- AlertDialog.

      נעקוב אחר יצירת ה-builder. שורות 6-9 מגדירות את הדיאלוג.
      נעתיק שוב את 4 השורות:
      1.     builder.setMessage("האם לפרמט את הדיסק?");
      2.     builder.setTitle("הודעה חשובה");
      3.     builder.setIcon(R.drawable.my_monster1);
      4.     builder.setCancelable(false);
      4 הפעולות מבוצעות ע"י מתודות public של ה-class:
        שורה 1: קביעת תוכן ההודעה
        שורה 2: קביעת כותרת
        שורה 3: icon
        שורה 4: האם ניתן לבטל את הדיאלוג (ע"י back, Esc וכו").

        יצירת כפתורים
        שורות 10-14 ו-15-19 מגדירות כל אחת כפתור של חלון הדיאלוג (אפשר להגדיר עד 3 כפתורים), כולל הכיתוב עליו וה-callback.
        נעתיק 5 שורות אלה שוב:

        1.   builder.setNegativeButton("לא", new DialogInterface.OnClickListener() {
        2.                        public void onClick(DialogInterface dialog, int id) {
        3.                            msgDisplay("Answer was No");
        4.                        }
        5.                    });

        לכפתור הזה מוגדרים שני פרמטרים (שורה 1):
        1. הכיתוב על הכפתור
        2. ה-callback שיופעל עם לחיצה.
        בדומה ל-callbacks של כפתורים רגילים, יש לממש interface מסוג DialogInterface.OnClickListener  - שורה 2.
        במקרה של לחיצת כפתור בחלון ה-Alert, תודפס הודעה עם TOAST Message -שורה 3 מפעילה את מתודת העזר msgDisplay:
        1.                 private void msgDisplay(String msg)
        2.                 {
        3.                     Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
        4.                 }


        הטיפול בכפתור השני - שורות 15-20 בקוד למעלה, זהה

         עד כאן ה-Alert Dialog. אגב, מתוך כל הדיאלוגים שנסקור כאן, הוא המורכב יותר.



        Progress Dialog -Horizontal

         בדומה ל-Alert Dialog גם כאן מתבצעים אותם שלושה שלבים:
        1. הפעלת המתודה להצגת הדיאלוג - Dialog Show.
        2. יצירת ה-Dialog ב-Dialog Create callback.
        3. הפעלת ה-on click callback עבור כפתור הדיאלוג.
        שלב 1: בתוך ה-callback של כפתור ה-menu:

        1. case R.id.progress_dialog_horizontal:
        2.                     showDialog(PROGRESS_HORIZONTAL_DIALOG_ID);
        3.                        progressThread = new ProgressThread(handler);
        4.                         progressThread.start();


        שורה 1: ענף של ה-switch על כפתור ה-menu (המתודה כולה מוצגת כאן למעלה).
        שורה 2: הקריאה הסטנדרטית להפעלת - Dialog.
        שורות 3-4 הפעלת Thread: לצורך אילוסטרציה של עידכון פס ההתקדמות, נגדיר Thread שירוץ ברקע ויתן עידכונים שלפיהם נעדכן את התקדמות תהליך ה-processing. ראה תמונה 3 למעלה. שורה 4 מפעילה את ה-Thread. המימוש של ה-thread נעשה ב-class נפרד ובקובץ נפרד - במטרה לפשט את ה-class הראשי. נעבור על ה-Thread בהמשך.

        שלב 2: יצירת ה-Dialog ב-callback.
         הנה קטע הקוד הרלוונטי:


        1.             case PROGRESS_HORIZONTAL_DIALOG_ID:
        2.                 progressDialog= new ProgressDialog(this);
        3.            progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        4.                 progressDialog.setMessage("טוען..נא להמתין");
        5.                 progressDialog.setCancelable(false);
        6.                 progressDialog.setIndeterminate(false);
        7.                 progressDialog.setButton(ProgressDialog.BUTTON_POSITIVE, "cancel",new DialogInterface.OnClickListener() {
        8.                        public void onClick(DialogInterface dialog, int id) {
        9.                            msgDisplay("Action is cancelled");
        10.                     }
        11.                    });




        שורה 1 היא חלק ממנגנון ה- switch של ה-menu, ומכאן יטופלו רק דיאלוגים מהסוג LINEAR HORIZONTAL.
        שורות 2-6 יוצרות אובייקט מסוג ProgressDialog, שהוא sub class שך Dialog, ומאפיינות את הפרמטרים שלו תוך שימוש ב- public methods של ה-class:

        שורה 3: סוג ה-progressing dialog. ניתן לבחור בין  STYLE_HORIZONTAL לבין STYLE_SPINNER
        שורה 4 : קביעת תוכן ההודעה
        שורה 5: האם ניתן לסלק את ההודעה עם כפתור back וכו" (false משמעותו שלא ניתן). 
        שורה 6:  האם יוצג סרגל התקדמות: indeterminate = false משמעותו שאכן יוצג.


        שורות 7-9מגדירות כפתור ואת ה-callback שלו. בדומה לכל הדיאלוגים, גם כאן כתגובה ללחיצה על הכפתור תוצג הודעת TOAST מתאימה, שמופעלת בתוך מתודת העזר msgDisplay.

        Thread העזר
        הזכרנו את ה-Thread שאחראי על עדכון ההתקדמות. ה-Thread שולח הודעה בכל 100 מילישניה, בה הוא מגדיל את המונה ב-1.
         ה-Handler מקבל את ההודעה ומעדכן את מצב ההתקדמות בהתאם:

         progressDialog.setProgress(total);


        כשמד ההתקדמות יגיע לסוף (100, כלומר 10 שניות), הדיאלוג יסגר:

                                     dismissDialog(PROGRESS_HORIZONTAL_DIALOG_ID);

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


        Progress Dialog -SPINNER

        כאן מדובר במימוש פשוט תוך שימוש במתודה אחת סטטית של ה-ProgressDialog.

        1.                 case R.id.progress_dialog_spinner:
        2.                     ProgressDialog.show(this, "טוען...נא להמתין","הדגמת דיאלוג", false,true,new DialogInterface.OnCancelListener(){
        3.                         public void onCancel(DialogInterface dialog){
        4.                             msgDisplay("Action is cancelled");
        5.                            }
        6.                        });




        המתודה הסטטית show מקבלת את הפרמטרים הבאים:
        • קונטקסט
        • כותרת\title
        • טקסט ההודעה
        • indeterminate - האם אחוז ההתקדמות מוגדר. פרמטר זה רלוונטי רק ל-HORIZONTAL PROGRESSING DIALOG.
        • cancelable - האם ניתן לבטל את ההודעה עם כפתור back וכו". במקרה הנוכחי -איפשרנו  זאת.
        • cancel listener callback - ה-callback שיופעל במקרה שיופעל cancel. 
        • בדומה לדיאלוגים האחרים, גם ב-callback הנ"ל תופעל מתודת השירות msgDisplay שתציג הודעת TOAST.

        סקרנו את הקוד של שלושה סוגי Dialog - אחד מסוג Alert Dialog ועוד שני מימושים של Progress Dialog.
        ה-Time Pick וה- Date Pick בהחלט דומים לנ"ל ואף פשוטים מהם. הטיפול בהם  ובדיאלוגים שסקרנו לע"יל מודגם בקבצי הפרויקט.


        5 תגובות:

        1. אחלה מדריך.....
          רק שאלה אחת: איך אפשר להצמיד את כותרת הדיאלוג ימינה?

          השבמחק
        2. תודה. ניתן לדחוף את הכותרת ימינה ע"י הכנסת רווחים משמאל. זו הדרך הפשוטה. אפשר גם ליצור subclass ל-Dialog ולהגדיר שם את ה-layout.

          השבמחק
        3. היי רונן,
          ברצוני רק לציין כי הלינק של קבצי הפרוייקט שבור.
          אשמח אם תוכל לתקן זאת
          תודה רבה

          השבמחק
        4. תגובה זו הוסרה על ידי מנהל המערכת.

          השבמחק