จัดการการเคลื่อนไหวและภาพเคลื่อนไหวของวิดเจ็ตด้วย MotionLayout

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

MotionLayout เป็นตัวเชื่อมระหว่างการเปลี่ยนเลย์เอาต์กับการจัดการการเคลื่อนไหวที่ซับซ้อน โดยนำเสนอฟีเจอร์ที่ผสมผสานระหว่างเฟรมเวิร์กภาพเคลื่อนไหวของพร็อพเพอร์ตี้, TransitionManager และ CoordinatorLayout

รูปที่ 1 การเคลื่อนไหวพื้นฐานที่ควบคุมด้วยการสัมผัส

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

MotionLayout เป็นคำสั่งแบบสมบูรณ์ ซึ่งหมายความว่าคุณสามารถอธิบายการเปลี่ยนผ่านใน XML ได้อย่างซับซ้อนเพียงใดก็ได้

ข้อควรพิจารณาเกี่ยวกับการออกแบบ

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

เริ่มต้นใช้งาน

ทำตามขั้นตอนต่อไปนี้เพื่อเริ่มใช้ MotionLayout ในโปรเจ็กต์

  1. เพิ่ม Dependency ConstraintLayout: หากต้องการใช้ MotionLayout ในโปรเจ็กต์ ให้เพิ่ม Dependency ConstraintLayout 2.0 ลงในไฟล์ build.gradle ของแอป หากคุณใช้ AndroidX ให้เพิ่มข้อกําหนดต่อไปนี้

    ดึงดูด

    dependencies {
        implementation "androidx.constraintlayout:constraintlayout:2.2.0-beta01"
        // To use constraintlayout in compose
        implementation "androidx.constraintlayout:constraintlayout-compose:1.1.0-beta01"
    }
    

    Kotlin

    dependencies {
        implementation("androidx.constraintlayout:constraintlayout:2.2.0-beta01")
        // To use constraintlayout in compose
        implementation("androidx.constraintlayout:constraintlayout-compose:1.1.0-beta01")
    }
    
  2. สร้างไฟล์ MotionLayout: MotionLayout เป็นคลาสย่อยของ ConstraintLayout เพื่อให้คุณแปลง ConstraintLayout ที่มีอยู่ให้เป็น MotionLayout ได้โดยแทนที่ชื่อคลาสในไฟล์ทรัพยากรเลย์เอาต์ ดังที่แสดงในตัวอย่างต่อไปนี้

    AndroidX

    <!-- before: ConstraintLayout -->
    <androidx.constraintlayout.widget.ConstraintLayout .../>
    <!-- after: MotionLayout -->
    <androidx.constraintlayout.motion.widget.MotionLayout .../>
              

    ไลบรารีการสนับสนุน

    <!-- before: ConstraintLayout -->
    <android.support.constraint.ConstraintLayout .../>
    <!-- after: MotionLayout -->
    <android.support.constraint.motion.MotionLayout .../>
              

    ต่อไปนี้คือตัวอย่างไฟล์ MotionLayout แบบเต็ม ซึ่งกำหนดเลย์เอาต์ที่แสดงในรูปภาพ 1

    AndroidX

    <?xml version="1.0" encoding="utf-8"?>
    <!-- activity_main.xml -->
    <androidx.constraintlayout.motion.widget.MotionLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/motionLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutDescription="@xml/scene_01"
        tools:showPaths="true">
    
        <View
            android:id="@+id/button"
            android:layout_width="64dp"
            android:layout_height="64dp"
            android:background="@color/colorAccent"
            android:text="Button" />
    
    </androidx.constraintlayout.motion.widget.MotionLayout>
            

    ไลบรารีการสนับสนุน

    <?xml version="1.0" encoding="utf-8"?>
    <!-- activity_main.xml -->
    <android.support.constraint.motion.MotionLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/motionLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutDescription="@xml/scene_01"
        tools:showPaths="true">
    
        <View
            android:id="@+id/button"
            android:layout_width="64dp"
            android:layout_height="64dp"
            android:background="@color/colorAccent"
            android:text="Button" />
    
    </android.support.constraint.motion.MotionLayout>
            
  3. สร้าง MotionScene: ในตัวอย่าง MotionLayout ก่อนหน้า แอตทริบิวต์ app:layoutDescription อ้างอิงฉากการเคลื่อนไหว ฉากการเคลื่อนไหวคือไฟล์ทรัพยากร XML ฉากการเคลื่อนไหวจะมีคำอธิบายการเคลื่อนไหวทั้งหมดสำหรับเลย์เอาต์ที่เกี่ยวข้องภายในองค์ประกอบราก <MotionScene> MotionLayout แต่ละรายการจะอ้างอิงฉากการเคลื่อนไหวแยกกันเพื่อแยกข้อมูลเลย์เอาต์ออกจากคำอธิบายการเคลื่อนไหว คําจํากัดความในฉากภาพเคลื่อนไหวจะมีลําดับความสําคัญเหนือกว่าคําจํากัดความที่คล้ายกันใน MotionLayout

    ต่อไปนี้คือตัวอย่างไฟล์ฉากการเคลื่อนไหวที่อธิบายการเคลื่อนไหวแนวนอนพื้นฐานในรูปที่ 1

    <?xml version="1.0" encoding="utf-8"?>
    <MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:motion="http://schemas.android.com/apk/res-auto">
    
        <Transition
            motion:constraintSetStart="@+id/start"
            motion:constraintSetEnd="@+id/end"
            motion:duration="1000">
            <OnSwipe
                motion:touchAnchorId="@+id/button"
                motion:touchAnchorSide="right"
                motion:dragDirection="dragRight" />
        </Transition>
    
        <ConstraintSet android:id="@+id/start">
            <Constraint
                android:id="@+id/button"
                android:layout_width="64dp"
                android:layout_height="64dp"
                android:layout_marginStart="8dp"
                motion:layout_constraintBottom_toBottomOf="parent"
                motion:layout_constraintStart_toStartOf="parent"
                motion:layout_constraintTop_toTopOf="parent" />
        </ConstraintSet>
    
        <ConstraintSet android:id="@+id/end">
            <Constraint
                android:id="@+id/button"
                android:layout_width="64dp"
                android:layout_height="64dp"
                android:layout_marginEnd="8dp"
                motion:layout_constraintBottom_toBottomOf="parent"
                motion:layout_constraintEnd_toEndOf="parent"
                motion:layout_constraintTop_toTopOf="parent" />
        </ConstraintSet>
    
    </MotionScene>
        

    ข้อควรทราบ

    • <Transition> มีคำจำกัดความฐานของการเคลื่อนไหว

      • motion:constraintSetStart และ motion:constraintSetEnd เป็นการอ้างอิงถึงจุดสิ้นสุดของการเคลื่อนไหว ระบบจะกําหนดปลายทางเหล่านี้ในองค์ประกอบ <ConstraintSet> ในฉากการเคลื่อนไหวในภายหลัง

      • motion:duration ระบุจำนวนมิลลิวินาทีที่ใช้ในการเคลื่อนไหวให้เสร็จสมบูรณ์

    • <OnSwipe> ให้คุณสร้างการควบคุมด้วยการสัมผัสสำหรับการเคลื่อนไหว

      • motion:touchAnchorId หมายถึงมุมมองที่ผู้ใช้สามารถปัดและลาก

      • motion:touchAnchorSide หมายความว่ามีการลากมุมมองจากด้านขวา

      • motion:dragDirection หมายถึงความคืบหน้าของทิศทางการลาก เช่น motion:dragDirection="dragRight" หมายความว่าความคืบหน้าจะเพิ่มขึ้นเมื่อมีการลากมุมมองไปทางขวา

    • <ConstraintSet> คือที่ที่คุณกำหนดข้อจำกัดต่างๆ ที่อธิบายการเคลื่อนไหว ในตัวอย่างนี้ มีการกําหนด <ConstraintSet> 1 รายการสําหรับปลายทางแต่ละจุดของการเคลื่อนไหว ปลายทางเหล่านี้จะจัดกึ่งกลางในแนวตั้งโดยใช้ app:layout_constraintTop_toTopOf="parent" และ app:layout_constraintBottom_toBottomOf="parent" ส่วนปลายในแนวนอนจะอยู่ด้านซ้ายและขวาสุดของหน้าจอ

    ดูรายละเอียดเพิ่มเติมเกี่ยวกับองค์ประกอบต่างๆ ที่ฉากการเคลื่อนไหวรองรับได้ที่ตัวอย่าง MotionLayout

แอตทริบิวต์ที่หาค่าเฉลี่ย

ภายในไฟล์ฉากเคลื่อนไหว องค์ประกอบ ConstraintSet อาจมีแอตทริบิวต์เพิ่มเติมที่ปัดเศษระหว่างการเปลี่ยน นอกจากตำแหน่งและขอบเขตแล้ว ยังมีการประมาณแอตทริบิวต์ต่อไปนี้โดย MotionLayout

  • alpha
  • visibility
  • elevation
  • rotation, rotationX, rotationY
  • translationX, translationY, translationZ
  • scaleX, scaleY

แอตทริบิวต์ที่กำหนดเอง

ภายใน <Constraint> คุณสามารถใช้องค์ประกอบ <CustomAttribute> เพื่อระบุการเปลี่ยนแอตทริบิวต์ที่ไม่ได้เกี่ยวข้องกับตำแหน่งหรือแอตทริบิวต์ View เพียงอย่างเดียว

<Constraint
    android:id="@+id/button" ...>
    <CustomAttribute
        motion:attributeName="backgroundColor"
        motion:customColorValue="#D81B60"/>
</Constraint>

<CustomAttribute> มีแอตทริบิวต์ 2 รายการ ได้แก่

  • motion:attributeName ต้องระบุและต้องตรงกับออบเจ็กต์ที่มีเมธอด getter และ setter Getter และ Setter ต้องตรงกับรูปแบบที่เฉพาะเจาะจง ตัวอย่างเช่น ระบบรองรับ backgroundColor เนื่องจากมุมมองมีวิธีการ getBackgroundColor() และ setBackgroundColor() อยู่เบื้องหลัง
  • แอตทริบิวต์อื่นๆ ที่คุณต้องระบุจะขึ้นอยู่กับประเภทค่า เลือกจากประเภทที่รองรับต่อไปนี้
    • motion:customColorValue สำหรับสี
    • motion:customIntegerValue สำหรับจำนวนเต็ม
    • motion:customFloatValue สำหรับตัวเลขทศนิยม
    • motion:customStringValue สำหรับสตริง
    • motion:customDimension สำหรับมิติข้อมูล
    • motion:customBoolean สำหรับบูลีน

เมื่อระบุแอตทริบิวต์ที่กำหนดเอง ให้กำหนดค่าปลายทางทั้งในองค์ประกอบ <ConstraintSet> เริ่มต้นและสิ้นสุด

เปลี่ยนสีพื้นหลัง

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

รูปที่ 2 มุมมองจะเปลี่ยนสีพื้นหลังเมื่อเคลื่อนที่

เพิ่มองค์ประกอบ <CustomAttribute> ลงในองค์ประกอบ ConstraintSet แต่ละรายการ ดังที่แสดงในข้อมูลโค้ดต่อไปนี้

<ConstraintSet android:id="@+id/start">
    <Constraint
        android:id="@+id/button"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:layout_marginStart="8dp"
        motion:layout_constraintBottom_toBottomOf="parent"
        motion:layout_constraintStart_toStartOf="parent"
        motion:layout_constraintTop_toTopOf="parent">
        <CustomAttribute
            motion:attributeName="backgroundColor"
            motion:customColorValue="#D81B60" />
    </Constraint>
</ConstraintSet>

<ConstraintSet android:id="@+id/end">
    <Constraint
        android:id="@+id/button"
        android:layout_width="64dp"
        android:layout_height="64dp"
        android:layout_marginEnd="8dp"
        motion:layout_constraintBottom_toBottomOf="parent"
        motion:layout_constraintEnd_toEndOf="parent"
        motion:layout_constraintTop_toTopOf="parent">
        <CustomAttribute
            motion:attributeName="backgroundColor"
            motion:customColorValue="#9999FF" />
    </Constraint>
</ConstraintSet>

แอตทริบิวต์ MotionLayout เพิ่มเติม

นอกจากแอตทริบิวต์ในตัวอย่างก่อนหน้านี้แล้ว MotionLayout ยังมีแอตทริบิวต์อื่นๆ ที่คุณอาจต้องการระบุด้วย ดังนี้

  • app:applyMotionScene="boolean" ระบุว่าจะใช้ฉากที่มีการเคลื่อนไหวหรือไม่ ค่าเริ่มต้นสำหรับแอตทริบิวต์นี้คือ true
  • app:showPaths="boolean" ระบุว่าจะแสดงเส้นทางการเคลื่อนไหวขณะที่การเคลื่อนไหวทำงานอยู่หรือไม่ ค่าเริ่มต้นสำหรับแอตทริบิวต์นี้คือ false
  • app:progress="float" ช่วยให้คุณระบุความคืบหน้าของการเปลี่ยนผ่านได้อย่างชัดเจน คุณใช้ค่าทศนิยมใดก็ได้ตั้งแต่ 0 (จุดเริ่มต้นของการเปลี่ยน) ถึง 1 (จุดสิ้นสุดของการเปลี่ยน)
  • app:currentState="reference" ช่วยให้คุณระบุ ConstraintSet ที่เฉพาะเจาะจงได้
  • app:motionDebug ช่วยให้คุณแสดงข้อมูลเพิ่มเติมเกี่ยวกับการแก้ไขข้อบกพร่องของการเคลื่อนไหวได้ ค่าที่เป็นไปได้คือ "SHOW_PROGRESS", "SHOW_PATH" หรือ "SHOW_ALL"

แหล่งข้อมูลเพิ่มเติม

ดูข้อมูลเพิ่มเติมเกี่ยวกับ MotionLayout ได้ที่แหล่งข้อมูลต่อไปนี้