יום ראשון, דצמבר 19

גרפיקה 2D: אפליקציית "צייר"\יצירת ווידג'ט


זהו פוסט נוסף בסדרת הגרפיקה הדו מימדית. קדמו לו שני פוסטים:
למי שאינו מכיר את הנושא מומלץ לקרוא קודם את שני הפוסטים הנ"ל.
בפוסט הציור האינטראקטייבי יצרנו subclass של view בו מימשנו יכולות של ציור על המסך תוך שימוש ב-onDraw.
הדגשים העיקיריים שיוצגו כאן:
  • שילוב ה-view עם layout שמכיל ווידג'טים סטנדרטים (Buttons. TextEdit).
  • יצירת Listener Callback עבור ה-view.

עוד כמה מילות רקע לפני שנתחיל בעבודה:
התצוגה באנדרואיד מאורגנת ב-Layouts. זהו מבנה  בצורת עץ היררכי כשהענפים הנמוכים יותר הם הקונטיינרים (למשל LinearLAyout,FrameLayout, והם מארגנים את הענפים שנמצאים בהיררכיות הגבוהות יותר מבחינת סדר ומאפייני תצוגה. העלים והענפים שבהיררכיות הגבוהות הם המרכיבים אותם רואה המשתמש ואותם הוא יכול להפעיל - תלוי בסוג שלהם. אלה הם הווידג'טים. אגב, העברת העץ לתצוגה נעשית באותו אופן היררכי, מתחתית העץ כלפי מעלה, כשהעלים שבקצה והמאפיינים שלהם נכנסים אחרונים.

View הוא המחלקה באנדרואיד שמטפלת בממשק המשתמש. הממשק שבאחריותה כולל גרפיקה, טקסט, שליחת אודיו להפעלת שמע, וידאו וכו.
 הווידג'טים הם subclass של android.view.View, בעוד הקונטיינרים הם subclass של android.view.View.ViewGroup.
מעבר לתצוגה, הווידג'טים מאפשרים גם ביצוע control  בעזרת ה-Listener callbacks שמחוברים אליהם: ל-Button למשל מחובר ה-OnClickListener שיופעל בלחיצה על הכפתור.
אנחנו ניצור כאמור  subclass של View עם Listener callback, או במילים אחרות, ניצור ווידג'ט.
הוא יתחבר לאפליקציית paint המופעלת עם מגע. חווית המשתמש נהדרת - לדעתי.

נתחיל בהצגת מסך האפליקציה:


 הציור על המסך קצת עליז, אבל הרי ציינתי שחווית המשתמש ב"צייר" הזה מאד נעימה.

תאור האפליקציה
  • ציור של שתי צורות, קוים ועיגולים, ע"י מגע על המסך.
  • אפשרות קביעת צבע של כל צורה.
  • אפשרות למחוק את הצורה האחרונה שצוירה.
  • אפשרות למחוק את כל התמונה.

תאור ה-UI 
נסקור את התמונה הנ"ל מלמעלה למטה:
4 חלונות EditText המציגים את הקואורדינטות של הצורה האחרונה שצוירה.
שני החלונות השמאליים מציגים את הקואורדינטות של נקודת ההתחלה של הקו ואם מדובר בעיגול -קואורדינטות מרכז המעגל.
שני החלונות הימניים מציגים את הקואורדינטות של נקודת הסיום של הקו ובמקרה של עיגול, נקודת מגע האצבע על קשת המעגל.
אגב, החלונות הנ"ל מטופלים ב-Listener callback שניצור עבור ה-ווידגט שלנו, כך שהם בהחלט מספקים מטרה מתודית.
4 כפתורים שליטה:
Clear Last: מוחק את הצורה האחרונה. כל לחיצה נוספת תמחק צורה נוספת.
כפתור Line/Circle: קובע את הצורה שתצויר.
Clear All: מוחק את הציור.
Set Color: בוחר צבע.
מרכיבי התוכנית

התוכנית מורכבת מ-4 classes ראשיים.

  1. Line: מתאר צורה בודדת - קו או עיגול. 
  2. Lines: מטפל בכל הצורות שמרכיבות את התמונה. הוספת ומחיקת צורות נעשית אצלו. במרכז ה-class ישנה רשימה מקושרת אליה מוסיפים וממנה מוחקים את הצורות.
  3. LinesView: זהו subclass של View, שמטפל בציור על המסך. הוא  מכיל את onDraw ו-invalidate. קביעת הפרמטרים הגרפים של הל משטח העבודה וגם של הצורות (צבע, עובי קו, וכו') נעשים אצלו..
  4. DrawControlActivity: זהו ה-subclass של Activity, שמטפל ב-UI: מעביר את ה-view לתצוגה, מכיל את הווידג'טים ואת ה-Listener callbacks שלהם. 
שלושת ה-classes הראשונים מרכיבים את ה-ווידג'ט, בעוד ה-DrawControlActivity הוא המעטפת.




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


סקירת הקוד

נעבור על הקוד Top Down. נתחיל ב-DrawControlActivity ונסיים ב-class שמטפל בצורה הבודדת - Line.


 DrawControlActivity

הנה הקוד של ה-class כולו:

  1. public class DrawControlActivity extends Activity {
  2.     /** Dot diameter */
  3.     final int LINE = 0;
  4.     final int CIRCLE = 1;
  5.     final int shape[] = {CIRCLE, LINE,};
  6.     int mColorIndex = 0;
  7.     int mShapeIndex = 0;
  8.     Button mButtonSetColor;
  9.     Button mButtonSetShape;
  10.     Button mButtonDelLastShape;
  11.     EditText editTextstartX;
  12.     EditText editTextstartY;
  13.     EditText editTextstopX;
  14.     EditText editTextstopY;
  15.     final Lines lines = new Lines();
  16.     /** The application view */
  17.     LinesView linesView;
  18.  
  19.     /** Called when the activity is first created. */
  20.     @Override public void onCreate(Bundle state) {
  21.         super.onCreate(state);
  22.         mLinesChangeListenerSet();
  23.          mViewSetup();
  24.         mButtonsSet();
  25.         mEditTextSet();
  26.     }
  27.     private void mViewSetup(){
  28.     setContentView(R.layout.main);
  29. linesView = new LinesView(this, lines);
  30.         ((LinearLayout) findViewById(R.id.root2)).addView(linesView);
  31.     }
  32.      private void mLinesChangeListenerSet(){
  33.     lines.setLinesChangeListener(new Lines.LinesChangeListener() {
  34.         public void onLinesChange(Lines lines) {
  35.             Line localLine = lines.getLastLine();
  36.             editTextstartX.setText((null == localLine) ? "" : String.valueOf(localLine.getStartX()));
  37.             editTextstartY.setText((null == localLine) ? "" : String.valueOf(localLine.getStartY()));
  38.             editTextstopX.setText((null == localLine) ? "" : String.valueOf(localLine.getStopX()));
  39.             editTextstopY.setText((null == localLine) ? "" : String.valueOf(localLine.getStopY()));
  40.         } });
  41.     }
  42.     private void mButtonsSet(){
  43.         final int mLineColor[] = { Color.GREEN, Color.RED, Color.BLUE, Color.BLACK};
  44.         mButtonSetColor = (Button)findViewById(R.id.button_lines_color);
  45.         mButtonSetColor.setOnClickListener(
  46.             new Button.OnClickListener() {public void onClick(View v) {
  47.                 setLinesColor(mLineColor[(mColorIndex)%mLineColor.length]);
  48.                  mButtonSetColor.setTextColor( mLineColor[(mColorIndex++)%mLineColor.length] );
  49.                 } });
  50.         ((Button) findViewById(R.id.butoon_clear_screen)).setOnClickListener(
  51.             new Button.OnClickListener() {
  52.                 public void onClick(View v) {
  53.                     viewScreenClean();
  54.                  } });
  55.         mButtonSetShape = (Button)findViewById(R.id.button_shape_set);
  56.         mButtonSetShape.setOnClickListener(
  57.         new Button.OnClickListener() {
  58.             public void onClick(View v) {
  59.             switch(shape[(mShapeIndex++)%shape.length]){
  60.                 case LINE:
  61.                     mShapeSet(LINE);
  62.                     mButtonSetShape.setText("Line");
  63.                        break;
  64.                 case CIRCLE:
  65.                    mShapeSet(CIRCLE);
  66.                 mButtonSetShape.setText("Circle");
  67.                    break;
  68.             }
  69.         } });
  70.         mButtonDelLastShape = (Button)findViewById(R.id.button_last_draw_del);
  71.         mButtonDelLastShape.setOnClickListener( new Button.OnClickListener() {
  72.             public void onClick(View v) {
  73.                 lastShapeDelete();
  74.              } });
  75.     }
  76.     private void mEditTextSet(){
  77.     editTextstartX = (EditText) findViewById(R.id.start_point_x);
  78.     editTextstartY = (EditText) findViewById(R.id.start_point_y);
  79.     editTextstopX = (EditText) findViewById(R.id.stop_point_x);
  80.     editTextstopY = (EditText) findViewById(R.id.stop_point_y);
  81.     }
  82.     /* Callbacks' Service Methods */
  83.     private void setLinesColor(int color){
  84.            linesView.mShapeColorSet(color );
  85.        }
  86.      private void viewScreenClean(){
  87.         linesView.viewScreenClean();
  88.     }
  89.         private void lastShapeDelete(){
  90.         linesView.lastShapeDelete();
  91.     }
  92.     private void mShapeSet(int shape){
  93.         switch (shape){
  94.         case LINE:
  95.             linesView.mShapeSetLine();
  96.             break;
  97.         case CIRCLE:
  98.             linesView.mShapeSetCircle();
  99.             break;
  100.         }
  101.     }
  102. }






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

onCreate - היא המתודה המרכזית כאן. זו המתודה  שמופעלת עם יצירת האובייקט. היא מבצעת את עיקר העבודה, כולל יצירת ה-Listener callbacks כמובן.
נסתכל עליה ונקבל את תמונת השלד של המתודות של ה-class הזה.



  1.    @Override public void onCreate(Bundle state) {
  2.         super.onCreate(state);
  3.         mLinesChangeListenerSet();
  4.          mViewSetup();
  5.         mButtonsSet();
  6.         mEditTextSet();
  7. }

מלבד הקריאה ל-super היא מפעילה 4 מתודות 
ראה שורות 3-6 כאן למעלה. נסקור את 4 המתודות הללו.
מתודה ראשונה: mViewSetup. פריסת ה-view לתצוגה. כאן נראה לראשונה את החיבור של ה-view שיצרנו לבד ל-layout הכללי.

  1.    private void mViewSetup(){
  2.     setContentView(R.layout.main);
  3.   linesView = new LinesView(this, lines);
  4.         ((LinearLayout) findViewById(R.id.root2)).addView(linesView);
  5.     }

 השורה 1, זהה לאופן בו תמיד העברנו את ה-layout לתצוגה. כעת, נשתול view נוסף בתוך העץ:
בשורה 2 מיוצר האובייקט של ה-view (אליו נגיע בהמשך).
בשורה 3: מוסיפים את ה-view כבן של root2 ע"י המתודה  addView. האלמנט root2 הוא LinearLayout בתוך קובץ ה-layout.  לא אציג כאן את קובץ ה-layout, היות שאין בו שום דבר מיוחד. הקובץ נמצא בחבילת קבצי הפרויקט - ראה קישור בתחתית הדף.

מתודה שניה: mLinesChangeListenerSet.


  1. private void mLinesChangeListenerSet(){
  2.     lines.setLinesChangeListener(new Lines.LinesChangeListener() {
  3.         public void onLinesChange(Lines lines) {
  4.             Line localLine = lines.getLastLine();
  5.             editTextstartX.setText((null == localLine) ? "" : String.valueOf(localLine.getStartX()));
  6.             editTextstartY.setText((null == localLine) ? "" : String.valueOf(localLine.getStartY()));
  7.             editTextstopX.setText((null == localLine) ? "" : String.valueOf(localLine.getStopX()));
  8.             editTextstopY.setText((null == localLine) ? "" : String.valueOf(localLine.getStopY()));
  9.         } });
  10.     }


 נראה בהחלט כיצירה רגילה של listener's callback. המתודה onLinesChange של ה-callback אמורה להיות מופעלת כשיש שינוי בתצוגת ה-view שיצרנו, ולהציג את הקואורדינטות של הצורה האחרונה שנוספה ב-4 חלונות ה- EditText (כפי שתארנו למעלה). את המתודות שמפעילות את ה-listener נראה ב-classes הבאים.
שורה 4: שולפת את אובייקט הצורה האחרון שהתווסף לציור.
שורות 5-8: שולפות את הקואורדינטות של האובייקט , (x,y של שתי הנקודות, ראה תאור האפליקציה למעלה), ומצידות אותן ב-4 חלונות התצוגה.


מתודה שלישית: mButtonsSet. כאן מאותחלים 4 הכפתורים וה-callbacks שלהם. ה-callbacks כשלעצמם סטנדרטים. כדי לבצע פעולות המחיקה, שינוי הצבע וקביעת הצורה, הם משתמשים במתודות עזר - ראה שורה xx ואילך.


מתודה רביעית: mEditTextSet.

כאן רוכזו כל האיתחולים של אובייקטי ה-EditText. מיד נראה את ה-callback שכותב לשם.


נעבור לסקירת ה-class בא - ה- LinesView.

הנה הוא בשלמותו:


  1. class LinesView extends View {
  2.     private int mShapeColor = Color.BLACK;
  3.     /* enum could be nicer but takes more system resources: */
  4.     private final int LINE = 0;
  5.     private final int CIRCLE = 1;
  6.     private int mShapeType = LINE;
  7.     private final Lines lines;
  8.     float mStartLineX;
  9.     float mStartLineY;
  10.     float mStopLineX;
  11.     float mStopLineY;
  12.     Paint paint;
  13.     public LinesView(Context context, Lines lines) {
  14.         super(context);
  15.         this.lines = lines;
  16.         setMinimumWidth(400);
  17.         setMinimumHeight(400);
  18.         paint = new Paint(Paint.ANTI_ALIAS_FLAG);
  19.         paint.setStyle(Style.STROKE);
  20.         paint.setStrokeWidth(4);
  21.     }
  22.     @Override
  23.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  24.         setMeasuredDimension(
  25.             getSuggestedMinimumWidth(),
  26.             getSuggestedMinimumHeight());
  27.     }
  28.     @Override protected void onDraw(Canvas canvas) {
  29.         canvas.drawColor(Color.WHITE);
  30.          mCurrentlyTouchedShapeDraw(canvas, paint);
  31.         mAllShapesDraw(canvas, paint);
  32.     }
  33.     @Override
  34.     public boolean onTouchEvent(MotionEvent event) {
  35.         int action = event.getAction();
  36.         if (action==MotionEvent.ACTION_DOWN){
  37.             mStartLineX = event.getX();
  38.             mStartLineY = event.getY();
  39.         }
  40.         else if (action==MotionEvent.ACTION_MOVE){
  41.             mStopLineX = event.getX();
  42.             mStopLineY = event.getY();
  43.             invalidate();
  44.         }
  45.         else if (action==MotionEvent.ACTION_UP){
  46.             lines.addLine(mStartLineX, mStartLineY, event.getX(), event.getY(), mShapeColorGet(), mShapeTypeGet());
  47.             invalidate();
  48.         }
  49.         return true;
  50.     }
  51.     public void  lastShapeDelete()
  52.     {
  53.           currentDrawCancel();
  54.           lines.lastLineRemove();
  55.           invalidate();
  56.     }
  57.    
  58.     public void  viewScreenClean(){
  59.       currentDrawCancel();
  60.       lines.clearLines();
  61.       invalidate();
  62.     }
  63.  
  64.     public void  currentDrawCancel()
  65.     {
  66.           mStartLineX     = 0;
  67.           mStartLineY     = 0;
  68.           mStopLineX      = 0;
  69.           mStopLineY      = 0;
  70.     }
  71.   
  72.     private void mCurrentlyTouchedShapeDraw(Canvas canvas, Paint paint){
  73.         paint.setColor(mShapeColorGet());
  74.        switch(mShapeTypeGet()){
  75.             case    LINE:
  76.                 canvas.drawLine(mStartLineX, mStartLineY, mStopLineX, mStopLineY, paint);
  77.                 break;
  78.             case    CIRCLE:
  79.                 float startX =mStartLineX;
  80.                 float startY =mStartLineY;
  81.                 float stopX = mStopLineX;
  82.                 float stopY = mStopLineY;
  83.                 float radius = (float)(Math.sqrt(Math.pow(stopX-startX,2) + Math.pow(stopY-startY,2)));
  84.                 canvas.drawCircle(startX, startY, radius, paint);
  85.                 break;
  86.         }
  87.     }   
  88.     private void mAllShapesDraw(Canvas canvas, Paint paint){
  89.         for (Line line : lines.getLines()) {
  90.             paint.setColor(line.getColor());
  91.             switch (line.shapeGet()){
  92.             case LINE:
  93.                 canvas.drawLine(line.getStartX(), line.getStartY(), line.getStopX(), line.getStopY(), paint);
  94.                 break;
  95.             case CIRCLE:
  96.                 float stopX = line.getStopX();
  97.                 float stopY = line.getStopY();
  98.                 float startX = line.getStartX();
  99.                 float startY = line.getStartY();
  100.                 float radius = (float)(Math.sqrt(Math.pow(stopX-startX,2) + Math.pow(stopY-startY,2)));
  101.                 canvas.drawCircle(startX, startY, radius, paint);
  102.                 break;
  103.             }   
  104.         }
  105.     }
  106.     public void mShapeColorSet(int color){
  107.         mShapeColor = color;
  108.     }
  109.     public int mShapeColorGet(){
  110.         return(mShapeColor);
  111.     }
  112.     public void mShapeSetLine()
  113.     {
  114.             mShapeType = LINE;
  115.     }
  116.     public void mShapeSetCircle()
  117.     {
  118.             mShapeType = CIRCLE;
  119.     }
  120.     private int  mShapeTypeGet()
  121.     {
  122.             return(mShapeType);
  123.     }





רשימת המתודות של ה-class:
  1.  LinesView - ה-constructor.
  2. onMeasure - זהו callback שמופעל לקביעת גודל ה-view. הכנסתי אותו רק לצרכים מתודיים.
  3.  onDraw - זהו  ה-callback המוכר בו נעשות פעולות הציור.
  4.   onTouchEvent - זהו ה-callback שמופעל עם נגיעה על המסך. הנגיעה על המסך מגדיר את מיקום וגודל הציור.
  5. lastShapeDelete- מתודה למחיקת הצורה האחרונה. התהליך ניזום ע"י לחצה על כפתור.
  6. viewScreenClean- מתודה למחיקת כל התצוגה. גם תהליך זה ניזום ע"י כפתור.
  7. currentDrawCancel - זו מתודת עזר מסוג private שמוחקת את הצורה האחרונה. נדבר עליה מיד.
  8.  mCurrentlyTouchedShapeDraw - זו מתודת עזר שמציירת את הצורה שכרגע בתהליך הציור,        ועדיין לא הוכנסה לרשימה המקושרת, כי המגע עדיין לא עזב את המסך.
  9. mAllShapesDraw- זו מתודת עזר שסורקת את הרשימה המקושרת המכילה את הצורות ומציירת אותן.
ועוד כמה מתודות באורך שורה שכותבות וקוראות משתנים חברים ב-class:

  • mShapeColorSet
  • mShapeColorGet
  • mShapeSetLine
  •  mShapeSetCircle
  • mShapeTypeGet
נסקור את תשע המתודות הנ"ל:


מתודה 1: LinesView - ה-constructor.

  1.  public LinesView(Context context, Lines lines) {
  2.         super(context);
  3.         this.lines = lines;
  4.         setMinimumWidth(400);
  5.         setMinimumHeight(400);
  6.         paint = new Paint(Paint.ANTI_ALIAS_FLAG);
  7.         paint.setStyle(Style.STROKE);
  8.         paint.setStrokeWidth(4);
  9.  
  10.     }

 שורה 3: שמירה של אובייקט ה-lines, שכאמור מחזיק את הצורות אותן נצייר.
שורות 4-5 קובעות את גודל החלון המינימלי שה-view דורש. במתודה הבאה - onMeasure - נשתמש בגדלים אלה לקביעת גודל החלון. הגודל המינימלי שהוכנס כאן הוא יותר מהגודל שנשאר על המסך ל-view שלנו...
שורות 6-8: קביעת חלק מהפרמטרים של paint. אלה הפרמטרים שלא נשנה באופן דינמי.
שורה 6: איתחול האובייקט, עם הדגל ANTI_ALIAS. עם הדגל הציורים חלקים יותר.
שורה 7: STROKE יוצר צורות חלולות, בניגוד ל-FILL.
שורה 8: רוחב הקו.


מתודה 2:onMeasure. 
זהו callback שמופעל לקביעת גודל ה-view. הכנסתי אותו רק לצרכים מתודיים.
onMesure הוא callback שמופעל בתהליך יצירת ה-view וניתן לעשות לו override כמו שעשיתי לו כאן, וקבעתי את גודל חלון ה-view. עשיתי את זה מסיבות מתודיות בלבד: הגודל שמוכנס כאן, קונפג ב-constructor והוא גדול יותר מהגודל המקסימלי שנשאר על המסך, כך שגם ללא onMeasure גודל החלון היה בהתאם למאפיין fill_parent של root2 בקובץ ה-layout




מתודה 3 onDraw 
זהו  ה-callback  בו נעשות פעולות הציור.


  1. @Override protected void onDraw(Canvas canvas) {
  2.         canvas.drawColor(Color.WHITE);
  3.          mCurrentlyTouchedShapeDraw(canvas, paint);
  4.         mAllShapesDraw(canvas, paint);
  5.     }

 onDraw מופעל באופן יחסית אינטנסיבי לכן כדאי להוציא פעולות ככל שניתן.
שורה 2: צבע הרקע.
שורה 3: קריאה למתודת העזר mCurrentlyTouchedShapeDraw שמציירת את הצורה שכרגע האצבע מציירת. נראה בהמשך שכל אחת מהצורות שציירנו מוכנסת לרשימה המקושרת ("הזיכרון") רק כשהאצבע מורמת מהמסך. כל עוד היא גוררת את הציור, היא עדיין לא בזיכרון.  המתודה הנ"ל- מציירת את הצורה בזמן שהאצבע זזה על המסך. 
 שורה 4: הפעלת  mAllShapesDraw. זוהי מתודת עזר שסורקת את הרשימה המקושרת של הצורות ומציירת את כולן.






מתודה 4:  onTouchEvent 
זהו ה-callback שמופעל עם נגיעה על המסך. הנגיעה על המסך מגדיר את מיקום וגודל הציור.


  1. public boolean onTouchEvent(MotionEvent event) {
  2.         int action = event.getAction();
  3.         if (action==MotionEvent.ACTION_DOWN){
  4.             mStartLineX = event.getX();
  5.             mStartLineY = event.getY();
  6.         }
  7.         else if (action==MotionEvent.ACTION_MOVE){
  8.             mStopLineX = event.getX();
  9.             mStopLineY = event.getY();
  10.             invalidate();
  11.         }
  12.         else if (action==MotionEvent.ACTION_UP){
  13.             lines.addLine(mStartLineX, mStartLineY, event.getX(), event.getY(), mShapeColorGet(), mShapeTypeGet());
  14.             invalidate();
  15.         }
  16.         return true;
  17.     }

 עשינו override ל-callback הזה, ומטפלים כאן ב-3 מצבים:
ACTION_DOWN- זה הארוע של הנחת האצבע על המסך. כאן שומרים את הקואורדינטות של נקודת ההתחלה - startX, startY. במקרה של ציור קו, זהו הקודקוד הראשון. במקרה של עיגול, זהו המרכז.
ACTION_MOVE- כאן אנו שומרים את הקואורדינטות הרגעיות, וקוראים ל-invalidate. כתוצאה מזה onDraw יצייר מחדש את כל הצורות ה"ישנות" + הצורה שהאצבע מציירת כרגע.
ACTION_UP - האצבע מתרוממת, מוסיפים את הצורה האחרונה לרשימה מקושרת ע"י המתודה addLine, ומפעילים invalidate.





מתודה חמישית:   lastShapeDelete 
מתודה למחיקת הצורה האחרונה. התהליך ניזום ע"י לחצה על כפתור. כאן מתבצעות שתי פעולות (מלבד ה-invalidate):

  1. public void  lastShapeDelete()
  2.     {
  3.           currentDrawCancel();
  4.           lines.lastLineRemove();
  5.           invalidate();
  6.     }
 הצורה האחרונה, אחרי שהוכנסה לזיכרון (אחרי ACTION_UP) למעשה מצוירת פעמיים:
פעם אחת מתוך mCurrentlyTouchedShapeDraw
פעם נוספת עם כל הצורות ע"י mAllShapesDraw.
לכן צריך למחוק אותה משני נמקומות עם שתי המתודות - שורות 3-4.

מתודה 6:viewScreenClean 
מתודה למחיקת כל התצוגה. 
מפעילים כאן שתי מתודות: אחת למחקית הקואורדינטות של הצורה האחרונה שצוירה, והשניה לניקוי הרשימה המקושרת, ע"י מתודה של האובייקט lines.
   

  1.   public void  viewScreenClean(){
  2.       currentDrawCancel();
  3.       lines.clearLines();
  4.       invalidate();
  5.     }


 מתודה 7: currentDrawCancel
זוהי מתודת עזר מסוג private, שמאפסת את הקואורדינטות של הצורה הנוכחית.


מתודה 8:mCurrentlyTouchedShapeDraw 
זו מתודת עזר שמציירת את הצורה שכרגע בתהליך הציור,        ועדיין לא הוכנסה לרשימה המקושרת, כי המגע עדיין לא עזב את המסך.

  1.   private void mCurrentlyTouchedShapeDraw(Canvas canvas, Paint paint){
  2.         paint.setColor(mShapeColorGet());
  3.        switch(mShapeTypeGet()){
  4.             case    LINE:
  5.                 canvas.drawLine(mStartLineX, mStartLineY, mStopLineX, mStopLineY, paint);
  6.                 break;
  7.             case    CIRCLE:
  8.                 float startX =mStartLineX;
  9.                 float startY =mStartLineY;
  10.                 float stopX = mStopLineX;
  11.                 float stopY = mStopLineY;
  12.                 float radius = (float)(Math.sqrt(Math.pow(stopX-startX,2) + Math.pow(stopY-startY,2)));
  13.                 canvas.drawCircle(startX, startY, radius, paint);
  14.                 break;
  15.         }
  16.     }   

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

מתודה 9: mAllShapesDraw .
 זו מתודת עזר שסורקת את הרשימה המקושרת המכילה את הצורות ומציירת אותן.
דומה למתודה שתוארה לע"יל (mCurrentlyTouchedShapeDraw), אך כאן מבצעים סריקה בלופ של הרשימה המקושרת ושולפים את הקואורדינטות, הצבע והצורה מכל איבר ברשימה.




נעבור ל-class הבא: ה-Lines.
כבר הוזכר למעלה שה-class הזה מנהל רשימה מקושרת של הצורות שצוירו, אותה סורקים ב-onDraw (או יותר נכון במתודת השרות שלה mAllShapesDraw).


הנה תוכן ה-class:
  1. public class Lines {
  2.     private final LinkedList<Line> lines = new LinkedList<Line>();
  3.     public Line getLastLine() {
  4.         return (lines.size() <= 0) ? null : lines.getLast();
  5.     }
  6.     public void lastLineRemove() {
  7.         if(lines.size() > 0){
  8.             lines.removeLast();
  9.         }
  10.         notifyListener();
  11.     }
  12.     public List<Line> getLines() {
  13.         return lines;
  14.     }
  15.     public void addLine(float startX, float startY, float stopX, float stopY, int color, int width) {
  16.         lines.add(new Line( startX,  startY,  stopX,  stopY,  color,  width));
  17.         notifyListener();
  18.     }
  19.     public void clearLines() {
  20.         lines.clear();
  21.         notifyListener();
  22.     }
  23.     public interface LinesChangeListener {
  24.         void onLinesChange(Lines lines);
  25.     }
  26.     private LinesChangeListener linesChangeListener;
  27.     public void setLinesChangeListener(LinesChangeListener listener) {
  28.         linesChangeListener = listener;
  29.     }
  30.     private void notifyListener() {
  31.         if (null != linesChangeListener) {
  32.             linesChangeListener.onLinesChange(this);
  33.         }
  34.     }
  35. }

שורה 2: יצירת ה-linked list של אובייקטים מטיפוס Line. אלה האובייקטים שמתארים קו - או עיגול. בהמשך נתאר גם את ה-class של Line.


שורות 3-22 מכילות מתודות שמטפלות ברשימה המקושרת, וכוללות למשל הוספה של צורה, מחיקה של הצורה האחרונה, מחיקת כל הצורות. 
addLine למשל, יוצרת אובייקט של Line עבור כל צורה שמתווספת.
בסוף כל מתודה שמעדכנת את הרשימה, מופעלת notifyListener. מיד נדבר עליה.


שורות 23-34 קשורות בהגדרת ויצירת ה-Listener callback  בשם linesChangeListener.
זוהי דוגמא ליצירת ה-Listener אותו אנו מקבלים מוכן כשמדובר בווידג'טים הסטנדרטים. יצירת  ה-Listener נעשית ב-3 צעדים:
 שורה 23: הגדרת interface ובו מתודת ה-callback. 
שורה 27: שמירת  ה-instance של האובייקט. אגב, בצעד זה בעצם קבענו שנוכל לצור רק listener יחיד עבור כל אובייקט. אם ניצור listener נוסף, הוא ידרוך על הקודם. 
כעת, כששמרנו את ה-instance של ה-listener, נוכל להפעיל אותו מתי שנחפוץ בכך. 
נפעיל אותו עם ה-notifyListener. פשוט מאד..:-).

סיימנו עם ה-Lines class. נעבור ל-class האחרון.
LINE Class

והנה הוא בשלמותו:

  1. public  class Line {
  2.     private final float mStartX;
  3.     private final float mStartY;
  4.     private final float mStopX;
  5.     private final float mStopY;
  6.     private final int mColor;
  7.     private final int mShape;
  8.     public Line(float startX, float startY, float stopX, float stopY, int color, int shape) {
  9.         mStartX = startX;
  10.         mStartY = startY;
  11.         mStopX = stopX;
  12.         mStopY = stopY;
  13.         mColor =  color;
  14.         mShape = shape;
  15.     }
  16.     public float getStartX() { return mStartX; }
  17.     public float getStartY() { return mStartY; }
  18.     public float getStopX() { return mStopX; }
  19.     public float getStopY() { return mStopY; }
  20.     public int getColor() { return mColor; }
  21.     public int shapeGet() { return mShape; }
  22. }

בבסיס האובייקט, הפרמטרים שמגדירים את הקו או העיגול:

  1.    private final float mStartX;
  2.     private final float mStartY;
  3.     private final float mStopX;
  4.     private final float mStopY;
  5.     private final int mColor;
  6.     private final int mShape;


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






4 תגובות:

  1. הקישור להורדת הקבצים, מוגדר פרטי.
    אי-אפשר להוריד

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

    השבמחק
  3. תודה על התשובה המהירה.
    הבלוג שלך על-הכיפעך.

    הבעיה נשארה

    This file is currently set to private.


    When a file is set to private by its owner only the owner of the file can access it. If you are the owner of the file please log into your account to access this file.
    If you believe you have reached this page in error, please contact support.
    Click here to view our help resources

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

    השבמחק