אפשר להטמיע את תהליך הגרירה והשחרור בתצוגות על ידי תגובה לאירועים שעלולות לגרום להתחלת הגרירה ולתגובה וצריכת אירועי שחרור.
התחלת גרירה
המשתמש מתחיל גרירה בתנועה, בדרך כלל על ידי נגיעה או לחיצה לוחצים לחיצה ארוכה על הפריט שהם רוצים לגרור.
כדי לטפל בזה ב-View
, צריך ליצור
אובייקט ClipData
ו
אובייקט של ClipData.Item
עבור
של הנתונים שמועברים. כחלק מ-ClipData
, יש לספק מטא-נתונים
מאוחסן
אובייקט ClipDescription
בתוך ClipData
. בפעולת גרירה ושחרור שלא מייצגת
בתנועת נתונים, כדאי להשתמש ב-null
במקום באובייקט בפועל.
לדוגמה, קטע הקוד הזה מראה איך להגיב לנגיעה & החזקה
תנועה ב-ImageView
על ידי יצירת אובייקט ClipData
שמכיל את
תג (או תווית) של ImageView
:
Kotlin
// Create a string for the ImageView label. val IMAGEVIEW_TAG = "icon bitmap" ... val imageView = ImageView(context).apply { // Set the bitmap for the ImageView from an icon bitmap defined elsewhere. setImageBitmap(iconBitmap) tag = IMAGEVIEW_TAG setOnLongClickListener { v -> // Create a new ClipData. This is done in two steps to provide // clarity. The convenience method ClipData.newPlainText() can // create a plain text ClipData in one step. // Create a new ClipData.Item from the ImageView object's tag. val item = ClipData.Item(v.tag as? CharSequence) // Create a new ClipData using the tag as a label, the plain text // MIME type, and the already-created item. This creates a new // ClipDescription object within the ClipData and sets its MIME type // to "text/plain". val dragData = ClipData( v.tag as? CharSequence, arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN), item) // Instantiate the drag shadow builder. We use this imageView object // to create the default builder. val myShadow = View.DragShadowBuilder(view: this) // Start the drag. v.startDragAndDrop(dragData, // The data to be dragged. myShadow, // The drag shadow builder. null, // No need to use local data. 0 // Flags. Not currently used, set to 0. ) // Indicate that the long-click is handled. true } }
Java
// Create a string for the ImageView label. private static final String IMAGEVIEW_TAG = "icon bitmap"; ... // Create a new ImageView. ImageView imageView = new ImageView(context); // Set the bitmap for the ImageView from an icon bitmap defined elsewhere. imageView.setImageBitmap(iconBitmap); // Set the tag. imageView.setTag(IMAGEVIEW_TAG); // Set a long-click listener for the ImageView using an anonymous listener // object that implements the OnLongClickListener interface. imageView.setOnLongClickListener( v -> { // Create a new ClipData. This is done in two steps to provide clarity. The // convenience method ClipData.newPlainText() can create a plain text // ClipData in one step. // Create a new ClipData.Item from the ImageView object's tag. ClipData.Item item = new ClipData.Item((CharSequence) v.getTag()); // Create a new ClipData using the tag as a label, the plain text MIME type, // and the already-created item. This creates a new ClipDescription object // within the ClipData and sets its MIME type to "text/plain". ClipData dragData = new ClipData( (CharSequence) v.getTag(), new String[] { ClipDescription.MIMETYPE_TEXT_PLAIN }, item); // Instantiate the drag shadow builder. We use this imageView object // to create the default builder. View.DragShadowBuilder myShadow = new View.DragShadowBuilder(imageView); // Start the drag. v.startDragAndDrop(dragData, // The data to be dragged. myShadow, // The drag shadow builder. null, // No need to use local data. 0 // Flags. Not currently used, set to 0. ); // Indicate that the long-click is handled. return true; });
תגובה להתחלת גרירה
במהלך פעולת הגרירה, המערכת שולחת אירועי גרירה אל אירוע הגרירה
מאזינים של האובייקטים View
בפריסה הנוכחית. המאזינים מגיבים באמצעות
קוראים לפונקציה DragEvent.getAction()
כדי לקבל את סוג הפעולה. בתחילת הגרירה,
השיטה הזו מחזירה את הערך ACTION_DRAG_STARTED
.
בתגובה לאירוע עם סוג הפעולה ACTION_DRAG_STARTED
, נוצר אירוע גרירה
ה-listener חייב לבצע את הפעולות הבאות:
שיחת טלפון
DragEvent.getClipDescription()
ולהשתמש ב-methods של סוג MIME ב-ClipDescription
שמוחזר כדי לראות האם המאזינים יכולים לקבל את הנתונים שגוררים.אם פעולת הגרירה והשחרור לא מייצגת את תנועת הנתונים, יכול להיות תהיה מיותרת.
אם ה-listener לאירוע הגרירה יכול לאשר שחרור, הוא צריך להחזיר את הערך
true
כדי לומר את המערכת להמשיך לשלוח אירועי גרירה ל-listener. אם המאזינים לא יכול לקבל ירידה, המאזינים חייבים להחזירfalse
והמערכת מפסיקה שולחת אירועי גרירה למאזינים עד שהמערכת שולחתACTION_DRAG_ENDED
כדי לסיים את פעולת הגרירה והשחרור.
עבור אירוע ACTION_DRAG_STARTED
, השיטות הבאות של DragEvent
לא
תקף: getClipData()
,
getX()
,
getY()
וגם
getResult()
.
טיפול באירועים שקרו במהלך הגרירה
במהלך פעולת הגרירה, גוררים פונקציות event listener שמחזירות את הערך true
בתגובה ל-
אירוע הגרירה ACTION_DRAG_STARTED
ימשיך לקבל אירועי גרירה. הסוגים
מאירועי הגרירה שהמאזינים מקבל במהלך הגרירה תלויים במיקום
לגרור את הצללית ואת החשיפה של View
של המאזין. מאזינים משתמשים בגרירה
כדי להחליט אם הם חייבים לשנות את המראה של ה-View
שלהם.
במהלך פעולת הגרירה, DragEvent.getAction()
מחזיר אחד מתוך שלושה ערכים:
ACTION_DRAG_ENTERED
: ה-listener מקבל את סוג פעולת האירוע הזה כשנקודת המגע – מצביעים על המסך מתחת לאצבע או לעכבר של המשתמש — מזינים את תיבה תוחמת (bounding box) שלView
של המאזינים.ACTION_DRAG_LOCATION
: ברגע שה-listener מקבל אירועACTION_DRAG_ENTERED
, הוא מקבל אירוע חדש אירוע אחד (ACTION_DRAG_LOCATION
) בכל פעם שנקודת המגע זזה עד שהיא מקבל אירועACTION_DRAG_EXITED
. השיטותgetX()
ו-getY()
מחזירה את נקודות ה-X וה-Y של נקודת המגע.ACTION_DRAG_EXITED
: סוג פעולת האירוע הזה נשלח למאזינים שקיבל בעברACTION_DRAG_ENTERED
האירוע נשלח כשנקודת המגע של הצללית של הגרירה נע מתוך התיבה התוחמת שלView
של המאזין אל מחוץ תיבה תוחמת (bounding box).
ה-listener לאירוע הגרירה לא צריך להגיב לאף אחד מסוגי הפעולות האלה. אם המיקום ה-listener מחזיר ערך למערכת והמערכת מתעלמת ממנו.
ריכזנו כאן כמה הנחיות שיעזרו לכם להגיב לכל אחד מסוגי הפעולות האלה:
- בתגובה ל-
ACTION_DRAG_ENTERED
או ל-ACTION_DRAG_LOCATION
, ה-listener יכול לשנות את המראה שלView
כדי לציין שהתצוגה יעד של ירידה פוטנציאלית. - אירוע עם סוג הפעולה
ACTION_DRAG_LOCATION
מכיל נתונים תקינים עבורgetX()
ו-getY()
תואמים למיקום של נקודת המגע. ה-listener יכול להשתמש במידע הזה כדי לשנות את המראה שלView
נקודת מגע או כדי לקבוע את המיקום המדויק שבו המשתמש יכול לשחרר תוכן. - בתגובה ל-
ACTION_DRAG_EXITED
, המאזינים חייבים לאפס את המראה שינויים שהוא חל בתגובה ל-ACTION_DRAG_ENTERED
אוACTION_DRAG_LOCATION
. מציין למשתמש שהשדהView
אינו יעד ירידה קרוב.
איך מגיבים לירידה
כשהמשתמש משחרר את הצללית של הגרירה מעל View
, ולפני כן View
מדווח שהוא יכול לקבל את התוכן במהלך הגרירה, המערכת שולחת
גוררים את האירוע אל View
עם סוג הפעולה ACTION_DROP
.
ה-listener לאירוע הגרירה חייב לבצע את הפעולות הבאות:
קוראים לפונקציה
getClipData()
כדי לקבל את האובייקטClipData
שהיה במקור שסופקה בשיחה אלstartDragAndDrop()
ולעבד את הנתונים. אם פעולת הגרירה והשחרור לא מייצגת נתונים אין כאן צורך.הפונקציה מחזירה ערך בוליאני
true
כדי לציין שהירידה עובדה בהצלחה, אוfalse
אם לא. הערך המוחזר הופך לערך המוחזרgetResult()
לאירוע הסופיACTION_DRAG_ENDED
. אם המערכת לא שולח אירועACTION_DROP
, הערך המוחזר על ידיgetResult()
לאירועACTION_DRAG_ENDED
הואfalse
.
עבור אירוע ACTION_DROP
, getX()
ו-getY()
משתמשים במערכת הקואורדינטות של
הפונקציה View
שמקבלת את הירידה כדי להחזיר את המיקום X ו-Y של
נקודת המגע ברגע הירידה.
המשתמש יכול לשחרר את הצללית של הגרירה מעל View
שאירוע הגרירה שלו יכול
ה-listener לא מקבל אירועי גרירה, אזורים ריקים בממשק המשתמש של האפליקציה שלכם או אפילו
באזורים מחוץ לאפליקציה, מערכת Android לא תשלח אירוע עם פעולה
צריך להקליד ACTION_DROP
ולשלוח רק אירוע ACTION_DRAG_ENDED
.
להגיב כשיש קצה גרירה
המערכת שולחת גרירה מיד לאחר שהמשתמש משחרר את צל הגרירה
אירוע עם סוג הפעולה ACTION_DRAG_ENDED
לכל המאזינים לאירועי גרירה
באפליקציה שלכם. סימן שפעולת הגרירה הסתיימה.
כל האזנה לאירוע גרירה חייב לבצע את הפעולות הבאות:
- אם ה-listener משנה את המראה שלו במהלך הפעולה, הוא אמור להתאפס בחזרה למראה ברירת המחדל כאינדיקציה ויזואלית למשתמש הפעולה הסתיימה.
- המאזינים יכולים להתקשר ל-
getResult()
כדי לקבל מידע נוסף פעולה. אם ה-listener מחזיר את הערךtrue
בתגובה לאירוע של פעולה מקלידיםACTION_DROP
, ואזgetResult()
מחזירים את הערך הבוליאניtrue
. בכל שאר במקרים מסוימים,getResult()
מחזירהfalse
בוליאני, כולל כשהמערכת לא שולח אירועACTION_DROP
. - כדי לציין שפעולת השחרור הושלמה בהצלחה, ה-listener
אמור להחזיר למערכת
true
בוליאני. אם לא מחזירים אתfalse
, סימן חזותי שמציג את הטלת הצללית שחוזרת למקור שלו עשוי להצביע על כך המשתמש שהפעולה נכשלה.
דוגמה לאירועי גרירה: תגובה
כל אירועי הגרירה מתקבלים על ידי שיטת אירוע הגרירה או על ידי ה-listener. קטע הקוד הבא הוא דוגמה לתגובה לאירועי גרירה:
Kotlin
val imageView = ImageView(this) // Set the drag event listener for the View. imageView.setOnDragListener { v, e -> // Handle each of the expected events. when (e.action) { DragEvent.ACTION_DRAG_STARTED -> { // Determine whether this View can accept the dragged data. if (e.clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) { // As an example, apply a blue color tint to the View to // indicate that it can accept data. (v as? ImageView)?.setColorFilter(Color.BLUE) // Invalidate the view to force a redraw in the new tint. v.invalidate() // Return true to indicate that the View can accept the dragged // data. true } else { // Return false to indicate that, during the current drag and // drop operation, this View doesn't receive events again until // ACTION_DRAG_ENDED is sent. false } } DragEvent.ACTION_DRAG_ENTERED -> { // Apply a green tint to the View. (v as? ImageView)?.setColorFilter(Color.GREEN) // Invalidate the view to force a redraw in the new tint. v.invalidate() // Return true. The value is ignored. true } DragEvent.ACTION_DRAG_LOCATION -> // Ignore the event. true DragEvent.ACTION_DRAG_EXITED -> { // Reset the color tint to blue. (v as? ImageView)?.setColorFilter(Color.BLUE) // Invalidate the view to force a redraw in the new tint. v.invalidate() // Return true. The value is ignored. true } DragEvent.ACTION_DROP -> { // Get the item containing the dragged data. val item: ClipData.Item = e.clipData.getItemAt(0) // Get the text data from the item. val dragData = item.text // Display a message containing the dragged data. Toast.makeText(this, "Dragged data is $dragData", Toast.LENGTH_LONG).show() // Turn off color tints. (v as? ImageView)?.clearColorFilter() // Invalidate the view to force a redraw. v.invalidate() // Return true. DragEvent.getResult() returns true. true } DragEvent.ACTION_DRAG_ENDED -> { // Turn off color tinting. (v as? ImageView)?.clearColorFilter() // Invalidate the view to force a redraw. v.invalidate() // Do a getResult() and display what happens. when(e.result) { true -> Toast.makeText(this, "The drop was handled.", Toast.LENGTH_LONG) else -> Toast.makeText(this, "The drop didn't work.", Toast.LENGTH_LONG) }.show() // Return true. The value is ignored. true } else -> { // An unknown action type is received. Log.e("DragDrop Example", "Unknown action type received by View.OnDragListener.") false } } }
Java
View imageView = new ImageView(this); // Set the drag event listener for the View. imageView.setOnDragListener( (v, e) -> { // Handle each of the expected events. switch(e.getAction()) { case DragEvent.ACTION_DRAG_STARTED: // Determine whether this View can accept the dragged data. if (e.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) { // As an example, apply a blue color tint to the View to // indicate that it can accept data. ((ImageView)v).setColorFilter(Color.BLUE); // Invalidate the view to force a redraw in the new tint. v.invalidate(); // Return true to indicate that the View can accept the dragged // data. return true; } // Return false to indicate that, during the current drag-and-drop // operation, this View doesn't receive events again until // ACTION_DRAG_ENDED is sent. return false; case DragEvent.ACTION_DRAG_ENTERED: // Apply a green tint to the View. ((ImageView)v).setColorFilter(Color.GREEN); // Invalidate the view to force a redraw in the new tint. v.invalidate(); // Return true. The value is ignored. return true; case DragEvent.ACTION_DRAG_LOCATION: // Ignore the event. return true; case DragEvent.ACTION_DRAG_EXITED: // Reset the color tint to blue. ((ImageView)v).setColorFilter(Color.BLUE); // Invalidate the view to force a redraw in the new tint. v.invalidate(); // Return true. The value is ignored. return true; case DragEvent.ACTION_DROP: // Get the item containing the dragged data. ClipData.Item item = e.getClipData().getItemAt(0); // Get the text data from the item. CharSequence dragData = item.getText(); // Display a message containing the dragged data. Toast.makeText(this, "Dragged data is " + dragData, Toast.LENGTH_LONG).show(); // Turn off color tints. ((ImageView)v).clearColorFilter(); // Invalidate the view to force a redraw. v.invalidate(); // Return true. DragEvent.getResult() returns true. return true; case DragEvent.ACTION_DRAG_ENDED: // Turn off color tinting. ((ImageView)v).clearColorFilter(); // Invalidate the view to force a redraw. v.invalidate(); // Do a getResult() and displays what happens. if (e.getResult()) { Toast.makeText(this, "The drop was handled.", Toast.LENGTH_LONG).show(); } else { Toast.makeText(this, "The drop didn't work.", Toast.LENGTH_LONG).show(); } // Return true. The value is ignored. return true; // An unknown action type is received. default: Log.e("DragDrop Example","Unknown action type received by View.OnDragListener."); break; } return false; });
התאמה אישית של צללית גרירה
אפשר להגדיר myDragShadowBuilder
מותאם אישית על ידי שינוי של השיטות ב-
View.DragShadowBuilder
. קטע הקוד הבא יוצר קטע קוד קטן,
צללית גרירה מלבנית ואפורה עבור TextView
:
Kotlin
private class MyDragShadowBuilder(view: View) : View.DragShadowBuilder(view) { private val shadow = ColorDrawable(Color.LTGRAY) // Define a callback that sends the drag shadow dimensions and touch point // back to the system. override fun onProvideShadowMetrics(size: Point, touch: Point) { // Set the width of the shadow to half the width of the original // View. val width: Int = view.width / 2 // Set the height of the shadow to half the height of the original // View. val height: Int = view.height / 2 // The drag shadow is a ColorDrawable. Set its dimensions to // be the same as the Canvas that the system provides. As a result, // the drag shadow fills the Canvas. shadow.setBounds(0, 0, width, height) // Set the size parameter's width and height values. These get back // to the system through the size parameter. size.set(width, height) // Set the touch point's position to be in the middle of the drag // shadow. touch.set(width / 2, height / 2) } // Define a callback that draws the drag shadow in a Canvas that the system // constructs from the dimensions passed to onProvideShadowMetrics(). override fun onDrawShadow(canvas: Canvas) { // Draw the ColorDrawable on the Canvas passed in from the system. shadow.draw(canvas) } }
Java
private static class MyDragShadowBuilder extends View.DragShadowBuilder { // The drag shadow image, defined as a drawable object. private static Drawable shadow; // Constructor. public MyDragShadowBuilder(View view) { // Store the View parameter. super(view); // Create a draggable image that fills the Canvas provided by the // system. shadow = new ColorDrawable(Color.LTGRAY); } // Define a callback that sends the drag shadow dimensions and touch point // back to the system. @Override public void onProvideShadowMetrics (Point size, Point touch) { // Define local variables. int width, height; // Set the width of the shadow to half the width of the original // View. width = getView().getWidth() / 2; // Set the height of the shadow to half the height of the original // View. height = getView().getHeight() / 2; // The drag shadow is a ColorDrawable. Set its dimensions to // be the same as the Canvas that the system provides. As a result, // the drag shadow fills the Canvas. shadow.setBounds(0, 0, width, height); // Set the size parameter's width and height values. These get back // to the system through the size parameter. size.set(width, height); // Set the touch point's position to be in the middle of the drag // shadow. touch.set(width / 2, height / 2); } // Define a callback that draws the drag shadow in a Canvas that the system // constructs from the dimensions passed to onProvideShadowMetrics(). @Override public void onDrawShadow(Canvas canvas) { // Draw the ColorDrawable on the Canvas passed in from the system. shadow.draw(canvas); } }