Các khái niệm quan trọng

Thử cách dùng Compose
Jetpack Compose là bộ công cụ giao diện người dùng được đề xuất cho Android. Tìm hiểu cách sử dụng tính năng kéo và thả trong Compose.

Các phần sau đây giải thích một số khái niệm chính về quy trình kéo và thả.

Quy trình kéo và thả

Có 4 bước hoặc trạng thái trong quá trình kéo và thả: bắt đầu, tiếp tục, thả và kết thúc.

Started (Đã khởi động)

Để phản hồi thao tác kéo của người dùng, ứng dụng của bạn sẽ gọi startDragAndDrop() để yêu cầu hệ thống bắt đầu thao tác kéo và thả. Các đối số của phương thức cung cấp các yếu tố sau:

  • Dữ liệu cần kéo.
  • Lệnh gọi lại để vẽ bóng khi kéo
  • Siêu dữ liệu mô tả dữ liệu đã kéo
  • Hệ thống sẽ phản hồi bằng cách gọi lại ứng dụng của bạn để lấy bóng khi kéo. Sau đó, hệ thống sẽ hiển thị bóng khi kéo trên thiết bị.
  • Tiếp theo, hệ thống sẽ gửi một sự kiện kéo có loại thao tác ACTION_DRAG_STARTED cho trình nghe sự kiện kéo của tất cả đối tượng View trong bố cục hiện tại. Để tiếp tục nhận các sự kiện kéo, bao gồm cả một sự kiện thả có thể xảy ra, trình nghe sự kiện kéo phải trả về true. Việc này sẽ đăng ký trình nghe với hệ thống. Chỉ những trình nghe đã đăng ký mới tiếp tục nhận được sự kiện kéo. Tại thời điểm này, trình nghe cũng có thể thay đổi giao diện của đối tượng View mục tiêu thả để cho biết rằng thành phần hiển thị có thể chấp nhận sự kiện thả.
  • Nếu trình nghe sự kiện kéo trả về false, thì trình nghe sự kiện sẽ không nhận được sự kiện kéo cho thao tác hiện tại cho đến khi hệ thống gửi sự kiện kéo có loại thao tác ACTION_DRAG_ENDED. Khi trả về false, trình nghe sẽ cho hệ thống biết rằng nó không quan tâm đến thao tác kéo và thả, cũng như không muốn chấp nhận dữ liệu đã kéo.
Đang tiếp tục
Người dùng tiếp tục kéo. Khi bóng khi kéo giao với hộp giới hạn của mục tiêu thả, hệ thống sẽ gửi một hoặc nhiều sự kiện kéo đến trình nghe sự kiện kéo của mục tiêu. Trình nghe có thể thay đổi giao diện của mục tiêu thả View để phản hồi sự kiện. Ví dụ: nếu sự kiện cho biết bóng khi kéo vào phạm vi hộp giới hạn của mục tiêu thả xuống (loại thao tác ACTION_DRAG_ENTERED), thì trình nghe có thể phản ứng bằng cách làm nổi bật View.
Thả
Người dùng thả bóng khi kéo trong hộp giới hạn của mục tiêu thả. Hệ thống sẽ gửi trình nghe của mục tiêu thả một sự kiện kéo có loại thao tác ACTION_DROP. Đối tượng sự kiện kéo chứa dữ liệu truyền đến hệ thống trong lệnh gọi startDragAndDrop() bắt đầu thao tác. Trình nghe dự kiến sẽ trả về boolean true cho hệ thống nếu trình nghe xử lý thành công dữ liệu được thả. : Bước này chỉ xảy ra nếu người dùng thả bóng khi kéo trong hộp giới hạn của View mà trình nghe đã đăng ký để nhận sự kiện kéo (mục tiêu thả). Nếu người dùng thả bóng khi kéo trong bất kỳ tình huống nào khác, thì sẽ không có sự kiện kéo ACTION_DROP nào được gửi.
Đã kết thúc

Sau khi người dùng thả bóng khi kéo và sau khi hệ thống gửi

một sự kiện kéo có loại thao tác ACTION_DROP, nếu cần, hệ thống sẽ gửi một sự kiện kéo có loại thao tác ACTION_DRAG_ENDED để cho biết thao tác kéo và thả đã kết thúc. Việc này được thực hiện bất kể người dùng đã thả bóng khi kéo ở đâu. Sự kiện này được gửi tới mọi trình nghe đã đăng ký để nhận sự kiện kéo, ngay cả khi trình nghe cũng nhận được sự kiện ACTION_DROP.

Mỗi bước trong số này đều được mô tả chi tiết hơn trong phần Thao tác kéo và thả.

Sự kiện kéo

Hệ thống sẽ gửi một sự kiện kéo dưới dạng đối tượng DragEvent, chứa loại thao tác mô tả những gì đang xảy ra trong quá trình kéo và thả. Tuỳ thuộc vào loại thao tác mà đối tượng cũng có thể chứa dữ liệu khác.

Trình nghe sự kiện kéo sẽ nhận đối tượng DragEvent. Để biết loại thao tác, trình nghe gọi DragEvent.getAction(). Có 6 giá trị có thể được xác định qua các hằng số trong lớp DragEvent, được mô tả trong bảng 1:

Bảng 1. Các loại thao tác sự kiện kéo

Loại thao tác Ý nghĩa
ACTION_DRAG_STARTED Ứng dụng gọi startDragAndDrop() và có một bóng khi kéo. Nếu muốn tiếp tục nhận các sự kiện kéo cho thao tác này, trình nghe phải trả về giá trị boolean true cho hệ thống.
ACTION_DRAG_ENTERED Bóng khi kéo vào phạm vi hộp giới hạn của View của trình nghe sự kiện kéo. Đây là loại thao tác sự kiện đầu tiên mà trình nghe nhận được lúc phần bóng khi kéo vào phạm vi hộp giới hạn.
ACTION_DRAG_LOCATION Sau sự kiện ACTION_DRAG_ENTERED, bóng khi kéo vẫn nằm trong hộp giới hạn của View của trình nghe sự kiện kéo.
ACTION_DRAG_EXITED Sau ACTION_DRAG_ENTERED và ít nhất một sự kiện ACTION_DRAG_LOCATION, bóng khi kéo sẽ di chuyển ra ngoài hộp giới hạn của View của trình nghe sự kiện kéo.
ACTION_DROP Bóng khi kéo được thả qua View của trình nghe sự kiện kéo. Loại thao tác này chỉ được gửi đến trình nghe của đối tượng View nếu trình nghe đã trả về boolean true để phản hồi lại sự kiện kéo ACTION_DRAG_STARTED. Loại thao tác này sẽ không được gửi nếu người dùng thả bóng khi kéo trên View có trình nghe chưa đăng ký hoặc nếu người dùng thả bóng khi kéo trên bất kỳ phần nào khác không thuộc bố cục hiện tại.

Trình nghe trả về giá trị boolean true nếu xử lý thành công sự kiện thả. Nếu không, hàm này phải trả về false.

ACTION_DRAG_ENDED Hệ thống đang kết thúc thao tác kéo và thả. Loại thao tác này không nhất thiết phải xảy ra sau sự kiện ACTION_DROP. Nếu hệ thống gửi một ACTION_DROP, thì việc nhận được loại thao tác ACTION_DRAG_ENDED không cho biết rằng sự kiện thả đã được thực hiện thành công. Trình nghe phải gọi getResult(), như trong bảng 2, để nhận giá trị được trả về khi phản hồi ACTION_DROP. Nếu ACTION_DROP không được gửi, thì getResult() sẽ trả về false.

Đối tượng DragEvent cũng chứa dữ liệu và siêu dữ liệu mà ứng dụng của bạn cung cấp cho hệ thống trong lệnh gọi đến startDragAndDrop(). Một số dữ liệu chỉ hợp lệ cho một số loại thao tác nhất định được tóm tắt trong bảng 2. Để biết thêm thông tin về các sự kiện và dữ liệu liên quan, hãy xem phần Thao tác kéo và thả.

Bảng 2. Dữ liệu DragEvent hợp lệ theo loại thao tác

Giá trị getAction()
Giá trị getClipDescription()
Giá trị getLocalState()
Giá trị getX()
Giá trị getY()
Giá trị getClipData()
Giá trị getResult()
ACTION_DRAG_STARTED ✓ ✓        
ACTION_DRAG_ENTERED ✓ ✓        
ACTION_DRAG_LOCATION ✓ ✓ ✓ ✓    
ACTION_DRAG_EXITED ✓ ✓        
ACTION_DROP ✓ ✓ ✓ ✓ ✓  
ACTION_DRAG_ENDED   ✓       ✓

Các phương thức DragEvent getAction(), describeContents(), writeToParcel()toString() luôn trả về dữ liệu hợp lệ.

Nếu không chứa dữ liệu hợp lệ cho một loại thao tác cụ thể, thì phương thức đó sẽ trả về null hoặc 0, tuỳ thuộc vào loại kết quả của phương thức đó.

Bóng khi kéo

Trong quá trình kéo và thả, hệ thống sẽ hiển thị một hình ảnh mà người dùng kéo. Đối với di chuyển dữ liệu, hình ảnh này đại diện cho dữ liệu đang được kéo. Đối với các thao tác khác, hình ảnh đại diện cho một số chương trình thành phần của thao tác kéo.

Hình ảnh này được gọi là bóng khi kéo. Bạn tạo bóng khi kéo bằng các phương thức bạn khai báo cho đối tượng View.DragShadowBuilder. Bạn truyền trình tạo tới hệ thống khi bắt đầu thao tác kéo và thả bằng startDragAndDrop(). Để phản hồi startDragAndDrop(), hệ thống gọi các phương thức gọi lại mà bạn xác định trong View.DragShadowBuilder để lấy bóng khi kéo.

Lớp View.DragShadowBuilder có hai hàm khởi tạo (constructor):

View.DragShadowBuilder(View)

Hàm khởi tạo này chấp nhận mọi đối tượng View của ứng dụng. Hàm khởi tạo lưu trữ đối tượng View trong đối tượng View.DragShadowBuilder, vì vậy, các lệnh gọi lại có thể truy cập vào đối tượng đó để tạo bóng khi kéo. Thành phần hiển thị không nhất thiết phải là View mà người dùng chọn để bắt đầu thao tác kéo.

Nếu sử dụng hàm khởi tạo này, bạn không cần phải mở rộng View.DragShadowBuilder hoặc ghi đè phương thức của hàm khởi tạo. Theo mặc định, bạn sẽ thấy một bóng khi kéo có giao diện giống như View mà bạn đã truyền vào làm đối số, nằm ở chính giữa vị trí mà người dùng chạm vào màn hình.

View.DragShadowBuilder()

Nếu bạn sử dụng hàm khởi tạo này, thì sẽ không có đối tượng View nào trong đối tượng View.DragShadowBuilder. Trường này được đặt thành null. Bạn phải mở rộng View.DragShadowBuilder và ghi đè các phương thức của hàm khởi tạo đó, nếu không bạn sẽ thấy một bóng khi kéo vô hình. Hệ thống không báo lỗi.

Lớp View.DragShadowBuilder có hai phương thức để tạo bóng khi kéo:

onProvideShadowMetrics()

Hệ thống sẽ gọi phương thức này ngay sau khi bạn gọi startDragAndDrop(). Sử dụng phương thức để gửi kích thước và điểm chạm của bóng khi kéo đến hệ thống. Phương thức có hai tham số:

outShadowSize: đối tượng Point. Chiều rộng bóng khi kéo được truyền vào x và chiều cao được truyền vào y.

outShadowTouchPoint: đối tượng Point. Điểm chạm là vị trí trong bóng khi kéo và phải nằm dưới ngón tay người dùng trong khi kéo. Vị trí X của bóng khi kéo được truyền vào x và vị trí Y được truyền vào y.

onDrawShadow()

Ngay sau khi gọi onProvideShadowMetrics(), hệ thống sẽ gọi onDrawShadow() để tạo bóng khi kéo. Phương thức này có một đối số duy nhất, đối tượng Canvas mà hệ thống tạo từ tham số bạn cung cấp trong onProvideShadowMetrics(). Phương thức này sẽ vẽ bóng khi kéo trên Canvas được cung cấp.

Để cải thiện hiệu suất, hãy giữ cho kích thước của bóng khi kéo nhỏ. Đối với một mục duy nhất, bạn có thể dùng biểu tượng. Để chọn nhiều mục, bạn có thể sử dụng các biểu tượng trong ngăn xếp thay vì toàn hình ảnh bung rộng trên màn hình.

Trình nghe sự kiện kéo và phương thức gọi lại

View nhận sự kiện kéo bằng trình nghe sự kiện kéo triển khai View.OnDragListener hoặc bằng phương thức gọi lại onDragEvent() của thành phần hiển thị. Khi gọi phương thức hoặc trình nghe sự kiện, hệ thống sẽ cung cấp một đối số DragEvent.

Trong hầu hết trường hợp, bạn nên sử dụng trình nghe thay vì phương thức gọi lại. Khi thiết kế giao diện người dùng, bạn thường không phân lớp các lớp View, nhưng khi sử dụng phương thức gọi lại, bạn sẽ phải tạo các lớp con để ghi đè phương thức. Khi so sánh, bạn có thể triển khai một lớp trình nghe rồi sử dụng lớp đó với nhiều đối tượng View riêng biệt. Bạn cũng có thể triển khai thẻ này dưới dạng lớp nội tuyến ẩn danh hoặc biểu thức lambda. Để thiết lập trình nghe cho đối tượng View, hãy gọi setOnDragListener().

Ngoài ra, bạn có thể thay đổi phương thức triển khai mặc định của onDragEvent() mà không cần ghi đè phương thức. Đặt OnReceiveContentListener trên một thành phần hiển thị; để biết thêm thông tin chi tiết, hãy xem setOnReceiveContentListener(). Sau đó, phương thức onDragEvent() sẽ thực hiện những việc sau theo mặc định:

  • Trả về giá trị đúng (true) khi gọi tới startDragAndDrop().
  • Gọi performReceiveContent() nếu dữ liệu kéo và thả được thả trên thành phần hiển thị. Dữ liệu được truyền vào phương thức dưới dạng đối tượng ContentInfo. Phương thức này gọi OnReceiveContentListener.

  • Trả về giá trị đúng (true) nếu dữ liệu kéo và thả được thả vào thành phần hiển thị và OnReceiveContentListener sử dụng bất kỳ nội dung nào.

Xác định OnReceiveContentListener để xử lý dữ liệu dành riêng cho ứng dụng của bạn. Để giảm khả năng tương thích ngược xuống API cấp 24, hãy sử dụng phiên bản Jetpack của OnReceiveContentListener.

Bạn có thể có trình nghe sự kiện kéo và phương thức gọi lại cho đối tượng View, trong trường hợp này, hệ thống sẽ gọi trình nghe trước. Hệ thống không gọi phương thức gọi lại trừ khi trình nghe trả về false.

Việc kết hợp phương thức onDragEvent()View.OnDragListener sẽ tương tự như cách kết hợp onTouchEvent()View.OnTouchListener dùng cho các sự kiện chạm.