יום שבת, דצמבר 25

גרפיקה 2D: ה-SurfaceView \האוירון

ניתן לגשת ל-UI רק מה-Thread הראשי. על המנטרה הזאת חזרתי כמדומני כבר מספר פעמים. הוצגו כאן מספר פוסטים ודוגמאות של גרפיקה בשני מימדים, ותמיד המתודות שטיפלו בגרפיקה היו שייכות ל-UI Thread. הנה הקישורים לפוסטים הנ"ל: 
השיטה בדוגמאות הנ"ל היתה:
  • יצירת subclass של View
  • ציור ב-onDraw.
  • עידכון הציור בעזרת invalidate.

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


איך זה עובד?
  • השיטה  מתבססת על SurfaceView. זהו subcalss  של View. 
  • ה-Thread המישני מצייר על משטח עבודה משל עצמו. 
  • ה-SurfaceView יעביר את המשטח לתצוגה - במסגרת ה-Thread הראשי.

ובאופן קצת יותר פרטני, תאור מסגרת הפעולות לביצוע:
  • יצירת subclass של SurfaceVeiw
  • ה-subclass צריך לממש את ה-SurfaceHolder.callback Interface, הכולל את המתודות הבאות-
  1. surfaceCreated
  2. surfaceDestroyed 
  3. surfaceChanged 
מדובר למעשה בשלושה callbacks שצריך לעשות להן override/
    • בנוסף למימוש 3 ה-callback הנ"ל, צריך להפעיל את המתודה  addCallback. היא "מדביקה" את ה-callbacks ל-SurfaceView subclass.
    • הפעולות על ה-SurfaceView נעשות למעשה באמצעות ה-SurfaceHolder class. שימו לב בדוגמא למתודה getHolder שמופעלת ב-constructor.
    • ו...קדימה לצייר, מתוך ה-Thread: 
      • המתודה lockCanvas מביאה את ה-canvas עליו נצייר.
      • המתודה unlockCanvasAndPost תשחרר את ה-canvas ותעביר אותו לתצוגה.


    תאור אפליקציית הדוגמא

    • האפליקציה מציגה מטוס שטס מסביב למסך, או בעצם מקיף את כדור הארץ...
    • תמונת הרקע היא של כדור הארץ כפי שצולם ע"י החללית אפולו.
    • תנועת המטוס מנוהלת ומצוירת  ב-SurfaceView במסגרת Thread מישני.
    • ניתן להתחיל\להפסיק את תנועת המטוס ולקבוע את מהירותו ע"י Context Menu (מופעל ע"י מגע על המסך).
    הנה תמונות של ה-UI:

    תמונה 1: הקפת כדור הארץ.




    הכיתוב על המסך כולל את קואורדינטות מיקום המטוס ומהירותו.

    תמונה מס 2: התפריט.





    כשהמערכת במצב "STOP" התפריט יכיל את האפשרות "START" במקום 4 פריטי התפריט בתמונה הנ"ל.




    תאור התוכנית

    הקוד מורכב משני קבצי ג'אווה:
    1. AirplaneActivity - מכיל  Subcalss של Activity. הוא משמש כמעטפת לאפליקציה, הפורסת את קובץ ה-layout ובכך גם מפעילה את ה-surfaceView, ומפעילה את ה-Context Menu. 
    2. AirplaneSurfaceView  - כאן נמצא הבשר של התוכנית. מממש את ה-SurfaceView subclass שבתוכו class משני מסוג Thread.  
     לפני שניכנס לג'אוות,  נעיף מבט על קובץ ה-Layout, ולו בגלל שיש כאן משהו חדש (רמז - שורה 7):
    1.  
    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.    <com.ahs.androheb.surfaceviewairplane.AirplaneSurfaceView
    8.       android:id="@+id/airplane"
    9.       android:layout_width="fill_parent"
    10.       android:layout_height="fill_parent"/>
    11. </LinearLayout>
    שורות 7-10 מתארות את ה-view שניצור וממקמות אותו בתוך העץ של ה-layout. שימו לב לשורה 7 הצבועה צהוב, בה נעשה היחוס ל-view ע"י הצבעה על ה-view class. 
    שתי הערות: 
    1. בדוגמא הנ"ל, ה-AirplaneSurfaceView הוא העלה היחידי ב-layout, אבל כדי לא לפגוע בכלליות אציין שהיה ניתן  לשלב  views, ווידגטים וכו' נוספים ב-layout הנ"ל.
    2. אם ניזכר בדוגמא שהוצגה ב-גרפיקה 2D: אפליקציית "צייר"\יצירת ווידג'ט , הרי שגם שם שילבנו  ב-layout view שיצרנו. שם  עשינו את החיבור  עם בדרך שונה, תוך שימוש בפקודה addView במקום לעשות את החיבור ב-xml.
    עד כאן ה-layout. נסקור עתה את שני קבצי הג'אווה. נתחיל בקובץ שהוא עיקר עינייננו, והוא גם המורכב יותר:
    SurfaceView
    הנה תוכנו של ה-class:

    package com.ahs.androheb.surfaceviewairplane;

    import android.content.Context;
    import android.content.res.Resources;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.Rect;
    import android.graphics.drawable.Drawable;
    import android.util.AttributeSet;
    import android.view.SurfaceHolder;
    import android.view.SurfaceView;
    1. class AirplaneSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
    2.     private Bitmap mBackgroundImage;
    3.     private int mCanvasHeight = 1;
    4.     private int mCanvasWidth = 1;
    5.     private int mAirplaneHeight;
    6.     private Drawable mAirplaneImage;
    7.     private int mAirplaneWidth;
    8.     private AirplaneThread thread;
    9.     public AirplaneSurfaceView(Context context, AttributeSet attrs) {
    10.         super(context, attrs);
    11.         Resources res = context.getResources();
    12.          mBackgroundImage = BitmapFactory
    13.                 .decodeResource(res,R.drawable.earth_apollo);
    14.         mAirplaneWidth = mAirplaneImage.getIntrinsicWidth();
    15.         mAirplaneHeight = mAirplaneImage.getIntrinsicHeight();
    16.         SurfaceHolder holder = getHolder();
    17.         holder.addCallback(this);
    18.         thread = new AirplaneThread(holder, context);
    19.         setFocusable(true);
    20.     }
    21.     public AirplaneThread getThread() {
    22.         return thread;
    23.     }
    24.   
    25.     public void surfaceCreated(SurfaceHolder holder) {
    26.         thread.setRunning(true);
    27.         thread.start();
    28.     }
    29.     public void surfaceDestroyed(SurfaceHolder holder) {
    30.         boolean retry = true;
    31.         thread.setRunning(false);
    32.         while (retry) {
    33.             try {
    34.                 thread.join();
    35.                 retry = false;
    36.             } catch (InterruptedException e) {
    37.             }
    38.         }
    39.     }
    40. public void surfaceChanged(SurfaceHolder holder, int format, int width,
    41.             int height) {
    42.         mCanvasWidth = width;
    43.         mCanvasHeight = height;
    44.         mBackgroundImage = Bitmap.createScaledBitmap(
    45.                 mBackgroundImage, width, height, true);
    46.     }
    47.  
    48.   
    49.  class AirplaneThread extends Thread {
    50.         final String TAG = "AirplaneThread";
    51.         private long mLastTime;
    52.         private boolean mRun = true;
    53.         private boolean runStopFlag;
    54.         private SurfaceHolder mSurfaceHolder;
    55.         private double mX;
    56.         private double mY;
    57.         private int mRotation;
    58.         public AirplaneThread(SurfaceHolder surfaceHolder, Context context) {
    59.             mSurfaceHolder = surfaceHolder;
    60.             }
    61.         @Override
    62.         public void run() {
    63.             mLastTime = 0;
    64.              while (mRun) {
    65.                 Canvas mCanvas = null;
    66.                 if(isRunning()){
    67.                     try{
    68.                         Thread.sleep(100);
    69.                     }
    70.                     catch(InterruptedException e){
    71.                     }
    72.                     try {
    73.                         mCanvas = mSurfaceHolder.lockCanvas(null);
    74.                             locationCalc();
    75.                             synchronized (mCanvas){
    76.                                    doDraw(mCanvas);
    77.                             }
    78.                     } finally {
    79.                         if (mCanvas != null) {
    80.                             mSurfaceHolder.unlockCanvasAndPost(mCanvas);
    81.                         }
    82.                     }
    83.                 }
    84.             }
    85.         }
    86.    
    87.         final float TEXT_LOCATION_X = 100;
    88.         final float TEXT_LOCATION_Y = 100;
    89.         private void doDraw(Canvas canvas) {
    90.             canvas.drawBitmap(mBackgroundImage, null , new Rect(0,0,getWidth(),getHeight()), null);//ronen
    91.             canvas.save();
    92.             canvas.rotate(mRotation, (float) mX, (float) mY);
    93.                mAirplaneImage.setBounds((int)mX - mAirplaneWidth/2, (int)mY - mAirplaneHeight/2,
    94.                        (int)mX + mAirplaneWidth/2, (int)mY + mAirplaneHeight/2);
    95.                mAirplaneImage.draw(canvas);
    96.                canvas.restore();
    97.             Paint paint = new Paint();
    98.             paint.setColor(Color.WHITE);
    99.             int intMx = (int)mX;
    100.             int intMy = (int)mY;
    101.             canvas.drawText("X= "+intMx+", Y= "+intMy+", speed= "+Math.max(Math.abs(velocityX),Math.abs(velocityY)) ,TEXT_LOCATION_X, TEXT_LOCATION_Y, paint);
    102.         }
    103.         private  int mPlaneVelocity = 80;
    104.         private int velocityX ;
    105.         private int velocityY;
    106.         private void locationCalc() {
    107.             int mMarginX = mAirplaneWidth/2;
    108.             int mMarginY = mAirplaneHeight/2;
    109.            
    110.             long now = System.currentTimeMillis();
    111.             double elapsed = (now - mLastTime) / 1000.0;
    112.              mX = mX + velocityX * elapsed;
    113.              mY = mY + velocityY * elapsed;
    114.               if(mX < (0 + mMarginX)){ /* Bottom left corner */
    115.                     velocityX = 0;
    116.                    velocityY = -mPlaneVelocity;
    117.                    mRotation = 0;
    118.                   mX = mMarginX;
    119.                   mY = mCanvasHeight - mMarginY;
    120.               }
    121.               else if (mY < (0 + mMarginY)){/* Top left corner */
    122.                       velocityX = mPlaneVelocity;
    123.                       velocityY = 0;
    124.                       mRotation = 90;
    125.                       mX = mMarginX;
    126.                       mY = mMarginY;
    127.               }
    128.               else if (mX > (mCanvasWidth - mMarginX)){ /* top right */
    129.                   velocityX = 0;
    130.                    velocityY = mPlaneVelocity;
    131.                    mRotation = 180;
    132.                  mX = mCanvasWidth-mMarginX;
    133.                    mY = mMarginY;
    134.                }
    135.              else if (mY > (mCanvasHeight - mMarginY)){/* bottom right */
    136.                   velocityX = -mPlaneVelocity;
    137.                    velocityY = 0;
    138.                    mRotation = 270;
    139.                    mX = mCanvasWidth-mMarginX;
    140.                    mY = mCanvasHeight - mMarginY;
    141.              }
    142.              mLastTime = now;
    143.         }
    144.         public final int SLOW = 40;
    145.         public final int MEDIUM = 100;
    146.         public final int FAST = 200;
    147.         public void mPlaneVelocitySet(int velocity){
    148.             mPlaneVelocity = velocity;
    149.         }
    150.     }
    151.     public void setRunning(boolean runCommand) {
    152.             runStopFlag = runCommand;
    153.         }
    154.         public boolean isRunning() {
    155.             return(runStopFlag);
    156.         }
    157.  
    158. }

    ה-class מכיל 3 מתודות - מלבד ה-constructor. מדובר ב 3 ה-callbacks של ה-SurfaceHolder.Callback אותן הזכרנו כבר למעלה. בנוסף למתודות הנ"ל, ה-SurfaceView מכיל class פנימי' subclass של ה-Thread , בו מתבצע הציור על גבי ב-SurfaceView (..כשלמעשה כאמור משתמשים ב-SurfaceHolder)
    שלושת ה-Callbacks  של SurfaceHolder.Callback

    1. surfaceCreated
    2. surfaceDestroyed 
    3. surfaceChanged
      (המתודות צבועות בטורקיז)

      נפרט עליהן קצת:
    • ה-surfaceCreated ו-surfaceDestroyed אלו ה-callbacks שמופעלים בימירת ובהריסת המשטח.
      • מותר לצייר על המשטח רק בפרק הזמן בו ה-Surface קיים, כלומר בין הפעלת ה-surfaceCreated callback לבין הפעלת ה- surfaceDestroyed callback. 
      •  נפעיל את ה-Thread ב-surfaceCreated  ונהרוג אותו ב- surfaceDestroyed.
    • surfaceChanged  - כפי ששמו מרמז, הוא מופעל בכל שינוי של ה-surface, ולפחות פעם אחת - אחרי surfaceCreated.  הנה המתודה:
    1. public void surfaceChanged(SurfaceHolder holder, int format, int width,
    2.             int height) {
    3.         mCanvasWidth = width;
    4.         mCanvasHeight = height;
    5.         mBackgroundImage = Bitmap.createScaledBitmap(
    6.                 mBackgroundImage, width, height, true);
    7.     }

    בעיקבות השינוי ב-surface נבצע:
    • שורה 3-4: עדכן את המשתנים שמחזיקים את גודל ה-canvas
    • שורה 5: ניצור bitmap חדש עפ"י המימדים העדכניים, ע"י פעולת  scale ל-bitmap הישן.

    ה-AirplaneSurfaceView Constructor ( רקע אפור??)

    1.   public AirplaneSurfaceView(Context context, AttributeSet attrs) {
    2.         super(context, attrs);
    3.         Resources res = context.getResources();
    4.          mBackgroundImage = BitmapFactory
    5.                 .decodeResource(res,R.drawable.earth_apollo);
    6.         mAirplaneWidth = mAirplaneImage.getIntrinsicWidth();
    7.         mAirplaneHeight = mAirplaneImage.getIntrinsicHeight();
    8.         SurfaceHolder holder = getHolder();
    9.         holder.addCallback(this);
    10.         thread = new AirplaneThread(holder, context);
    11.         setFocusable(true);
    12.     }
    עיקר פעולת ה-constructor: 
    • יצירת  ה-bitmap עבור תמונת הרקע מתוך התמונה שנמצאת ב-drawables,
    • יצירת ה-SurfaceHolder שמשמש לצורך גישה ל-Surface
    • ויצירת  את ה-Thread.
    וביתר פרוט:
    שורה 3-5: יצירת ה-Bitmap של תמונת הרקע. התמונה נמצאת ב-drawables. 
    שורה 3 יוצרת instance מסוג Resources  שישמש לפעולת ה-decodeResource של ה-BitmapFactory.
    שורות 6-7: שליפת המימדים של תמונת המטוס. 
    שורה 8: יצירת אובייקט SurfaceHolder ע"י getHolder. ה-holder הוא הממשק ל-SurfaceView.
    שורה 9: ...הזכרנו גם את זה...זה יחבר את 3 ה-callbacks (ראה צבע טורקיז) ל-class שלנו.
    שורה 10: יצירת ה-Thread. הוא יופעל, כאמור לעי"ל, רק ב-surfaceCreated.
    שורה 11: setFocusable מחבר את ה-view ל-touch. 

    נעבור לחלק הצהוב - ה-Thread subclass.


    המתודות של ה-Thread class:
    1. Constructor
    2. Run
    3. מתודות עזר של Run:
      1.  doDraw -locationCalc
      2. mPlaneVelocitySet
      3. setRunning
      4. isRunning

     פרוט:

    Constructor - בסה"כ שומר את האובייקט surfaceHolder, ואף על פעולה זו היה ניתן לוותר.
    Run - כאן כמובן ממומש ה-Thread שכלוא בלולאה האינסופית הכוללת:
    • המתנה של 100 מילישניה (שורה 8)
    • פעולת הציור ע"י  locationCalc ו-doDraw. (שורות 14, 15)

    1.       @Override
    2.         public void run() {
    3.             mLastTime = 0;
    4.              while (mRun) {
    5.                 Canvas mCanvas = null;
    6.                 if(isRunning()){
    7.                     try{
    8.                         Thread.sleep(100);
    9.                     }
    10.                     catch(InterruptedException e){
    11.                     }
    12.                     try {
    13.                         mCanvas = mSurfaceHolder.lockCanvas(null);
    14.                             locationCalc();
    15.                              synchronized(mCanvas){
    16.                                    doDraw(mCanvas);
    17.                             }
    18.                     } finally {
    19.                         if (mCanvas != null) {
    20.                             mSurfaceHolder.unlockCanvasAndPost(mCanvas);
    21.                         }
    22.                     }
    23.                 }
    24.             }
    25.         }

     השורות המעניינות במיוחד הן 13 ו-18:
    בשורה 13 ה-surfaceHolder מביא את ה-canvas עליו נצייר.
    ב-שורה 20 ה-canvas משוחרר ומועבר לתצוגה - תהליך שיעשה ב-Thread הראשי.
    אנחנו בתוך Thread לכן ישלסנכרן לפחות את Draw עם ה-SurfaceHolder - שורה 15.



     doDraw - כאן מתבצע הציור על ה-SurfaceView.

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

    1.    private void doDraw(Canvas canvas) {
    2.             canvas.drawBitmap(mBackgroundImage, null , new Rect(0,0,getWidth(),getHeight()), null);//ronen
    3.             canvas.save();
    4.             canvas.rotate(mRotation, (float) mX, (float) mY);
    5.                mAirplaneImage.setBounds((int)mX - mAirplaneWidth/2, (int)mY - mAirplaneHeight/2,
    6.                   (int)mX + mAirplaneWidth/2, (int)mY + mAirplaneHeight/2);
    7.                mAirplaneImage.draw(canvas);
    8.                canvas.restore();
    9.             Paint paint = new Paint();
    10.             paint.setColor(Color.WHITE);
    11.             int intMx = (int)mX;
    12.             int intMy = (int)mY;
    13.             canvas.drawText("X= "+intMx+", Y= "+intMy+", speed= "+Math.max(Math.abs(velocityX),Math.abs(velocityY)) ,TEXT_LOCATION_X, TEXT_LOCATION_Y, paint);
    14.         }
    15.  
    שורה 2: ציור של ה-bitmap - בכל פעם מחדש.  
    שאלה: למה לצייר מחדש? הרי הרקע סטטי.
    תשובה: ה-canvas שה-SurfaceHolder מביא "מלוכלך". יותר נכון, הוא מכיל את הציור שצויר בפעם הקודמת, או ליתר דיוק - על פי הבדיקה שלי- של הפעם הלפני קודמת (נראה שיש שני frames של canvas, והם מופיעים לסרוגין). בכל אופן, כדי למחוק את התמונה הקודמת, נצייר את הרקע מחדש. אתם מוזמנים לבדוק את התצוגה ללא שורה 2. 
    שורה 3: שמירת מצב ה-canvas.
    שורה 4: סיבוב המשטח כדי לקבל אפקט סיבוב המטוס.
    שורה 5: קביעת מיקום התחום בו נצייר את המטוס. 
    שורה 7: ציור המטוס!
    שורה 8: החזרת ה-canvas למצב שלפני הסיבוב. ה-save בשורה 3 השתלם.
    שורה 9-13: כתיבת נתוני מיקום ומהירות המטוס על המסך.

     locationCalc

    זהוי מתודת עזר בה נחשב את מיקום המטוס עפי הנתונים הבאים:

    mPlaneVelocity - מהירות המטוס. זה משתנה הניתן לשליטה ע"י המשתמש דרך ה-context menu  כפי שנראה בהמשך.

    elapsed- הזמן שעבר מאז הפעם הקודמת.


    1.   private void locationCalc() {
    2.             int mMarginX = mAirplaneWidth/2;
    3.             int mMarginY = mAirplaneHeight/2;
    4.            
    5.             long now = System.currentTimeMillis();
    6.             double elapsed = (now - mLastTime) / 1000.0;
    7.              mX = mX + velocityX * elapsed;
    8.              mY = mY + velocityY * elapsed;
    9.               if(mX < (0 + mMarginX)){ /* Bottom left corner */
    10.                     velocityX = 0;
    11.                    velocityY = -mPlaneVelocity;
    12.                    mRotation = 0;
    13.                   mX = mMarginX;
    14.                   mY = mCanvasHeight - mMarginY;
    15.               }
    16.               else if (mY < (0 + mMarginY)){/* Top left corner */
    17.                       velocityX = mPlaneVelocity;
    18.                       velocityY = 0;
    19.                       mRotation = 90;
    20.                       mX = mMarginX;
    21.                       mY = mMarginY;
    22.               }
    23.               else if (mX > (mCanvasWidth - mMarginX)){ /* top right */
    24.                   velocityX = 0;
    25.                    velocityY = mPlaneVelocity;
    26.                    mRotation = 180;
    27.                  mX = mCanvasWidth-mMarginX;
    28.                    mY = mMarginY;
    29.                }
    30.              else if (mY > (mCanvasHeight - mMarginY)){/* bottom right */
    31.                   velocityX = -mPlaneVelocity;
    32.                    velocityY = 0;
    33.                    mRotation = 270;
    34.                    mX = mCanvasWidth-mMarginX;
    35.                    mY = mCanvasHeight - mMarginY;
    36.              }
    37.              mLastTime = now;
    38.         }
    39.         public final int SLOW = 40;
    40.         public final int MEDIUM = 100;
    41.         public final int FAST = 200;
    42.         public void mPlaneVelocitySet(int velocity){
    43.             mPlaneVelocity = velocity;
    44.         }
    45.     }
    46.     public void setRunning(boolean runCommand) {
    47.             runStopFlag = runCommand;
    48.         }
    49.         public boolean isRunning() {
    50.             return(runStopFlag);
    51.         }
    52.  


    שורות 7-8: בעזרת שני נתונים אלה - מהירות וזמן -  מחושב מיקום המטוס שמוחזק בקואורדינטות mX ו-mY. 
    • כאשר המטוס מגיע לקצה צלע המלבן שלאורכה הוא נע - הבדיקה מתבצעת עם if/else שורות 9,16,13,30- משנים את כיוון הטיסה  ע"י עידכון רכיבי המהירות velocityX ו-velocityY.
    • וקובעים בהתאם את זוית הסיבוב הדרושה לחרטום המטוס -mRotation.
    mPlaneVelocitySet- קביעת מהירות המטוס. המתודה מופעלת ה-context menu
    setRunning - לעצירה\הפעלה של תנועת המטוס. מופעלת מתפריט ה-context.
    isRunning - בדיקת מצב מופעל\מופסק.


     AirplaneActivity

    עברנו לקובץ השני, כאן ממומש ה-Activity subcalss.


    package com.ahs.androheb.surfaceviewairplane;

    import android.app.Activity;
    import android.os.Bundle;
    import android.view.ContextMenu;
    import android.view.Menu;
    import android.view.MenuItem;
    import android.view.View;
    import android.view.Window;

    import com.ahs.androheb.surfaceviewairplane.AirplaneSurfaceView.AirplaneThread;
    1. public class AirplaneActivity extends Activity {
    2.     private AirplaneThread mAirplaneThread;
    3.     private AirplaneSurfaceView mAirplaneView;
    4.     @Override
    5.     protected void onCreate(Bundle savedInstanceState) {
    6.         super.onCreate(savedInstanceState);
    7.         requestWindowFeature(Window.FEATURE_NO_TITLE);
    8.         setContentView(R.layout.main);
    9.         mAirplaneView = (AirplaneSurfaceView) findViewById(R.id.airplane);
    10.         mAirplaneThread = mAirplaneView.getThread();
    11.         registerForContextMenu(mAirplaneView);
    12.     }
    13.     public static final int   SLOW  = Menu.FIRST+1;
    14.     public static final int   MEDIUM  = Menu.FIRST+2;
    15.     public static final int   FAST  = Menu.FIRST+3;
    16.     public static final int   STOP  = Menu.FIRST+4;
    17.     public static final int   START  = Menu.FIRST+5;
    18.     @Override
    19.     public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
    20.        if(!mAirplaneThread.isRunning()){
    21.            menu.add(Menu.NONE, START, Menu.NONE, "START");
    22.         }
    23.         else{
    24.             menu.add(Menu.NONE, SLOW, Menu.NONE, "SLOW");
    25.             menu.add(Menu.NONE, MEDIUM, Menu.NONE, "MEDIUM");
    26.             menu.add(Menu.NONE, FAST, Menu.NONE, "FAST");
    27.             menu.add(Menu.NONE, STOP, Menu.NONE, "STOP");
    28.            }
    29.         super.onCreateContextMenu(menu, v, menuInfo);
    30.         return;
    31.     }
    32.     @Override
    33.     public boolean onContextItemSelected(MenuItem item) {
    34.         switch (item.getItemId()) {
    35.             case START:
    36.                 mAirplaneThread.setRunning(true);
    37.                 break;
    38.                case STOP:
    39.                  mAirplaneThread.setRunning(false);
    40.                  break;
    41.             case SLOW:
    42.                 mAirplaneThread.mPlaneVelocitySet(mAirplaneThread.SLOW);
    43.                 break;
    44.             case MEDIUM:
    45.                 mAirplaneThread.mPlaneVelocitySet(mAirplaneThread.MEDIUM);
    46.                 break;
    47.             case FAST:
    48.                 mAirplaneThread.mPlaneVelocitySet(mAirplaneThread.FAST);
    49.                 break;
    50.        }
    51.        return(super.onContextItemSelected(item));
    52.     }
    53. }

     ה-class מכיל שלוש מתודות:
    1. onCreate - צהוב.
    2.  onCreateContextMenu- תכלת.
    3. onContextItemSelected.- בז'.
    השתיים האחרונות הן callbacks של ה-context menu.

    המתודה onCreate ללא חידושים מסעירים, ובכל זאת נציג אותה שוב:
    1. protected void onCreate(Bundle savedInstanceState) {
    2.         super.onCreate(savedInstanceState);
    3.         requestWindowFeature(Window.FEATURE_NO_TITLE);
    4.         setContentView(R.layout.main);
    5.         mAirplaneView = (AirplaneSurfaceView) findViewById(R.id.airplane);
    6.         mAirplaneThread = mAirplaneView.getThread();
    7.         registerForContextMenu(mAirplaneView);
    8.     }
    שורה 3: ביטול הכותרת להגדלת שטח התצוגה.
    שורה 6: הבאת משתנה האובייקט של ה-thread של ה-SurfaceView. נשתמש בו ב-onContextItemSelected להפעלת מתודות הקונפיגורציה (מהירות\הפסק\התחל).
    שורה 8: רגיסטרציה של ה-context menu ל-view.



     

    תגובה 1: