Tạo ảnh động cho chuyển động bằng vật lý lò xo

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 Ảnh động trong Compose.

Dựa trên kiến thức vật lý chuyển động đều chịu tác động của lực. Lực lò xo là một lực dẫn hướng tính tương tác và chuyển động. Lực của lò xo có các tính chất sau: giảm chấn và độ cứng. Trong ảnh động dựa trên lực lò xo, giá trị và thuộc tính vận tốc được tính dựa trên lực của lò xo tác dụng lên mỗi khung.

Nếu bạn muốn ảnh động của ứng dụng chỉ chậm lại theo một hướng, hãy cân nhắc sử dụng phương pháp ảnh động hất thay thế.

Vòng đời của một ảnh động lò xo

Trong ảnh động dựa trên lực lò xo, SpringForce cho phép bạn tuỳ chỉnh độ cứng của lò xo, tỷ lệ giảm chấn cũng như vị trí cuối cùng. Ngay khi ảnh động bắt đầu, hiệu ứng lò xo sẽ cập nhật giá trị ảnh động và tốc độ trên mỗi khung hình. Ảnh động tiếp tục cho đến khi lực của lò xo đạt trạng thái cân bằng.

Ví dụ: nếu bạn kéo một biểu tượng ứng dụng xung quanh màn hình rồi thả biểu tượng đó ra bằng cách nhấc ngón tay của bạn khỏi biểu tượng, biểu tượng sẽ kéo về ban đầu tạo ra nhờ một sức mạnh vô hình nhưng quen thuộc.

Hình 1 minh hoạ hiệu ứng lò xo tương tự. Dấu cộng (+) trong giữa vòng tròn biểu thị lực tác dụng thông qua một cử chỉ chạm.

Bản phát hành vào mùa xuân
Hình 1. Hiệu ứng thả lò xo

Tạo ảnh động có hiệu ứng lò xo

Các bước chung để tạo ảnh động lò xo cho ứng dụng là như sau:

Các phần sau đây thảo luận các bước chung của việc xây dựng lò xo ảnh động một cách chi tiết.

Thêm thư viện hỗ trợ

Để sử dụng thư viện hỗ trợ dựa trên vật lý, bạn phải thêm thư viện hỗ trợ vào dự án của mình như sau:

  1. Mở tệp build.gradle cho mô-đun ứng dụng.
  2. Thêm thư viện hỗ trợ vào phần dependencies.

    Groovy

            dependencies {
                def dynamicanimation_version = '1.0.0'
                implementation "androidx.dynamicanimation:dynamicanimation:$dynamicanimation_version"
            }
            

    Kotlin

            dependencies {
                val dynamicanimation_version = "1.0.0"
                implementation("androidx.dynamicanimation:dynamicanimation:$dynamicanimation_version")
            }
            

    Để xem các phiên bản hiện tại của thư viện này, hãy xem thông tin về Ảnh động động trên trang phiên bản.

Tạo ảnh động có hiệu ứng lò xo

Lớp SpringAnimation cho phép bạn tạo ảnh động có hiệu ứng lò xo cho một đối tượng. Để tạo ảnh động có hiệu ứng lò xo, bạn cần tạo một thực thể của SpringAnimation lớp và cung cấp một đối tượng, thuộc tính của đối tượng mà bạn muốn tạo ảnh động và một vị trí lò xo cuối cùng (không bắt buộc) mà bạn muốn ảnh động nghỉ.

Lưu ý: Tại thời điểm tạo ảnh động lò xo, ảnh động cuối cùng vị trí của lò xo là không bắt buộc. Tuy nhiên, bạn phải xác định giá trị này trước khi bắt đầu tạo ảnh động.

Kotlin

val springAnim = findViewById<View>(R.id.imageView).let { img ->
    // Setting up a spring animation to animate the view’s translationY property with the final
    // spring position at 0.
    SpringAnimation(img, DynamicAnimation.TRANSLATION_Y, 0f)
}

Java

final View img = findViewById(R.id.imageView);
// Setting up a spring animation to animate the view’s translationY property with the final
// spring position at 0.
final SpringAnimation springAnim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y, 0);

Ảnh động dựa trên lực lò xo có thể tạo ảnh động cho khung hiển thị trên màn hình bằng cách thay đổi thuộc tính thực tế trong đối tượng khung hiển thị. Bạn có thể xem các chế độ xem sau đây trong hệ thống:

  • ALPHA: Đại diện cho độ trong suốt alpha trên khung hiển thị. Giá trị là 1 (không rõ ràng) theo mặc định, với giá trị 0 thể hiện độ trong suốt hoàn toàn (không hiển thị).
  • TRANSLATION_X, TRANSLATION_YTRANSLATION_Z: Đây thuộc tính kiểm soát vị trí của chế độ xem dưới dạng delta từ bên trái toạ độ, toạ độ trên cùng và độ cao được thiết lập theo bố cục của nó vùng chứa.
  • ROTATION, ROTATION_XROTATION_Y: Đây thuộc tính kiểm soát việc xoay ở chế độ 2D (thuộc tính rotation) và 3D xung quanh điểm trung tâm.
  • SCROLL_XSCROLL_Y: Các các thuộc tính cho biết độ lệch cuộn của nguồn bên trái và cạnh trên tính bằng pixel. Chỉ số này cũng cho biết vị trí của trang dựa trên đã cuộn.
  • SCALE_XSCALE_Y: Các các thuộc tính điều khiển việc điều chỉnh tỷ lệ 2D của một khung hiển thị xung quanh điểm tổng hợp.
  • X, YZ: Đây là các bước cơ bản các thuộc tính tiện ích để mô tả vị trí cuối cùng của khung hiển thị trong vùng chứa.

Đăng ký trình nghe

Lớp DynamicAnimation cung cấp hai trình nghe: OnAnimationUpdateListenerOnAnimationEndListener. Những trình nghe này theo dõi nội dung cập nhật bằng ảnh động, chẳng hạn như khi có thay đổi về giá trị ảnh động và khi ảnh động kết thúc.

OnAnimationUpdateListener

Khi muốn tạo ảnh động cho nhiều khung hiển thị để tạo ảnh động theo chuỗi, bạn có thể thiết lập OnAnimationUpdateListener để nhận lệnh gọi lại mỗi khi có thay đổi về thuộc tính thuộc tính này. Lệnh gọi lại thông báo cho thành phần hiển thị khác để cập nhật vị trí lò xo của nó dựa trên thay đổi phát sinh trong thuộc tính của chế độ xem hiện tại. Để đăng ký trình nghe, hãy thực hiện các bước sau:

  1. Gọi addUpdateListener() và đính kèm trình nghe vào ảnh động.

    Lưu ý: Bạn cần đăng ký bản cập nhật trình nghe trước khi ảnh động bắt đầu. Tuy nhiên, trình nghe cập nhật nên chỉ được đăng ký nếu bạn cần cập nhật trên mỗi khung hình về giá trị ảnh động thay đổi. Trình nghe cập nhật ngăn ảnh động có thể chạy trên một luồng riêng.

  2. Ghi đè onAnimationUpdate() để thông báo cho phương thức gọi về thay đổi trong đối tượng hiện tại. Chiến lược phát hành đĩa đơn mã mẫu sau đây minh hoạ cách sử dụng chung OnAnimationUpdateListener.

Kotlin

// Setting up a spring animation to animate the view1 and view2 translationX and translationY properties
val (anim1X, anim1Y) = findViewById<View>(R.id.view1).let { view1 ->
    SpringAnimation(view1, DynamicAnimation.TRANSLATION_X) to
            SpringAnimation(view1, DynamicAnimation.TRANSLATION_Y)
}
val (anim2X, anim2Y) = findViewById<View>(R.id.view2).let { view2 ->
    SpringAnimation(view2, DynamicAnimation.TRANSLATION_X) to
            SpringAnimation(view2, DynamicAnimation.TRANSLATION_Y)
}

// Registering the update listener
anim1X.addUpdateListener { _, value, _ ->
    // Overriding the method to notify view2 about the change in the view1’s property.
    anim2X.animateToFinalPosition(value)
}

anim1Y.addUpdateListener { _, value, _ -> anim2Y.animateToFinalPosition(value) }

Java

// Creating two views to demonstrate the registration of the update listener.
final View view1 = findViewById(R.id.view1);
final View view2 = findViewById(R.id.view2);

// Setting up a spring animation to animate the view1 and view2 translationX and translationY properties
final SpringAnimation anim1X = new SpringAnimation(view1,
        DynamicAnimation.TRANSLATION_X);
final SpringAnimation anim1Y = new SpringAnimation(view1,
    DynamicAnimation.TRANSLATION_Y);
final SpringAnimation anim2X = new SpringAnimation(view2,
        DynamicAnimation.TRANSLATION_X);
final SpringAnimation anim2Y = new SpringAnimation(view2,
        DynamicAnimation.TRANSLATION_Y);

// Registering the update listener
anim1X.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {

// Overriding the method to notify view2 about the change in the view1’s property.
    @Override
    public void onAnimationUpdate(DynamicAnimation dynamicAnimation, float value,
                                  float velocity) {
        anim2X.animateToFinalPosition(value);
    }
});

anim1Y.addUpdateListener(new DynamicAnimation.OnAnimationUpdateListener() {

  @Override
    public void onAnimationUpdate(DynamicAnimation dynamicAnimation, float value,
                                  float velocity) {
        anim2Y.animateToFinalPosition(value);
    }
});

OnAnimationEndListener

OnAnimationEndListener thông báo cho biết vị trí kết thúc của một ảnh động. Bạn có thể thiết lập trình nghe để nhận lệnh gọi lại bất cứ khi nào ảnh động đạt đến trạng thái cân bằng hoặc bị huỷ. Người nhận đăng ký trình nghe, hãy thực hiện các bước sau:

  1. Gọi addEndListener() và đính kèm trình nghe vào ảnh động.
  2. Ghi đè onAnimationEnd() phương thức nhận thông báo bất cứ khi nào ảnh động đạt đến trạng thái cân bằng hoặc bị huỷ.

Loại bỏ người nghe

Để ngừng nhận lệnh gọi lại cập nhật ảnh động và lệnh gọi lại kết thúc ảnh động, gọi cho removeUpdateListener()removeEndListener() tương ứng.

Đặt giá trị bắt đầu ảnh động

Để đặt giá trị bắt đầu của ảnh động, hãy gọi phương thức setStartValue() và chuyển giá trị bắt đầu của ảnh động. Nếu bạn không đặt giá trị bắt đầu, ảnh động sử dụng giá trị hiện tại của thuộc tính của đối tượng làm giá trị bắt đầu.

Đặt phạm vi giá trị ảnh động

Bạn có thể đặt giá trị ảnh động tối thiểu và tối đa khi muốn giới hạn giá trị thuộc tính trong một phạm vi nhất định. Điều này cũng giúp kiểm soát phạm vi trong trường hợp bạn tạo ảnh động cho các thuộc tính có một phạm vi nội tại, chẳng hạn như alpha (từ 0 đến 1).

  • Để đặt giá trị nhỏ nhất, hãy gọi hàm setMinValue() và chuyển giá trị nhỏ nhất của thuộc tính.
  • Để đặt giá trị lớn nhất, hãy gọi setMaxValue() và chuyển giá trị tối đa của thuộc tính.

Cả hai phương thức đều trả về ảnh động mà giá trị đang được đặt.

Lưu ý: Nếu bạn đã đặt giá trị bắt đầu và đã đặt đã xác định phạm vi giá trị ảnh động, hãy đảm bảo giá trị bắt đầu nằm trong phạm vi giá trị tối thiểu và lớn nhất.

Đặt tốc độ bắt đầu

Tốc độ bắt đầu xác định tốc độ thay đổi thuộc tính ảnh động tại phần đầu của ảnh động. Vận tốc bắt đầu mặc định được đặt thành 0 pixel/giây. Bạn có thể đặt vận tốc theo vận tốc chạm cử chỉ hoặc bằng cách sử dụng một giá trị cố định làm tốc độ bắt đầu. Nếu bạn chọn cung cấp một giá trị cố định, bạn nên xác định giá trị theo dp mỗi giây và sau đó chuyển đổi nó thành pixel/giây. Xác định giá trị tính bằng dp mỗi giây cho phép vận tốc độc lập với mật độ và các hệ số hình dạng. Để biết thêm thông tin về chuyển đổi giá trị thành pixel trên giây, hãy tham khảo Chuyển đổi dp/giây thành pixel/giây .

Để thiết lập vận tốc, hãy gọi phương thức setStartVelocity() và truyền tốc độ tính bằng pixel/giây. Phương thức này trả về vật thể lực của lò xo mà tại đó vận tốc được đặt.

Lưu ý: Hãy sử dụng GestureDetector.OnGestureListener hoặc Các phương thức của lớp VelocityTracker để truy xuất và tính toán tốc độ của các cử chỉ chạm.

Kotlin

findViewById<View>(R.id.imageView).also { img ->
    SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply {
        …
        // Compute velocity in the unit pixel/second
        vt.computeCurrentVelocity(1000)
        val velocity = vt.yVelocity
        setStartVelocity(velocity)
    }
}

Java

final View img = findViewById(R.id.imageView);
final SpringAnimation anim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y);
…
// Compute velocity in the unit pixel/second
vt.computeCurrentVelocity(1000);
float velocity = vt.getYVelocity();
anim.setStartVelocity(velocity);

Đang chuyển đổi dp/giây thành pixel/giây

Vận tốc của lò xo phải tính bằng pixel trên giây. Nếu bạn chọn cung cấp giá trị cố định làm thời điểm bắt đầu vận tốc, cung cấp giá trị tính bằng dp mỗi giây rồi chuyển đổi thành pixel/giây. Đối với chuyển đổi, hãy sử dụng applyDimension() từ lớp TypedValue. Tham khảo mã mẫu sau đây:

Kotlin

val pixelPerSecond: Float =
    TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond, resources.displayMetrics)

Java

float pixelPerSecond = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpPerSecond, getResources().getDisplayMetrics());

Đặt thuộc tính lò xo

Lớp SpringForce xác định phương thức getter và phương thức setter cho từng thuộc tính lò xo, chẳng hạn như làm giảm chấn tỷ số và độ cứng. Để đặt các thuộc tính lò xo, điều quan trọng là bạn phải truy xuất đối tượng lực lò xo hoặc tạo một lực lò xo tuỳ chỉnh bạn có thể đặt các thuộc tính. Để biết thêm thông tin về cách tạo lực của lò xo, tham khảo Tạo lực lò xo tuỳ chỉnh .

Mẹo: Trong khi sử dụng phương thức setter, bạn có thể tạo một chuỗi phương thức khi tất cả các phương thức setter trả về lực lò xo .

Tỷ lệ giảm chấn

Tỷ lệ tắt dần mô tả sự giảm dần dao động của lò xo. Theo bằng tỷ lệ giảm chấn, bạn có thể xác định tốc độ phân rã của dao động từ lần thoát này sang trang khác. Có bốn cách khác nhau để bạn giảm độ ẩm mùa xuân:

  • Tình trạng quá mức xảy ra khi tỷ lệ giảm chấn lớn hơn 1. Chiến dịch này cho phép vật thể nhẹ nhàng trở về vị trí nghỉ.
  • Giảm chấn quan trọng xảy ra khi tỷ lệ giảm chấn bằng 1. Chiến dịch này cho phép vật trở về vị trí nghỉ trong một khoảng thời gian ngắn nhất.
  • Giảm chấn xảy ra khi tỷ lệ giảm chấn nhỏ hơn 1. Chiến dịch này cho phép đối tượng vượt quá nhiều lần bằng cách truyền vị trí còn lại, sau đó dần dần đến vị trí nghỉ ngơi.
  • Không làm giảm chấn xảy ra khi tỷ lệ giảm chấn bằng 0. Chiến dịch này cho phép đối tượng dao động vĩnh viễn.

Để thêm tỷ lệ giảm chấn cho lò xo, hãy thực hiện các bước sau:

  1. Gọi getSpring() để truy xuất lò xo nhằm thêm tỷ lệ giảm chấn.
  2. Gọi setDampingRatio() và truyền tỷ lệ giảm chấn mà bạn muốn thêm vào lò xo. Chiến lược phát hành đĩa đơn phương thức trả về đối tượng lực lò xo mà trên đó đã đặt tỷ lệ giảm chấn.

    Lưu ý: Tỷ lệ giảm chấn phải là số không âm. Nếu bạn đặt tỷ lệ giảm chấn thành 0, lò xo sẽ không bao giờ đạt đến vị trí nghỉ ngơi. Nói cách khác, mã này sẽ dao động vĩnh viễn.

Những hằng số tỷ lệ giảm chấn sau đây có sẵn trong hệ thống:

Hình 2: Độ nảy cao

Hình 3: Độ nảy trung bình

Hình 4: Độ nảy thấp

Hình 5: Không nảy

Tỷ lệ giảm chấn mặc định được đặt thành DAMPING_RATIO_MEDIUM_BOUNCY.

Kotlin

findViewById<View>(R.id.imageView).also { img ->
    SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply {
        …
        // Setting the damping ratio to create a low bouncing effect.
        spring.dampingRatio = SpringForce.DAMPING_RATIO_LOW_BOUNCY
        …
    }
}

Java

final View img = findViewById(R.id.imageView);
final SpringAnimation anim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y);
…
// Setting the damping ratio to create a low bouncing effect.
anim.getSpring().setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
…

Độ cứng

Độ cứng xác định hằng số lò xo, dùng để đo độ bền của mùa xuân. Một lò xo cứng tác dụng thêm lực lên vật được gắn khi lò xo không ở vị trí nghỉ. Để thêm độ cứng cho lò xo, hãy thực hiện các bước sau:

  1. Gọi getSpring() để truy xuất lò xo để thêm độ cứng.
  2. Gọi setStiffness() và truyền giá trị độ cứng mà bạn muốn thêm vào lò xo. Chiến lược phát hành đĩa đơn phương thức trả về đối tượng lực lò xo mà trên đó độ cứng được đặt.

    Lưu ý: Độ cứng phải là số dương.

Trong hệ thống có các hằng số độ cứng sau đây:

Hình 6: Độ cứng cao

Hình 7: Độ cứng trung bình

Hình 8: Độ cứng thấp

Hình 9: Độ cứng rất thấp

Độ cứng mặc định được đặt thành STIFFNESS_MEDIUM.

Kotlin

findViewById<View>(R.id.imageView).also { img ->
    SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply {
        …
        // Setting the spring with a low stiffness.
        spring.stiffness = SpringForce.STIFFNESS_LOW
        …
    }
}

Java

final View img = findViewById(R.id.imageView);
final SpringAnimation anim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y);
…
// Setting the spring with a low stiffness.
anim.getSpring().setStiffness(SpringForce.STIFFNESS_LOW);
…

Tạo lực lò xo tuỳ chỉnh

Bạn có thể tạo một lực lò xo tuỳ chỉnh để thay thế việc sử dụng giá trị mặc định lực của lò xo. Lực lượng lò xo tuỳ chỉnh cho phép bạn chia sẻ cùng một lực của lò xo thực thể trên nhiều ảnh động lò xo. Sau khi bạn tạo xong mùa xuân lực, bạn có thể đặt các thuộc tính như tỷ lệ giảm chấn và độ cứng.

  1. Tạo đối tượng SpringForce.

    SpringForce force = new SpringForce();

  2. Chỉ định thuộc tính bằng cách gọi các phương thức tương ứng. Bạn cũng có thể tạo một chuỗi phương thức.

    force.setDampingRatio(DAMPING_RATIO_LOW_BOUNCY).setStiffness(STIFFNESS_LOW);

  3. Gọi setSpring() để đặt lò xo vào ảnh động.

    setSpring(force);

Bắt đầu ảnh động

Có hai cách để bạn có thể bắt đầu ảnh động lò xo: Bằng cách gọi phương thức start() hoặc bằng cách gọi animateToFinalPosition() . Cả hai phương thức cần được gọi trên luồng chính.

animateToFinalPosition() phương thức thực hiện hai tác vụ:

  • Đặt vị trí cuối cùng của lò xo.
  • Khởi động ảnh động nếu nó chưa bắt đầu.

Vì phương thức này cập nhật vị trí cuối cùng của lò xo và bắt đầu ảnh động nếu cần, bạn có thể gọi phương thức này bất cứ lúc nào để thay đổi khoá học của ảnh động. Ví dụ: trong một ảnh động lò xo theo chuỗi, ảnh động của một khung hiển thị phụ thuộc vào một khung hiển thị khác. Đối với một hoạt ảnh như vậy, thuận tiện để sử dụng animateToFinalPosition() . Bằng cách sử dụng phương thức này trong ảnh động lò xo theo chuỗi, bạn sẽ không cần lo lắng liệu ảnh động bạn muốn cập nhật tiếp theo có đang chạy hay không.

Hình 10 minh hoạ ảnh động lò xo theo chuỗi, trong đó ảnh động của một thành phần hiển thị phụ thuộc vào một thành phần hiển thị khác.

Bản minh hoạ lò xo theo chuỗi
Hình 10. Bản minh hoạ lò xo theo chuỗi

Để sử dụng animateToFinalPosition() phương thức, hãy gọi phương thức animateToFinalPosition() và truyền vị trí còn lại của lò xo. Bạn cũng có thể đặt các vị trí của lò xo bằng cách gọi phương thức setFinalPosition() .

Phương thức start() thực hiện không đặt ngay giá trị thuộc tính thành giá trị bắt đầu. Cơ sở lưu trú thay đổi giá trị ở mỗi xung ảnh động, xảy ra trước khi truyền bản vẽ. Kết quả là, những thay đổi được phản ánh trong khung tiếp theo, như thể các giá trị được đặt ngay lập tức.

Kotlin

findViewById<View>(R.id.imageView).also { img ->
    SpringAnimation(img, DynamicAnimation.TRANSLATION_Y).apply {
        …
        // Starting the animation
        start()
        …
    }
}

Java

final View img = findViewById(R.id.imageView);
final SpringAnimation anim = new SpringAnimation(img, DynamicAnimation.TRANSLATION_Y);
…
// Starting the animation
anim.start();
…

Huỷ ảnh động

Bạn có thể huỷ hoặc chuyển đến cuối ảnh động. Trường hợp lý tưởng trong đó bạn cần huỷ hoặc chuyển đến cuối màn thư chào là khi một người dùng thì hoạt động tương tác đó sẽ yêu cầu chấm dứt ngay lập tức. Đây là chủ yếu là khi người dùng thoát khỏi ứng dụng đột ngột hoặc khung hiển thị bị ẩn.

Có 2 phương pháp mà bạn có thể sử dụng để chấm dứt ảnh động. Phương thức cancel() kết thúc ảnh động tại giá trị có sẵn. Chiến lược phát hành đĩa đơn Phương thức skipToEnd() bỏ qua ảnh động đến giá trị cuối cùng rồi chấm dứt.

Trước khi có thể chấm dứt ảnh động, điều quan trọng là bạn phải kiểm tra trạng thái của lò xo. Nếu trạng thái không được làm mờ, ảnh động sẽ không bao giờ đạt được vị trí nghỉ ngơi. Để kiểm tra trạng thái của lò xo, hãy gọi phương thức canSkipToEnd(). Nếu thì lò xo bị làm ẩm, phương thức này sẽ trả về true, nếu không false

Sau khi biết trạng thái của mùa xuân, bạn có thể chấm dứt ảnh động bằng cách sử dụng Phương thức skipToEnd() hoặc cancel(). Chiến lược phát hành đĩa đơn cancel() phương thức chỉ được gọi trên luồng chính.

Lưu ý: Nhìn chung, Nguyên nhân của phương thức skipToEnd() một bước nhảy trực quan.