تفعيل السحب والإفلات

يتيح لك إطار عمل السحب والإفلات في Android إضافة إمكانيات السحب والإفلات التفاعلية إلى تطبيقك. باستخدام السحب والإفلات، يمكن للمستخدمين نسخ أو نقل النص والصور والكائنات وأي محتوى يمكن تمثيله بمعرّف موارد منتظم (URI)، من View إلى آخر داخل تطبيق أو بين التطبيقات في وضع النوافذ المتعددة.

السلسلة النصية والصورة يتم سحبها وإفلاتها داخل أحد التطبيقات. السلسلة النصية والصورة يتم سحبها وإفلاتها بين التطبيقات في وضع تقسيم الشاشة.
الشكل 1. السحب والإفلات داخل التطبيق
الشكل 2. السحب والإفلات بين التطبيقات

يتضمن إطار العمل فئة حدث السحب، وأدوات معالجة السحب، وفئات وطرق مساعدة. على الرغم من أنّه تم تصميمه في المقام الأول لإتاحة نقل البيانات، يمكنك استخدام إطار العمل لإجراءات أخرى في واجهة المستخدم. على سبيل المثال، يمكنك إنشاء تطبيق يمزج بين الألوان عندما يسحب المستخدم أيقونة لون فوق أيقونة أخرى. ومع ذلك، تصف بقية المستند إطار عمل السحب والإفلات في سياق نقل البيانات.

نظرة عامة

تبدأ عملية السحب والإفلات عندما يُجري المستخدم إيماءة في واجهة المستخدم يتعرّف عليها تطبيقك كإشارة لبدء سحب البيانات. استجابةً لذلك، يبلغ التطبيق النظام ببدء عملية السحب والإفلات. يتصل النظام بالتطبيق مرة أخرى للحصول على تمثيل للبيانات التي يتم سحبها، ويسمى ظل السحب.

عندما يحرّك المستخدم ظل السحب فوق تنسيق التطبيق، يُرسِل النظام أحداث السحب إلى أدوات معالجة أحداث السحب وطرق معاودة الاتصال المرتبطة بكائنات View في التنسيق. إذا حرر المستخدم ظل السحب فوق طريقة عرض يمكنها قبول البيانات (هدف إفلات)، فسيرسل النظام البيانات إلى الهدف. تنتهي عملية السحب والإفلات عندما يحرر المستخدم ظل السحب، سواء كان ظل السحب فوق هدف الإفلات أم لا.

يمكنك إنشاء أداة معالجة حدث السحب من خلال تنفيذ View.OnDragListener. اضبط أداة معالجة الحدث لهدف الانخفاض باستخدام الطريقة setOnDragListener() للكائن View. تحتوي كل طريقة عرض في التنسيق أيضًا على طريقة معاودة الاتصال بـ onDragEvent().

يرسل تطبيقك إشعارًا إلى النظام ببدء عملية السحب والإفلات من خلال استدعاء الطريقة startDragAndDrop() التي تطلب من النظام إرسال أحداث السحب. توفر الطريقة أيضًا للنظام البيانات التي يسحبها المستخدم وبيانات التعريف التي تصف البيانات. يمكنك استدعاء startDragAndDrop() على أي View بالتنسيق الحالي. يستخدم النظام العنصر View فقط للوصول إلى الإعدادات العامة في التنسيق.

أثناء عملية السحب والإفلات، يرسل النظام أحداث السحب إلى أدوات معالجة أحداث السحب أو طرق رد الاتصال لكائنات View في التنسيق. ويستخدم المستمعون أو طريقة معاودة الاتصال البيانات الوصفية لتحديد ما إذا كانوا يريدون قبول البيانات عند إسقاطها. إذا أفلت المستخدم البيانات في هدف إفلات، وهو View يقبل البيانات، يرسل النظام عنصر سحب يحتوي على البيانات إلى طريقة استماع حدث السحب أو طريقة معاودة الاتصال في هدف الإفلات.

سحب أدوات معالجة الأحداث وطرق رد الاتصال

يتلقّى View أحداث السحب باستخدام أداة معالجة حدث سحب تنفّذ View.OnDragListener أو باستخدام طريقة استدعاء onDragEvent() لطريقة العرض. عندما يستدعي النظام الطريقة أو أداة معالجة البيانات، يقدّم وسيطة DragEvent.

في معظم الحالات، يُفضَّل الاستعانة بمستمعين على استخدام طريقة معاودة الاتصال. عند تصميم واجهات المستخدم، لا أنّك عادةً لا تصنّف فئات View الفرعية، إلا أنّ استخدام طريقة معاودة الاتصال يفرض عليك إنشاء فئات فرعية لإلغاء الطريقة. وللمقارنة، يمكنك تطبيق فئة مستمع واحدة، ثم استخدامها مع عدة كائنات View مختلفة. يمكنك أيضًا تنفيذه كفئة مضمّنة مجهولة أو تعبير lambda. لضبط أداة معالجة الحدث لكائن View، يمكنك استدعاء setOnDragListener().

كخيار بديل، يمكنك تغيير طريقة التنفيذ التلقائية للسمة onDragEvent() بدون تجاوز الطريقة. اضبط OnReceiveContentListener على طريقة عرض. ولمزيد من التفاصيل، راجِع setOnReceiveContentListener(). بعد ذلك، تنفِّذ الطريقة onDragEvent() ما يلي تلقائيًا:

  • يتم عرض "صحيح" استجابةً لطلب البيانات من "startDragAndDrop()".
  • المكالمات performReceiveContent() إذا تم إسقاط بيانات السحب والإفلات في العرض. يتم تمرير البيانات إلى الطريقة ككائن ContentInfo. تستدعي الطريقة OnReceiveContentListener.

  • يتم عرض القيمة "صحيح" إذا تم إسقاط بيانات السحب والإفلات في العرض وكان OnReceiveContentListener يستهلك أيًا من المحتوى.

حدِّد OnReceiveContentListener لمعالجة بيانات تطبيقك تحديدًا. للتوافق مع الأنظمة القديمة وصولاً إلى المستوى 24 من واجهة برمجة التطبيقات، استخدِم إصدار Jetpack من OnReceiveContentListener.

يمكنك استخدام أداة معالجة أحداث السحب وطريقة استدعاء لكائن View، وفي هذه الحالة يستدعي النظام المستمع أولاً. ولا يستدعي النظام طريقة معاودة الاتصال إلا إذا عرض المستمع الخطأ false.

يماثل الجمع بين طريقة onDragEvent() وView.OnDragListener الجمع بين onTouchEvent() وView.OnTouchListener المستخدَمين مع أحداث اللمس.

عملية السحب والإفلات

توجد أربع خطوات أو حالات في عملية السحب والإفلات: بدأت، ومستمرة، وأسقطت، وانتهت.

تم بدء التنفيذ.

استجابة لإيماءة السحب التي أجراها المستخدم، يطلب تطبيقك الرمز startDragAndDrop() لتطلب من النظام بدء عملية سحب وإفلات. توفر وسيطات الطريقة ما يلي:

  • البيانات التي سيتم سحبها.
  • استدعاء لرسم ظل السحب
  • البيانات الوصفية التي تصف البيانات التي يتم سحبها : يستجيب النظام من خلال معاودة الاتصال بالتطبيق للحصول على تظليل للسحب. يعرض النظام بعد ذلك تظليل السحب على الجهاز. : بعد ذلك، يرسل النظام حدث سحب مع نوع الإجراء ACTION_DRAG_STARTED إلى أداة معالجة حدث السحب لجميع عناصر View في التنسيق الحالي. لمتابعة استلام أحداث السحب، بما في ذلك حدث إفلات محتمل، يجب أن تعرض أداة معالجة حدث السحب true. يؤدي هذا إلى تسجيل المستمع في النظام. يواصل المستمعون المسجّلون فقط تلقّي أحداث السحب. في هذه المرحلة، يمكن للمستمعين أيضًا تغيير مظهر العنصر View المخصّص للإفلات من أجل توضيح أنّ المشاهدة قادرة على قبول حدث الإفلات. : إذا عرضت أداة معالجة حدث السحب false، لن تستقبل أحداث السحب للعملية الحالية إلى أن يرسل النظام حدث سحب مع نوع الإجراء ACTION_DRAG_ENDED. من خلال عرض false، يخبر المستمع النظام بأنّه غير مهتم بعملية السحب والإفلات ولا يريد قبول البيانات التي يتم سحبها.
جارٍ المتابعة
يواصل المستخدم عملية السحب. عندما يتقاطع ظل السحب مع مربّع الإحاطة بهدف إفلات، يرسل النظام حدث سحب واحدًا أو أكثر إلى أداة معالجة حدث السحب للهدف. قد يغيِّر المستمع شكل هدف الانخفاض View استجابةً للحدث. على سبيل المثال، إذا أشار الحدث إلى أنّ تظليل السحب يدخل في مربّع الإحاطة بهدف الإفلات هو نوع الإجراء ACTION_DRAG_ENTERED، يمكن أن يتفاعل المستمع من خلال تمييز View.
تم الإسقاط
أطلق المستخدم تظليل السحب ضمن مربّع الإحاطة بهدف الإفلات. يُرسِل النظام حدث سحب إلى مستمع هدف الإفلات مع نوع الإجراء ACTION_DROP. يحتوي كائن حدث السحب على البيانات التي يتم إرسالها إلى النظام في الطلب startDragAndDrop() الذي يبدأ العملية. من المتوقّع أن يعرض المستمع true المنطقي إلى النظام إذا نجح المستمع في معالجة البيانات التي تم إسقاطها. : تحدث هذه الخطوة فقط إذا وضع المستخدم ظلّ السحب ضمن مربّع الإحاطة في View الذي تم تسجيل أداة الاستماع الخاصة به لتلقّي أحداث السحب (هدف الإفلات). وإذا أطلق المستخدم ظل السحب في أي حالة أخرى، لن يتم إرسال أي حدث سحب ACTION_DROP.
انتهى الاستطلاع

بعد قيام المستخدم بتحرير ظل السحب، وبعد إرسال النظام

حدث سحب من نوع الإجراء ACTION_DROP، يُرسِل النظام إذا لزم الأمر حدث سحب مع نوع الإجراء ACTION_DRAG_ENDED للإشارة إلى انتهاء عملية السحب والإفلات. يتم ذلك بغض النظر عن المكان الذي يطلق فيه المستخدم ظل السحب. يتم إرسال الحدث إلى كل مستمع تم تسجيله لتلقّي أحداث السحب، حتى إذا تلقّى المستمع حدث ACTION_DROP أيضًا.

يتم وصف كل خطوة من هذه الخطوات بمزيد من التفصيل في القسم الذي يحمل اسم عملية السحب والإفلات.

أحداث السحب

يرسل النظام حدث سحب في شكل كائن DragEvent يحتوي على نوع الإجراء يصف ما يحدث في عملية السحب والإفلات. استنادًا إلى نوع الإجراء، يمكن أن يحتوي الكائن أيضًا على بيانات أخرى.

تتلقى أدوات معالجة الأحداث في الحدث العنصر DragEvent. للحصول على نوع الإجراء، يتصل المستمعون بالرقم DragEvent.getAction(). هناك ست قيم محتملة مُحددة بالثوابت في الفئة DragEvent، والموضّحة في الجدول 1:

الجدول 1. أنواع إجراءات DragEvent

نوع الإجراء المعنى
ACTION_DRAG_STARTED يطلب التطبيق الرمز startDragAndDrop() ويحصل على ظل سحب. إذا أراد المستمع الاستمرار في تلقّي أحداث السحب لهذه العملية، يجب أن يعرض قيمة true المنطقية إلى النظام.
ACTION_DRAG_ENTERED تدخل ظل السحب مربع الإحاطة في View لمستمع حدث السحب. هذا هو نوع الإجراء الأول الذي يتلقّاه المستمع عندما يدخل ظل السحب إلى مربّع الإحاطة.
ACTION_DRAG_LOCATION بعد حدث ACTION_DRAG_ENTERED، لا يزال تظليل السحب داخل المربّع المحيط لمعالج حدث السحب View.
ACTION_DRAG_EXITED بعد حدث ACTION_DRAG_ENTERED وحدث ACTION_DRAG_LOCATION واحد على الأقل، يتحرك ظل السحب خارج مربّع الإحاطة بعنصر View لمستمع حدث السحب.
ACTION_DROP يتحرّك ظلّ السحب فوق View الخاص بمستمع حدث السحب. لا يتم إرسال نوع الإجراء هذا إلى مستمع العنصر View إلا إذا عرض المستمع قيمة true المنطقية استجابةً لحدث السحب ACTION_DRAG_STARTED. لا يتم إرسال نوع الإجراء هذا إذا أطلق المستخدم ظل السحب فوق View الذي لم يتم تسجيل مستمعه أو إذا أطلق المستخدم ظل السحب فوق أي عنصر ليس جزءًا من التنسيق الحالي.

يعرض المستمع true المنطقي إذا عالج عملية الانخفاض بنجاح. وبخلاف ذلك، يجب أن تعرض القيمة false.

ACTION_DRAG_ENDED يُنهي النظام عملية السحب والإفلات. وليس بالضرورة أن يكون نوع الإجراء هذا مسبوقًا بحدث ACTION_DROP. إذا أرسل النظام خطأ ACTION_DROP، لا يعني تلقّي نوع الإجراء ACTION_DRAG_ENDED أنّ عملية الانخفاض قد تمت بنجاح. يجب أن يطلب المستمع getResult()، كما هو موضّح في الجدول 2، للحصول على القيمة التي يتم عرضها استجابةً للسمة ACTION_DROP. إذا لم يتم إرسال حدث ACTION_DROP، ستعرض الدالة getResult() القيمة false.

يحتوي الكائن DragEvent أيضًا على البيانات والبيانات الوصفية التي يوفّرها تطبيقك للنظام في طلب startDragAndDrop(). بعض البيانات صالحة فقط لأنواع إجراءات معينة كما هو موضح في الجدول 2. للحصول على مزيد من المعلومات حول الأحداث والبيانات المرتبطة بها، يمكنك الاطلاع على قسم عملية السحب والإفلات.

الجدول 2. بيانات DragEvent الصالحة حسب نوع الإجراء

القيمة getAction()
القيمة getClipDescription()
القيمة getLocalState()
القيمة getX()
القيمة getY()
القيمة getClipData()
القيمة getResult()
ACTION_DRAG_STARTED ✓ ✓ ✓ ✓    
ACTION_DRAG_ENTERED ✓ ✓        
ACTION_DRAG_LOCATION ✓ ✓ ✓ ✓    
ACTION_DRAG_EXITED ✓ ✓        
ACTION_DROP ✓ ✓ ✓ ✓ ✓  
ACTION_DRAG_ENDED   ✓       ✓

تعرض طرق DragEvent getAction() وdescribeContents() وwriteToParcel() وtoString() بيانات صالحة دائمًا.

وإذا كانت إحدى الطرق لا تحتوي على بيانات صالحة لنوع إجراء معيّن، يتم عرض null أو 0، بناءً على نوع النتيجة.

ظل السحب

أثناء عملية السحب والإفلات، يعرض النظام صورة يسحبها المستخدم. لحركة البيانات، تمثل هذه الصورة البيانات التي يتم سحبها. بالنسبة للعمليات الأخرى، تمثل الصورة أحد جوانب عملية السحب.

يُطلق على الصورة اسم ظل السحب. يمكنك إنشاؤها باستخدام الطرق التي تحددها لكائن View.DragShadowBuilder. أنت تمرّر أداة الإنشاء إلى النظام عند بدء عملية سحب وإفلات باستخدام startDragAndDrop(). وكجزء من استجابته لـ startDragAndDrop()، يستدعي النظام طرق معاودة الاتصال التي تحدّدها في View.DragShadowBuilder للحصول على تظليل السحب.

تحتوي الفئة View.DragShadowBuilder على دالتَين لإنشاء:

View.DragShadowBuilder(View)

تقبل دالة الإنشاء هذه أي كائنات View لتطبيقك. تخزّن دالة الإنشاء العنصر View في الكائن View.DragShadowBuilder، وبالتالي يمكن لدوال الاستدعاء الوصول إليه لإنشاء ظل السحب. وليس من الضروري أن تكون طريقة العرض View التي يختارها المستخدم لبدء عملية السحب.

وإذا كنت تستخدم دالة الإنشاء هذه، لن تحتاج إلى تمديد View.DragShadowBuilder أو إلغاء طرقها. ستحصل تلقائيًا ظلال سحب على نفس مظهر View الذي تمرِّره كوسيطة، يتم توسيطه أسفل الموقع الذي يلمس فيه المستخدم الشاشة.

View.DragShadowBuilder()

في حال استخدام دالة الإنشاء هذه، لن يتوفر كائن View في الكائن View.DragShadowBuilder. تم ضبط الحقل على null. يجب تمديد View.DragShadowBuilder وإلغاء طرقه، وإلّا سيظهر ظل سحب غير مرئي. لا يعرض النظام أي خطأ.

تستخدم الفئة View.DragShadowBuilder طريقتين لإنشاء ظل السحب معًا:

onProvideShadowMetrics()

يستدعي النظام هذه الطريقة مباشرةً بعد طلب الرقم startDragAndDrop(). استخدم طريقة إرسال الأبعاد ونقطة لمس ظل السحب إلى النظام. تتضمن الطريقة مُعلَّمتين:

outShadowSize: كائن Point. يبدأ عرض تظليل السحب في x، ويصل ارتفاعه إلى y.

outShadowTouchPoint: كائن Point. نقطة الاتصال هي الموقع داخل ظل السحب الذي يجب أن يكون تحت إصبع المستخدم أثناء السحب. ينتقل الموضع س إلى x، وينتقل الموضع ص في y.

onDrawShadow()

بعد اتصال onProvideShadowMetrics() مباشرةً، يستدعي النظام onDrawShadow() لإنشاء تظليل السحب. تتضمّن الطريقة وسيطة واحدة، وهي كائن Canvas ينشئها النظام من المَعلمات التي تقدّمها في onProvideShadowMetrics(). ترسم هذه الطريقة تظليل السحب على Canvas المقدَّم.

لتحسين الأداء، اجعل حجم تظليل السحب صغيرًا. لعنصر واحد، قد ترغب في استخدام أيقونة. للحصول على تحديد متعدد العناصر، قد ترغب في استخدام الأيقونات في مكدس بدلاً من الصور الكاملة المنتشرة على الشاشة.

عملية السحب والإفلات

يعرض هذا القسم خطوة بخطوة كيفية بدء عملية السحب، والاستجابة للأحداث أثناء السحب، والاستجابة لحدث الإفلات، وإنهاء عملية السحب والإفلات.

بدء السحب

يبدأ المستخدم عملية سحب بإيماءة سحب، وهي عادةً عبر النقر مع الاستمرار على عنصر View. استجابةً لذلك، يجب أن ينفّذ تطبيقك الإجراءات التالية:

  1. أنشئ كائن 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.
            val myShadow = MyDragShadowBuilder(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.
    View.DragShadowBuilder myShadow = new MyDragShadowBuilder(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;
    });
    
  2. حدِّد 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);
    }
    }
    

الاستجابة لبداية السحب

أثناء عملية السحب، يرسل النظام أحداث السحب إلى مستمعي أحداث السحب لعناصر View في التنسيق الحالي. يتفاعل المستمعون من خلال استدعاء DragEvent.getAction() للحصول على نوع الإجراء. في بداية السحب، تعرض هذه الطريقة ACTION_DRAG_STARTED.

استجابةً لحدث من نوع الإجراء ACTION_DRAG_STARTED، يجب أن ينفّذ مستمع حدث السحب ما يلي:

  1. عليك استدعاء DragEvent.getClipDescription() واستخدام طرق نوع MIME في ClipDescription المعروض لمعرفة ما إذا كان بإمكان المستمع قبول البيانات التي يتم سحبها.

    إذا كانت عملية السحب والإفلات لا تمثل حركة البيانات، فقد لا يكون ذلك ضروريًا.

  2. إذا كان بإمكان أداة معالجة حدث السحب قبول عملية الإفلات، يجب أن تعرض السياسة true لإبلاغ النظام بمواصلة إرسال أحداث السحب إلى المستمع. إذا لم يقبل المستمع الإفلات، على المستمع عرض false، ويتوقّف النظام عن إرسال أحداث السحب إلى المستمع إلى أن يرسل النظام ACTION_DRAG_ENDED لإنهاء عملية السحب والإفلات.

بالنسبة إلى حدث ACTION_DRAG_STARTED، تكون طرق DragEvent التالية غير صالحة: getClipData() وgetX() وgetY() وgetResult().

التعامل مع الأحداث أثناء السحب

أثناء إجراء السحب، تستمر مستمعي أحداث السحب التي تعرض الرمز true استجابةً لحدث السحب ACTION_DRAG_STARTED في تلقّي أحداث السحب. تعتمد أنواع أحداث السحب التي يتلقّاها المستمع أثناء السحب على موقع ظل السحب ورؤية View للمستمع. يستخدم المستمعون أحداث السحب بشكل أساسي لتحديد ما إذا كان عليهم تغيير مظهر View أم لا.

أثناء إجراء السحب، تعرض الدالة DragEvent.getAction() قيمة من ثلاث قيم:

  • ACTION_DRAG_ENTERED: يتلقّى المستمع نوع إجراء الحدث هذا عندما تدخل نقطة الاتصال، النقطة على الشاشة أسفل إصبع المستخدم أو الماوس، في المربّع المحيط بعنصر View للمستمع.
  • ACTION_DRAG_LOCATION: عندما يتلقّى المستمع حدث ACTION_DRAG_ENTERED، يتلقّى حدث ACTION_DRAG_LOCATION جديد في كل مرّة تنتقل فيها نقطة الاتصال إلى أن تتلقّى حدث ACTION_DRAG_EXITED. تعرض الطرق getX() وgetY() الإحداثي X وY لنقطة الاتصال.
  • ACTION_DRAG_EXITED: يتم إرسال نوع إجراء الحدث هذا إلى المستمع الذي يتلقّى ACTION_DRAG_ENTERED في السابق. يتم إرسال الحدث عندما تنتقل نقطة لمس ظل السحب من داخل مربّع الإحاطة في View للمستمع إلى خارج مربّع الإحاطة.

لا تحتاج أداة معالجة حدث السحب إلى التفاعل مع أي من أنواع الإجراءات هذه. وإذا عرض المستمع قيمة إلى النظام، سيتم تجاهلها.

في ما يلي بعض الإرشادات للاستجابة إلى كل نوع من أنواع الإجراءات هذه:

  • استجابةً للسمة ACTION_DRAG_ENTERED أو ACTION_DRAG_LOCATION، يمكن للمستمع تغيير مظهر View للإشارة إلى أنّ المشاهدة هي الهدف المحتمل لانخفاض عدد المشاهدات.
  • يحتوي حدث بنوع الإجراء ACTION_DRAG_LOCATION على بيانات صالحة لـ getX() وgetY() بما يتوافق مع الموقع الجغرافي لنقطة الاتصال. يمكن للمستمعين استخدام هذه المعلومات لتغيير مظهر View عند نقطة اللمس أو لتحديد الموضع الدقيق الذي يمكن للمستخدم فيه إطلاق ظل السحب، أي حذف البيانات.
  • استجابةً لـ ACTION_DRAG_EXITED، على المستمع إعادة ضبط أي تغييرات في الظهور يتم تطبيقها استجابةً إلى ACTION_DRAG_ENTERED أو ACTION_DRAG_LOCATION. يشير ذلك إلى المستخدم أنّ View لم يعُد هدفًا قريبًا للانخفاض.

الردّ على الانخفاض المفاجئ

عندما يرفع المستخدم ظل السحب على View، ويذكر View في السابق أنّه يمكنه قبول المحتوى الذي يتم سحبه، يرسِل النظام حدث السحب إلى View بنوع الإجراء ACTION_DROP.

على أداة معالجة حدث السحب إجراء ما يلي:

  1. عليك استدعاء getClipData() للحصول على الكائن ClipData الذي تم توفيره في الأصل في الطلب إلى startDragAndDrop() ومعالجة البيانات. إذا كانت عملية السحب والإفلات لا تمثل حركة البيانات، فهذا غير ضروري.

  2. يمكنك عرض القيمة المنطقية true للإشارة إلى أنّه تمت معالجة الانخفاض المفاجئ بنجاح أو إلى false إذا لم تتم معالجته. وتصبح القيمة المعروضة هي القيمة التي يعرضها getResult() لحدث ACTION_DRAG_ENDED النهائي. إذا لم يرسل النظام حدث ACTION_DROP، ستكون القيمة التي يعرضها getResult() لحدث ACTION_DRAG_ENDED هي false.

بالنسبة إلى حدث ACTION_DROP، يستخدم getX() وgetY() نظام إحداثيات View الذي يتلقى الانخفاض لعرض الموضعين X وY لنقطة الاتصال في وقت الانخفاض.

يتيح النظام للمستخدم إزالة ظل السحب على View الذي لا يتلقى مستمع أحداث السحب فيه أحداث السحب. كما يتيح للمستخدم إطلاق ظل السحب فوق المناطق الفارغة من واجهة المستخدم للتطبيق أو فوق مناطق خارج التطبيق. في جميع هذه الحالات، لا يرسل النظام حدثًا من نوع الإجراء ACTION_DROP، على الرغم من أنّ النظام يرسل حدث ACTION_DRAG_ENDED.

الرد على نهاية سحب

وبعد أن يحرر المستخدم ظل السحب مباشرةً، يرسل النظام حدث السحب بنوع الإجراء ACTION_DRAG_ENDED إلى جميع أدوات معالجة أحداث السحب في تطبيقك. ويشير هذا إلى انتهاء عملية السحب والإفلات.

يجب على كل أداة معالجة حدث سحب إجراء ما يلي:

  1. إذا غيّر المستمع مظهر عنصر View أثناء العملية، على المستمع إعادة ضبط View على المظهر التلقائي. هذا مؤشر مرئي للمستخدم أن العملية قد انتهت.
  2. يمكن للمستمع الاتصال بـ getResult() بشكل اختياري لمعرفة المزيد عن العملية. إذا عرض المستمع true استجابة لحدث من نوع الإجراء ACTION_DROP، تعرض getResult() القيمة true المنطقية. في جميع الحالات الأخرى، تعرض getResult() القيمة المنطقية false، بما في ذلك عندما لا يرسل النظام حدث ACTION_DROP.
  3. للإشارة إلى اكتمال عملية السحب والإفلات بنجاح، على المستمع عرض true المنطقي إلى النظام.

الرد على أحداث السحب: مثال

يتم استلام جميع أحداث السحب بواسطة طريقة حدث السحب أو أداة معالجة الحدث. مقتطف الرمز التالي هو مثال بسيط على الاستجابة لأحداث السحب:

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;

});

السحب والإفلات في وضع النوافذ المتعددة

تتوافق الأجهزة التي تعمل بالإصدار 7.0 من نظام التشغيل Android (المستوى 24 لواجهة برمجة التطبيقات) أو الإصدارات الأحدث مع وضع النوافذ المتعددة، الذي يتيح للمستخدمين نقل البيانات من تطبيق إلى آخر باستخدام عملية السحب والإفلات. لمزيد من المعلومات، يُرجى الاطّلاع على إتاحة النوافذ المتعددة.

يقدِّم التطبيق المصدر، حيث تبدأ عملية السحب والإفلات، البيانات. يتلقى التطبيق المستهدف البيانات حيث تنتهي عملية السحب والإفلات.

عند بدء عملية السحب والإفلات، يجب أن يضبط التطبيق المصدر علامة DRAG_FLAG_GLOBAL للإشارة إلى أنّه يمكن للمستخدم سحب البيانات إلى تطبيق آخر.

ولأنّ البيانات تتحرك عبر حدود التطبيق، تشارك التطبيقات الوصول إلى البيانات باستخدام معرّف موارد منتظم (URI) للمحتوى. وهذا يتطلّب ما يلي:

  • يجب أن يضبط التطبيق المصدر إحدى علامتَي DRAG_FLAG_GLOBAL_URI_READ وDRAG_FLAG_GLOBAL_URI_WRITE أو كليهما، استنادًا إلى إذن الوصول للقراءة أو الكتابة إلى البيانات التي يريد التطبيق المصدر منحها للتطبيق المستهدَف.
  • يجب أن يطلب التطبيق المستهدَف الرمز requestDragAndDropPermissions() مباشرةً قبل معالجة البيانات التي يسحبها المستخدم إلى التطبيق. إذا لم يعد التطبيق الهدف بحاجة إلى الوصول إلى بيانات السحب والإفلات، يمكن للتطبيق بعد ذلك استدعاء release() للكائن الذي تم عرضه من requestDragAndDropPermissions(). وبخلاف ذلك، يتم تحرير الأذونات عند تدمير النشاط الذي يحتوي عليه. إذا كانت عملية التنفيذ تتضمّن بدء نشاط جديد لمعالجة عناوين URL التي تم إسقاطها، ستحتاج إلى منح النشاط الجديد الأذونات نفسها. يجب تعيين بيانات المقطع وعلامة:

    Kotlin

    intent.setClipData(clipData)
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    

    Java

    intent.setClipData(clipData);
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    

توضح مقتطفات الرمز التالية كيفية إصدار حق الوصول للقراءة فقط لسحب البيانات وإفلاتها مباشرةً بعد إجراء عملية السحب والإفلات. اطّلع على نموذج DrragAndDrop على GitHub للحصول على مثال أكثر اكتمالاً.

نشاط سحب وإفلات المصدر

Kotlin

// Drag a file stored in an images/ directory in internal storage.
val internalImagesDir = File(context.filesDir, "images")
val imageFile = File(internalImagesDir, imageFilename)
val uri = FileProvider.getUriForFile(context, contentAuthority, imageFile)

val listener = OnDragStartListener@{ view: View, _: DragStartHelper ->
    val clipData = ClipData(ClipDescription("Image Description",
                                            arrayOf("image/*")),
                            ClipData.Item(uri))
    // Must include DRAG_FLAG_GLOBAL to permit dragging data between apps.
    // This example provides read-only access to the data.
    val flags = View.DRAG_FLAG_GLOBAL or View.DRAG_FLAG_GLOBAL_URI_READ
    return@OnDragStartListener view.startDragAndDrop(clipData,
                                                     View.DragShadowBuilder(view),
                                                     null,
                                                     flags)
}

// Container where the image originally appears in the source app.
val srcImageView = findViewById<ImageView>(R.id.imageView)

// Detect and start the drag event.
DragStartHelper(srcImageView, listener).apply {
    attach()
}

Java

// Drag a file stored in an images/ directory in internal storage.
File internalImagesDir = new File(context.getFilesDir(), "images");
File imageFile = new File(internalImagesDir, imageFilename);
final Uri uri = FileProvider.getUriForFile(context, contentAuthority, imageFile);

// Container where the image originally appears in the source app.
ImageView srcImageView = findViewById(R.id.imageView);

// Enable the view to detect and start the drag event.
new DragStartHelper(srcImageView, (view, helper) -> {
    ClipData clipData = new ClipData(new ClipDescription("Image Description",
                                                          new String[] {"image/*"}),
                                     new ClipData.Item(uri));
    // Must include DRAG_FLAG_GLOBAL to permit dragging data between apps.
    // This example provides read-only access to the data.
    int flags = View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ;
    return view.startDragAndDrop(clipData,
                                 new View.DragShadowBuilder(view),
                                 null,
                                 flags);
}).attach();

نشاط السحب والإفلات المستهدف

Kotlin

// Container where the image is to be dropped in the target app.
val targetImageView = findViewById<ImageView>(R.id.imageView)

targetImageView.setOnDragListener { view, event ->

    when (event.action) {

        ACTION_DROP -> {
            val imageItem: ClipData.Item = event.clipData.getItemAt(0)
            val uri = imageItem.uri

            // Request permission to access the image data being dragged into
            // the target activity's ImageView element.
            val dropPermissions = requestDragAndDropPermissions(event)
            (view as ImageView).setImageURI(uri)

            // Release the permission immediately afterward because it's no
            // longer needed.
            dropPermissions.release()
            return@setOnDragListener true
        }

        // Implement logic for other DragEvent cases here.

        // An unknown action type is received.
        else -> {
            Log.e("DragDrop Example", "Unknown action type received by View.OnDragListener.")
            return@setOnDragListener false
        }

    }
}

Java

// Container where the image is to be dropped in the target app.
ImageView targetImageView = findViewById(R.id.imageView);

targetImageView.setOnDragListener( (view, event) -> {

    switch (event.getAction()) {

        case ACTION_DROP:
            ClipData.Item imageItem = event.getClipData().getItemAt(0);
            Uri uri = imageItem.getUri();

            // Request permission to access the image data being dragged into
            // the target activity's ImageView element.
            DragAndDropPermissions dropPermissions =
                requestDragAndDropPermissions(event);

            ((ImageView)view).setImageURI(uri);

            // Release the permission immediately afterward because it's no
            // longer needed.
            dropPermissions.release();

            return true;

        // Implement logic for other DragEvent cases here.

        // An unknown action type was received.
        default:
            Log.e("DragDrop Example","Unknown action type received by View.OnDragListener.");
            break;
    }

    return false;
});

DropHelper للسحب والإفلات المبسّط

تعمل فئة DropHelper على تبسيط تنفيذ إمكانات السحب والإفلات. عضو في مكتبة Jetpack DragAndDrop، DropHelper يوفّر توافقًا مع الأنظمة القديمة وصولاً إلى المستوى 24 من واجهة برمجة التطبيقات.

استخدِم DropHelper لتحديد أهداف الإفلات وتخصيص تمييز الهدف وتحديد طريقة التعامل مع البيانات التي تم إفلاتها.

تحديد أهداف الإفلات

DropHelper.configureView() هي طريقة ثابتة ومحمّلة بشكل زائد تتيح لك تحديد أهداف إفلات. وتشمل معلماته ما يلي:

على سبيل المثال، لإنشاء هدف إفلات يقبل الصور، استخدِم إحدى الطريقتين التاليتين لإجراء استدعاءات:

Kotlin

configureView(
    myActivity,
    targetView,
    arrayOf("image/*"),
    options,
    onReceiveContentListener)

// or

configureView(
    myActivity,
    targetView,
    arrayOf("image/*"),
    onReceiveContentListener)

Java

DropHelper.configureView(
    myActivity,
    targetView,
    new String[] {"image/*"},
    options,
    onReceiveContentlistener);

// or

DropHelper.configureView(
    myActivity,
    targetView,
    new String[] {"image/*"},
    onReceiveContentlistener);

يتجاهل الاستدعاء الثاني خيارات إعداد هدف الإفلات، وفي هذه الحالة يتم ضبط لون تمييز الهدف الإفلات على اللون الثانوي (أو التمييز) للمظهر، وضبط نصف قطر زاوية التمييز على 16 وحدة بكسل مستقلة الكثافة، وتكون قائمة مكوّنات EditText فارغة. راجِع القسم التالي للحصول على التفاصيل.

ضبط أهداف الإفلات

تتيح لك الفئة الداخلية DropHelper.Options ضبط أهداف الإفلات. قدم مثيلاً للفئة إلى الطريقة DropHelper.configureView(Activity, View, String[], Options, OnReceiveContentListener). راجِع القسم السابق للحصول على مزيد من المعلومات.

تخصيص إبراز هدف الإفلات

وتضبط DropHelper أهداف الإفلات لعرض نص مميّز بينما يسحب المستخدمون المحتوى فوق الأهداف. DropHelper يوفر النمط التلقائي، ويسمح لك DropHelper.Options بضبط لون التمييز وتحديد نصف قطر الزاوية لمستطيل التمييز.

استخدِم الفئة DropHelper.Options.Builder لإنشاء مثيل DropHelper.Options وضبط خيارات الضبط، كما هو موضّح في المثال التالي:

Kotlin

val options: DropHelper.Options = DropHelper.Options.Builder()
                                      .setHighlightColor(getColor(R.color.purple_300))
                                      .setHighlightCornerRadiusPx(resources.getDimensionPixelSize(R.dimen.drop_target_corner_radius))
                                      .build()

Java

DropHelper.Options options = new DropHelper.Options.Builder()
                                     .setHighlightColor(getColor(R.color.purple_300))
                                     .setHighlightCornerRadiusPx(getResources().getDimensionPixelSize(R.dimen.drop_target_corner_radius))
                                     .build();

معالجة مكونات EditText في أهداف الإفلات

تتحكّم DropHelper أيضًا في التركيز ضمن هدف الانخفاض عندما يحتوي الهدف على حقول نصية قابلة للتعديل.

يمكن أن تكون استهدافات الإفلات هي مرة مشاهدة واحدة أو تسلسلاً هرميًا لطريقة العرض. إذا كان التدرّج الهرمي لطريقة الإفلات المستهدفة يحتوي على مكوِّن واحد أو أكثر من مكوّنات EditText، قدِّم قائمة بالمكوّنات إلى DropHelper.Options.Builder.addInnerEditTexts(EditText...) لضمان عمل ميزة تمييز الهدف المستهدَف ومعالجة بيانات النص بشكلٍ صحيح.

تمنع علامة DropHelper مكوِّنات EditText ضمن التدرّج الهرمي للعرض المستهدَف للإفلات من سرقة التركيز من العرض الذي يتضمّنه أثناء تفاعلات السحب.

بالإضافة إلى ذلك، إذا كان رمز السحب والإفلات ClipData يتضمّن بيانات نص ومعرّف موارد منتظم (URI)، يختار DropHelper أحد مكوّنات EditText في هدف الإفلات لمعالجة بيانات النص. يعتمد التحديد على الترتيب التالي للأسبقية:

  1. تمثّل هذه السمة EditText الذي يتم إسقاط ClipData عليه.
  2. علامة EditText التي تحتوي على مؤشر النص (علامة الإقحام).
  3. تم توفير أول EditText للمكالمة إلى DropHelper.Options.Builder.addInnerEditTexts(EditText...).

لضبط EditText كمعالج تلقائي للبيانات النصية، اضبط EditText على أنّها الوسيطة الأولى لاستدعاء الترميز DropHelper.Options.Builder.addInnerEditTexts(EditText...). على سبيل المثال، إذا كان هدف الإفلات يعالج الصور ولكنه يحتوي على حقول نصية قابلة للتعديل T1 وT2 وT3، اجعل T2 الإعداد التلقائي على النحو التالي:

Kotlin

val options: DropHelper.Options = DropHelper.Options.Builder()
                                      .addInnerEditTexts(T2, T1, T3)
                                      .build()

Java

DropHelper.Options options = new DropHelper.Options.Builder()
                                     .addInnerEditTexts(T2, T1, T3)
                                     .build();

معالجة البيانات في أهداف الانخفاض

تقبل الطريقة DropHelper.configureView() عنصر OnReceiveContentListener الذي تنشئه للتعامل مع عملية السحب والإفلات ClipData. يتم توفير بيانات السحب والإفلات للمستمع في كائن ContentInfoCompat. توجد بيانات نصية في الكائن. يتم تمثيل الوسائط، مثل الصور، بمعرّفات الموارد المنتظمة (URI).

تعالج OnReceiveContentListener أيضًا البيانات المقدَّمة لهدف الإفلات من خلال تفاعلات المستخدم بخلاف السحب والإفلات، مثل النسخ واللصق، عند استخدام DropHelper.configureView() لضبط أنواع طرق العرض التالية:

  • كل المشاهدات، إذا كان المستخدم يعمل بالإصدار 12 من نظام التشغيل Android أو إصدار أحدث
  • AppCompatEditText، إذا كان المستخدم يعمل بإصدار نظام التشغيل Android وصولاً إلى الإصدار 7.0 من نظام التشغيل Android.

أنواع MIME والأذونات والتحقق من المحتوى

يعتمد التحقّق من نوع MIME من قِبل DropHelper على طريقة السحب والإفلات ClipDescription التي ينشئها التطبيق الذي يوفّر بيانات السحب والإفلات. تحقَّق من صحة ClipDescription لضمان ضبط أنواع MIME بشكلٍ صحيح.

يطلب DropHelper جميع أذونات الوصول لمعرّفات الموارد المنتظمة (URI) للمحتوى المضمّنة في ClipData للسحب والإفلات. ولمزيد من المعلومات، يمكنك الاطّلاع على DragAndDropPermissions. تتيح لك الأذونات حل عناوين URI للمحتوى عند معالجة بيانات السحب والإفلات.

لا يتحقّق DropHelper من صحة البيانات التي يعرضها موفّرو المحتوى عند حلّ معرّفات الموارد المنتظمة (URI) في البيانات التي تم إسقاطها. تحقق من عدم وجود قيمة فارغة وتحقق من صحة أي بيانات تم حلها.