Vẽ giao diện người dùng chỉ là một phần trong quá trình tạo chế độ xem tuỳ chỉnh. Bạn cũng cần làm cho khung hiển thị phản hồi hoạt động đầu vào của người dùng theo cách giống với hành động thực tế mà bạn đang mô phỏng.
Làm cho các đối tượng trong ứng dụng của bạn hoạt động như các đối tượng thực. Ví dụ: đừng để hình ảnh trong ứng dụng của bạn biến mất rồi xuất hiện lại ở nơi khác, vì các đối tượng trong thế giới thực không làm như vậy. Thay vào đó, hãy di chuyển hình ảnh từ nơi này sang nơi khác.
Người dùng cảm nhận được ngay cả những hành vi hoặc cảm giác tinh tế trong một giao diện và phản ứng tốt nhất với những điểm tinh tế mô phỏng thế giới thực. Ví dụ: khi người dùng hất một đối tượng trên giao diện người dùng, hãy tạo cho họ cảm giác quán tính ở đầu, làm chậm chuyển động. Ở cuối chuyển động, hãy tạo cho người dùng cảm giác về đà đẩy vật thể vượt ra ngoài thao tác hất.
Trang này minh hoạ cách sử dụng các tính năng của khung Android để thêm những hành vi thực tế này vào khung hiển thị tuỳ chỉnh.
Bạn có thể tìm thấy thông tin liên quan khác trong phần Tổng quan về sự kiện đầu vào và Tổng quan về ảnh động thuộc tính.
Xử lý cử chỉ nhập
Giống như nhiều khung giao diện người dùng khác, Android hỗ trợ một mô hình sự kiện đầu vào. Các thao tác của người dùng sẽ chuyển thành các sự kiện kích hoạt lệnh gọi lại và bạn có thể ghi đè các lệnh gọi lại để tuỳ chỉnh cách ứng dụng phản hồi người dùng. Sự kiện đầu vào phổ biến nhất trong hệ thống Android là chạm, sự kiện này sẽ kích hoạt onTouchEvent(android.view.MotionEvent).
Ghi đè phương thức này để xử lý sự kiện, như sau:
Kotlin
override fun onTouchEvent(event: MotionEvent): Boolean { return super.onTouchEvent(event) }
Java
@Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); }
Bản thân các sự kiện chạm không đặc biệt hữu ích. Giao diện người dùng cảm ứng hiện đại xác định các hoạt động tương tác theo cử chỉ, chẳng hạn như nhấn, kéo, đẩy, hất và thu phóng. Để chuyển đổi các sự kiện chạm thô thành cử chỉ, Android cung cấp GestureDetector.
Tạo một GestureDetector bằng cách truyền vào một thực thể của lớp triển khai GestureDetector.OnGestureListener.
Nếu chỉ muốn xử lý một vài cử chỉ, bạn có thể mở rộng GestureDetector.SimpleOnGestureListener thay vì triển khai giao diện GestureDetector.OnGestureListener. Ví dụ: mã này tạo một lớp mở rộng GestureDetector.SimpleOnGestureListener và ghi đè onDown(MotionEvent).
Kotlin
private val myListener = object : GestureDetector.SimpleOnGestureListener() { override fun onDown(e: MotionEvent): Boolean { return true } } private val detector: GestureDetector = GestureDetector(context, myListener)
Java
class MyListener extends GestureDetector.SimpleOnGestureListener { @Override public boolean onDown(MotionEvent e) { return true; } } detector = new GestureDetector(getContext(), new MyListener());
Dù bạn có sử dụng GestureDetector.SimpleOnGestureListener hay không, hãy luôn triển khai một phương thức onDown() trả về true. Điều này là cần thiết vì mọi cử chỉ đều bắt đầu bằng thông báo onDown(). Nếu bạn trả về false từ onDown(), như GestureDetector.SimpleOnGestureListener, hệ thống sẽ giả định rằng bạn muốn bỏ qua phần còn lại của cử chỉ và các phương thức khác của GestureDetector.OnGestureListener sẽ không được gọi. Chỉ trả về false từ onDown() nếu bạn muốn bỏ qua toàn bộ cử chỉ.
Sau khi triển khai GestureDetector.OnGestureListener và tạo một phiên bản của GestureDetector, bạn có thể sử dụng GestureDetector để diễn giải các sự kiện chạm mà bạn nhận được trong onTouchEvent().
Kotlin
override fun onTouchEvent(event: MotionEvent): Boolean { return detector.onTouchEvent(event).let { result -> if (!result) { if (event.action == MotionEvent.ACTION_UP) { stopScrolling() true } else false } else true } }
Java
@Override public boolean onTouchEvent(MotionEvent event) { boolean result = detector.onTouchEvent(event); if (!result) { if (event.getAction() == MotionEvent.ACTION_UP) { stopScrolling(); result = true; } } return result; }
Khi bạn truyền onTouchEvent() một sự kiện chạm mà sự kiện đó không nhận dạng được là một phần của cử chỉ, sự kiện này sẽ trả về false. Sau đó, bạn có thể chạy mã phát hiện cử chỉ tuỳ chỉnh của riêng mình.
Tạo chuyển động hợp lý về mặt vật lý
Cử chỉ là một cách hiệu quả để điều khiển các thiết bị có màn hình cảm ứng, nhưng có thể không trực quan và khó nhớ trừ phi chúng tạo ra kết quả hợp lý về mặt vật lý.
Ví dụ: giả sử bạn muốn triển khai một cử chỉ hất theo chiều ngang để đặt mục được vẽ trong khung hiển thị xoay quanh trục dọc. Cử chỉ này sẽ hợp lý nếu giao diện người dùng phản hồi bằng cách di chuyển nhanh theo hướng hất, sau đó chậm lại, như thể người dùng đẩy bánh đà và làm cho bánh đà quay.
Tài liệu về cách tạo hiệu ứng cho cử chỉ cuộn giải thích chi tiết về cách triển khai hành vi cuộn của riêng bạn. Tuy nhiên, việc mô phỏng cảm giác của một bánh đà không phải là điều dễ dàng. Bạn cần có kiến thức về vật lý và toán học để mô hình bánh đà hoạt động đúng cách. Rất may là Android cung cấp các lớp trợ giúp để mô phỏng hành vi này và các hành vi khác. Lớp Scroller là cơ sở để xử lý các cử chỉ hất theo kiểu bánh đà.
Để bắt đầu một thao tác hất, hãy gọi fling() với vận tốc ban đầu và các giá trị x và y tối thiểu và tối đa của thao tác hất. Đối với giá trị vận tốc, bạn có thể dùng giá trị do GestureDetector tính toán.
Kotlin
fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean { scroller.fling( currentX, currentY, (velocityX / SCALE).toInt(), (velocityY / SCALE).toInt(), minX, minY, maxX, maxY ) postInvalidate() return true }
Java
@Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { scroller.fling(currentX, currentY, velocityX / SCALE, velocityY / SCALE, minX, minY, maxX, maxY); postInvalidate(); return true; }
Lệnh gọi đến fling() sẽ thiết lập mô hình vật lý cho cử chỉ hất. Sau đó, hãy cập nhật Scroller bằng cách gọi Scroller.computeScrollOffset() theo định kỳ. computeScrollOffset() cập nhật trạng thái nội bộ của đối tượng Scroller bằng cách đọc thời gian hiện tại và sử dụng mô hình vật lý để tính toán vị trí x và y tại thời điểm đó. Gọi getCurrX() và getCurrY() để truy xuất các giá trị này.
Hầu hết các khung hiển thị đều truyền trực tiếp các vị trí x và y của đối tượng Scroller đến scrollTo().
Ví dụ này có một chút khác biệt: ví dụ này sử dụng vị trí x cuộn hiện tại để đặt góc xoay của khung hiển thị.
Kotlin
scroller.apply { if (!isFinished) { computeScrollOffset() setItemRotation(currX) } }
Java
if (!scroller.isFinished()) { scroller.computeScrollOffset(); setItemRotation(scroller.getCurrX()); }
Lớp Scroller sẽ tính toán vị trí cuộn cho bạn, nhưng không tự động áp dụng những vị trí đó cho khung hiển thị. Áp dụng toạ độ mới đủ thường xuyên để ảnh động cuộn trông mượt mà. Có hai cách để thực hiện việc này:
- Bắt buộc vẽ lại bằng cách gọi
postInvalidate()sau khi gọifling(). Kỹ thuật này yêu cầu bạn tính toán độ lệch cuộn trongonDraw()và gọipostInvalidate()mỗi khi độ lệch cuộn thay đổi. - Thiết lập
ValueAnimatorđể tạo hiệu ứng trong thời gian hất và thêm một trình nghe để xử lý các bản cập nhật hiệu ứng bằng cách gọiaddUpdateListener(). Kỹ thuật này cho phép bạn tạo hiệu ứng cho các thuộc tính của mộtView.
Tạo hiệu ứng chuyển cảnh mượt mà
Người dùng mong muốn giao diện người dùng hiện đại chuyển đổi mượt mà giữa các trạng thái: các phần tử giao diện người dùng mờ dần thay vì xuất hiện và biến mất, đồng thời các chuyển động bắt đầu và kết thúc mượt mà thay vì bắt đầu và dừng đột ngột. Khung ảnh động thuộc tính của Android giúp các hiệu ứng chuyển đổi mượt mà trở nên dễ dàng hơn.
Để sử dụng hệ thống ảnh động, bất cứ khi nào một thuộc tính thay đổi những gì ảnh hưởng đến giao diện của khung hiển thị, đừng thay đổi trực tiếp thuộc tính đó. Thay vào đó, hãy dùng ValueAnimator để thực hiện thay đổi. Trong ví dụ sau, việc sửa đổi thành phần con đã chọn trong khung hiển thị sẽ khiến toàn bộ khung hiển thị được kết xuất xoay để con trỏ lựa chọn nằm ở giữa.
ValueAnimator thay đổi hướng xoay trong khoảng thời gian vài trăm mili giây, thay vì đặt ngay giá trị hướng xoay mới.
Kotlin
autoCenterAnimator = ObjectAnimator.ofInt(this, "Rotation", 0).apply { setIntValues(targetAngle) duration = AUTOCENTER_ANIM_DURATION start() }
Java
autoCenterAnimator = ObjectAnimator.ofInt(this, "Rotation", 0); autoCenterAnimator.setIntValues(targetAngle); autoCenterAnimator.setDuration(AUTOCENTER_ANIM_DURATION); autoCenterAnimator.start();
Nếu giá trị mà bạn muốn thay đổi là một trong các thuộc tính cơ sở View, thì việc tạo ảnh động sẽ dễ dàng hơn nữa, vì các thành phần hiển thị có ViewPropertyAnimator tích hợp được tối ưu hoá để tạo ảnh động đồng thời cho nhiều thuộc tính, như trong ví dụ sau:
Kotlin
animate() .rotation(targetAngle) .duration = ANIM_DURATION .start()
Java
animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();