אנדיבמהלך השיעורים הקודמים, לימדנו אתכם מהם רכיבים גראפיים, כיצד למקם אותם על המסך ולעבוד איתם דרך הקוד. השבוע אנחנו משלימים את הנושא על ידי התייחסות לנושא חדש – ארועים (או Events בשמם הלועזי).

אז מה הם בעצם "ארועים"?
ארועים הם דרכם של רכיבים לספר לנו שמשהו בעצם קרה, ולתת לנו את ההזדמנות להגיב לכך.
דוגמא לכך היא למשל רכיב מסוג כפתור (Button) המודיע לנו שמישהו לחץ עליו. דוגמא נוספת היא שרכיב מסוג תיבת טקסט (EditText) מודיע לנו שהמשתמש הקיש בתוכו תו מסויים. רכיבים גראפיים מכילים ארועים רבים שאנחנו יכולים להאזין להם.

Interface (ממשק)

רגע לפני שנסביר כיצד מבצעים את זה, בואו ניתן סקירה לאלו מכם שאינם ותיקים בתחום התיכנות אודות נושא ה- Interfaces (ממשקים) בשפת Java וכיצד יש להשתמש בהם.
אחת הדרכים הטובות והפשוטות להעביר נושא זה, היא באמצעות סיפור.

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

יקותיאל כתב את הקוד לכפתור הזה והחליט שהוא כל כך מוצלח! שהוא רוצה למכור אותו דרך האינטרנט ולעשות מליונים! הוא קרא לכפתור שלו SuperButton.
כאשר לוחצים על הכפתור של יקותיאל, יקותיאל רוצה להכריז לאפליקציה שמשתמשת בכפתור שלו "לחצו עלי!!", כדי שהאפליקציה תוכל להגיב בהתאם.

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

מה שהיה חסר בעצם ליקותיאל זה בעצם הגדרה של Interface, שמאפשרת לו לסכם מראש על מתודה אליה הוא יקרא תמיד, לאחר לחיצה על הכפתור.

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

הגדרת Interface

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

הגדרה של Interface בשם OnDoubleClickListener

public interface OnDoubleClickListener {
	public void onClick();
}

הגדרנו interface חדש בשם OnDoubleClickListener המכיל הגדרה של מתודה אחת בשם onClick.
בעצם כאשר לוחצים על הכפתור של יקותיאל פעמיים (Double Click), תבוצע קריאה יזומה למתודה בשם הזה.
שימו לב גם לכך שהגדרת interface מתחילה במשפט public interface ולא במשפט public class.

public class SuperButton {
	private OnDoubleClickListener myListener;

	public void setOnClickListener(OnDoubleClickListener Listener) {
		myListener = Listener;
	}

	public void someMethod() {
		if (myListener != null)
			myListener.onClick();
	}
}

זאת בעצם ההגדרה של המחלקה המיוחדת של יקותיאל, SuperButton. המימוש עצמו של המחלקה לא משנה להסבר הנוכחי שלנו ולכן הוא לא כתוב פה בכלל והמחלקה נראית ריקה.
מה שמאד מעניין הוא השורות הבאות:
שורה 02: הגדרה של משתנה בשם myListener מסוג DoubleClickListener. המשתנה בעצם יכול להכיל כל עצם אחר, אשר מממש את המתודה (או המתודות, במקרה אחר) שהוגדרו בה- Interface שלנו OnDoubleClickListener.
שורות 04-06: הגדרה של מתודה בשם setOnClickListener המקבלת כפרמטר מחלקה המממשת את DoubleClickListener ומזינה אותה לתוך myListener. בעצם כל מחלקה מחוץ לכפתור המיוחד של יקותיאל יכולה לקרוא למתודה הזאת ולהעביר כפרמטר כל מחלקה אשר מממשת את המתודות שנמצאות בתוך DoubleClickListener.

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

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

להלן דוגמא למחלקה שמממשת את OnDoubleClickListener

public class UnknownClass implements OnDoubleClickListener {
	@Override
	public void onClick() {
		// ..some code...
		// ..some code...
	}
}

בעצם את כל המחלקה UnknownClass אפשר להעביר כפרמטר למתודה SetOnClickListener בתוך SuperButton, והיא כבר תדאג לקרוא ל- onClick שנמצאת בתוכה וזאת תוך ידיעה ודאית ש- onClick ממומש.

סיכום Interfaces

השימוש ב- Interfaces הינו נפוץ ושימושי ביותר, ובעזרתו אנחנו יכולים למשל להתחבר לכל הארועים שמתרחשים ברכיבים הגראפיים השונים (כפתורים, תיבות טקסט, טבלאות וכל רכיב גראפי אחר). ישנם שימושים רבים אחרים לממשקים, אבל לא נדון עליהם בשיעור זה.

ארועים (Events)

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

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

Button

כדי להאזין לארוע שמתרחש בכל פעם שהמשתמש לוחץ על הכפתור, אנו צריכים לממש Interface בשם View.OnKeyListener (כלומר, ה- Interface נמצא בתוך המחלקה View ולכן שמה מופיע קודם) ולאחר מכן לממש מתודה בשם onClick (אל תשכחו שברגע שממשתם Interface בעצם "חתמתם על חוזה" ואתם מחוייבים לממש את המתודות שלו).

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

יאללה לעבודה

צרו פרוייקט חדש באנדרואיד, ובממשק המשתמש הוסיפו רכיב מסוג EditText ומתחתיו רכיב מסוג Button. קובץ ה- xml של המסך צריך להראות בסוף כך:

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

<EditText android:text="" android:id="@+id/EditText01" android:layout_width="wrap_content" android:layout_height="wrap_content"></EditText>
<Button android:text="" android:id="@+id/Button01" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>
</LinearLayout>

הכנסו לקובץ ה- Activity המרכזי שלכם, והוסיפו בשורת הגדרת המחלקה מימוש ל- Interfaces שתיארתי לעיל (View.OnKeyListener, View.OnClickListener). שורת הקוד צריכה להראות כך:

	public class GUI extends Activity implements View.OnClickListener {

זאת כמובן בתנאי ששם המחלקה היא באמת GUI, אחרת יופיע לכם שם אחר.

אתם יכולים לראות שישר אחרי שהגדרתם את המחלקה, ה- Eclipse ישים לכם X אדום בשורה שלהם ויתריע לכם שמשהו לא בסדר. כפי שציינתי מקודם, הבעיה היא שממשנו שני Interfaces (כלומר שני "חוזים") אבל לא מימשנו את המתודות שהוגדרו בתוכם. לחיצה אחת על ה- X האדום תקפיץ תפריט שיקל לכם על העבודה ויאפשר לכם לבחור "add unimplemented methods", כלומר "הוסף מתודות חסרות". ביחרו באפשרות זו.

מיד תוכלו לראות שה- Eclipse הוסיף לכם אוטומטית את המתודות onKey ו- onClick.
כעת כל מה שצריך לעשות הוא ללכת לרכיבים עצמם ולהגיד להם שאנחנו מאזינים להם.
לאחר מכן יש כמובן להוסיף את הלוגיקה הרצויה לתוך המתודות.
להלן קטע הקוד המלא:

package iAndroid.GUI;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

public class GUI extends Activity implements View.OnClickListener {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        Button myButton = (Button)findViewById(R.id.Button01);
        myButton.setOnClickListener(this);
    }

	@Override
	public void onClick(View v) {
		EditText myEditText = (EditText)findViewById(R.id.EditText01);

		Button myButton = (Button)v;
		myButton.setText(myEditText.getText());
	}
}

בואו נעבור על השורות החשובות בקטע הקוד הזה:

שורה 9: הגדרת המחלקה ומימוש ה- Interfaces. כבר דיברנו על כך לעיל.
שורה 16: מציאת הכפתור שלנו מתוך ה- Resource
שורה 17: קריאה למתודה setOnClickListener אשר מקבלת פרמטר אחד, והוא מחלקה אשר מממשת את ה- Interface (ה- "חוזה") onClick. גם כאן אנחנו מעבירים את this כי מחלקת GUI מממשת אותו.

שורות 33 עד 37: מתודת onClick שרצה לאחר שמשתמש לחץ על הכפתור. בקצרה, הקוד בעצם משנה את טקסט של הכפתור לטקסט הרשום בתוך תיבת הטקסט.

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

התוצאה של הרצת האפליקצה

סיכום ארועים (Events)

השיעור למדנו מה הם Interfaces, מה הם ארועים (Events) וכיצד ניתן להאזין להם ולהגיב בהתאם.

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

אנדיאחלה שיעורי בית, פרופסור! אפשר לעשות המון דברים עם החומר שלמדנו היום! ואנחנו נמשיך לדבר ולהרחיב עליו גם בשיעורים הבאים.

אז עד אז, שבוע מצויין ונתראה בשיעורים הבאים!