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

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

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

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

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

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

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

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

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

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

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

  • กำหนดภาพเคลื่อนไหวด้วย 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 ที่ระบุทั้งข้อจำกัดเริ่มต้นและสิ้นสุดของการเปลี่ยน

มาดูแต่ละข้อกัน โดยเริ่มจาก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: แปลงเป็น Motion Layout

หากต้องการสร้างภาพเคลื่อนไหวโดยใช้ 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 รายการในเครื่องมือแก้ไขการเคลื่อนไหวมีดังนี้

  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. เลือก start ConstraintSet ในแผงภาพรวม

6e57661ed358b860.png

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

f9564c574b86ea8.gif

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

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เดียว

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

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

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

346e1248639b6f1e.png

  1. ทำตามขั้นตอนเดียวกับที่เคยทำเพื่อเพิ่ม Constraint สำหรับ red_star ใน end ConstraintSet
  2. หากต้องการใช้เครื่องมือแก้ไขการเคลื่อนไหวเพื่อทำขั้นตอนนี้ให้เสร็จ ให้เพิ่มข้อจำกัดลงใน 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 รายการด้วย การเปลี่ยนฉากจะกำหนดทุกส่วนของภาพเคลื่อนไหวตั้งแต่ต้นจนจบ

การเปลี่ยนฉากต้องระบุ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

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

  1. เปิดโปรแกรมแก้ไขการเคลื่อนไหว แล้วเลือกทรานซิชันโดยคลิกลูกศรระหว่าง start กับ end ในแผงภาพรวม

1dc541ae8c43b250.png

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

a0fd2593384dfb36.png

ขั้นตอนที่ 6: เพิ่มตัวแฮนเดิลการคลิก

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

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

b6f94b344ce65290.png

  1. คลิก 699f7ae04024ccf6.png Create click or swipe handler ในแถบเครื่องมือสำหรับแผงภาพรวม ซึ่งจะเพิ่มตัวแฮนเดิลที่จะเริ่มการเปลี่ยนฉาก
  2. เลือก Click Handler จากป๊อปอัป

ccf92d06335105fe.png

  1. เปลี่ยนดูเพื่อคลิกเป็น red_star

b0d3f0c970604f01.png

  1. คลิกเพิ่ม ตัวแฮนเดิลการคลิกจะแสดงด้วยจุดเล็กๆ ในการเปลี่ยนในเครื่องมือแก้ไขการเคลื่อนไหว

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 หนึ่งรายการซึ่งชี้ไปยัง 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และTransition

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

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

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 จะจำกัดจุดเริ่มต้นของ View ให้เป็นจุดเริ่มต้นของ View อื่น ซึ่งอาจทำให้สับสนในตอนแรกเนื่องจากมีการใช้ชื่อ start ทั้งสำหรับ และ และทั้ง 2 อย่างนี้ใช้ในบริบทของข้อจำกัด start ใน layout_constraintStart หมายถึง "จุดเริ่มต้น" ของมุมมอง ซึ่งคือด้านซ้ายในภาษาที่อ่านจากซ้ายไปขวา และด้านขวาในภาษาที่อ่านจากขวาไปซ้าย ชุดข้อจำกัด 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 มุมมองจะค่อยๆ ปรากฏขึ้นเมื่อภาพเคลื่อนไหวคืบหน้า

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

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

  • วงกลมแสดงตำแหน่งเริ่มต้นหรือสิ้นสุดของการดู 1 ครั้ง
  • เส้นแสดงเส้นทางของมุมมองหนึ่ง
  • เพชรแสดงถึง 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 คำนวณเส้นทางของดวงจันทร์ระหว่างจุดเริ่มต้นและจุดสิ้นสุด 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ที่อยู่กึ่งกลางหน้าจอ 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 เส้น ความแตกต่างที่สำคัญระหว่าง 2 แกนนี้คือตำแหน่งที่แกน X อยู่บนหน้าจอ (แกน Y จะตั้งฉากกับแกน X เสมอ)

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

หากฟังดูเหมือนวิชาพีชคณิตมากเกินไป โปรดดูรูปภาพด้านล่าง

parentRelative coordinates

a7b7568d46d9dec7.png

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

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

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

พิกัด deltaRelative

5680bf553627416c.png

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

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

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

pathRelative coordinates

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 จะสร้างมิติที่มีประโยชน์เพียงมิติเดียว อย่างไรก็ตาม 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

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

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

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

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

  1. หากต้องการเลื่อนเวลาการแสดงเครดิต ให้กำหนด KeyAttribute อีกรายการที่รับประกันว่า alpha จะยังคงเป็น 0 จนกว่าจะถึง keyPosition="85" MotionLayout จะยังคงเปลี่ยนจากอัลฟ่า 0 เป็น 100 อย่างราบรื่น แต่จะเปลี่ยนในช่วง 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 ใช้เพื่อตั้งค่าใดก็ได้ที่มี Setter เช่น คุณตั้งค่า backgroundColor ใน View ได้โดยใช้ 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 จะเปลี่ยนเส้นทางของดวงจันทร์จากเส้นตรงเป็นเส้นโค้งที่ซับซ้อน โดยเพิ่มการตีลังกาสองรอบ การปรับขนาด และการเปลี่ยนสีในช่วงกลางของภาพเคลื่อนไหว

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

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

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

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

ขั้นตอนที่ 1: สำรวจลักษณะการทำงานของ OnSwipe

  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 รองรับฟีเจอร์อื่นๆ อีกมากมายที่ไม่ได้กล่าวถึงในโค้ดแล็บนี้ เช่น KeyCycle, ซึ่งช่วยให้คุณควบคุมเส้นทางหรือแอตทริบิวต์ด้วยรอบที่ทำซ้ำ และ KeyTimeCycle, ซึ่งช่วยให้คุณสร้างภาพเคลื่อนไหวตามเวลาของนาฬิกาได้ ดูตัวอย่างของแต่ละรายการได้ในตัวอย่าง

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