ปรับแต่งภาพเคลื่อนไหว

API ของภาพเคลื่อนไหวหลายรายการมักยอมรับพารามิเตอร์สำหรับการปรับแต่งลักษณะการทำงาน

ปรับแต่งภาพเคลื่อนไหวด้วยพารามิเตอร์ AnimationSpec

API ภาพเคลื่อนไหวส่วนใหญ่ช่วยให้นักพัฒนาซอฟต์แวร์ปรับแต่งข้อกำหนดภาพเคลื่อนไหวได้โดยใช้พารามิเตอร์ AnimationSpec ที่ไม่บังคับ

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"
)

AnimationSpecมีหลายประเภทสำหรับการสร้างภาพเคลื่อนไหวประเภทต่างๆ

สร้างภาพเคลื่อนไหวตามหลักฟิสิกส์ด้วย spring

spring สร้างภาพเคลื่อนไหวตามหลักฟิสิกส์ระหว่างค่าเริ่มต้นและค่าสิ้นสุด โดยมีพารามิเตอร์ 2 รายการ ได้แก่ dampingRatio และ stiffness

dampingRatio กำหนดว่าสปริงควรจะเด้งมากน้อยเพียงใด ค่าเริ่มต้นคือ Spring.DampingRatioNoBouncy

รูปที่ 1 การตั้งค่าอัตราส่วนการหน่วงสปริงที่แตกต่างกัน

stiffness กำหนดความเร็วที่สปริงควรเคลื่อนที่ไปยังค่าสิ้นสุด ค่าเริ่มต้นคือ Spring.StiffnessMedium

รูปที่ 2 การตั้งค่าความแข็งของสปริงที่แตกต่างกัน

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

springจัดการการหยุดชะงักได้ราบรื่นกว่าประเภทที่อิงตามระยะเวลา AnimationSpec เนื่องจากรับประกันความต่อเนื่องของความเร็วเมื่อ ค่าเป้าหมายเปลี่ยนแปลงระหว่างภาพเคลื่อนไหว spring ใช้เป็น AnimationSpec เริ่มต้นโดย API ภาพเคลื่อนไหวหลายรายการ เช่น animate*AsState และ updateTransition

เช่น หากเราใช้spring config กับภาพเคลื่อนไหวต่อไปนี้ที่ ทำงานเมื่อผู้ใช้แตะ เมื่อขัดจังหวะภาพเคลื่อนไหวขณะที่กำลังทำงาน คุณจะ เห็นว่าการใช้ tween ไม่ตอบสนองได้อย่างราบรื่นเท่ากับการใช้ spring

รูปที่ 3 การตั้งค่าสเปค tween กับ spring สำหรับภาพเคลื่อนไหวและการขัดจังหวะ

สร้างภาพเคลื่อนไหวระหว่างค่าเริ่มต้นและค่าสิ้นสุดด้วยเส้นโค้งการค่อยๆ เปลี่ยนโดยใช้ tween

tween จะเคลื่อนไหวระหว่างค่าเริ่มต้นและค่าสิ้นสุดในช่วงเวลาที่ระบุ durationMillis โดยใช้เส้นโค้งการค่อยๆ เปลี่ยน tween ย่อมาจากคำว่า "ระหว่าง" เนื่องจากค่านี้อยู่ระหว่างค่า 2 ค่า

นอกจากนี้ คุณยังระบุ delayMillis เพื่อเลื่อนการเริ่มต้นของภาพเคลื่อนไหวได้ด้วย

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

ดูข้อมูลเพิ่มเติมได้ที่การผ่อนปรน

เคลื่อนไหวไปยังค่าที่เฉพาะเจาะจงในเวลาที่กำหนดด้วย keyframes

keyframes จะเคลื่อนไหวตามค่าสแนปชอตที่ระบุใน การประทับเวลาต่างๆ ในระยะเวลาของภาพเคลื่อนไหว ระบบจะหาค่าประมาณของค่าภาพเคลื่อนไหวระหว่างค่าคีย์เฟรม 2 ค่าในเวลาใดก็ตาม สำหรับคีย์เฟรมแต่ละรายการเหล่านี้ คุณสามารถระบุการผ่อนแรงเพื่อกำหนดเส้นโค้งการประมาณค่าได้

คุณจะระบุค่าที่ 0 มิลลิวินาทีและที่ระยะเวลาได้หรือไม่ก็ได้ หากคุณไม่ระบุค่าเหล่านี้ ค่าเริ่มต้นจะเป็นค่าเริ่มต้นและค่าสิ้นสุดของ ภาพเคลื่อนไหวตามลำดับ

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"
)

สร้างภาพเคลื่อนไหวระหว่างคีย์เฟรมได้อย่างราบรื่นด้วย keyframesWithSplines

หากต้องการสร้างภาพเคลื่อนไหวที่เคลื่อนที่ตามเส้นโค้งที่ราบรื่นขณะเปลี่ยนค่า คุณสามารถใช้ keyframesWithSplines แทนข้อกำหนดภาพเคลื่อนไหว keyframes ได้

val offset by animateOffsetAsState(
    targetValue = Offset(300f, 300f),
    animationSpec = keyframesWithSpline {
        durationMillis = 6000
        Offset(0f, 0f) at 0
        Offset(150f, 200f) atFraction 0.5f
        Offset(0f, 100f) atFraction 0.7f
    }
)

คีย์เฟรมแบบสไปลน์มีประโยชน์อย่างยิ่งสำหรับการเคลื่อนไหวแบบ 2 มิติของรายการบนหน้าจอ

วิดีโอต่อไปนี้แสดงความแตกต่างระหว่าง keyframes กับ keyframesWithSpline เมื่อกำหนดชุดพิกัด x, y เดียวกันที่วงกลม ควรทำตาม

keyframes keyframesWithSplines

ดังที่เห็น คีย์เฟรมแบบสปไลน์ช่วยให้การเปลี่ยนผ่านระหว่างจุดต่างๆ เป็นไปอย่างราบรื่นยิ่งขึ้น เนื่องจากใช้เส้นโค้งเบซิเยร์เพื่อสร้างภาพเคลื่อนไหวระหว่างรายการต่างๆ อย่างราบรื่น ข้อกำหนดนี้ มีประโยชน์สำหรับการเคลื่อนไหวที่กำหนดไว้ล่วงหน้า อย่างไรก็ตาม หากคุณทำงานกับจุดที่ผู้ใช้กำหนดเอง ขอแนะนำให้ใช้สปริงเพื่อให้ได้ความราบรื่นที่คล้ายกันระหว่างจุดต่างๆ เนื่องจากสปริงสามารถหยุดชะงักได้

เล่นภาพเคลื่อนไหวซ้ำด้วย repeatable

repeatableจะเรียกใช้ภาพเคลื่อนไหวตามระยะเวลา (เช่น tween หรือ keyframes) ซ้ำๆ จนกว่าจะถึงจำนวนการทำซ้ำที่ระบุ คุณสามารถส่งพารามิเตอร์ repeatMode เพื่อระบุว่าภาพเคลื่อนไหวควรเล่นซ้ำโดย เริ่มจากต้น (RepeatMode.Restart) หรือจากท้าย (RepeatMode.Reverse)

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

เล่นภาพเคลื่อนไหวซ้ำไม่สิ้นสุดด้วย infiniteRepeatable

infiniteRepeatable คล้ายกับ repeatable แต่จะทำซ้ำแบบไม่รู้จบ

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

ในการทดสอบที่ใช้ ComposeTestRule ระบบจะไม่เรียกใช้ภาพเคลื่อนไหวที่ใช้ infiniteRepeatable คอมโพเนนต์จะแสดงโดยใช้ค่าเริ่มต้นของค่าเคลื่อนไหวแต่ละค่า

สแนปไปยังค่าสิ้นสุดทันทีด้วย snap

snap เป็น AnimationSpec พิเศษที่เปลี่ยนค่าเป็นค่าสุดท้ายทันที คุณระบุ delayMillis เพื่อเลื่อนเวลาเริ่มต้นของ ภาพเคลื่อนไหวได้

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

ตั้งค่าฟังก์ชันการลดที่กำหนดเอง

การดำเนินการตามระยะเวลา AnimationSpec (เช่น tween หรือ keyframes) ใช้ Easing เพื่อปรับเศษส่วนของภาพเคลื่อนไหว ซึ่งช่วยให้ค่าที่เคลื่อนไหว เร่งและชะลอความเร็วได้ แทนที่จะเคลื่อนไหวด้วยอัตราคงที่ Fraction คือค่าระหว่าง 0 (เริ่มต้น) ถึง 1.0 (สิ้นสุด) ซึ่งระบุจุดปัจจุบันในภาพเคลื่อนไหว

การเปลี่ยนค่าเป็นฟังก์ชันที่รับค่าเศษส่วนระหว่าง 0 ถึง 1.0 และ แสดงผลเป็นค่าลอย ค่าที่แสดงอาจอยู่นอกขอบเขตเพื่อแสดง การยิงเกินหรือยิงไม่ถึง คุณสร้างการลดความเร็วที่กำหนดเองได้เหมือนโค้ดด้านล่าง

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 มีEasing ฟังก์ชันในตัวหลายอย่างที่ครอบคลุม Use Case ส่วนใหญ่ ดูข้อมูลเพิ่มเติมเกี่ยวกับ Easing ที่ควรใช้ตามสถานการณ์ได้ที่ความเร็ว - Material Design

แทนเมธอด getInterpolation()

สร้างภาพเคลื่อนไหวให้กับประเภทข้อมูลที่กำหนดเองโดยการแปลงเป็นและจาก AnimationVector

API การเคลื่อนไหวของ Compose ส่วนใหญ่รองรับ Float, Color, Dp และข้อมูลพื้นฐานอื่นๆ เป็นค่าการเคลื่อนไหวโดยค่าเริ่มต้น แต่บางครั้งคุณอาจต้องเคลื่อนไหว ข้อมูลประเภทอื่นๆ รวมถึงข้อมูลประเภทที่กำหนดเอง ในระหว่างภาพเคลื่อนไหว ค่าที่เคลื่อนไหวจะแสดงเป็น AnimationVector TwoWayConverter ที่เกี่ยวข้องจะแปลงค่าเป็น AnimationVector และในทางกลับกัน เพื่อให้ ระบบภาพเคลื่อนไหวหลักจัดการค่าเหล่านี้ได้อย่างสม่ำเสมอ เช่น Int จะแสดงเป็น AnimationVector1D ที่มีค่าลอยตัวค่าเดียว TwoWayConverter สำหรับ Int มีลักษณะดังนี้

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

Color เป็นชุดค่า 4 ค่า ได้แก่ แดง เขียว น้ำเงิน และอัลฟ่า ดังนั้น Color จะแปลงเป็น AnimationVector4D ที่มีค่าแบบลอย 4 ค่า ด้วยวิธีนี้ ระบบจะแปลงข้อมูลทุกประเภทที่ใช้ในภาพเคลื่อนไหวเป็น AnimationVector1D, AnimationVector2D, AnimationVector3D หรือ AnimationVector4D โดยขึ้นอยู่กับมิติข้อมูล ซึ่งจะช่วยให้คอมโพเนนต์ต่างๆ ของออบเจ็กต์เคลื่อนไหวแยกกันได้ โดยแต่ละคอมโพเนนต์จะมีการติดตามความเร็วของตัวเอง เข้าถึงตัวแปลงในตัวสำหรับประเภทข้อมูลพื้นฐานได้ โดยใช้ตัวแปลง เช่น Color.VectorConverter หรือ Dp.VectorConverter

เมื่อต้องการเพิ่มการรองรับประเภทข้อมูลใหม่เป็นค่าภาพเคลื่อนไหว คุณสามารถ สร้าง TwoWayConverter ของคุณเองและระบุให้กับ API ตัวอย่างเช่น คุณ ใช้ animateValueAsState เพื่อเคลื่อนไหวประเภทข้อมูลที่กำหนดเองได้ดังนี้

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"
    )
}

รายการต่อไปนี้ประกอบด้วย VectorConverters ในตัวบางรายการ