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คุณระบุมุมมองทั้งหมดที่จะเคลื่อนไหวภายในแท็กMotionLayoutMotionScene,ซึ่งเป็นไฟล์ XML ที่อธิบายภาพเคลื่อนไหวสำหรับMotionLayout.Transition,ซึ่งเป็นส่วนหนึ่งของMotionSceneที่ระบุระยะเวลาของภาพเคลื่อนไหว ทริกเกอร์ และวิธีย้ายมุมมองConstraintSetที่ระบุทั้งข้อจำกัดเริ่มต้นและสิ้นสุดของการเปลี่ยน
มาดูแต่ละข้อกัน โดยเริ่มจากMotionLayout
ขั้นตอนที่ 1: สำรวจโค้ดที่มีอยู่
MotionLayout เป็นคลาสย่อยของ ConstraintLayout จึงรองรับฟีเจอร์เดียวกันทั้งหมดขณะเพิ่มภาพเคลื่อนไหว หากต้องการใช้ MotionLayout ให้เพิ่มมุมมอง MotionLayout ในตำแหน่งที่คุณจะใช้ ConstraintLayout.
- ใน
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
หากต้องการให้เลย์เอาต์ใช้ฉากเคลื่อนไหว เลย์เอาต์จะต้องชี้ไปที่ฉากนั้น
- โดยเปิดพื้นที่ออกแบบ ใน Android Studio 4.0 คุณจะเปิดพื้นผิวการออกแบบได้โดยใช้ไอคอนแยกหรือไอคอนออกแบบที่ด้านขวาบนเมื่อดูไฟล์ XML ของเลย์เอาต์

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

ซึ่งจะแทนที่แท็ก 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

องค์ประกอบ UI ใหม่ 3 รายการในเครื่องมือแก้ไขการเคลื่อนไหวมีดังนี้
- ภาพรวม - นี่คือการเลือกแบบโมดัลที่ช่วยให้คุณเลือกส่วนต่างๆ ของภาพเคลื่อนไหวได้ ในรูปภาพนี้ เราเลือก
startConstraintSetนอกจากนี้ คุณยังเลือกการเปลี่ยนระหว่างstartกับendได้โดยคลิกลูกศรระหว่างคลิป - ส่วน - ใต้ภาพรวมคือหน้าต่างส่วนที่จะเปลี่ยนไปตามรายการภาพรวมที่เลือกในปัจจุบัน ในรูปภาพนี้ ข้อมูล
startConstraintSetจะแสดงในหน้าต่างการเลือก - แอตทริบิวต์ - แผงแอตทริบิวต์จะแสดงและให้คุณแก้ไขแอตทริบิวต์ของรายการที่เลือกในปัจจุบันจากหน้าต่างภาพรวมหรือหน้าต่างการเลือก ในรูปภาพนี้ แสดงแอตทริบิวต์สำหรับ
startConstraintSet
ขั้นตอนที่ 3: กำหนดข้อจำกัดเริ่มต้นและสิ้นสุด
คุณกำหนดภาพเคลื่อนไหวทั้งหมดได้ในแง่ของจุดเริ่มต้นและจุดสิ้นสุด จุดเริ่มต้นอธิบายลักษณะของหน้าจอก่อนภาพเคลื่อนไหว และจุดสิ้นสุดอธิบายลักษณะของหน้าจอหลังจากที่ภาพเคลื่อนไหวเสร็จสมบูรณ์ MotionLayout มีหน้าที่ในการหาวิธีสร้างภาพเคลื่อนไหวระหว่างสถานะเริ่มต้นและสถานะสิ้นสุด (เมื่อเวลาผ่านไป)
MotionScene ใช้แท็ก ConstraintSet เพื่อกำหนดสถานะเริ่มต้นและสถานะสิ้นสุด ConstraintSet คือชุดข้อจำกัดที่ใช้กับมุมมองได้ ซึ่งรวมถึงความกว้าง ความสูง และConstraintLayoutข้อจำกัด นอกจากนี้ ยังรวมถึงแอตทริบิวต์บางอย่าง เช่น alpha โดยจะไม่มีข้อมูลยอดดู แต่จะมีเพียงข้อจำกัดของยอดดูเหล่านั้น
ข้อจำกัดที่ระบุใน ConstraintSet จะลบล้างข้อจำกัดที่ระบุในไฟล์เลย์เอาต์ หากคุณกำหนดข้อจำกัดทั้งในเลย์เอาต์และ MotionScene ระบบจะใช้เฉพาะข้อจำกัดใน MotionScene
ในขั้นตอนนี้ คุณจะจำกัดมุมมองดาวให้เริ่มที่ด้านบนของหน้าจอและสิ้นสุดที่ด้านล่างของหน้าจอ
คุณทำขั้นตอนนี้ให้เสร็จสมบูรณ์ได้โดยใช้เครื่องมือแก้ไขการเคลื่อนไหวหรือแก้ไขข้อความของ activity_step1_scene.xml โดยตรง
- เลือก
startConstraintSet ในแผงภาพรวม

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

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

- เปิด
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มุมมองให้อยู่ที่ด้านบนสุดขององค์ประกอบหลัก
- เลือก
endConstraintSet ในแผงภาพรวม

- ทำตามขั้นตอนเดียวกับที่เคยทำเพื่อเพิ่ม
Constraintสำหรับred_starในendConstraintSet - หากต้องการใช้เครื่องมือแก้ไขการเคลื่อนไหวเพื่อทำขั้นตอนนี้ให้เสร็จ ให้เพิ่มข้อจำกัดลงใน
bottomและendโดยคลิกปุ่ม + สีน้ำเงิน

- โค้ดใน 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เริ่มต้นและสิ้นสุดสำหรับการเปลี่ยนฉาก การเปลี่ยนฉากยังระบุวิธีแก้ไขภาพเคลื่อนไหวในลักษณะอื่นๆ ได้ด้วย เช่น ระยะเวลาในการเรียกใช้ภาพเคลื่อนไหว หรือวิธีสร้างภาพเคลื่อนไหวโดยการลากมุมมอง
- 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: ดูตัวอย่างภาพเคลื่อนไหวในโปรแกรมแก้ไขการเคลื่อนไหว

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

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

ขั้นตอนที่ 6: เพิ่มตัวแฮนเดิลการคลิก
คุณต้องมีวิธีเริ่มภาพเคลื่อนไหว วิธีหนึ่งในการทำเช่นนี้คือการทำให้ MotionLayout ตอบสนองต่อเหตุการณ์คลิกใน @id/red_star
- เปิดโปรแกรมแก้ไขการเคลื่อนไหว แล้วเลือกทรานซิชันโดยคลิกลูกศรระหว่างจุดเริ่มต้นและจุดสิ้นสุดในแผงภาพรวม

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

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

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

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

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

ไฟล์ฉากเคลื่อนไหวที่เสร็จสมบูรณ์จะกำหนด 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: ตรวจสอบโค้ดเริ่มต้น
- หากต้องการเริ่มต้นใช้งาน ให้เปิดไฟล์เลย์เอาต์
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 เริ่มต้น
- เปิดฉากเคลื่อนไหว
xml/step2.xmlเพื่อกำหนดภาพเคลื่อนไหว - เพิ่มข้อจำกัดสำหรับข้อจำกัดเริ่มต้น
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 สุดท้าย
- กำหนดข้อจำกัดปลายทางเพื่อใช้เชนในการจัดตำแหน่งดาวทั้ง 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
- แทนที่ 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 จากนิ้วตลอดทั้งภาพเคลื่อนไหว
ลองเลย
- เรียกใช้แอปอีกครั้ง แล้วเปิดหน้าจอขั้นตอนที่ 2 คุณจะเห็นภาพเคลื่อนไหว
- ลอง "ดีด" หรือปล่อยนิ้วกลางภาพเคลื่อนไหวเพื่อดูว่า
MotionLayoutแสดงภาพเคลื่อนไหวตามหลักฟิสิกส์ของไหลอย่างไร

MotionLayout สามารถสร้างภาพเคลื่อนไหวระหว่างดีไซน์ที่แตกต่างกันมากได้โดยใช้ฟีเจอร์จาก ConstraintLayout เพื่อสร้างเอฟเฟกต์ที่สมบูรณ์
ในภาพเคลื่อนไหวนี้ มุมมองทั้ง 3 จะอยู่ในตำแหน่งที่สัมพันธ์กับมุมมองหลักที่ด้านล่างของหน้าจอเพื่อเริ่มต้น ในตอนท้าย มุมมองทั้ง 3 จะอยู่ในตำแหน่งที่สัมพันธ์กับ @id/credits ในห่วงโซ่
แม้ว่าเลย์เอาต์จะแตกต่างกันมาก แต่ MotionLayout จะสร้างภาพเคลื่อนไหวที่ลื่นไหลระหว่างจุดเริ่มต้นและจุดสิ้นสุด
5. การแก้ไขเส้นทาง
ในขั้นตอนนี้ คุณจะสร้างภาพเคลื่อนไหวที่เคลื่อนที่ตามเส้นทางที่ซับซ้อนระหว่างภาพเคลื่อนไหว และเคลื่อนไหวเครดิตระหว่างการเคลื่อนที่ MotionLayout สามารถแก้ไขเส้นทางที่มุมมองจะใช้ระหว่างจุดเริ่มต้นและจุดสิ้นสุดได้โดยใช้ KeyPosition
ขั้นตอนที่ 1: สำรวจโค้ดที่มีอยู่
- เปิด
layout/activity_step3.xmlและxml/step3.xmlเพื่อดูเลย์เอาต์และฉากเคลื่อนไหวที่มีอยู่ImageViewและTextViewแสดงข้อความดวงจันทร์และเครดิต - เปิดไฟล์ฉากเคลื่อนไหว (
xml/step3.xml) คุณจะเห็นว่ามีการกำหนดTransitionจาก@id/startถึง@id/endภาพเคลื่อนไหวจะย้ายรูปภาพดวงจันทร์จากด้านซ้ายล่างของหน้าจอไปยังด้านขวาล่างของหน้าจอโดยใช้ConstraintSets2 รายการ ข้อความเครดิตจะค่อยๆ ปรากฏจากalpha="0.0"เป็นalpha="1.0"ขณะที่ดวงจันทร์เคลื่อนที่ - เรียกใช้แอปตอนนี้ แล้วเลือกขั้นตอนที่ 3 คุณจะเห็นว่าดวงจันทร์เคลื่อนที่ตามเส้นทางเชิงเส้น (หรือเส้นตรง) จากจุดเริ่มต้นไปยังจุดสิ้นสุดเมื่อคลิกที่ดวงจันทร์
ขั้นตอนที่ 2: เปิดใช้การแก้ไขข้อบกพร่องของเส้นทาง
ก่อนเพิ่มส่วนโค้งในการเคลื่อนที่ของดวงจันทร์ คุณควรเปิดใช้การแก้ไขข้อบกพร่องของเส้นทางใน MotionLayout
หากต้องการช่วยพัฒนาภาพเคลื่อนไหวที่ซับซ้อนด้วย MotionLayout คุณสามารถวาดเส้นทางภาพเคลื่อนไหวของทุกมุมมองได้ ซึ่งจะเป็นประโยชน์เมื่อคุณต้องการเห็นภาพเคลื่อนไหวและปรับแต่งรายละเอียดเล็กๆ น้อยๆ ของการเคลื่อนไหว
- หากต้องการเปิดใช้เส้นทางการแก้ไขข้อบกพร่อง ให้เปิด
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" >
หลังจากเปิดใช้การแก้ไขข้อบกพร่องของเส้นทางแล้ว เมื่อเรียกใช้แอปอีกครั้ง คุณจะเห็นเส้นทางของมุมมองทั้งหมดที่แสดงด้วยเส้นประ

- วงกลมแสดงตำแหน่งเริ่มต้นหรือสิ้นสุดของการดู 1 ครั้ง
- เส้นแสดงเส้นทางของมุมมองหนึ่ง
- เพชรแสดงถึง
KeyPositionที่แก้ไขเส้นทาง
เช่น ในภาพเคลื่อนไหวนี้ วงกลมตรงกลางคือตำแหน่งของข้อความเครดิต
ขั้นตอนที่ 3: แก้ไขเส้นทาง
ภาพเคลื่อนไหวทั้งหมดใน MotionLayout จะกำหนดโดยConstraintSetจุดเริ่มต้นและจุดสิ้นสุด ซึ่งกำหนดลักษณะของหน้าจอก่อนที่ภาพเคลื่อนไหวจะเริ่มและหลังจากที่ภาพเคลื่อนไหวเสร็จสิ้น โดยค่าเริ่มต้น MotionLayout จะวางแผนเส้นทางเชิงเส้น (เส้นตรง) ระหว่างตำแหน่งเริ่มต้นและตำแหน่งสิ้นสุดของแต่ละวิวที่เปลี่ยนตำแหน่ง
หากต้องการสร้างเส้นทางที่ซับซ้อน เช่น เส้นโค้งของดวงจันทร์ในตัวอย่างนี้ MotionLayout จะใช้ KeyPosition เพื่อแก้ไขเส้นทางที่มุมมองใช้ระหว่างจุดเริ่มต้นและจุดสิ้นสุด
- เปิด
xml/step3.xmlแล้วเพิ่มKeyPositionลงในฉาก วางแท็กKeyPositionไว้ภายในแท็กTransition

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

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

<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

keyPositionType ของ parentRelative ใช้ระบบพิกัดเดียวกับหน้าจอ โดยจะกำหนด (0, 0) ไปที่ด้านซ้ายบนของ MotionLayout ทั้งหมด และ (1, 1) ไปที่ด้านขวาล่าง
คุณใช้ parentRelative ได้ทุกเมื่อที่ต้องการสร้างภาพเคลื่อนไหวที่เคลื่อนที่ผ่าน MotionLayout ทั้งหมด เช่น เส้นโค้งของดวงจันทร์ในตัวอย่างนี้
อย่างไรก็ตาม หากต้องการแก้ไขเส้นทางที่สัมพันธ์กับการเคลื่อนไหว เช่น ทำให้โค้งเล็กน้อย ระบบพิกัดอีก 2 ระบบจะเป็นตัวเลือกที่ดีกว่า
พิกัด deltaRelative

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

ระบบพิกัดสุดท้ายใน MotionLayout คือ pathRelative ซึ่งแตกต่างจากอีก 2 แกนอย่างมาก เนื่องจากแกน X จะเคลื่อนที่ตามเส้นทางการเคลื่อนไหวตั้งแต่ต้นจนจบ ดังนั้น (0,0) คือตำแหน่งเริ่มต้น และ (1,0) คือตำแหน่งสิ้นสุด
ทำไมคุณถึงต้องการทำเช่นนี้ ซึ่งดูเหมือนจะน่าประหลาดใจในตอนแรก โดยเฉพาะอย่างยิ่งเนื่องจากระบบพิกัดนี้ไม่ได้สอดคล้องกับระบบพิกัดหน้าจอด้วยซ้ำ
pathRelative มีประโยชน์จริงๆ สำหรับบางสิ่ง
- เร่ง ลดความเร็ว หรือหยุดมุมมองชั่วคราวระหว่างภาพเคลื่อนไหว เนื่องจากมิติข้อมูล X จะตรงกับเส้นทางที่มุมมองใช้เสมอ คุณจึงใช้
pathRelativeKeyPositionเพื่อเปลี่ยนframePositionที่จุดใดจุดหนึ่งในเส้นทางนั้นได้ ดังนั้นKeyPositionที่framePosition="50"ที่มีpercentX="0.1"จะทำให้ภาพเคลื่อนไหวใช้เวลา 50% ในการเคลื่อนที่ 10% แรก - การเพิ่มส่วนโค้งเล็กๆ ลงในเส้นทาง เนื่องจากมิติ Y ตั้งฉากกับการเคลื่อนไหวเสมอ การเปลี่ยน Y จะเปลี่ยนเส้นทางให้โค้งสัมพันธ์กับการเคลื่อนไหวโดยรวม
- การเพิ่มมิติข้อมูลที่ 2 เมื่อ
deltaRelativeใช้ไม่ได้ สำหรับการเคลื่อนไหวในแนวนอนและแนวตั้งโดยสมบูรณ์deltaRelativeจะสร้างมิติที่มีประโยชน์เพียงมิติเดียว อย่างไรก็ตามpathRelativeจะสร้างพิกัด X และ Y ที่ใช้ได้เสมอ
ในขั้นตอนถัดไป คุณจะได้เรียนรู้วิธีสร้างเส้นทางที่ซับซ้อนยิ่งขึ้นโดยใช้ KeyPosition มากกว่า 1 รายการ
7. สร้างเส้นทางที่ซับซ้อน
เมื่อดูภาพเคลื่อนไหวที่คุณสร้างในขั้นตอนสุดท้าย จะเห็นว่าเส้นโค้งนั้นราบรื่น แต่รูปร่างอาจดู "เหมือนดวงจันทร์" มากขึ้นได้
แก้ไขเส้นทางที่มีองค์ประกอบ KeyPosition หลายรายการ
MotionLayout สามารถแก้ไขเส้นทางเพิ่มเติมได้โดยกำหนด KeyPosition ตามจำนวนที่ต้องการเพื่อให้ได้การเคลื่อนไหว สำหรับภาพเคลื่อนไหวนี้ คุณจะสร้างส่วนโค้ง แต่จะทำให้ดวงจันทร์กระโดดขึ้นลงตรงกลางหน้าจอก็ได้หากต้องการ
- เปิด
xml/step4.xmlคุณจะเห็นว่าวิดีโอมีจำนวนยอดดูเท่ากันและมีKeyFrameที่คุณเพิ่มในขั้นตอนสุดท้าย - หากต้องการปัดส่วนบนของเส้นโค้ง ให้เพิ่ม
KeyPositionsอีก 2 รายการลงในเส้นทางของ@id/moonโดยวาง 1 รายการก่อนถึงจุดสูงสุด และอีก 1 รายการหลังจากนั้น

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

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
- เปิด
xml/step5.xmlซึ่งมีภาพเคลื่อนไหวเดียวกันกับที่คุณสร้างในขั้นตอนสุดท้าย หน้าจอนี้ใช้ภาพอวกาศอื่นเป็นพื้นหลังเพื่อความหลากหลาย - หากต้องการให้ดวงจันทร์ขยายขนาดและหมุน ให้เพิ่มแท็ก
KeyAttribute2 แท็กในKeyFrameSetที่keyFrame="50"และkeyFrame="100"

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: หน่วงเวลาการแสดงเครดิต
เป้าหมายอย่างหนึ่งของขั้นตอนนี้คือการอัปเดตภาพเคลื่อนไหวเพื่อให้ข้อความเครดิตไม่ปรากฏจนกว่าภาพเคลื่อนไหวจะเสร็จสมบูรณ์เกือบทั้งหมด
- หากต้องการเลื่อนเวลาการแสดงเครดิต ให้กำหนด
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 นี้คือเครดิตจะปรากฏในช่วงท้ายของภาพเคลื่อนไหว ซึ่งจะทำให้ดูเหมือนว่าทั้งสองอย่างสอดคล้องกัน โดยดวงจันทร์จะค่อยๆ เคลื่อนลงไปที่มุมขวาของหน้าจอ
การหน่วงเวลาภาพเคลื่อนไหวในมุมมองหนึ่งขณะที่อีกมุมมองหนึ่งเคลื่อนไหวแบบนี้จะช่วยให้คุณสร้างภาพเคลื่อนไหวที่น่าประทับใจซึ่งให้ความรู้สึกแบบไดนามิกแก่ผู้ใช้ได้
ลองเลย
- เรียกใช้แอปอีกครั้งแล้วไปที่ขั้นตอนที่ 5 เพื่อดูภาพเคลื่อนไหวที่ทำงาน เมื่อคลิกที่ดวงจันทร์ ดวงจันทร์จะเคลื่อนที่ตามเส้นทางจากจุดเริ่มต้นไปยังจุดสิ้นสุด โดยผ่านแต่ละ
KeyAttributeที่ระบุไว้ในKeyFrameSet

เนื่องจากคุณหมุนดวงจันทร์ 2 รอบเต็ม ตอนนี้ดวงจันทร์จะตีลังกากลับหลัง 2 รอบ และเครดิตจะปรากฏช้าลงจนกว่าภาพเคลื่อนไหวจะเกือบเสร็จ
สำรวจด้วยตนเอง
ก่อนที่จะไปยังKeyFrameประเภทสุดท้าย ให้ลองแก้ไขแอตทริบิวต์มาตรฐานอื่นๆ ใน KeyAttributes เช่น ลองเปลี่ยน rotation เป็น rotationX เพื่อดูว่าภาพเคลื่อนไหวที่ได้จะเป็นอย่างไร
ต่อไปนี้คือรายการแอตทริบิวต์มาตรฐานที่คุณลองใช้ได้
android:visibilityandroid:alphaandroid:elevationandroid:rotationandroid:rotationXandroid:rotationYandroid:scaleXandroid:scaleYandroid:translationXandroid:translationYandroid:translationZ
9. การเปลี่ยนแอตทริบิวต์ที่กำหนดเอง
ภาพเคลื่อนไหวที่ซับซ้อนเกี่ยวข้องกับการเปลี่ยนสีหรือแอตทริบิวต์อื่นๆ ของมุมมอง แม้ว่า MotionLayout จะใช้ KeyAttribute เพื่อเปลี่ยนแอตทริบิวต์มาตรฐานที่ระบุไว้ในงานก่อนหน้าได้ แต่คุณต้องใช้ CustomAttribute เพื่อระบุแอตทริบิวต์อื่นๆ
CustomAttribute ใช้เพื่อตั้งค่าใดก็ได้ที่มี Setter เช่น คุณตั้งค่า backgroundColor ใน View ได้โดยใช้ CustomAttribute MotionLayout จะใช้การสะท้อนเพื่อค้นหาตัวตั้งค่า จากนั้นเรียกใช้ซ้ำๆ เพื่อเคลื่อนไหวมุมมอง
ในขั้นตอนนี้ คุณจะใช้ CustomAttribute เพื่อตั้งค่าแอตทริบิวต์ colorFilter บนดวงจันทร์เพื่อสร้างภาพเคลื่อนไหวที่แสดงด้านล่าง

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

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ในDrawablemotion:custom*Valueคือค่าที่กำหนดเองของประเภทที่ระบุไว้ในชื่อ ในตัวอย่างนี้ ค่าที่กำหนดเองคือสีที่ระบุ
ค่าที่กำหนดเองอาจมีประเภทใดประเภทหนึ่งต่อไปนี้
- สี
- จำนวนเต็ม
- ทศนิยม
- สตริง
- มิติข้อมูล
- บูลีน
เมื่อใช้ API นี้ MotionLayout จะทำให้ทุกอย่างที่มีตัวตั้งค่าในมุมมองใดก็ได้เคลื่อนไหวได้
ลองเลย
- เรียกใช้แอปอีกครั้ง แล้วไปที่ขั้นตอนที่ 6 เพื่อดูภาพเคลื่อนไหวที่ทำงาน เมื่อคลิกที่ดวงจันทร์ ดวงจันทร์จะเคลื่อนที่ตามเส้นทางจากจุดเริ่มต้นไปยังจุดสิ้นสุด โดยผ่านแต่ละ
KeyAttributeที่ระบุไว้ในKeyFrameSet

เมื่อเพิ่ม KeyFrames มากขึ้น MotionLayout จะเปลี่ยนเส้นทางของดวงจันทร์จากเส้นตรงเป็นเส้นโค้งที่ซับซ้อน โดยเพิ่มการตีลังกาสองรอบ การปรับขนาด และการเปลี่ยนสีในช่วงกลางของภาพเคลื่อนไหว
ในภาพเคลื่อนไหวจริง คุณมักจะเคลื่อนไหวหลายๆ มุมมองพร้อมกัน โดยควบคุมการเคลื่อนไหวตามเส้นทางและความเร็วต่างๆ การระบุ KeyFrame ที่แตกต่างกันสำหรับแต่ละมุมมองจะช่วยให้คุณออกแบบท่าทางของภาพเคลื่อนไหวที่สมบูรณ์ซึ่งทำให้มุมมองหลายรายการเคลื่อนไหวด้วย MotionLayout ได้
10. ลากเหตุการณ์และเส้นทางที่ซับซ้อน
ในขั้นตอนนี้ คุณจะได้ดูการใช้ OnSwipe กับเส้นทางที่ซับซ้อน จนถึงตอนนี้ ภาพเคลื่อนไหวของดวงจันทร์จะทริกเกอร์โดยOnClick Listener และทำงานเป็นระยะเวลาที่แน่นอน
การควบคุมภาพเคลื่อนไหวที่มีเส้นทางซับซ้อนโดยใช้ OnSwipe เช่น ภาพเคลื่อนไหวของดวงจันทร์ที่คุณสร้างใน 2-3 ขั้นตอนล่าสุด จำเป็นต้องทำความเข้าใจวิธีการทำงานของ OnSwipe
ขั้นตอนที่ 1: สำรวจลักษณะการทำงานของ OnSwipe
- เปิด
xml/step7.xmlแล้วค้นหาประกาศOnSwipeที่มีอยู่
step7.xml
<!-- Fix OnSwipe by changing touchAnchorSide →
<OnSwipe
motion:touchAnchorId="@id/moon"
motion:touchAnchorSide="bottom"
/>
- เรียกใช้แอปในอุปกรณ์ แล้วไปที่ขั้นตอนที่ 7 ลองดูว่าคุณจะสร้างภาพเคลื่อนไหวที่ราบรื่นได้ไหมโดยการลากดวงจันทร์ไปตามเส้นทางของส่วนโค้ง
เมื่อเรียกใช้ภาพเคลื่อนไหวนี้ ภาพที่ได้จะไม่ค่อยดีนัก หลังจากดวงจันทร์ขึ้นไปถึงจุดสูงสุดของส่วนโค้งแล้ว ก็จะเริ่มกระโดดไปมา

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

ขั้นตอนที่ 2: ใช้ด้านขวา
การเลือก touchAnchorId และ touchAnchorSide ที่มีความคืบหน้าไปในทิศทางเดียวตลอดระยะเวลาของภาพเคลื่อนไหวทั้งหมดจึงเป็นสิ่งสำคัญที่จะช่วยหลีกเลี่ยงข้อบกพร่องเช่นนี้
ในภาพเคลื่อนไหวนี้ ทั้งด้านrightและด้านleftของดวงจันทร์จะเคลื่อนที่ข้ามหน้าจอไปในทิศทางเดียว
อย่างไรก็ตาม ทั้ง bottom และ top จะเปลี่ยนทิศทาง เมื่อ OnSwipe พยายามติดตามผู้ใช้ ระบบจะสับสนเมื่อทิศทางของผู้ใช้เปลี่ยนไป
- หากต้องการให้ภาพเคลื่อนไหวนี้เป็นไปตามเหตุการณ์การแตะ ให้เปลี่ยน
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
- อัปเดต
OnSwipeเพื่อติดตามการเคลื่อนที่ของดวงจันทร์อย่างถูกต้อง
step7.xml
<!-- Using dragDirection to control the direction of drag tracking →
<OnSwipe
motion:touchAnchorId="@id/moon"
motion:touchAnchorSide="bottom"
motion:dragDirection="dragRight"
/>
ลองเลย
- เรียกใช้แอปอีกครั้งแล้วลองลากดวงจันทร์ไปตามเส้นทางทั้งหมด แม้ว่าจะมีเส้นโค้งที่ซับซ้อน แต่
MotionLayoutจะสามารถทำให้ภาพเคลื่อนไหวคืบหน้าได้เพื่อตอบสนองต่อเหตุการณ์การปัด

11. การเคลื่อนไหวด้วยโค้ด
MotionLayout ใช้สร้างภาพเคลื่อนไหวที่สมบูรณ์ได้เมื่อใช้ร่วมกับ CoordinatorLayout ในขั้นตอนนี้ คุณจะได้สร้างส่วนหัวที่ยุบได้โดยใช้ MotionLayout
ขั้นตอนที่ 1: สำรวจโค้ดที่มีอยู่
- เริ่มต้นใช้งานโดยเปิด
layout/activity_step8.xml - ใน
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 ได้
- เรียกใช้แอป แล้วไปที่ขั้นตอนที่ 8 คุณจะเห็นว่าเมื่อเลื่อนข้อความ ดวงจันทร์จะไม่ขยับ
ขั้นตอนที่ 2: ทำให้ MotionLayout เลื่อนได้
- หากต้องการให้มุมมอง
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" >
- เรียกใช้แอปอีกครั้งแล้วไปที่ขั้นตอนที่ 8 คุณจะเห็นว่า
MotionLayoutจะยุบลงเมื่อเลื่อนขึ้น อย่างไรก็ตาม ภาพเคลื่อนไหวจะยังไม่ดำเนินไปตามลักษณะการเลื่อน
ขั้นตอนที่ 3: ย้ายการเคลื่อนไหวด้วยโค้ด
- เปิด
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กับความคืบหน้าเป็นเปอร์เซ็นต์ ให้หารด้วยช่วงการเลื่อนทั้งหมด
ลองเลย
- ทำให้แอปใช้งานได้อีกครั้งและเรียกใช้ภาพเคลื่อนไหวขั้นตอนที่ 8 คุณจะเห็นว่า
MotionLayoutจะทำให้ภาพเคลื่อนไหวคืบหน้าตามตำแหน่งการเลื่อน

คุณสามารถสร้างภาพเคลื่อนไหวแถบเครื่องมือแบบยุบแบบไดนามิกที่กำหนดเองได้โดยใช้ MotionLayout การใช้ลำดับของ KeyFrames จะช่วยให้คุณสร้างเอฟเฟกต์ที่โดดเด่นได้
12. ขอแสดงความยินดี
Codelab นี้ครอบคลุม API พื้นฐานของ MotionLayout
ดูตัวอย่างเพิ่มเติมของ MotionLayout ในทางปฏิบัติได้ที่ตัวอย่างอย่างเป็นทางการ และอย่าลืมดูเอกสารประกอบ
ดูข้อมูลเพิ่มเติม
MotionLayout รองรับฟีเจอร์อื่นๆ อีกมากมายที่ไม่ได้กล่าวถึงในโค้ดแล็บนี้ เช่น KeyCycle, ซึ่งช่วยให้คุณควบคุมเส้นทางหรือแอตทริบิวต์ด้วยรอบที่ทำซ้ำ และ KeyTimeCycle, ซึ่งช่วยให้คุณสร้างภาพเคลื่อนไหวตามเวลาของนาฬิกาได้ ดูตัวอย่างของแต่ละรายการได้ในตัวอย่าง
ดูลิงก์ไปยัง Codelab อื่นๆ ในหลักสูตรนี้ได้ที่หน้า Landing Page ของ Codelab Android ขั้นสูงใน Kotlin