Tạo khung hiển thị tuỳ chỉnh có tính tương tác

Thử cách 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 bố cục trong ứng dụng Compose.

Vẽ giao diện người dùng chỉ là một phần trong quá trình tạo khung hiển thị tuỳ chỉnh. Bạn cũng cần làm cho khung hiển thị của bạn phản hồi hoạt động đầu vào của người dùng theo cách gần giống với hành động trong thế giới thực mà bạn đang bắt chước.

Làm cho các đối tượng trong ứng dụng hoạt động giống như đối tượng thực. Ví dụ: đừng để hình ảnh trong ứng dụng của bạn hiện ra và xuất hiện lại ở nơi khác, do đối tượng trong thế giới thực, đừng làm điều đó. Thay vào đó, hãy di chuyển hình ảnh từ một nơi sang khác.

Người dùng cảm nhận được 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 chi tiết tinh tế bắt chước thế giới thực. Ví dụ: khi người dùng hất một đối tượng giao diện người dùng, tạo cho chúng cảm giác quán tính tại thời điểm ban đầu làm trì hoãn chuyển động. Ở cuối của chuyển động, cho chúng cảm giác về động lượng đưa vật thể vượt ra ngoài hất.

Trang này minh hoạ cách sử dụng các tính năng của khung Android để thêm các hành vi trong thế giới thực vào khung hiển thị tuỳ chỉnh.

Bạn có thể tìm thêm thông tin liên quan trong Tổng quan về sự kiện đầu vàoẢnh động thuộc tính tổng quan.

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ô hình sự kiện đầu vào. Người dùng sẽ chuyển thành các sự kiện kích hoạt lệnh gọi lại, đồng thời bạn có thể ghi đè lệnh gọi lại để tuỳ chỉnh cách ứng dụng phản hồi người dùng. Thông tin đầu vào phổ biến nhất sự kiện 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 tương tác bằng cử chỉ 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 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 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, luôn triển khai một onDown() trả về true. Điều này là cần thiết vì tất cả cử chỉ bắt đầu bằng thông báo onDown(). Nếu bạn trả về false từ onDown(), dưới dạng GestureDetector.SimpleOnGestureListener thì có, hệ thống giả định bạn muốn bỏ qua phần còn lại của cử chỉ và các phương pháp khác GestureDetector.OnGestureListener không được gọi. Chỉ trả lại hàng false từ onDown() nếu bạn muốn bỏ qua toàn bộ cử chỉ.

Sau khi bạn triển khai GestureDetector.OnGestureListener và tạo một thực thể 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 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à nó không nhận dạng dưới dạng một phần của một cử chỉ, nó 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 bạn.

Tạo chuyển động vật lý hợp lý

Cử chỉ là một cách hiệu quả để điều khiển thiết bị màn hình cảm ứng, nhưng chúng có thể phản trực giác và khó nhớ trừ phi chúng tạo ra kết quả chính đáng.

Ví dụ: giả sử bạn muốn triển khai cử chỉ hất theo chiều ngang đặt mục được vẽ trong chế độ xem quay quanh trục tung của nó. 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 đó giảm tốc độ, như thể người dùng đẩy bánh đà và làm cho nó quay tròn.

Tài liệu hướng dẫn cách tạo hiệu ứng chuyển động cho một cuộn cử chỉ giải thích chi tiết về cách triển khai scoll của riêng bạn hành vi. Tuy nhiên, việc mô phỏng cảm giác của bánh đà không phải là một việc đơn giản. Rất nhiều vật lý và toán học là cần thiết để làm cho mô hình bánh đà hoạt động chính xác. 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. Chiến lược phát hành đĩa đơn Scroller là cơ sở để xử lý các cử chỉ hất kiểu bánh đà.

Để bắt đầu hất, hãy gọi fling() với vận tốc ban đầu cùng với xy tối thiểu và cực đại giá trị của cử chỉ hất. Đối với giá trị vận tốc, bạn có thể sử dụng giá trị được tính bằng GestureDetector.

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() thiết lập mô hình vật lý cho cử chỉ hất cử chỉ. 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 bên trong của đối tượng Scroller bằng cách đọc thời gian hiện tại và bằng cách sử dụng mô hình vật lý để tính vị trí xy tại đó bất cứ lúc nào. Gọi điện getCurrX()getCurrY() để truy xuất các giá trị này.

Hầu hết thành phần hiển thị đều truyền xy của đối tượng Scroller trực tiếp đến scrollTo(). Ví dụ này hơi khác một chút: nó sử dụng vị trí cuộn x hiện tại để đặt góc quay 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 tính toán vị trí cuộn cho bạn, nhưng không tự động áp dụng các vị trí đó vào chế độ xem của bạn. Áp dụng toạ độ mới đủ thường xuyên để làm cho ảnh động cuộn trông mượt mà. Có 2 cách thực hiện việc này:

  • Buộc vẽ lại bằng cách gọi postInvalidate() sau khi gọi fling(). Kỹ thuật này yêu cầu bạn tính toán độ lệch cuộn trong onDraw() và gọi postInvalidate() mỗi khi độ lệch cuộn thay đổi.
  • Thiết lập một ValueAnimator để tạo hiệu ứng động trong thời gian hất và thêm trình nghe để xử lý cập nhật ảnh động bằng cách gọi addUpdateListener() Kỹ thuật này cho phép bạn tạo ảnh động cho các thuộc tính của View.

Giúp chuyển cảnh mượt mà

Người dùng mong muốn có một giao diện người dùng hiện đại để chuyển đổi suôn sẻ giữa các trạng thái: các thành phần trên giao diện người dùng hiện dần trong và biến mất, thay vì xuất hiện rồi biến mất, và chuyển động bắt đầu và kết thúc suôn sẻ thay vì bắt đầu rồi dừng đột ngột. Hệ điều hành Android ảnh động thuộc tính khung giúp chuyển đổi mượt mà 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, điều gì ảnh hưởng đến giao diện của chế độ xem, đừng trực tiếp thay đổi thuộc tính. Thay vào đó, hãy sử dụng ValueAnimator để thực hiện thay đổi. Trong ví dụ sau đây: Việc sửa đổi thành phần con đã chọn trong chế độ xem sẽ khiến toàn bộ thành phần được kết xuất xoay khung nhìn để con trỏ lựa chọn nằm chính giữa. ValueAnimator thay đổi chế độ xoay trong khoảng thời gian vài trăm mili giây thay vì đặt ngay giá trị xoay vòng 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ị bạn muốn thay đổi là một trong các giá trị cơ sở View các thuộc tính, việc tạo ảnh động thậm chí còn dễ dàng hơn vì các chế độ xem đã được tích hợp sẵn ViewPropertyAnimator được tối ưu hoá cho ảnh động đồng thời của 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();