Tuỳ chỉnh ảnh động

Nhiều API ảnh động thường chấp nhận các tham số để tuỳ chỉnh hoạt động của chúng.

Tuỳ chỉnh ảnh động bằng tham số AnimationSpec

Hầu hết các API ảnh động đều cho phép nhà phát triển tuỳ chỉnh thông số kỹ thuật của ảnh động bằng tham số AnimationSpec tuỳ chọn.

val alpha: Float by animateFloatAsState(
    targetValue = if (enabled) 1f else 0.5f,
    // Configure the animation duration and easing.
    animationSpec = tween(durationMillis = 300, easing = FastOutSlowInEasing),
    label = "alpha"
)

Có nhiều loại AnimationSpec khác nhau để tạo các loại ảnh động khác nhau.

Tạo ảnh động dựa trên vật lý bằng spring

spring tạo một ảnh động dựa trên kiến thức vật lý giữa giá trị bắt đầu và giá trị kết thúc. Nội dung này có 2 thông số: dampingRatiostiffness.

dampingRatio xác định khả năng bật của spring. Giá trị mặc định là Spring.DampingRatioNoBouncy.

Hình 1. Đặt các tỷ lệ giảm chấn lò xo khác nhau.

stiffness xác định tốc độ chuyển động của spring hướng về giá trị cuối cùng. Giá trị mặc định là Spring.StiffnessMedium.

Hình 2. Đặt độ cứng lò xo khác nhau

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = spring(
        dampingRatio = Spring.DampingRatioHighBouncy,
        stiffness = Spring.StiffnessMedium
    ),
    label = "spring spec"
)

spring có thể xử lý sự cố gián đoạn một cách mượt mà hơn các mã AnimationSpec dựa trên thời lượng vì các mã này đảm bảo tính liên tục của tốc độ khi giá trị mục tiêu thay đổi giữa các nội dung ảnh động. spring được sử dụng như thông số ảnh động mặc định của nhiều API ảnh động, chẳng hạn như animate*AsStateupdateTransition.

Ví dụ: nếu chúng ta áp dụng cấu hình spring cho ảnh động sau đây do thao tác chạm của người dùng, khi làm gián đoạn ảnh động trong khi ảnh động đang diễn ra, bạn có thể thấy rằng việc sử dụng tween không phản hồi mượt mà như khi sử dụng spring.

Hình 3. Đặt thông số tween so với spring cho ảnh động và làm gián đoạn ảnh động.

Tạo ảnh động giữa giá trị bắt đầu và giá trị kết thúc bằng easing curve với tween

tween tạo ảnh động giữa giá trị bắt đầu và giá trị kết thúc qua việc sử dụng một easing curve durationMillis được chỉ định. tween là viết tắt của từ between (giữa) vì nó nằm giữa hai giá trị.

Bạn cũng có thể chỉ định delayMillis để trì hoãn việc bắt đầu ảnh động.

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = tween(
        durationMillis = 300,
        delayMillis = 50,
        easing = LinearOutSlowInEasing
    ),
    label = "tween delay"
)

Hãy xem phần Easing để biết thêm thông tin.

Tạo ảnh động cho các giá trị cụ thể tại các thời điểm nhất định bằng keyframes

keyframes tạo ảnh động dựa trên các giá trị ảnh chụp nhanh được chỉ định tại các dấu thời gian trong suốt thời gian tạo ảnh động. Tại một thời điểm bất kỳ, giá trị ảnh động sẽ được tự ý thêm vào giữa 2 giá trị khung hình chính. Đối với mỗi khung hình chính, bạn có thể chỉ định Easing để xác định đường cong nội suy.

Có thể tuỳ chọn chỉ định các giá trị ở 0 giây và tại khoảng thời gian. Nếu bạn không chỉ định các giá trị này, thì giá trị mặc định lần lượt là giá trị bắt đầu và kết thúc của ảnh động.

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = keyframes {
        durationMillis = 375
        0.0f at 0 using LinearOutSlowInEasing // for 0-15 ms
        0.2f at 15 using FastOutLinearInEasing // for 15-75 ms
        0.4f at 75 // ms
        0.4f at 225 // ms
    },
    label = "keyframe"
)

Lặp lại ảnh động bằng repeatable

repeatable chạy một ảnh động dựa trên thời lượng (chẳng hạn như tween hoặc keyframes) lặp lại cho đến khi ảnh động đạt đến số vòng lặp được chỉ định. Bạn có thể chuyển thông số repeatMode để chỉ định xem ảnh động nên lặp lại bằng cách bắt đầu từ đầu (RepeatMode.Restart) hay từ cuối (RepeatMode.Reverse).

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = repeatable(
        iterations = 3,
        animation = tween(durationMillis = 300),
        repeatMode = RepeatMode.Reverse
    ),
    label = "repeatable spec"
)

Lặp lại ảnh động vô hạn bằng infiniteRepeatable

infiniteRepeatable giống như repeatable nhưng số lượng vòng lặp lại là vô hạn.

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = infiniteRepeatable(
        animation = tween(durationMillis = 300),
        repeatMode = RepeatMode.Reverse
    ),
    label = "infinite repeatable"
)

Trong các thử nghiệm sử dụng ComposeTestRule, các ảnh động sử dụng infiniteRepeatable sẽ không chạy. Thành phần sẽ được hiển thị bằng việc sử dụng giá trị ban đầu của từng giá trị ảnh động.

Ngay lập tức chuyển sang giá trị kết thúc bằng snap

snap là một AnimationSpec đặc biệt sẽ ngay lập tức chuyển giá trị đó sang giá trị kết thúc. Bạn có thể chỉ định delayMillis để hoãn việc khởi động ảnh động.

val value by animateFloatAsState(
    targetValue = 1f,
    animationSpec = snap(delayMillis = 50),
    label = "snap spec"
)

Đặt hàm easing tuỳ chỉnh

Các thao tác AnimationSpec dựa trên thời lượng (chẳng hạn như tween hoặc keyframes) sử dụng Easing để điều chỉnh một phân đoạn của ảnh động. Các hoạt động này cho phép giá trị ảnh động tăng tốc và chậm lại, thay vì di chuyển với tốc độ không đổi. Một phân đoạn là một giá trị nằm trong khoảng từ 0 (bắt đầu) đến 1.0 (kết thúc) cho biết điểm hiện tại trong ảnh động.

Easing thực chất là một hàm nhận giá trị phân số từ 0 đến 1.0 và trả về một số thực có độ chính xác đơn. Giá trị được trả về có thể nằm ngoài ranh giới đại diện cho tình trạng quá mức hoặc không đạt mức. Hàm Easing tuỳ chỉnh có thể được tạo giống như mã bên dưới.

val CustomEasing = Easing { fraction -> fraction * fraction }

@Composable
fun EasingUsage() {
    val value by animateFloatAsState(
        targetValue = 1f,
        animationSpec = tween(
            durationMillis = 300,
            easing = CustomEasing
        ),
        label = "custom easing"
    )
    // ……
}

Compose cung cấp nhiều hàm Easing được tích hợp sẵn cho hầu hết các trường hợp sử dụng. Hãy xem bài viết Tốc độ – Material Design để biết thêm thông tin về cách sử dụng hàm Easing theo tình huống của bạn.

  • FastOutSlowInEasing
  • LinearOutSlowInEasing
  • FastOutLinearEasing
  • LinearEasing
  • CubicBezierEasing
  • Xem thêm

Tạo ảnh động cho các loại dữ liệu tuỳ chỉnh bằng cách chuyển đổi sang và từ AnimationVector

Theo mặc định, hầu hết các API ảnh động trong Compose đều hỗ trợ Float, Color, Dp và các kiểu dữ liệu cơ bản khác làm giá trị ảnh động, nhưng đôi khi bạn cần tạo ảnh động cho các loại dữ liệu khác, bao gồm cả dữ liệu tuỳ chỉnh. Trong suốt thời gian tạo ảnh động, mọi giá trị ảnh động đều được biểu thị dưới dạng AnimationVector. Giá trị được chuyển đổi thành AnimationVector và ngược lại bằng TwoWayConverter tương ứng để hệ thống ảnh động chính có thể xử lý thống nhất. Ví dụ: Int được biểu thị dưới dạng AnimationVector1D và có một giá trị số thực duy nhất. TwoWayConverter cho Int có dạng như:

val IntToVector: TwoWayConverter<Int, AnimationVector1D> =
    TwoWayConverter({ AnimationVector1D(it.toFloat()) }, { it.value.toInt() })

Color cơ bản là một tập hợp gồm 4 giá trị: đỏ, xanh lá cây, xanh lam và alpha, vì vậy, Color được chuyển đổi thành AnimationVector4D chứa 4 giá trị số thực chính xác. Bằng cách này mọi loại dữ liệu sử dụng trong ảnh động sẽ được chuyển đổi thành AnimationVector1D, AnimationVector2D, AnimationVector3D hoặc AnimationVector4D tuỳ thuộc vào kích thước. Điều này cho phép các thành phần khác nhau của đối tượng được tạo ảnh động độc lập, mỗi thành phần có theo dõi vận tốc riêng. Bạn có thể truy cập các bộ chuyển đổi tích hợp cho kiểu dữ liệu cơ bản bằng cách sử dụng các bộ chuyển đổi như Color.VectorConverter hoặc Dp.VectorConverter.

Khi muốn hỗ trợ thêm cho một loại dữ liệu mới dưới dạng giá trị ảnh động, bạn có thể tạo TwoWayConverter riêng của mình và cung cấp dữ liệu đó cho API. Ví dụ: bạn có thể sử dụng animateValueAsState để tạo ảnh động cho loại dữ liệu tuỳ chỉnh của mình như sau:

data class MySize(val width: Dp, val height: Dp)

@Composable
fun MyAnimation(targetSize: MySize) {
    val animSize: MySize by animateValueAsState(
        targetSize,
        TwoWayConverter(
            convertToVector = { size: MySize ->
                // Extract a float value from each of the `Dp` fields.
                AnimationVector2D(size.width.value, size.height.value)
            },
            convertFromVector = { vector: AnimationVector2D ->
                MySize(vector.v1.dp, vector.v2.dp)
            }
        ),
        label = "size"
    )
}

Danh sách sau đây bao gồm một số VectorConverter tích hợp: