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

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 ประเภทที่อิงตามระยะเวลา เนื่องจากรับประกันความต่อเนื่องของความเร็วเมื่อค่าเป้าหมายเปลี่ยนแปลงระหว่างภาพเคลื่อนไหว API ของภาพเคลื่อนไหวหลายรายการ เช่น animate*AsState และ updateTransition ใช้ spring เป็น AnimationSpec เริ่มต้น

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

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

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

tween สร้างภาพเคลื่อนไหวระหว่างค่าเริ่มต้นและค่าสิ้นสุดในช่วง durationMillis ที่ระบุโดยใช้เส้นโค้งการค่อยๆ เปลี่ยน tween เป็นคำย่อของคำว่า "between" เนื่องจากจะเคลื่อนที่ ระหว่าง ค่า 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 เพื่อปรับเศษส่วนของภาพเคลื่อนไหว ซึ่งจะช่วยให้ค่าที่เคลื่อนไหวเร็วขึ้นและช้าลงได้แทนที่จะเคลื่อนที่ด้วยอัตราคงที่ เศษส่วนคือค่าระหว่าง 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 ในตัวหลายรายการที่ครอบคลุมกรณีการใช้งานส่วนใหญ่ ดูข้อมูลเพิ่มเติมเกี่ยวกับฟังก์ชันการค่อยๆ เปลี่ยนที่ควรใช้ตามสถานการณ์ได้ที่ความเร็ว - Material Design

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

API ของภาพเคลื่อนไหว Compose ส่วนใหญ่รองรับ Float, Color, Dp และประเภทข้อมูลพื้นฐานอื่นๆ เป็นค่าภาพเคลื่อนไหวโดยค่าเริ่มต้น แต่บางครั้งคุณอาจต้องสร้างภาพเคลื่อนไหวของประเภทข้อมูลอื่นๆ รวมถึงประเภทข้อมูลที่กำหนดเอง ระหว่างภาพเคลื่อนไหว ระบบจะแสดงค่าที่เคลื่อนไหวเป็น AnimationVector ระบบจะแปลงค่าเป็น AnimationVector และในทางกลับกันด้วย TwoWayConverter ที่เกี่ยวข้อง เพื่อให้ระบบภาพเคลื่อนไหวหลักจัดการค่าเหล่านั้นได้อย่างสม่ำเสมอ ตัวอย่างเช่น ระบบจะแสดง 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"
    )
}

รายการต่อไปนี้แสดง VectorConverter ในตัวบางรายการ