Android ขั้นสูงใน Kotlin 03.2: ภาพเคลื่อนไหวด้วย MotionLayout

1. ก่อนเริ่มต้น

Codelab นี้เป็นส่วนหนึ่งของหลักสูตร Advanced Android ใน Kotlin คุณจะได้รับประโยชน์สูงสุดจากหลักสูตรนี้หากทำงานผ่าน Codelab ตามลำดับ แต่ไม่ได้เป็นข้อบังคับ Codelab ทั้งหมดของหลักสูตรแสดงอยู่ในหน้า Landing Page สำหรับ Android ขั้นสูงใน Kotlin

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

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

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

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

ข้อกำหนดเบื้องต้น

Codelab นี้ได้รับการออกแบบมาสำหรับนักพัฒนาซอฟต์แวร์ที่มีประสบการณ์ด้านการพัฒนาซอฟต์แวร์ Android มาบ้างแล้ว ก่อนที่จะพยายามทำ Codelab นี้ให้เสร็จสิ้น คุณควรทำดังนี้

  • รู้วิธีสร้างแอปด้วยกิจกรรม เลย์เอาต์พื้นฐาน และเรียกใช้ในอุปกรณ์หรือโปรแกรมจำลองโดยใช้ Android Studio ทำความคุ้นเคยกับ ConstraintLayout อ่าน Constraint Layout Codelab เพื่อดูข้อมูลเพิ่มเติมเกี่ยวกับ ConstraintLayout

สิ่งที่คุณจะต้องทำ

  • กำหนดภาพเคลื่อนไหวด้วย ConstraintSets และ MotionLayout
  • เคลื่อนไหวโดยอิงจากเหตุการณ์การลาก
  • เปลี่ยนภาพเคลื่อนไหวด้วย KeyPosition
  • เปลี่ยนแอตทริบิวต์ด้วย KeyAttribute
  • เรียกใช้ภาพเคลื่อนไหวด้วยโค้ด
  • ทำให้ส่วนหัวที่ยุบได้เป็นภาพเคลื่อนไหวด้วย MotionLayout

สิ่งที่คุณต้องมี

  • Android Studio 4.0 (ตัวแก้ไข MotionLayout ใช้งานได้กับ Android Studio เวอร์ชันนี้เท่านั้น)

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

หากต้องการดาวน์โหลดแอปตัวอย่าง ให้ดำเนินการอย่างใดอย่างหนึ่งต่อไปนี้

... หรือโคลนที่เก็บ GitHub จากบรรทัดคำสั่งโดยใช้คำสั่งต่อไปนี้

$ git clone https://github.com/googlecodelabs/motionlayout.git

3. การสร้างภาพเคลื่อนไหวด้วย MotionLayout

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

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

  • MotionLayout, ซึ่งเป็นคลาสย่อยของ ConstraintLayout คุณระบุมุมมองทั้งหมดให้เป็นภาพเคลื่อนไหวภายในแท็ก MotionLayout
  • MotionScene, ซึ่งเป็นไฟล์ XML ที่อธิบายภาพเคลื่อนไหวสำหรับ MotionLayout.
  • Transition, ซึ่งเป็นส่วนหนึ่งของ MotionScene ที่ระบุระยะเวลาของภาพเคลื่อนไหว ทริกเกอร์ และวิธีย้ายมุมมอง
  • ConstraintSet ที่ระบุทั้งข้อจำกัด start และ end ของการเปลี่ยน

เรามาดูแต่ละปัจจัยตามลำดับกันเลย เริ่มจาก MotionLayout

ขั้นตอนที่ 1: สำรวจโค้ดที่มีอยู่

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

  1. ใน res/layout ให้เปิด activity_step1.xml. ที่นี่คุณมี ConstraintLayout ที่มีดาว ImageView ดวงเดียว ซึ่งมีการทาสีด้านใน

activity_step1.xml

<!-- initial code -->
<androidx.constraintlayout.widget.ConstraintLayout
       ...
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       >

   <ImageView
           android:id="@+id/red_star"
           ...
   />

</androidx.constraintlayout.motion.widget.MotionLayout>

ConstraintLayout นี้ไม่มีข้อจำกัดใดๆ ดังนั้นถ้าคุณเรียกใช้แอปตอนนี้ คุณจะเห็นการแสดงดาวไม่จำกัด ซึ่งหมายความว่าแอปจะอยู่ในตำแหน่งที่ไม่รู้จัก Android Studio จะแจ้งเตือนเกี่ยวกับการไม่มีข้อจำกัด

ขั้นตอนที่ 2: แปลงเป็นเลย์เอาต์แบบเคลื่อนไหว

หากต้องการให้เคลื่อนไหวโดยใช้ MotionLayout, คุณต้องแปลง ConstraintLayout เป็น MotionLayout

เลย์เอาต์จะต้องชี้ไปที่ฉากนั้น เลย์เอาต์จึงจะใช้ฉากเคลื่อนไหวได้

  1. โดยเปิดแพลตฟอร์มการออกแบบ ใน Android Studio 4.0 คุณจะเปิดพื้นผิวการออกแบบโดยใช้ไอคอนแยกหรือไอคอนการออกแบบที่ด้านขวาบนเมื่อดูไฟล์ XML ของเลย์เอาต์

a2beea710c2decb7.png

  1. เมื่อคุณเปิดพื้นที่การออกแบบแล้ว ให้คลิกขวาที่ตัวอย่างและเลือกแปลงเป็น MotionLayout

4fa936a98a8393b9.png

การดำเนินการนี้จะแทนที่แท็ก ConstraintLayout ด้วยแท็ก MotionLayout และเพิ่ม motion:layoutDescription ลงในแท็ก MotionLayout ที่ชี้ไปยัง @xml/activity_step1_scene.

activity_step1**.xml**

<!-- explore motion:layoutDescription="@xml/activity_step1_scene" -->
<androidx.constraintlayout.motion.widget.MotionLayout
       ...
       motion:layoutDescription="@xml/activity_step1_scene">

ฉากเคลื่อนไหวคือไฟล์ XML ไฟล์เดียวที่อธิบายภาพเคลื่อนไหวใน MotionLayout

เมื่อคุณแปลงเป็น MotionLayout แพลตฟอร์มการออกแบบจะแสดง Motion Editor

66d0e80d5ab4daf8.png

องค์ประกอบ UI ใหม่ 3 รายการใน Motion Editor มีดังนี้

  1. ภาพรวม – นี่คือการเลือกโมดัลที่ให้คุณสามารถเลือกส่วนต่างๆ ของภาพเคลื่อนไหว ในภาพนี้ระบบเลือก start ConstraintSet นอกจากนี้ คุณยังสามารถเลือกการเปลี่ยนแปลงระหว่าง start และ end ได้โดยคลิกที่ลูกศรที่อยู่ตรงกลาง
  2. ส่วน - ด้านล่างของภาพรวมคือหน้าต่างหัวข้อที่เปลี่ยนแปลงตามรายการภาพรวมที่เลือกไว้ในปัจจุบัน ในภาพนี้ ข้อมูล start ConstraintSet จะแสดงในหน้าต่างการเลือก
  3. แอตทริบิวต์ – แผงแอตทริบิวต์จะแสดงและช่วยให้คุณแก้ไขแอตทริบิวต์ของรายการที่เลือกในปัจจุบันได้จากหน้าต่างภาพรวมหรือหน้าต่างการเลือก ในภาพนี้แสดงแอตทริบิวต์ของ start ConstraintSet

ขั้นตอนที่ 3: กำหนดข้อจำกัดเริ่มต้นและสิ้นสุด

ภาพเคลื่อนไหวทั้งหมดสามารถกำหนดจุดเริ่มต้นและจุดสิ้นสุดได้ จุดเริ่มต้นจะอธิบายลักษณะของหน้าจอก่อนที่จะแสดงภาพเคลื่อนไหว และตอนท้ายจะอธิบายลักษณะของหน้าจอหลังจากที่ภาพเคลื่อนไหวจบลงแล้ว MotionLayout มีหน้าที่หาวิธีภาพเคลื่อนไหวระหว่างสถานะเริ่มต้นกับสถานะสิ้นสุด (เมื่อเวลาผ่านไป)

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

ข้อจำกัดใดๆ ที่ระบุใน ConstraintSet จะลบล้างข้อจำกัดที่ระบุไว้ในไฟล์เลย์เอาต์ หากคุณกำหนดข้อจำกัดทั้งในเลย์เอาต์และ MotionScene ระบบจะนำเฉพาะข้อจำกัดใน MotionScene ไปใช้เท่านั้น

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

คุณสามารถทำขั้นตอนนี้ให้เสร็จโดยใช้เครื่องมือแก้ไขภาพเคลื่อนไหวหรือแก้ไขข้อความของ activity_step1_scene.xml โดยตรง

  1. เลือก ConstraintSet ของ start ในแผงภาพรวม

6E57661ed358b860.png

  1. ในแผงการเลือก ให้เลือก red_star ปัจจุบันแสดงแหล่งที่มาของ layout ซึ่งหมายความว่าไม่มีการจํากัดใน ConstraintSet นี้ ใช้ไอคอนดินสอที่ด้านขวาบนเพื่อสร้างข้อจํากัด

f9564c574b86ea8.gif

  1. ยืนยันว่า red_star แสดงแหล่งที่มา start เมื่อเลือก ConstraintSet ของ start ในแผงภาพรวม
  2. ในแผงแอตทริบิวต์ ที่มีการเลือก red_star ใน ConstraintSet ของ start ให้เพิ่มข้อจำกัดที่ด้านบน แล้วเริ่มด้วยการคลิกปุ่ม + สีน้ำเงิน

2fce076cd7b04bd.png

  1. เปิด xml/activity_step1_scene.xml เพื่อดูโค้ดที่ Motion Editor สร้างขึ้นสำหรับข้อจำกัดนี้

activity_step1_scene.xml

<!-- Constraints to apply at the start of the animation -->
<ConstraintSet android:id="@+id/start">
   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>

ConstraintSet มี id เป็น @id/start และระบุข้อจำกัดทั้งหมดที่จะนำไปใช้กับการแสดงผลทั้งหมดใน MotionLayout เนื่องจาก MotionLayout นี้มีข้อมูลพร็อพเพอร์ตี้เดียว จึงต้องการใช้ Constraint เพียง 1 รายการ

Constraint ภายใน ConstraintSet ระบุรหัสของข้อมูลพร็อพเพอร์ตี้ที่มีการจำกัด @id/red_star ซึ่งกำหนดไว้ใน activity_step1.xml โปรดทราบว่าแท็ก Constraint ระบุเฉพาะข้อจำกัดและข้อมูลเลย์เอาต์เท่านั้น แท็ก Constraint ไม่ทราบว่ากําลังใช้กับ ImageView อยู่

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

  1. เลือก ConstraintSet ของ end ในแผงภาพรวม

346e1248639b6f1e.png

  1. ทำตามขั้นตอนเดิมเพื่อเพิ่ม Constraint สำหรับ red_star ใน ConstraintSet ของ end
  2. หากต้องการใช้ Motion Editor เพื่อดำเนินการขั้นตอนนี้ ให้เพิ่มข้อจำกัดลงใน bottom และ end โดยคลิกปุ่ม + สีน้ำเงิน

fd33c779ff83c80a.png

  1. โค้ดใน XML มีลักษณะดังนี้

activitiy_step1_scene.xml

<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">
   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>

ConstraintSet นี้มี Constraint รายการเดียวใน @id/red_star เหมือนกับ @id/start ซึ่งตอนนี้จะจำกัดให้อยู่ที่ด้านล่างของหน้าจอ

คุณไม่จำเป็นต้องตั้งชื่อว่า @id/start และ @id/end แต่ใช้ชื่อนี้ได้สะดวก

ขั้นตอนที่ 4: กำหนดการเปลี่ยน

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

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

  1. Motion Editor จะสร้างการเปลี่ยนสำหรับเราโดยค่าเริ่มต้นเมื่อสร้างไฟล์ MotionScene เปิด activity_step1_scene.xml เพื่อดูการเปลี่ยนที่สร้างขึ้น

activity_step1_scene.xml

<!-- A transition describes an animation via start and end state -->
<Transition
   motion:constraintSetEnd="@+id/end"
   motion:constraintSetStart="@id/start"
   motion:duration="1000">
  <KeyFrameSet>
  </KeyFrameSet>
</Transition>

นี่คือทุกสิ่งที่ MotionLayout ต้องใช้ในการสร้างภาพเคลื่อนไหว โดยดูที่แอตทริบิวต์แต่ละรายการ

  • ระบบจะใช้ constraintSetStart กับมุมมองเมื่อภาพเคลื่อนไหวเริ่มขึ้น
  • ระบบจะใช้ constraintSetEnd กับมุมมองที่ตอนท้ายของภาพเคลื่อนไหว
  • duration ระบุระยะเวลาที่ภาพเคลื่อนไหวควรจะแสดงในหน่วยมิลลิวินาที

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

ขั้นตอนที่ 5: แสดงตัวอย่างภาพเคลื่อนไหวในเครื่องมือแก้ไขภาพเคลื่อนไหว

dff9ecdc1f4a0740.gif

ภาพเคลื่อนไหว: วิดีโอแสดงตัวอย่างการเปลี่ยนใน Motion Editor

  1. เปิด Motion Editor และเลือกการเปลี่ยนโดยคลิกลูกศรระหว่าง start และ end ในแผงภาพรวม

1dc541ae8c43b250.png

  1. แผงการเลือกจะแสดงตัวควบคุมการเล่นและแถบกรอเมื่อเลือกการเปลี่ยน คลิกเล่นหรือลากตำแหน่งปัจจุบันเพื่อดูตัวอย่างภาพเคลื่อนไหว

a0fd2593384dfb36.png

ขั้นตอนที่ 6: เพิ่ม Click Handler

คุณต้องมีวิธีเริ่มภาพเคลื่อนไหว วิธีหนึ่งในการทำเช่นนี้คือการทำให้ MotionLayout ตอบสนองต่อกิจกรรมการคลิกใน @id/red_star

  1. เปิดเครื่องมือแก้ไขการเคลื่อนไหวแล้วเลือกการเปลี่ยนโดยคลิกลูกศรที่อยู่ระหว่างจุดเริ่มต้นและจุดสิ้นสุดในแผงภาพรวม

b6f94b344ce65290.png

  1. คลิก 699f7ae04024ccf6.png สร้างเครื่องจัดการการคลิกหรือเลื่อนในแถบเครื่องมือสำหรับแผงภาพรวม การดำเนินการนี้จะเพิ่มตัวจัดการที่จะเริ่มการเปลี่ยน
  2. เลือกคลิกเครื่องจัดการจากป๊อปอัป

ccf92d06335105fe.png

  1. เปลี่ยนมุมมองเพื่อคลิกเป็น red_star

b0d3f0c970604f01.png

  1. คลิกเพิ่มที่เครื่องจัดการการคลิกจะแสดงด้วยจุดเล็กๆ บน "การเปลี่ยน" ใน Motion Editor

cec3913e67fb4105.png

  1. เมื่อเลือกการเปลี่ยนในแผงภาพรวม ให้เพิ่มแอตทริบิวต์ clickAction ของ toggle ในเครื่องจัดการ OnClick ที่เพิ่งเพิ่มในแผงแอตทริบิวต์

9af6fc60673d093d.png

  1. เปิด activity_step1_scene.xml เพื่อดูโค้ดที่ Motion Editor สร้างขึ้น

activity_step1_scene.xml

<!-- A transition describes an animation via start and end state -->
<Transition
    motion:constraintSetStart="@+id/start"
    motion:constraintSetEnd="@+id/end"
    motion:duration="1000">
    <!-- MotionLayout will handle clicks on @id/red_star to "toggle" the animation between the start and end -->
    <OnClick
        motion:targetId="@id/red_star"
        motion:clickAction="toggle" />
</Transition>

Transition จะบอกให้ MotionLayout เรียกใช้ภาพเคลื่อนไหวเพื่อตอบสนองต่อเหตุการณ์การคลิกโดยใช้แท็ก <OnClick> โดยดูที่แอตทริบิวต์แต่ละรายการ

  • targetId คือจำนวนการดูที่น่าจับตามองสำหรับการคลิก
  • clickAction จาก toggle จะสลับระหว่างสถานะเริ่มต้นและสิ้นสุดเมื่อคลิก คุณดูตัวเลือกอื่นๆ ของ clickAction ได้ในเอกสารประกอบ
  1. รันโค้ดของคุณ คลิกขั้นตอนที่ 1 แล้วคลิกดาวสีแดงและดูภาพเคลื่อนไหว!

ขั้นตอนที่ 5: การทำงานของภาพเคลื่อนไหว

เรียกใช้แอป คุณควรเห็นภาพเคลื่อนไหวทำงานเมื่อคลิกที่ดาว

7ba88af963fdfe10.gif

ไฟล์ฉากเคลื่อนไหวที่เสร็จสมบูรณ์จะกำหนด Transition 1 รายการที่ชี้ไปยังจุดเริ่มต้นและสิ้นสุด ConstraintSet

ที่จุดเริ่มต้นของภาพเคลื่อนไหว (@id/start) ไอคอนดาวจะจำกัดอยู่ที่จุดเริ่มต้นของหน้าจอ ในช่วงท้ายของภาพเคลื่อนไหว (@id/end) ไอคอนดาวจะถูกจำกัดอยู่ที่ด้านล่างของหน้าจอ

<?xml version="1.0" encoding="utf-8"?>

<!-- Describe the animation for activity_step1.xml -->
<MotionScene xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:android="http://schemas.android.com/apk/res/android">
   <!-- A transition describes an animation via start and end state -->
   <Transition
           motion:constraintSetStart="@+id/start"
           motion:constraintSetEnd="@+id/end"
           motion:duration="1000">
       <!-- MotionLayout will handle clicks on @id/star to "toggle" the animation between the start and end -->
       <OnClick
               motion:targetId="@id/red_star"
               motion:clickAction="toggle" />
   </Transition>

   <!-- Constraints to apply at the end of the animation -->
   <ConstraintSet android:id="@+id/start">
       <Constraint
               android:id="@+id/red_star"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               motion:layout_constraintStart_toStartOf="parent"
               motion:layout_constraintTop_toTopOf="parent" />
   </ConstraintSet>

   <!-- Constraints to apply at the end of the animation -->
   <ConstraintSet android:id="@+id/end">
       <Constraint
               android:id="@+id/red_star"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               motion:layout_constraintEnd_toEndOf="parent"
               motion:layout_constraintBottom_toBottomOf="parent" />
   </ConstraintSet>
</MotionScene>

4. ภาพเคลื่อนไหวตามเหตุการณ์การลาก

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

ขั้นตอนที่ 1: ตรวจสอบโค้ดเริ่มต้น

  1. ในการเริ่มต้นใช้งาน ให้เปิดไฟล์เลย์เอาต์ activity_step2.xml ซึ่งมี MotionLayout อยู่แล้ว ตรวจสอบโค้ด

activity_step2.xml

<!-- initial code -->

<androidx.constraintlayout.motion.widget.MotionLayout
       ...
       motion:layoutDescription="@xml/step2" >

   <ImageView
           android:id="@+id/left_star"
           ...
   />

   <ImageView
           android:id="@+id/right_star"
           ...
   />

   <ImageView
           android:id="@+id/red_star"
           ...
   />

   <TextView
           android:id="@+id/credits"
           ...
           motion:layout_constraintTop_toTopOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.motion.widget.MotionLayout>

เค้าโครงนี้จะกำหนดมุมมองทั้งหมดสำหรับภาพเคลื่อนไหว ไอคอนดาว 3 ดวงจะไม่ถูกจำกัดในเลย์เอาต์เนื่องจากไอคอนจะเคลื่อนไหวในฉากเคลื่อนไหว

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

ขั้นตอนที่ 2: ทำให้ฉากเคลื่อนไหว

ภาพเคลื่อนไหวจะกำหนดโดย ConstraintSet, เริ่มต้นและสิ้นสุดและ Transition เช่นเดียวกับภาพเคลื่อนไหวล่าสุด

กำหนดการเริ่มต้น ConstraintSet

  1. เปิดฉากเคลื่อนไหว xml/step2.xml เพื่อกำหนดภาพเคลื่อนไหว
  2. เพิ่มข้อจำกัดสำหรับข้อจำกัดเริ่มต้น start ตอนเริ่มต้น ดาวทั้ง 3 ดวงจะอยู่ตรงกลางด้านล่างของหน้าจอ ดาวด้านขวาและซ้ายมีค่า alpha เป็น 0.0 ซึ่งหมายความว่ามีความโปร่งใสและซ่อนไว้ทั้งหมด

step2.xml

<!-- TODO apply starting constraints -->

<!-- Constraints to apply at the start of the animation -->
<ConstraintSet android:id="@+id/start">
   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />

   <Constraint
           android:id="@+id/left_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="0.0"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />

   <Constraint
           android:id="@+id/right_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="0.0"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>

ใน ConstraintSet นี้ คุณต้องระบุ Constraint 1 รายการสำหรับดาวแต่ละดวง MotionLayout จะใช้ข้อจำกัดแต่ละข้อเมื่อเริ่มต้นภาพเคลื่อนไหว

มุมมองดาวแต่ละมุมมองจะอยู่ตรงกลางที่ด้านล่างของหน้าจอโดยใช้ข้อจำกัดเริ่มต้น สิ้นสุด และด้านล่าง ทั้ง 2 ดาว @id/left_star และ @id/right_star มีค่าอัลฟ่าเพิ่มเติมที่ทำให้มองไม่เห็น ซึ่งจะนำมาใช้เมื่อเริ่มภาพเคลื่อนไหว

ชุดข้อจำกัด start และ end กำหนดจุดเริ่มต้นและจุดสิ้นสุดของภาพเคลื่อนไหว ข้อจำกัดในการเริ่มต้น เช่น motion:layout_constraintStart_toStartOf จะจำกัดจุดเริ่มต้นของมุมมองไปยังจุดเริ่มต้นของอีกมุมมองหนึ่ง สิ่งนี้อาจทำให้สับสนในตอนแรก เนื่องจากชื่อ start ใช้กับทั้ง และ ทั้งคู่ในบริบทของข้อจำกัด เพื่อช่วยให้แยกแยะความแตกต่างได้ start ใน layout_constraintStart จะหมายถึง "start" ของมุมมอง ซึ่งแสดงด้านซ้ายเป็นภาษาจากซ้ายไปขวาและในภาษาจากขวาไปซ้าย ชุดข้อจำกัด start อ้างถึงจุดเริ่มต้นของภาพเคลื่อนไหว

กำหนด ConstraintSet ส่วนท้าย

  1. กำหนดจุดยึดสุดท้ายเพื่อใช้เชนเพื่อจัดตำแหน่งดาวทั้ง 3 ดวงให้อยู่ด้านล่าง @id/credits นอกจากนี้ยังกำหนดค่าสิ้นสุดของ alpha ของดาวด้านซ้ายและขวาเป็น 1.0 ด้วย

step2.xml

<!-- TODO apply ending constraints -->

<!-- Constraints to apply at the end of the animation -->
<ConstraintSet android:id="@+id/end">

   <Constraint
           android:id="@+id/left_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="1.0"
           motion:layout_constraintHorizontal_chainStyle="packed"
           motion:layout_constraintStart_toStartOf="parent"
           motion:layout_constraintEnd_toStartOf="@id/red_star"
           motion:layout_constraintTop_toBottomOf="@id/credits" />

   <Constraint
           android:id="@+id/red_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           motion:layout_constraintStart_toEndOf="@id/left_star"
           motion:layout_constraintEnd_toStartOf="@id/right_star"
           motion:layout_constraintTop_toBottomOf="@id/credits" />

   <Constraint
           android:id="@+id/right_star"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:alpha="1.0"
           motion:layout_constraintStart_toEndOf="@id/red_star"
           motion:layout_constraintEnd_toEndOf="parent"
           motion:layout_constraintTop_toBottomOf="@id/credits" />
</ConstraintSet>

ผลลัพธ์สุดท้ายคือมุมมองจะกระจายออกไปจากจุดศูนย์กลางเมื่อมีการเคลื่อนไหว

นอกจากนี้ เนื่องจากตั้งค่าพร็อพเพอร์ตี้ alpha บน @id/right_start และ @id/left_star ใน ConstraintSets ทั้ง 2 มุมมอง มุมมองทั้ง 2 จะจางลงเมื่อภาพเคลื่อนไหวดำเนินไป

ภาพเคลื่อนไหวตามการปัดของผู้ใช้

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

  1. แทนที่ TODO เพื่อเพิ่มแท็ก OnSwipe ด้วย <OnSwipe motion:touchAnchorId="@id/red_star" />

step2.xml

<!-- TODO add OnSwipe tag -->

<!-- A transition describes an animation via start and end state -->
<Transition
       motion:constraintSetStart="@+id/start"
       motion:constraintSetEnd="@+id/end">
   <!-- MotionLayout will track swipes relative to this view -->
   <OnSwipe motion:touchAnchorId="@id/red_star" />
</Transition>

OnSwipe มีแอตทริบิวต์ 2-3 รายการ แอตทริบิวต์ที่สำคัญที่สุดคือ touchAnchorId

  • touchAnchorId เป็นมุมมองที่ติดตามซึ่งเลื่อนไปตามการแตะ MotionLayout จะทำให้มุมมองนี้อยู่ห่างจากนิ้วที่เลื่อนเท่ากับระยะเท่าๆ กัน
  • touchAnchorSide กำหนดว่าควรติดตามข้อมูลพร็อพเพอร์ตี้ด้านใด ซึ่งเป็นสิ่งสำคัญสำหรับมุมมองที่ปรับขนาด ไปตามเส้นทางที่ซับซ้อน หรือมีด้านหนึ่งที่เคลื่อนเร็วกว่าอีกด้าน
  • dragDirection จะกำหนดทิศทางที่สำคัญสำหรับภาพเคลื่อนไหวนี้ (ขึ้น ลง ซ้าย หรือขวา)

เมื่อ MotionLayout ฟังเหตุการณ์การลาก ระบบจะลงทะเบียน Listener ในมุมมอง MotionLayout ไม่ใช่มุมมองที่ touchAnchorId ระบุ เมื่อผู้ใช้เริ่มท่าทางสัมผัสที่ใดก็ตามบนหน้าจอ MotionLayout จะรักษาระยะห่างระหว่างนิ้วผู้ใช้กับ touchAnchorSide ของการแสดงผล touchAnchorId ให้คงที่ ตัวอย่างเช่น หากแตะด้านที่ห่างจากจุดยึด 100dp จะทำให้ MotionLayout รักษาระยะห่างจากนิ้วด้านนั้นไว้ที่ 100dp สำหรับภาพเคลื่อนไหวทั้งหมด

ลองเลย

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

fefcdd690a0dcaec.gif

MotionLayout สามารถสร้างภาพเคลื่อนไหวระหว่างการออกแบบที่แตกต่างกันอย่างมากโดยใช้ฟีเจอร์จาก ConstraintLayout เพื่อสร้างเอฟเฟกต์ที่มีสีสัน

ในภาพเคลื่อนไหวนี้ มุมมองทั้ง 3 มุมมองจะมีตำแหน่งสัมพันธ์กับผู้ปกครองที่ด้านล่างของหน้าจอเพื่อเริ่มต้น ในตอนท้าย มุมมองทั้ง 3 จะมีตำแหน่งเทียบกับ @id/credits ในเชน

แม้ว่าเลย์เอาต์จะต่างกันมาก แต่ MotionLayout จะสร้างภาพเคลื่อนไหวแบบไหลระหว่างจุดเริ่มต้นและจุดสิ้นสุดได้

5. การแก้ไขเส้นทาง

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

ขั้นตอนที่ 1: สำรวจโค้ดที่มีอยู่

  1. เปิด layout/activity_step3.xml และ xml/step3.xml เพื่อดูเลย์เอาต์และฉากเคลื่อนไหวที่มีอยู่ ImageView และ TextView แสดงข้อความเกี่ยวกับดวงจันทร์และเครดิต
  2. เปิดไฟล์ฉากเคลื่อนไหว (xml/step3.xml) คุณจะเห็นว่ามีการกำหนด Transition ตั้งแต่ @id/start ถึง @id/end ภาพเคลื่อนไหวจะย้ายรูปภาพดวงจันทร์จากมุมซ้ายล่างของหน้าจอไปที่ด้านขวาล่างของหน้าจอโดยใช้ ConstraintSets 2 ตัว ข้อความเครดิตจะค่อยๆ ปรากฏขึ้นจาก alpha="0.0" เป็น alpha="1.0" ขณะที่ดวงจันทร์กำลังเคลื่อนที่
  3. เรียกใช้แอปทันที และเลือกขั้นตอนที่ 3 คุณจะเห็นว่าดวงจันทร์เคลื่อนที่ตามเส้นทางเชิงเส้น (หรือเส้นตรง) ตั้งแต่เริ่มต้นจนจบเมื่อคุณคลิกที่ดวงจันทร์

ขั้นตอนที่ 2: เปิดใช้การแก้ไขข้อบกพร่องของเส้นทาง

ก่อนเพิ่มเส้นโค้งในการเคลื่อนที่ของดวงจันทร์ คุณควรเปิดใช้การแก้ไขข้อบกพร่องของเส้นทางใน MotionLayout

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

  1. หากต้องการเปิดใช้เส้นทางการแก้ไขข้อบกพร่อง ให้เปิด layout/activity_step3.xml แล้วเพิ่ม motion:motionDebug="SHOW_PATH" ลงในแท็ก MotionLayout

activity_step3.xml

<!-- Add motion:motionDebug="SHOW_PATH" -->

<androidx.constraintlayout.motion.widget.MotionLayout
       ...
       motion:motionDebug="SHOW_PATH" >

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

23bbb604f456f65c.png

  • วงกลมแสดงตำแหน่งเริ่มต้นหรือจุดสิ้นสุดของมุมมองหนึ่ง
  • เส้นแสดงเส้นทางของมุมมองหนึ่ง
  • Diamonds แสดงถึง KeyPosition ที่เปลี่ยนแปลงเส้นทาง

ตัวอย่างเช่น ในภาพเคลื่อนไหวนี้ วงกลมตรงกลางคือตำแหน่งของข้อความเครดิต

ขั้นตอนที่ 3: แก้ไขเส้นทาง

ภาพเคลื่อนไหวทั้งหมดใน MotionLayout กำหนดโดยจุดเริ่มต้นและสิ้นสุด ConstraintSet ซึ่งกำหนดลักษณะของหน้าจอก่อนที่ภาพเคลื่อนไหวจะเริ่มต้นและหลังจากเล่นภาพเคลื่อนไหวเสร็จแล้ว โดยค่าเริ่มต้น MotionLayout จะพล็อตเส้นทางเชิงเส้น (เส้นตรง) ระหว่างตำแหน่งเริ่มต้นและสิ้นสุดของแต่ละมุมมองที่เปลี่ยนตำแหน่ง

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

  1. เปิด xml/step3.xml แล้วเพิ่ม KeyPosition ลงในฉาก แท็ก KeyPosition จะอยู่ภายในแท็ก Transition

eae4dae9a12d0410.png

step3.xml

<!-- TODO: Add KeyFrameSet and KeyPosition -->
<KeyFrameSet>
   <KeyPosition
           motion:framePosition="50"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.5"
   />
</KeyFrameSet>

KeyFrameSet เป็นองค์ประกอบย่อยของ Transition และเป็นชุดของ KeyFrames ทั้งหมด เช่น KeyPosition ที่ควรนำมาใช้ในช่วงการเปลี่ยนผ่าน

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

KeyPosition มีแอตทริบิวต์หลายรายการที่อธิบายวิธีแก้ไขเส้นทาง ซึ่งปัจจัยที่สำคัญที่สุดมีดังนี้

  • framePosition เป็นตัวเลขระหว่าง 0 ถึง 100 ซึ่งกำหนดเวลาที่ควรใช้ KeyPosition นี้ในภาพเคลื่อนไหว โดย 1 คือ 1% ถึงภาพเคลื่อนไหว และ 99 คือ 99% ผ่านภาพเคลื่อนไหว ดังนั้นหากค่าเป็น 50 ให้ใช้ค่าตรงกลาง
  • motionTarget คือข้อมูลพร็อพเพอร์ตี้ที่ KeyPosition นี้แก้ไขเส้นทาง
  • keyPositionType คือวิธีที่ KeyPosition นี้แก้ไขเส้นทาง ซึ่งจะเป็น parentRelative, pathRelative หรือ deltaRelative ก็ได้ (ตามที่อธิบายไว้ในขั้นตอนถัดไป)
  • percentX | percentY คือจำนวนเงินที่จะแก้ไขเส้นทางที่ framePosition (ค่าระหว่าง 0.0 ถึง 1.0 โดยมีค่าลบและค่ามากกว่า 1)

คุณสามารถมองภาพในลักษณะนี้: "ที่ framePosition ให้ปรับเส้นทางของ motionTarget โดยเลื่อนเส้นทางโดย percentX หรือ percentY ตามพิกัดที่กำหนดโดย keyPositionType"

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

ลองเลย

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

46b179c01801f19e.gif

ดวงจันทร์ตามแนวโค้งเนื่องจากวิ่งผ่าน KeyPosition ที่ระบุไว้ใน Transition

<KeyPosition
       motion:framePosition="50"
       motion:motionTarget="@id/moon"
       motion:keyPositionType="parentRelative"
       motion:percentY="0.5"
/>

คุณสามารถอ่าน KeyPosition นี้ว่า: "ที่ framePosition 50 (เมื่อภาพเคลื่อนไหวครึ่งหนึ่ง) ให้แก้ไขเส้นทางของ motionTarget @id/moon โดยเลื่อนเส้นทางไปทาง 50% Y (เลื่อนลงไปครึ่งหนึ่งของหน้าจอ) ตามพิกัดที่กำหนดโดย parentRelative (MotionLayout ทั้งหน้า)"

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

ถ้าดูดีๆ จะเห็นว่าข้อความเครดิตถูกจำกัดตามตำแหน่งของดวงจันทร์ ทำไมไม่ขยับในแนวตั้งด้วย

1c7cf779931e45cc.gif

<Constraint
       android:id="@id/credits"
       ...
       motion:layout_constraintBottom_toBottomOf="@id/moon"
       motion:layout_constraintTop_toTopOf="@id/moon"
/>

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

หากต้องการให้เครดิตย้ายไปตามดวงจันทร์ ให้เพิ่ม KeyPosition ลงในเครดิต หรือแก้ไขข้อจำกัดเริ่มต้นใน @id/credits

ในส่วนถัดไปเราจะเจาะลึกเกี่ยวกับ keyPositionType ประเภทต่างๆ ใน MotionLayout

6. ทำความเข้าใจ keyPositionType

ในขั้นตอนสุดท้าย คุณใช้ประเภท keyPosition ของ parentRelative เพื่อชดเชยเส้นทางเป็น 50% ของหน้าจอ แอตทริบิวต์ keyPositionType จะกำหนดวิธีการ MotionLayout จะแก้ไขเส้นทางตาม percentX หรือ percentY

<KeyFrameSet>
   <KeyPosition
           motion:framePosition="50"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.5"
   />
</KeyFrameSet>

keyPosition มี 3 ประเภทที่เป็นไปได้ ได้แก่ parentRelative, pathRelative และ deltaRelative การระบุประเภทจะเปลี่ยนระบบพิกัดที่ใช้คำนวณ percentX และ percentY

ระบบพิกัดคืออะไร

ระบบพิกัดเป็นวิธีระบุจุดในอวกาศ และมีประโยชน์ในการอธิบายตำแหน่งบนหน้าจอ

ระบบพิกัด MotionLayout เป็นระบบพิกัดคาร์ทีเซีย ซึ่งหมายความว่ามีแกน X และ Y ที่กำหนดด้วยเส้นตั้งฉาก 2 เส้น ความแตกต่างสำคัญระหว่างแกน X คือตำแหน่งบนหน้าจอที่แกน X ไป (แกน Y จะตั้งฉากกับแกน X เสมอ)

ระบบพิกัดทั้งหมดใน MotionLayout จะใช้ค่าระหว่าง 0.0 ถึง 1.0 ทั้งบนแกน X และ Y สามารถมีค่าติดลบและค่าที่มากกว่า 1.0 ได้ เช่น ค่า percentX ของ -2.0 หมายความว่า ให้ไปในทิศทางตรงกันข้ามกับแกน X 2 ครั้ง

ถ้าฟังดูแล้วคล้ายกับคลาสพีชคณิตมากกว่า ก็ลองดูรูปภาพด้านล่างนี้ได้เลย

พิกัดระดับบนสุด

a7b7568d46d9dec7.png

keyPositionType ของ parentRelative ใช้ระบบพิกัดเดียวกันกับหน้าจอ ซึ่งจะกำหนด (0, 0) ที่ด้านซ้ายบนของ MotionLayout ทั้งหมด และ (1, 1) ที่ด้านล่างขวา

คุณสามารถใช้ parentRelative ได้ทุกเมื่อที่ต้องการสร้างภาพเคลื่อนไหวที่เคลื่อนผ่าน MotionLayout ทั้งหมด เช่น รูปโค้งดวงจันทร์ในตัวอย่างนี้

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

พิกัดเดลต้าสัมพัทธ์

5680bf553627416c.png

Delta เป็นคำศัพท์ทางคณิตศาสตร์สำหรับการเปลี่ยนแปลง ดังนั้น deltaRelative จึงเป็นวิธีในการสอนว่า "การเปลี่ยนแปลงสัมพัทธ์" ในพิกัด deltaRelative(0,0) คือตำแหน่งเริ่มต้นของมุมมอง และ (1,1) คือตำแหน่งสิ้นสุด แกน X และ Y จะอยู่ในแนวเดียวกับหน้าจอ

แกน X จะอยู่ในแนวนอนเสมอบนหน้าจอ และแกน Y จะอยู่ในหน้าจอแนวตั้งเสมอ เมื่อเทียบกับ parentRelative ความแตกต่างที่สำคัญคือพิกัดจะอธิบายเฉพาะส่วนของหน้าจอที่มุมมองจะเคลื่อนไหว

deltaRelative เป็นระบบพิกัดที่ยอดเยี่ยมในการควบคุมการเคลื่อนที่ในแนวนอนหรือแนวตั้งโดยลำพัง ตัวอย่างเช่น คุณสามารถสร้างภาพเคลื่อนไหวที่มีการเคลื่อนไหวในแนวตั้ง (Y) เพียง 50% และสร้างภาพเคลื่อนไหวต่อไปในแนวนอน (X)

พิกัด pathRelative

f3aaadaac8b4a93f.png

ระบบพิกัดสุดท้ายใน MotionLayout คือ pathRelative ซึ่งจะค่อนข้างแตกต่างจากอีก 2 รูปเนื่องจากแกน X จะตามเส้นทางการเคลื่อนไหวจากจุดเริ่มต้นถึงจุดสิ้นสุด ดังนั้น (0,0) คือตำแหน่งเริ่มต้น และ (1,0) คือตำแหน่งสิ้นสุด

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

ดูเหมือนว่า pathRelative จะมีประโยชน์มากในบางเรื่อง

  • การเร่ง ลดความเร็ว หรือหยุดมุมมองระหว่างส่วนหนึ่งของภาพเคลื่อนไหว เนื่องจากมิติข้อมูล X จะตรงกับเส้นทางที่มุมมองใช้ทุกประการเสมอ คุณจึงสามารถใช้ pathRelative KeyPosition เพื่อเปลี่ยนแปลงframePositionจุดใดจุดหนึ่งในเส้นทางดังกล่าว ดังนั้น KeyPosition ที่ framePosition="50" ที่มี percentX="0.1" จะทำให้ภาพเคลื่อนไหวใช้เวลา 50% ในการเดินทาง 10% แรกของการเคลื่อนไหว
  • การเพิ่มเส้นโค้งเล็กๆ น้อยๆ ลงในเส้นทาง เนื่องจากมิติ Y จะตั้งฉากกับการเคลื่อนไหวเสมอ การเปลี่ยน Y จะเปลี่ยนเส้นทางเป็นเส้นโค้งเมื่อเทียบกับการเคลื่อนไหวโดยรวม
  • การเพิ่มมิติข้อมูลที่ 2 เมื่อ deltaRelative ไม่ทำงาน สำหรับการเคลื่อนไหวแนวตั้งและแนวนอนทั้งหมด deltaRelative จะสร้างมิติข้อมูลที่เป็นประโยชน์เพียง 1 รายการ อย่างไรก็ตาม pathRelative จะสร้างพิกัด X และ Y ที่ใช้ได้เสมอ

ในขั้นตอนถัดไป คุณจะได้เรียนรู้วิธีสร้างเส้นทางที่ซับซ้อนยิ่งขึ้นโดยใช้ KeyPosition มากกว่า 1 รายการ

7. การสร้างเส้นทางที่ซับซ้อน

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

แก้ไขเส้นทางที่มีองค์ประกอบ KeyPosition หลายรายการ

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

  1. เปิด xml/step4.xml คุณจะเห็นว่ามีการดูเหมือนกับและมีKeyFrameที่เพิ่มไว้ในขั้นตอนสุดท้าย
  2. หากต้องการปัดเศษของเส้นโค้ง เพิ่ม KeyPositions อีก 2 รายการลงในเส้นทางของ @id/moon โดย 1 เส้นก่อนที่จะถึงจุดสูงสุด และอีก 1 เส้นหลังจากนั้น

500b5ac2db48ef87.png

step4.xml

<!-- TODO: Add two more KeyPositions to the KeyFrameSet here -->
<KeyPosition
       motion:framePosition="25"
       motion:motionTarget="@id/moon"
       motion:keyPositionType="parentRelative"
       motion:percentY="0.6"
/>
<KeyPosition
       motion:framePosition="75"
       motion:motionTarget="@id/moon"
       motion:keyPositionType="parentRelative"
       motion:percentY="0.6"
/>

ระบบจะใช้ KeyPositions เป็น 25% และ 75% ของภาพเคลื่อนไหวทั้งหมด และทำให้ @id/moon เคลื่อนที่ไปตามเส้นทางที่ห่างจากด้านบนของหน้าจอ 60% เมื่อรวมกับ KeyPosition ที่มีอยู่ที่ 50% จะทำให้เกิดเส้นโค้งที่ราบเรียบขึ้นเพื่อให้ดวงจันทร์เคลื่อนที่ตามมา

ใน MotionLayout คุณจะเพิ่ม KeyPositions ได้มากเท่าที่ต้องการเพื่อให้ได้เส้นทางการเคลื่อนไหวที่ต้องการ MotionLayout จะใช้แต่ละ KeyPosition ตาม framePosition ที่ระบุ และหาวิธีสร้างภาพเคลื่อนไหวที่ลื่นไหลตลอดทั้งKeyPositions

ลองเลย

  1. เรียกใช้แอปอีกครั้ง ไปที่ขั้นตอนที่ 4 เพื่อดูการทำงานของภาพเคลื่อนไหว เมื่อคุณคลิกดวงจันทร์ ดวงจันทร์จะตามเส้นทางตั้งแต่ต้นจนจบ โดยจะผ่าน KeyPosition แต่ละรายการที่ระบุไว้ใน KeyFrameSet

สำรวจด้วยตนเอง

ก่อนจะเปลี่ยนไปใช้ KeyFrame ประเภทอื่นๆ ให้ลองเพิ่ม KeyPositions ใน KeyFrameSet เพื่อดูว่าสร้างเอฟเฟกต์แบบใดได้บ้างโดยใช้ KeyPosition

นี่คือตัวอย่างหนึ่งที่แสดงวิธีสร้างเส้นทางที่ซับซ้อนซึ่งเคลื่อนที่กลับไปกลับมาระหว่างการสร้างภาพเคลื่อนไหว

cd9faaffde3dfef.png

step4.xml

<!-- Complex paths example: Dancing moon -->
<KeyFrameSet>
   <KeyPosition
           motion:framePosition="25"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.6"
           motion:percentX="0.1"
   />
   <KeyPosition
           motion:framePosition="50"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.5"
           motion:percentX="0.3"
   />
   <KeyPosition
           motion:framePosition="75"
           motion:motionTarget="@id/moon"
           motion:keyPositionType="parentRelative"
           motion:percentY="0.6"
           motion:percentX="0.1"
   />
</KeyFrameSet>

เมื่อคุณสำรวจ KeyPosition เสร็จแล้ว คุณจะไปยัง KeyFrames ประเภทอื่นๆ ในขั้นตอนถัดไป

8. การเปลี่ยนแอตทริบิวต์ระหว่างการเคลื่อนไหว

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

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

ขั้นตอนที่ 1: ปรับขนาดและหมุนด้วย KeyAttribute

  1. เปิด xml/step5.xml ซึ่งมีภาพเคลื่อนไหวเดียวกันกับที่คุณสร้างในขั้นตอนสุดท้าย หน้าจอนี้ใช้ภาพพื้นที่อื่นเป็นพื้นหลังเพื่อความหลากหลาย
  2. หากต้องการให้ดวงจันทร์ขยายขนาดและหมุน ให้เพิ่มแท็ก KeyAttribute 2 แท็กใน KeyFrameSet ที่ keyFrame="50" และ keyFrame="100"

bbae524a2898569.png

step5.xml

<!-- TODO: Add KeyAttributes to rotate and resize @id/moon -->

<KeyAttribute
       motion:framePosition="50"
       motion:motionTarget="@id/moon"
       android:scaleY="2.0"
       android:scaleX="2.0"
       android:rotation="-360"
/>
<KeyAttribute
       motion:framePosition="100"
       motion:motionTarget="@id/moon"
       android:rotation="-720"
/>

KeyAttributes นี้จะใช้ที่ 50% และ 100% ของภาพเคลื่อนไหว KeyAttribute แรกที่ 50% จะปรากฏที่ด้านบนของแนวโค้ง และทำให้มุมมองมีขนาดใหญ่ขึ้นเป็น 2 เท่า รวมทั้งหมุน -360 องศา (หรือวงกลมเต็ม 1 วง) KeyAttribute รอบที่ 2 จะหมุนรอบที่ 2 จนเสร็จเป็น -720 องศา (วงกลมเต็ม 2 วง) และย่อขนาดกลับเป็นปกติเนื่องจากค่า scaleX และ scaleY มีค่าเริ่มต้นเป็น 1.0

เช่นเดียวกับ KeyPosition KeyAttribute จะใช้ framePosition และ motionTarget เพื่อระบุว่าเมื่อใดจะใช้ KeyFrame และมุมมองใดที่จะแก้ไข MotionLayout จะหาค่าประมาณระหว่าง KeyPositions เพื่อสร้างภาพเคลื่อนไหวแบบไหล

KeyAttributes รองรับแอตทริบิวต์ที่สามารถใช้กับข้อมูลพร็อพเพอร์ตี้ทั้งหมด โดยรองรับการเปลี่ยนแปลงแอตทริบิวต์พื้นฐาน เช่น visibility, alpha หรือ elevation คุณยังสามารถเปลี่ยนการหมุนได้เช่นเดียวกับที่คุณทำที่นี่ หมุนแบบ 3 มิติด้วย rotateX และ rotateY ปรับขนาดด้วย scaleX และ scaleY หรือแปลตำแหน่งมุมมองเป็น X, Y หรือ Z

ขั้นตอนที่ 2: ชะลอการแสดงเครดิต

เป้าหมายอย่างหนึ่งของขั้นตอนนี้คือการอัปเดตภาพเคลื่อนไหวเพื่อไม่ให้ข้อความเครดิตปรากฏจนกว่าภาพเคลื่อนไหวจะเสร็จสมบูรณ์เป็นส่วนใหญ่

  1. หากต้องการชะลอการแสดงเครดิต ให้ระบุ KeyAttribute อีก 1 รายการเพื่อให้ alpha มีค่าเป็น 0 จนถึง keyPosition="85" MotionLayout จะยังคงเปลี่ยนจาก 0 เป็น 100 Alpha ได้อย่างราบรื่น แต่จะเปลี่ยนในช่วง 15% สุดท้ายของภาพเคลื่อนไหว

step5.xml

<!-- TODO: Add KeyAttribute to delay the appearance of @id/credits -->

<KeyAttribute
       motion:framePosition="85"
       motion:motionTarget="@id/credits"
       android:alpha="0.0"
/>

KeyAttribute นี้ทำให้ alpha ของ @id/credits เท่ากับ 0.0 สำหรับ 85% แรกของภาพเคลื่อนไหว และเนื่องจากเริ่มที่อัลฟ่าเป็น 0 ดังนั้นจะมองไม่เห็นใน 85% แรกของภาพเคลื่อนไหว

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

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

ลองเลย

  1. เรียกใช้แอปอีกครั้ง แล้วไปที่ขั้นตอนที่ 5 เพื่อดูการทำงานของภาพเคลื่อนไหว เมื่อคุณคลิกดวงจันทร์ ดวงจันทร์จะขับไปตามเส้นทางตั้งแต่ต้นจนจบ โดยจะผ่าน KeyAttribute แต่ละจุดที่ระบุไว้ใน KeyFrameSet

2f4bfdd681c1fa98.gif

เนื่องจากคุณหมุนดวงจันทร์เต็มดวง 2 วง คราวนี้จึงจะหมุนกลับ 2 รอบ และเครดิตจะหน่วงเวลาลักษณะที่ปรากฏจนกว่าภาพเคลื่อนไหวจะเสร็จสมบูรณ์

สำรวจด้วยตนเอง

ก่อนที่จะดำเนินการต่อไปยังประเภท KeyFrame ขั้นสุดท้าย ให้ลองแก้ไขแอตทริบิวต์มาตรฐานอื่นๆ ใน KeyAttributes เช่น ลองเปลี่ยน rotation เป็น rotationX เพื่อดูว่าระบบสร้างภาพเคลื่อนไหวแบบใด

รายการแอตทริบิวต์มาตรฐานที่คุณลองใช้ได้มีดังนี้

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

9. การเปลี่ยนแอตทริบิวต์ที่กำหนดเอง

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

คุณสามารถใช้ CustomAttribute เพื่อตั้งค่าใดๆ ที่มีตัวตั้งค่า เช่น คุณตั้งค่า backgroundColor ในมุมมองโดยใช้ CustomAttribute ได้ MotionLayout จะใช้การสะท้อนเพื่อค้นหาตัวตั้งค่า แล้วเรียกใช้ซ้ำๆ เพื่อให้มุมมองเคลื่อนไหว

ในขั้นตอนนี้ คุณจะใช้ CustomAttribute ในการตั้งค่าแอตทริบิวต์ colorFilter บนดวงจันทร์เพื่อสร้างภาพเคลื่อนไหวที่แสดงด้านล่าง

5fb6792126a09fda.gif

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

  1. ในการเริ่มต้นใช้งาน ให้เปิด xml/step6.xml ซึ่งมีภาพเคลื่อนไหวเดียวกันกับที่คุณสร้างไว้ในขั้นตอนสุดท้าย
  2. หากต้องการทำให้ดวงจันทร์เปลี่ยนสี ให้เพิ่ม KeyAttribute จำนวน 2 รายการที่มี CustomAttribute ใน KeyFrameSet ที่ keyFrame="0", keyFrame="50" และ keyFrame="100".

214699d5fdd956da.png

step6.xml

<!-- TODO: Add Custom attributes here -->
<KeyAttribute
       motion:framePosition="0"
       motion:motionTarget="@id/moon">
   <CustomAttribute
           motion:attributeName="colorFilter"
           motion:customColorValue="#FFFFFF"
   />
</KeyAttribute>
<KeyAttribute
       motion:framePosition="50"
       motion:motionTarget="@id/moon">
   <CustomAttribute
           motion:attributeName="colorFilter"
           motion:customColorValue="#FFB612"
   />
</KeyAttribute>
<KeyAttribute
       motion:framePosition="100"
       motion:motionTarget="@id/moon">
   <CustomAttribute
           motion:attributeName="colorFilter"
           motion:customColorValue="#FFFFFF"
   />
</KeyAttribute>

คุณเพิ่ม CustomAttribute ภายใน KeyAttribute ระบบจะใช้ CustomAttribute ตาม framePosition ที่ระบุโดย KeyAttribute

ภายใน CustomAttribute คุณต้องระบุ attributeName และค่า 1 ค่าที่จะตั้งค่า

  • motion:attributeName คือชื่อของตัวตั้งค่าที่แอตทริบิวต์ที่กำหนดเองนี้จะเรียกใช้ ในตัวอย่างนี้ setColorFilter ใน Drawable จะถูกเรียก
  • motion:custom*Value คือค่าที่กำหนดเองสำหรับประเภทที่ระบุไว้ในชื่อ ในตัวอย่างนี้ค่าที่กำหนดเองจะเป็นสีที่ระบุ

ค่าที่กำหนดเองมีประเภทต่อไปนี้ได้

  • สี
  • จำนวนเต็ม
  • ทศนิยม
  • สตริง
  • มิติข้อมูล
  • บูลีน

เมื่อใช้ API นี้ MotionLayout สามารถทำให้ทุกอย่างที่เป็นตัวตั้งค่าภาพเคลื่อนไหวได้

ลองเลย

  1. เรียกใช้แอปอีกครั้ง แล้วไปที่ขั้นตอนที่ 6 เพื่อดูการทำงานของภาพเคลื่อนไหว เมื่อคุณคลิกดวงจันทร์ ดวงจันทร์จะขับไปตามเส้นทางตั้งแต่ต้นจนจบ โดยจะผ่าน KeyAttribute แต่ละจุดที่ระบุไว้ใน KeyFrameSet

5fb6792126a09fda.gif

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

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

10. ลากเหตุการณ์และเส้นทางที่ซับซ้อน

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

การควบคุมภาพเคลื่อนไหวที่มีเส้นทางที่ซับซ้อนโดยใช้ OnSwipe เช่น ภาพเคลื่อนไหวของดวงจันทร์ที่สร้างขึ้นในช่วง 2-3 ขั้นตอนที่แล้วต้องเข้าใจวิธีการทำงานของ OnSwipe

ขั้นตอนที่ 1: สำรวจลักษณะการปัดเมื่อ

  1. เปิด xml/step7.xml และค้นหาการประกาศ OnSwipe ที่มีอยู่

step7.xml

<!-- Fix OnSwipe by changing touchAnchorSide 

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="bottom"
/>
  1. เรียกใช้แอปในอุปกรณ์ และไปที่ขั้นตอนที่ 7 ดูว่าคุณสามารถสร้างภาพเคลื่อนไหวที่ราบรื่นด้วยการลากดวงจันทร์ไปตามเส้นทางโค้งหรือไม่

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

ed96e3674854a548.gif

ในการทำความเข้าใจข้อบกพร่อง ให้พิจารณาสิ่งที่เกิดขึ้นเมื่อผู้ใช้แตะที่ส่วนล่างของส่วนโค้ง เนื่องจากแท็ก OnSwipe มี motion:touchAnchorSide="bottom" MotionLayout จะพยายามทำให้ระยะห่างระหว่างนิ้วกับด้านล่างของมุมมองคงที่ตลอดภาพเคลื่อนไหว

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

56cd575c5c77eddd.png

ขั้นตอนที่ 2: ใช้ด้านขวา

เพื่อหลีกเลี่ยงข้อบกพร่องเช่นนี้ สิ่งสำคัญคือการเลือก touchAnchorId และ touchAnchorSide ที่มีความคืบหน้าในทิศทางเดียวตลอดระยะเวลาของภาพเคลื่อนไหวทั้งหมดเสมอ

ในภาพเคลื่อนไหวนี้ ทั้งด้านrightและด้านleftของดวงจันทร์จะเคลื่อนผ่านหน้าจอไปในทิศทางเดียว

อย่างไรก็ตาม ทั้ง bottom และ top จะกลับทิศทาง เมื่อ OnSwipe พยายามติดตามตัว จะสับสนเมื่อทิศทางเปลี่ยนไป

  1. หากต้องการให้ภาพเคลื่อนไหวนี้เกิดขึ้นหลังเหตุการณ์การแตะ ให้เปลี่ยน touchAnchorSide เป็น right

step7.xml

<!-- Fix OnSwipe by changing touchAnchorSide 

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="right"
/>

ขั้นตอนที่ 3: ใช้ DragDirection

คุณยังรวม dragDirection กับ touchAnchorSide เพื่อทำให้แทร็กด้านข้างมีทิศทางต่างจากปกติได้ด้วย ยังเป็นสิ่งสำคัญที่ touchAnchorSide จะเดินหน้าไปในทิศทางเดียวเท่านั้น แต่คุณบอก MotionLayout ได้ว่าจะต้องติดตามทิศทางใด ตัวอย่างเช่น คุณสามารถเก็บ touchAnchorSide="bottom" ไว้ แต่เพิ่ม dragDirection="dragRight" ซึ่งจะทำให้ MotionLayout ติดตามตำแหน่งด้านล่างของมุมมอง แต่จะพิจารณาเฉพาะตำแหน่งเมื่อเลื่อนไปทางขวาเท่านั้น (ไม่พิจารณาการเคลื่อนไหวในแนวตั้ง) ดังนั้นแม้ว่าด้านล่างขึ้นและลง แต่จะยังเคลื่อนไหวอย่างถูกต้องด้วย OnSwipe

  1. อัปเดต OnSwipe เพื่อติดตามการเคลื่อนที่ของดวงจันทร์อย่างถูกต้อง

step7.xml

<!-- Using dragDirection to control the direction of drag tracking 

<OnSwipe
       motion:touchAnchorId="@id/moon"
       motion:touchAnchorSide="bottom"
       motion:dragDirection="dragRight"
/>

ลองเลย

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

5458dff382261427.gif

11. ภาพเคลื่อนไหวที่ทำงานอยู่เมื่อใช้โค้ด

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

ขั้นตอนที่ 1: สำรวจโค้ดที่มีอยู่

  1. เปิด layout/activity_step8.xml เพื่อเริ่มต้นใช้งาน
  2. ใน layout/activity_step8.xml คุณเห็นว่ามีการสร้าง CoordinatorLayout และ AppBarLayout ที่ใช้งานได้แล้ว

activity_step8.xml

<androidx.coordinatorlayout.widget.CoordinatorLayout
       ...>
   <com.google.android.material.appbar.AppBarLayout
           android:id="@+id/appbar_layout"
           android:layout_width="match_parent"
           android:layout_height="180dp">
       <androidx.constraintlayout.motion.widget.MotionLayout
               android:id="@+id/motion_layout"
               ... >
           ...
       </androidx.constraintlayout.motion.widget.MotionLayout>
   </com.google.android.material.appbar.AppBarLayout>
  
   <androidx.core.widget.NestedScrollView
           ...
           motion:layout_behavior="@string/appbar_scrolling_view_behavior" >
           ...
   </androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

เลย์เอาต์นี้ใช้ CoordinatorLayout เพื่อแชร์ข้อมูลการเลื่อนระหว่าง NestedScrollView และ AppBarLayout ดังนั้น เมื่อ NestedScrollView เลื่อนขึ้นก็จะบอก AppBarLayout เกี่ยวกับการเปลี่ยนแปลง นี่คือวิธีการใช้งานการยุบแถบเครื่องมือแบบนี้ใน Android โดยที่การเลื่อนข้อความจะเป็นแบบ "ประสานกัน" ด้วยส่วนหัวการย่อ

ฉากเคลื่อนไหวที่ @id/motion_layout ชี้ไปจะคล้ายกับฉากเคลื่อนไหวในขั้นตอนสุดท้าย อย่างไรก็ตาม ระบบนำการประกาศ OnSwipe ออกเพื่อให้ทำงานร่วมกับ CoordinatorLayout ได้

  1. เรียกใช้แอป แล้วไปที่ขั้นตอนที่ 8 คุณจะเห็นว่าเมื่อคุณเลื่อนข้อความ ดวงจันทร์ไม่เคลื่อนที่

ขั้นตอนที่ 2: ทำให้ MotionLayout เลื่อน

  1. หากต้องการทำให้มุมมอง MotionLayout เลื่อนทันทีที่ NestedScrollView เลื่อน ให้เพิ่ม motion:minHeight และ motion:layout_scrollFlags ลงใน MotionLayout

activity_step8.xml

<!-- Add minHeight and layout_scrollFlags to the MotionLayout -->

<androidx.constraintlayout.motion.widget.MotionLayout
       android:id="@+id/motion_layout"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       motion:layoutDescription="@xml/step8"
       motion:motionDebug="SHOW_PATH"
       android:minHeight="80dp"
       motion:layout_scrollFlags="scroll|enterAlways|snap|exitUntilCollapsed"  >
  1. เรียกใช้แอปอีกครั้ง แล้วไปที่ขั้นตอนที่ 8 คุณจะเห็นว่า MotionLayout ยุบลงเมื่อคุณเลื่อนขึ้น แต่ภาพเคลื่อนไหวจะยังไม่คืบหน้าตามลักษณะการเลื่อน

ขั้นตอนที่ 3: ย้ายการเคลื่อนไหวด้วยโค้ด

  1. เปิด Step8Activity.kt แก้ไขฟังก์ชัน coordinateMotion() เพื่อแจ้ง MotionLayout เกี่ยวกับการเปลี่ยนแปลงตำแหน่งการเลื่อน

Step8Activity.kt

// TODO: set progress of MotionLayout based on an AppBarLayout.OnOffsetChangedListener

private fun coordinateMotion() {
    val appBarLayout: AppBarLayout = findViewById(R.id.appbar_layout)
    val motionLayout: MotionLayout = findViewById(R.id.motion_layout)

    val listener = AppBarLayout.OnOffsetChangedListener { unused, verticalOffset ->
        val seekPosition = -verticalOffset / appBarLayout.totalScrollRange.toFloat()
        motionLayout.progress = seekPosition
    }

    appBarLayout.addOnOffsetChangedListener(listener)
}

โค้ดนี้จะบันทึก OnOffsetChangedListener ซึ่งจะเรียกใช้ทุกครั้งที่ผู้ใช้เลื่อนด้วยค่าออฟเซ็ตการเลื่อนปัจจุบัน

MotionLayout รองรับการกรอการเปลี่ยนด้วยการตั้งค่าพร็อพเพอร์ตี้ความคืบหน้า หากต้องการแปลงความคืบหน้าระหว่าง verticalOffset กับเปอร์เซ็นต์ ให้หารด้วยช่วงการเลื่อนทั้งหมด

ลองเลย

  1. ทำให้แอปใช้งานได้อีกครั้งและเรียกใช้ภาพเคลื่อนไหวขั้นตอนที่ 8 คุณจะเห็นว่า MotionLayout จะเลื่อนภาพเคลื่อนไหวตามตำแหน่งการเลื่อน

ee5ce4d9e33a59ca.gif

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

12. ขอแสดงความยินดี

Codelab นี้ครอบคลุม API พื้นฐานของ MotionLayout

ดูตัวอย่างเพิ่มเติมของ MotionLayout ในทางปฏิบัติได้ที่ตัวอย่างอย่างเป็นทางการ และอย่าลืมอ่านเอกสารประกอบ

ดูข้อมูลเพิ่มเติม

MotionLayout รองรับฟีเจอร์เพิ่มเติมที่ไม่ครอบคลุมใน Codelab เช่น KeyCycle, ซึ่งให้คุณควบคุมเส้นทางหรือแอตทริบิวต์ที่มีรอบการทำงานซ้ำได้ และ KeyTimeCycle, ที่ช่วยให้คุณเคลื่อนไหวตามเวลาของนาฬิกาได้ ดูตัวอย่างของแต่ละวิธี

สําหรับลิงก์ไปยัง Codelab อื่นๆ ในหลักสูตรนี้ โปรดดูหน้า Landing Page สำหรับ Android ขั้นสูงใน Kotlin