การลากและวาง — ท่าทางเชิงโต้ตอบสำหรับการลากวัตถุที่มองเห็นได้บนหน้าจอด้วยนิ้ว ท่าทางประกอบด้วยสามเฟส: การจับ (กดค้างหรือแตะ), การเคลื่อนย้าย (ขยับนิ้วพร้อมกับวัตถุ), และการปล่อย (วางในพื้นที่เป้าหมาย) ใช้สำหรับจัดลำดับองค์ประกอบใหม่ ย้ายไฟล์ระหว่างโฟลเดอร์ และจัดระเบียบอินเทอร์เฟซ (drag to reorder, drag to resize) บน iOS ใช้งานผ่าน UIDragInteraction, บน Android — ผ่าน DragEvent และ startDragAndDrop
ประเด็นสำคัญ
การลากและวาง (DnD) เป็นวิธีการจัดการองค์ประกอบอินเทอร์เฟซแบบกราฟิกที่ผู้ใช้จับวัตถุด้วยนิ้วหรือเมาส์ ย้ายไปยังตำแหน่งอื่น และปล่อย ในอินเทอร์เฟซมือถือ การลากและวางมักเริ่มต้นด้วยการกดค้าง — ผู้ใช้กดองค์ประกอบค้างไว้ 300–500 มิลลิวินาที หลังจากนั้นระบบจะเปลี่ยนอินเทอร์เฟซเป็นโหมดลาก และการเคลื่อนไหวของนิ้วจะย้ายวัตถุตามไป
ตามประวัติศาสตร์ การลากและวางเกิดขึ้นในอินเทอร์เฟซเดสก์ท็อป (Xerox Star, 1981; Macintosh, 1984) เพื่อเป็นทางเลือกแทนคำสั่งคัดลอก/วาง บนแพลตฟอร์มมือถือ การสนับสนุนอย่างเต็มรูปแบบปรากฏใน iOS 11 (2017) ด้วย UIDragInteraction และใน Android 7.0 (2016) ด้วยการลากและวางหลายหน้าต่าง ก่อนหน้านั้น การลากในแอปมือถือถูกนำไปใช้ด้วยโซลูชันที่กำหนดเองผ่านตัวจดจำท่าทาง
แนวคิดหลัก: เซสชันการลาก (drag session), รายการลาก (drag items), ตัวอย่างลาก (drag preview — การแสดงภาพของวัตถุ), เป้าหมายวาง (drop target), ข้อเสนอวาง (drop proposal — การกระทำเมื่อวาง: ย้าย, คัดลอก, เชื่อมโยง) ระบบจัดการเซสชันในระดับระบบปฏิบัติการ ทำให้สามารถลากวัตถุระหว่างแอปพลิเคชันต่างๆ ได้ (เช่น รูปภาพจากแกลเลอรีไปยังบันทึก)
การลากและวาง แบ่งออกเป็นสามเฟสตามลำดับ เฟสที่ 1 — การจับ (เริ่มลาก): ผู้ใช้เริ่มท่าทาง (โดยปกติคือการกดค้าง), ระบบสร้างเซสชันการลาก, จับข้อมูลของวัตถุ และแสดงตัวอย่างการลาก — สำเนากึ่งโปร่งใสขององค์ประกอบที่ตามนิ้ว ในเฟสนี้ ระบบจะปฏิเสธท่าทางอื่นๆ (การเลื่อน, การเลือกข้อความ) จนกว่า DnD จะเสร็จสมบูรณ์
เฟสที่ 2 — การเคลื่อนย้าย (การเคลื่อนที่ลาก): นิ้วเคลื่อนที่บนหน้าจอ ตัวอย่างการลากตามจุดสัมผัส ระบบจะระบุเป้าหมายวางใต้นิ้วอย่างต่อเนื่องและส่งเหตุการณ์ drag-enter, drag-move และ drag-exit เป้าหมายวางสามารถตอบสนองทางภาพ: ไฮไลต์ ขยาย หรือแสดงเส้นแทรก เฟสนี้ใช้เวลา 0.5 ถึง 10+ วินาที ขึ้นอยู่กับความซับซ้อนของงาน
เฟสที่ 3 — การปล่อย (การวาง): นิ้วยกขึ้นจากหน้าจอ ระบบสิ้นสุดเซสชันการลากและเรียก callback การวางบนองค์ประกอบเป้าหมาย หากเป้าหมายวางปฏิเสธการวาง (เช่น ลากไฟล์ไปยังโฟลเดอร์ที่ไม่มีสิทธิ์เขียน) ตัวอย่างการลากจะเคลื่อนไหวกลับไปยังตำแหน่งเดิม (การเคลื่อนไหวยกเลิก) หากการวางได้รับการยอมรับ องค์ประกอบจะหายไปจากตำแหน่งเดิมและปรากฏที่เป้าหมาย
| เฟส | เหตุการณ์ iOS | เหตุการณ์ Android | สถานะทางภาพ |
|---|---|---|---|
| การจับ | UIDragInteractionDelegate.dragSessionWillBegin | View.OnLongClickListener → startDragAndDrop | ตัวอย่างการลากปรากฏใต้นิ้ว |
| การเคลื่อนย้าย | UIDropInteractionDelegate.dragSessionDidUpdate | View.OnDragListener.onDrag (ACTION_DRAG_MOVED) | ตัวอย่างเคลื่อนที่ เป้าหมายถูกไฮไลต์ |
| การปล่อย | UIDropInteractionDelegate.performDrop | View.OnDragListener.onDrag (ACTION_DROP) | การเคลื่อนไหววางหรือกลับ |
การลากข้ามแอป เป็นฟังก์ชันที่พร้อมใช้งานบนทั้งสองแพลตฟอร์ม iOS รองรับการลากและวางระหว่างแอปในโหมด Split View และ Slide Over (iPad) Android รองรับการลากระหว่างแอปในโหมด Split Screen ข้อมูลถูกถ่ายโอนผ่าน UIPasteConfiguration (iOS) หรือ ClipData (Android) นักพัฒนาระบุประเภทข้อมูลที่รองรับ (UTI สำหรับ iOS, MIME สำหรับ Android)
UIDragInteraction เป็น API หลักของ iOS สำหรับการลาก ซึ่งเพิ่มใน iOS 11 หากต้องการเปิดใช้งานการลากจาก View ให้เพิ่ม UIDragInteraction พร้อม UIDragInteractionDelegate ตัวแทนกำหนดว่าองค์ประกอบใดบ้างที่ลากได้ ลักษณะที่ปรากฏและพฤติกรรมขององค์ประกอบเหล่านั้น หากต้องการรับองค์ประกอบ ให้ใช้ UIDropInteraction พร้อม UIDropInteractionDelegate ซึ่งกำหนดว่า View สามารถยอมรับการวางได้หรือไม่และจะประมวลผลข้อมูลอย่างไร
// UIDragInteraction — การลากองค์ประกอบจากคอลเล็กชัน
class DragViewController: UIViewController, UIDragInteractionDelegate {
@IBOutlet var dragSourceView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let dragInteraction = UIDragInteraction(delegate: self)
dragSourceView.addInteraction(dragInteraction)
}
func dragInteraction(_ interaction: UIDragInteraction,
itemsForBeginning session: UIDragSession) -> [UIDragItem] {
guard let data = Data(contentsOf: selectedFileURL) else { return [] }
let itemProvider = NSItemProvider(object: data as NSData)
let dragItem = UIDragItem(itemProvider: itemProvider)
// ตัวอย่างการลาก — สำเนาที่ลดขนาดขององค์ประกอบ
dragItem.previewProvider = {
let previewView = UIImageView(image: thumbnailImage)
previewView.alpha = 0.8
return UIDragPreview(view: previewView)
}
return [dragItem]
}
}UIDropInteractionDelegate เป็นอินเทอร์เฟซสะท้อนสำหรับรับการวาง เมธอด canHandle(_:) ตรวจสอบว่าประเภทข้อมูลรองรับหรือไม่ sessionDidUpdate(_:) อัปเดตสถานะทางภาพ (ไฮไลต์, เส้นแทรก) performDrop(_:) ดำเนินการประมวลผลข้อมูลขั้นสุดท้าย iOS เรียก UIDropProposal โดยอัตโนมัติพร้อมประเภทการดำเนินการ: .move, .copy หรือ .forbidden ประเภทการดำเนินการส่งผลต่อการแสดงทางภาพ (ไอคอน + ที่มุมของตัวอย่างสำหรับการคัดลอก, เครื่องหมายห้ามสำหรับ forbidden)
View.startDragAndDrop() เป็นเมธอด Android SDK สำหรับเริ่มการลาก ซึ่งพร้อมใช้งานตั้งแต่ API 24 (Android 7.0) ถูกเรียกหลังจากจับองค์ประกอบ (โดยปกติใน onLongClick) พารามิเตอร์: ClipData (ข้อมูล), DragShadowBuilder (เงาที่มองเห็น), Object myLocalState (สถานะภายใน), flags DragShadowBuilder สร้าง Bitmap ที่ตามนิ้ว ระบบจัดการการเคลื่อนไหวและเหตุการณ์สัมผัสโดยอัตโนมัติระหว่างการลาก
// การลากและวางขององค์ประกอบ RecyclerView พร้อมการจัดลำดับใหม่
class ReorderAdapter(private val items: MutableList<String>) :
RecyclerView.Adapter<ReorderAdapter.ViewHolder>() {
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.itemView.setOnLongClickListener {
// การสร้าง ClipData สำหรับการถ่ายโอนข้อมูล
val clipData = ClipData.newPlainText("item", items[position])
// DragShadowBuilder ที่กำหนดเองพร้อมตัวอย่างที่ลดขนาด
val shadowBuilder = object : View.DragShadowBuilder(holder.itemView) {
override fun onProvideShadowMetrics(shadowSize: Point, shadowTouchPoint: Point) {
shadowSize.set(itemView.width, itemView.height / 2)
}
}
holder.itemView.startDragAndDrop(clipData, shadowBuilder, position, 0)
true
}
}
}การรับการวาง บน Android ถูกนำไปใช้ผ่าน View.setOnDragListener() ตัวฟังรับเหตุการณ์ ACTION_DRAG_STARTED, ACTION_DRAG_ENTERED, ACTION_DRAG_LOCATION, ACTION_DRAG_EXITED, ACTION_DROP และ ACTION_DRAG_ENDED ในเหตุการณ์ ACTION_DROP ข้อมูลจะถูกแยกจาก ClipData และดำเนินการตามเป้าหมาย สำหรับ RecyclerView ที่มีการจัดลำดับใหม่ แนะนำให้ใช้ ItemTouchHelper.SimpleCallback — กลไกในตัวที่จัดการการกดค้างสำหรับการจับ การเคลื่อนไหว และเมธอดอรรถประโยชน์ onMove/onSwiped โดยอัตโนมัติ
การจัดลำดับรายการใหม่ (drag to reorder) เป็นสถานการณ์ DnD ที่พบบ่อยที่สุด: ผู้ใช้ลากรายการในรายการเพื่อเปลี่ยนลำดับ ตัวอย่าง: จัดเรียงรายชื่อในรายการโปรดใหม่, จัดเรียงงานในตัวติดตาม (Trello, Todoist), จัดลำดับเพลงในเพลย์ลิสต์ใหม่ (Spotify, Apple Music) ถูกนำไปใช้ผ่าน ItemTouchHelper บน Android หรือ UILongPressGestureRecognizer พร้อมการจัดเรียงเซลล์ใหม่บน iOS
ตัวจัดการไฟล์ — การลากและวางสำหรับคัดลอกและย้ายไฟล์ระหว่างโฟลเดอร์ iOS Files App, Android Files by Google, Dropbox — ทั้งหมดรองรับ DnD การลากไฟล์ไปยังโฟลเดอร์จะย้ายเข้าไปข้างใน (move) การลากขณะกด Option/Ctrl ค้างจะคัดลอก (copy) เป้าหมายวางจะถูกไฮไลต์ทางภาพเมื่อเลื่อนผ่าน แสดงขอบเขตของโฟลเดอร์
บอร์ด Kanban — Trello, Jira, Notion ใช้การลากและวางเพื่อย้ายการ์ดระหว่างคอลัมน์ (To Do → In Progress → Done) เมื่อลาก การ์ดจะแยกจากคอลัมน์ต้นทาง ตามนิ้ว และเมื่อข้ามขอบเขตของอีกคอลัมน์หนึ่ง จะแทรกด้วยการเคลื่อนไหว iOS 11+ รองรับ UIDragPreview พร้อมเอฟเฟกต์พารัลแลกซ์ โดยที่ตัวอย่างจะเอียงเล็กน้อยจากจุดสัมผัส สร้างความลึก
ส่วน Springboard (iOS) — การลากไอคอนแอปบนหน้าจอหลักของ iOS การกดไอคอนค้างจะเปิดใช้งานโหมด “สั่น” (ไอคอนสั่น) หลังจากนั้นผู้ใช้สามารถย้ายไอคอนระหว่างหน้าและสร้างโฟลเดอร์ได้ นี่เป็นหนึ่งในรูปแบบ UX DnD ที่ซับซ้อนที่สุด เนื่องจากต้องจัดการการลาก การเคลื่อนไหวสั่น และการจัดกลุ่มเป็นโฟลเดอร์พร้อมกัน
การตอบกลับทางภาพ มีความสำคัญอย่างยิ่งสำหรับการลากและวาง วัตถุที่ถูกลากควรเปลี่ยนแปลง: หดลง (iOS: สเกล 0.8–0.9), กลายเป็นกึ่งโปร่งใส (alpha 0.7–0.8), และยกขึ้นเหนืออินเทอร์เฟซด้วยเงา (shadow offset) เป้าหมายวางควรตอบสนองทางภาพเมื่อเลื่อนผ่าน: ไฮไลต์ ขยาย หรือแสดงขอบ หากไม่มีสัญญาณเหล่านี้ ผู้ใช้จะไม่ทราบว่าสามารถวางวัตถุได้ที่ไหน
การเคลื่อนไหวกลับ (ยกเลิกการลาก) — หากผู้ใช้ปล่อยองค์ประกอบนอกโซนวางหรือบนโซนที่ไม่ถูกต้อง วัตถุควรเคลื่อนไหวกลับไปยังตำแหน่งเดิม iOS UIDragInteractionDelegate มี dragInteraction(_:sessionDidMove:) สำหรับการติดตาม และ dragInteraction(_:session:willEndWith:) สำหรับการเคลื่อนไหวกลับ Android View.setOnDragListener ได้รับ ACTION_DRAG_ENDED ซึ่งสามารถเริ่มการเคลื่อนไหวกลับผ่าน ValueAnimator หรือ ObjectAnimator
| คำแนะนำ | iOS | Android |
|---|---|---|
| การจับด้วยท่าทาง | กดค้าง (ค่าเริ่มต้น) | OnLongClickListener |
| ตัวอย่างลาก | UIDragItem.previewProvider | DragShadowBuilder |
| การตอบกลับแบบสัมผัส | UIImpactFeedbackGenerator | HapticFeedbackConstants.LONG_PRESS |
| การเคลื่อนไหวกลับ | UIDragPreviewTarget | ViewPropertyAnimator |
| ข้อเสนอวาง | UIDropProposal (.move/.copy) | ACTION_DROP + ธง |
| DnD ข้ามแอป | iOS 11+ Split View (iPad) | Android 7.0+ Split Screen |
การเข้าถึง: ผู้ใช้ที่มีข้อจำกัดด้านการเคลื่อนไหวอาจไม่สามารถทำท่าทางลากได้ ให้ทางเลือกผ่านปุ่ม “ย้ายขึ้น/ลง” สำหรับรายการ และเมนูบริบท “ย้ายไปยังโฟลเดอร์” สำหรับไฟล์ iOS VoiceOver รองรับการดำเนินการโรเตอร์ “ลาก” สำหรับการย้ายองค์ประกอบ Android TalkBack ให้การดำเนินการที่กำหนดเองผ่าน AccessibilityNodeInfo.addAction() Nike Run Club และ Apple Health ใช้ DnD เพื่อจัดลำดับเมตริกบนแดชบอร์ดใหม่ — ด้วยการเข้าถึงผ่านปุ่มมาตรฐาน
คำถามที่พบบ่อย
ท่าทางมาตรฐานสำหรับเริ่ม การลากและวาง คือการกดค้าง 300–500 มิลลิวินาที iOS ใช้กลไก UIDragInteraction ในตัวที่แปลงการแตะแบบต่อเนื่องเป็นการจับโดยอัตโนมัติ Android ต้องการการเรียก startDragAndDrop() อย่างชัดเจนใน callback OnLongClickListener ในบางกรณี (เช่น เกม) DnD สามารถเริ่มด้วยการแตะธรรมดาโดยไม่มีความล่าช้า
ได้ การลากและวางข้ามแอป รองรับบน iOS 11+ (iPad) และ Android 7.0+ บน iPad การลากทำงานในโหมด Split View และ Slide Over — ผู้ใช้สามารถนำข้อความจาก Safari และวางในบันทึกได้ บน Android DnD ระหว่างแอปทำงานใน Split Screen ข้อมูลถูกถ่ายโอนผ่าน UIPasteConfiguration (iOS) หรือ ClipData (Android) พร้อมประเภทที่รองรับที่ระบุ (UTI, MIME)
ข้อขัดแย้ง DnD และการเลื่อน ได้รับการแก้ไขผ่านการหน่วงเวลาการจับ หากผู้ใช้เริ่มขยับนิ้วก่อนที่การหมดเวลากดค้าง (500 มิลลิวินาที) จะสิ้นสุด ท่าทางจะถูกตีความว่าเป็นการเลื่อนมากกว่าการลากและวาง บน iOS UIDragInteraction จัดการข้อขัดแย้งนี้โดยอัตโนมัติผ่าน cancelsTouchesInView บน Android ItemTouchHelper ใช้เกณฑ์ — หากการเคลื่อนไหวเกินเกณฑ์ TouchSlop การกดค้างจะถูกยกเลิกเพื่อให้การเลื่อน優先
การลากและวาง รองรับข้อมูลทุกประเภท: ข้อความ, รูปภาพ, ไฟล์, URL, วัตถุที่กำหนดเอง iOS ใช้ NSItemProvider พร้อมประเภท UTI (public.text, public.image, public.file-url) Android ใช้ ClipData พร้อมประเภท MIME (text/plain, image/png, application/octet-stream) สำหรับข้อมูลที่กำหนดเอง จะสร้าง ClipData.Item พร้อม Intent สำหรับ DnD ข้ามแอป ข้อมูลจะถูกบรรจุในรูปแบบสากล: ข้อความธรรมดา, HTML, RTF, UIImage/PNG
บน iOS การเคลื่อนไหวกลับถูกจัดการผ่าน UIDragPreviewTarget — ตำแหน่งสุดท้ายที่ตัวอย่างเคลื่อนไหวกลับไป บน Android ใช้ ViewPropertyAnimator พร้อม translateX/translateY เพื่อนำองค์ประกอบกลับไปยังตำแหน่งเดิม ระยะเวลาการเคลื่อนไหวคือ 200–300 มิลลิวินาทีพร้อมเส้นโค้ง easing (EaseOut) UIKit เพิ่ม UIDragPreview พร้อมเอฟเฟกต์พารัลแลกซ์โดยอัตโนมัติ ซึ่งกลับมาอย่างนุ่มนวลเมื่อท่าทางถูกยกเลิก
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ