יום ראשון, אוקטובר 31

ווידג'טים 2: רשימה (ListView), וזה אפילו מדבר.


הכרנו שימוש בכפתורים וקלט\פלט של טקסט. כעת נכיר את ה-ListView המשמש להצגת רשימות. בהמשך נמשיך להכיר ווידג'טים נוספים. בתרגיל הזה נשלב גם TTS- Text To Speech. למרות הכלל הידוע לפיו יש להתמקד כל פעם בנושא חדש אחד, עברתי על הכלל כדי לגוון ולהוסיף עוד עניין. וגם כדי לראות שזה לא הרבה יותר מסובך מה- Android Inventor, שעליו עוד אכתוב, אבל רק אחרי שאקבל גישה אליו ואוכל לבדוק אותו מעבר לתעוד שפורסם. בכל מקרה, לפ הבנתי יש ל-Inventor מספר מגבלות, כולל אי יכולת לטפל במספר תצוגות באפליקציה אחת. ה-work around לכך הוא להפעיל אפליקציה אחרת לצורך החלפת התצוגה. אין אפשרות לייצר קוד ולשלב אותו באפליקציה או להכניס בו שינויים. אני בהחלט סקרן לבדוק את זה...בינתיים חזרה ל-ListView.
מטרה התרגיל: הצגת רשימת פריטים על המסך בעזרת ListView. בחירת אלמנט מהרשימה תגרום להשמעתו, תוך שימוש ביכולות Text To Speech הנתמכות החל מגרסה 1.5.
הנה התמונה של ה-UI בתרגיל זה:



השימוש ברשימה: השימוש ברשימות נעשה בעזרת רכיב מתאם - Adapter. ה-Adapter הוא רכיב המחבר בין אלמנט מסוג view, למשל ListView בו נשתמש כעת או ScrollView בו נשתמש בפוסט הבא, לבין רשימת הנתונים.
המחלקה class של אנדרואיד אותה נירש היא ListActivity. ה-ListActivity היא "בן" או Subclass של Activity עם התאמה לטיפול ברשימות, כך שהיא יודעת לטפל בארועים של בחירת אלמנט ברשימה על ידי המשתמש, כפי שנראה מיד.
אלמנט נוסף שנכיר הוא הממשק המתאם בין  ListView לבין רשימת הנתונים עצמה - ה - ListAdapter.

ניתן להוריד את כל חבילת הקוד ע"י לחצה על הקישור. לשם נוחות  myListActivity מוצג כאן בשלמותו. בהמשך נתעכב על החלקים המעניינים.


  1.  
  2. public class myListActivity extends ListActivity {
  3.     String[] listItems={"ikea", "porche", "b.m.w bmw", "volkswagen", "ronen",
  4.     "nike", "skype", "feijoa", "three thirty", "mushroom",
  5.     "android", "text to speech", "hizbulla", "hidden", "vladimir",
  6.     "munsento", "position"};
  7.     @Override
  8.     public void onCreate(Bundle icicle) {
  9.         super.onCreate(icicle);
  10.         setContentView(R.layout.main);
  11.         setListAdapter(new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,listItems));
  12.         initTextToSpeech();
  13.     }
  14.     public void onListItemClick(ListView parent, View v, int position,long id) {
  15.         speak(listItems[position]);
  16.     }
  17.     private static int TTS_DATA_CHECK = 1;
  18.     private TextToSpeech tts = null;
  19.     private boolean ttsIsInit = false;
  20.     private void initTextToSpeech() {
  21.         Intent intent = new Intent(Engine.ACTION_CHECK_TTS_DATA);
  22.         startActivityForResult(intent, TTS_DATA_CHECK);
  23.     }
  24.  
  25.     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  26.         if (requestCode == TTS_DATA_CHECK) {
  27.             if (resultCode != Engine.CHECK_VOICE_DATA_PASS) {
  28.                 Intent installVoice = new Intent(Engine.ACTION_INSTALL_TTS_DATA);
  29.                 startActivity(installVoice);
  30.             }
  31.             configTextToSpeech();
  32.         }
  33.     }
  34.     private void configTextToSpeech(){
  35.         tts = new TextToSpeech(this, new OnInitListener() {
  36.             public void onInit(int status) {
  37.                 if (status == TextToSpeech.SUCCESS) {
  38.                     ttsIsInit = true;
  39.                     if (tts.isLanguageAvailable(Locale.ENGLISH) >= 0)
  40.                         tts.setLanguage(Locale.ENGLISH);
  41.                     tts.setPitch(1);
  42.                     tts.setSpeechRate(1);
  43.                 }
  44.             }
  45.         });
  46.     }
  47.     private void speak(String inputString) {
  48.         if (tts != null && ttsIsInit) { 
  49.             tts.speak(inputString, TextToSpeech.QUEUE_ADD, null);
  50.         }
  51.     }
  52.    
  53.        
  54.     @Override
  55.     public void onDestroy() {
  56.         if (tts != null) {
  57.             tts.stop();
  58.             tts.shutdown();
  59.         }
  60.         super.onDestroy();
  61.     }
  62. }

    המתודות הממומשות ב- myListActivity הן:
    1.  onCreate - כרגיל נקראת עם יצירת האובייקט. אחראית לכל האיתחולים.
    2. onListItemClick - זהו callback של ListActivity שמופעל כשיש קליק על אלמנט ברשימה.
    3.  initTextToSpeech - מתודה לאיתחול ה TTS. מופעלת מתוך onCreate
    4. onActivityResult - זהו callback שמטפל בתשובה ל-Intent שנשלח לאובייקט ה-TTS. 
    5. configTextToSpeeh - יצירת האובייקט של ה-TTS, כולל קביעת פרמטרים של שפה ו- pitch.
    6. speak - המתודה שמבצעת את ה- TTS. מופעלת מתוך ה- onListItemClick.
    7. onDestroy - נקראת בסוף חיי האובייקט. נועדה לנקות את המערכת משאריות פעולת ה- TTS.

    נתחיל עם onCreate:
    1.     @Override
    2.     public void onCreate(Bundle icicle) {
    3.         super.onCreate(icicle);
    4.         setContentView(R.layout.main);
    5.         setListAdapter(new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,listItems));
    6.         initTextToSpeech();
    7.     }
     שורה 5: חיבור בין רשימת הdata, מערך בשם listItems, לבין אובייקט ה-ListView. המתודה
    setListAdapter מעבירה כפרמטר את המתאם של ListView. ה-Interface  מסוג ArrayAdapter נוצר.
    הפרמטרים ל-ArrayAdapter הם:
    context - ראה this.
    layout - במקרה הזה לא הגדרנו את ה-layout של הרשימה בקובץ xml והשתמשנו באלמנט אנדרואיד מוכן המייצג רשימה אנכית: simple_list_item_1.
    data array: מערך המחרוזות listItems.


    שורה 7 מפעילה מתודת השירות initTextToSpeech המאתחלת את ה- TTS - Text To Speech. נגיע למתודה זו בהמשך.
    onListItemClick: זוהי מתודה של ה- ListActivity  class. היא מופעלת ע"י הקלקה על פריט ברשימה.
    במקרה שלנו, היא מבצעת קריאה למתודה speak שמקבלת String כפרמטר ומפעילה את ה- TTS.

    initTextToSpeech: 
    שולח intent למודול ה- TTS כדי לבדוק אם ה- TTS מותקן במערכת.



    1.     private void initTextToSpeech() {
    2.         Intent intent = new Intent(Engine.ACTION_CHECK_TTS_DATA);
    3.         startActivityForResult(intent, TTS_DATA_CHECK);
    4.     }
    onActivityResult - זהו כאמור ה- callback שמטפל בתשובה של ה- TTS. אם עפ"י התשובה ה- TTS לא מותקן, הוא יותקן כעת ע"י שליחת Intent:

    1. protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    2.         if (requestCode == TTS_DATA_CHECK) {
    3.             if (resultCode != Engine.CHECK_VOICE_DATA_PASS) {
    4.                 Intent installVoice = new Intent(Engine.ACTION_INSTALL_TTS_DATA);
    5.                 startActivity(installVoice);
    6.             }
    7.             configTextToSpeech();
    8.         }
    9.     }
    שורה 2 בודקת שהתשובה קשורה ל-Intent שלנו.
    שורה 3 בודקת אם התשובה חיובית. אם אינה כזו, מתקינים את המודול ע"י שליחת Intent מתאים - שורות 4-6.
    שורה 7: קריאה למתודה שתקנפג את ה-tts - ראה בהמך.

    configTextToSpeeh : יצירת אובייקט tts והכנסת פרמטרים לאיתחול:


    1.     private void configTextToSpeech(){
    2.         tts = new TextToSpeech(this, new OnInitListener() {
    3.             public void onInit(int status) {
    4.                 if (status == TextToSpeech.SUCCESS) {
    5.                     ttsIsInit = true;
    6.                     if (tts.isLanguageAvailable(Locale.ENGLISH) >= 0)
    7.                         tts.setLanguage(Locale.ENGLISH);
    8.                     tts.setPitch(1);
    9.                     tts.setSpeechRate(1);
    10.                 }
    11.             }
    12.         });
    13.     }
     שורה 2: קריאה ל- constructor של TextToSpeech.הפרמטר שהוא מקבל הם:
    1. ה- context בו נשתמש - במקרה הנ"ל זה ה-Activity הנוכחי.
    2. onInitListener - ה-callback שיופעל כשה- TextToSpeech יסיים האיתחול ויהיה מוכן.
     המשך בשורה 3: ה-onInitListener נוצר בצורה "אנונימית",ומוגדרת המתודה onInit שתופעל עם כשה-TTS יהיה מוכן.
    שורה 6: בודקים האם השפה האנגלית נתמכת. במקרה שלנו התשובה תהיה או 0 שמשמעותה LANG_AVAILABLE או במקרה הפחות טוב,
    1- = LANG_MISSING_DATA או 2- =LANG_NOT_SUPPORTED.
    אם התשובה חיובית, נקנפג את השפה לאנגלית,  pitch =1 (זה תדר הדיבור הבסיסי) ו- קצב דיבור = 1. אפשר לשחק עם נפרמטרים האלה ולבחון את השפעתם על הדיבור המתקבל.

    speak: ראינו שהמתודה speak נקראת מתוך onClick.

    1.    private void speak(String inputString) {
    2.         if (tts != null && ttsIsInit) { 
    3.             tts.speak(inputString, TextToSpeech.QUEUE_ADD, null);
    4.         }
    5.     }

      שורה 2: בדיקת תקינות: tts קיים ומאותחל.
      שורה 3: הפעלת המתודה speak של tts שמבצעת את הדיבור. היא מקבלת 3 פרמטרים:
      • ה-input string
      • queueMode - קביעת אופן פעולת התור אליו שולחים את הנתונים להשמעה. במקרה שלנו בחרנו להוסיף את הנתונים לתור הקיים. האפשרות השניה - QUEUE_FLUSH - היא למחוק את כל הממתינים להשמעה ולדחוף את הקלט החדש שלנו במקום.
      • רשימה של פרמטרים לשימוש או null אם אין תוספת של פרמטרים. הפרמטרים ניתנים בצורת זוגות (key, value), ויכולים להיות   KEY_PARAM_STREAM או KEY_PARAM_UTTERANCE_ID. הראשון משמש להכוונת הדיבור ל-stream אחר והשני מאפשר לקנפג String שיוכל להיות trigger ל-callbak . אבל לא נרחיב על אלה עכשיו, אלא בפוסט מיוחד ל-TTS.
      onDestroy - כבר תארנו את מחזור חיי ה-Activity. המתודה onDestroy תסגור ותחזיר למערכת משאבים שנלקחו עבור ה-TTS.


      לפני סיום נציץ רגע לקובץ main.xml הלו הוא קובץ ה-layout:
      1. <?xml version="1.0" encoding="utf-8"?>
      2. <LinearLayout
      3. xmlns:android="http://schemas.android.com/apk/res/android"
      4. android:orientation="vertical"
      5. android:layout_width="fill_parent"
      6. android:layout_height="fill_parent" >
      7. <ListView
      8. android:id="@android:id/list"
      9. android:layout_width="fill_parent"
      10. android:layout_height="fill_parent"
      11. android:drawSelectorOnTop="false"
      12. />
      13. </LinearLayout>


      שים לב לשורה 8: ה-id של ה ListView מוגדר כ list. חובה להשתמש בשם זה אם ה-Activity הוא מסוג ListActivity - כמו במקרה שלנו. ה-ListActivity מניחה ש-list הוא השם שניתן לאלמנט.

      נסכם את הפוסט הזה: המטרה עיקרית היתה הכרות עם ה-class מסוג ListActivity. בדרך חיברנו לו גם יכולות TTS.



      6 תגובות:

      1. שאלה אם אתה מכיר . האם יש איזשהו משתנה דומה (באנדרואיד)למשתנה $result שנמצא בPHP ומקבל נתונים לתוכו מMYSQL, נגיד אם אני מקבל אובייקט מסוג זה יש איזשהו אופן להשתמש בו למימוש של ListActivity?

        השבמחק
      2. באנדרואיד ו-SQLite אין משתנה מיוחד לקליטת הנתונים. יש את Cursor אבל זה לא תואם בדיוק את הגדרתך.

        השבמחק
      3. אני אסביר את הבעיה שלי :
        יש לי שרת שכתוב בPHP אני מעביר מידע בין השרת לANDROID בעזרת SOAP , אני צריך לקבל את התשובה מבסיס הנתונים מהשרת .
        מה שאני מבצעה היום זה שאני מריץ PARSER שבניתי על האובייקט שאני מקבל ושומר אותו במערך .
        רציתי לבדוק אם יש איזשהו פתרון יותר יצירתי ? אני מקווה שאני לא היחיד שמשתמש בSOAP לגישה לשרת.
        נ.ב אני משתמש ב KSOAP2.

        השבמחק
      4. הי לאוניד, בקשר ל-SOAP. האם תתכן הוספת פרוקסי על השרת שיעביר את ה-SOAP לפרוטוקול פשוט יותר? למשל REST וכדומה?

        השבמחק
      5. אני יכול להגדיר PROXY אבל איך זה יכול לפתור לי את הבעיה , בשרת שלי אני משתמש גם לאפליקציה לIPHONE.

        השבמחק
      6. שלום, משום מה הקישור של הקבצים לא עובד.. זה רק אצלי??

        השבמחק