มาแต่งซิ่งไปกับ MotionLayout บน Android แล้วมาขิง iOS กันเถอะ
พอดีเม้ากับพี่เอก เขาแนะนำว่าใช้ MotionLayout เถอะ (เห็นเอาไปใช้ใน LINE MAN แล้วนะแต่ตอนนี้เรายังหาไม่เจอว่าตรงไหนนะ) อ่ะยังไงเราก็ win อ่ะ เพราะเหมือน iOS จะยังไม่มีมั้งนะ
![](https://www.mikkipastel.com/content/images/2023/04/IMG_6671-1.webp)
ความเดิมตอนที่แล้ว แน่นอนว่าเราจะขิงเป็น series
![](https://www.mikkipastel.com/content/images/2020/08/device-2020-08-27-152249.png)
MotionLayout เป็น subclass ของ ConstraintLayout ใช้ในการจัดการ motion และการแสดง animation ต่างๆ ตัวนี้จะเป็นส่วนผสมรวมกันระหว่าง property animation framework, TransitionManager, และ CoordinatorLayout
สามารถอ่านเพิ่มเติมได้ที่นี่
![](https://developer.android.com/static/images/social/android-developers.png)
![](https://developer.android.com/static/images/social/android-developers.png)
การแสดง animation ใน MotionLayout ก็หลากหลายมากๆเช่นกัน location, size, visibility, alpha, color, elevation, rotation และ attributes อื่นๆสำหรับแสดงหลายๆ view ในเวลาเดียวกัน ฮู้วว ว้าวว
แน่นอนว่าเจ้า MotionLayout มาใน Constraint Layout 2.0 นะเออ เพราะฉันนั้นถ้าจะลองใช้ช่วยอัพเดต dependency เป็น version 2 ขึ้นไปน้า (ในวันที่ปล่อยบล็อกเป็น 2.0.1 นะ)
dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.0.0'
}
ใน Constraint Layout 2.0 มีอะไรบ้าง นอกจาก MotionLayout แล้วยังมี Flow ช่วย wrap item ต่างๆให้สวยงาม และ Layer ตัวนี้ช่วยในการแสดง animation หลายๆ view พร้อมกัน ก็จะมี rotate
, translate
และ scale
นะ
![](https://miro.medium.com/max/1200/1*cQKSUJxHbM6A8IlydnP_DA.png)
เนื้อหาตัวอย่างทั้งหมดจะอ้างอิงจาก codelab ตัวนี้จ้า
![](https://codelabs.developers.google.com/static/codelabs/motion-layout/img/a1beacb239657647.png)
ก่อนอื่นเรามาสร้าง animation ง่ายๆบน MotionLayout กันเถอะ
เราจะให้ parent view เป็น MotionLayout
จะพบว่า มันขึ้น error แดงๆน้าาาา เพราะมันขาด LayoutDescription
นั่นเอง แล้วมันคืออะไรกันนะ
![](https://www.mikkipastel.com/content/images/2023/04/image-9.webp)
เพื่อให้หายข้องใจ เราไปกันต่อจ้า ไปเพิ่ม motion scene กันก่อน โดยไปสร้างไฟล์ xml ถ้าจาก codelab จะเป็น res/xml/step1.xml
นะ แล้วไปเพิ่ม LayoutDescription
ที่หายไป แบบนี้
<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/step1">
แล้วไฟล์ที่ชื่อว่า step1.xml
ข้างในมีอะไรบ้างนะ
ข้างในไฟล์นี้มีส่วนประกอบหลักๆคือ
MotionScene
เป็น parent ข้างในอธิบาย animation ที่จะเกิดขึ้นในMotionLayout
Transition
ในนี้จะต้องระบุว่า view เริ่มต้นคือตัวไหนในconstraintSetStart
และ view สิ้นสุดคือตัวไหนในconstraintSetEnd
ส่วนอื่นๆ เช่นduration
ระบุว่าเราจะให้แสดง animation ตั้งแต่ต้นจนจบใช้เวลาเท่าไหร่ ไม่ใส่ก็ได้แต่มันจะแบบวิ่งไวมากอ่ะ ในนี้สามารถใส่ลูกได้ 3 ตัว คือKeyFrameSet
,OnClick
และOnSwipe
ConstraintSet
เป็นกลุ่มก้อนของ view และจะมีลูกข้างในจะเป็นConstraint
ในที่นี้เราจะสร้างมา 2 อัน ชื่อว่าstart
และend
เนอะ
มาดูแต่ละส่วนกันเลยดีกว่า
view เริ่มต้น ชื่อว่า start
ตำแหน่งของรูปดาวนั้นจะอยู่บนซ้าย แน่นอนว่า id ใน Constraint
ต้องตรงกับ view ที่มีใน MotionLayout
นะเออ ไม่งั้นมันจะไม่ขยับนะ
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</ConstraintSet>
และ view สิ้นสุด ชื่อว่า end
ตำแหน่งรูปดาวอยู่ล่างขวาเนอะ
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>
และเราต้องการให้น้องดาวเคลื่อนที่โดยการกดที่รูปดาว โดยการเพิ่ม OnClick
เข้าไปว่าให้ targetId
view ที่ถูดกดคือตัวไหน และ clickAction
เป็นแบบไหน เช่น toggle
คือช่วย reverse ระหว่าง start กับ end ให้หน่อยหลังจากการกด
<Transition
app:constraintSetStart="@+id/start"
app:constraintSetEnd="@+id/end"
app:duration="2000">
<OnClick
app:targetId="@id/red_star"
app:clickAction="toggle" />
</Transition>
เมื่อทำเสร็จแล้วจะเป็นแบบนี้นะ
![](https://www.mikkipastel.com/content/images/2023/04/image_10.webp)
ผลที่ได้ ขอแปะรูปจาก codelab แล้วกัน
![](https://www.mikkipastel.com/content/images/2020/08/b289c7a42b0723b.gif)
ลองทำท่ายากขึ้นมาหน่อยสำหรับแสดง animation มากกว่า 1 view
![](https://www.mikkipastel.com/content/images/2020/08/475480a0ed97d029.gif)
ก่อนอื่นเรามากำหนด ConstraintSet
เริ่มต้น โดยให้ดาวแดงอยู่ตรงกลาง และดาวซ้ายขวามีค่า alpha เป็น 0 ก่อน
<ConstraintSet android:id="@+id/start">
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<Constraint
android:id="@+id/left_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<Constraint
android:id="@+id/right_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</ConstraintSet>
![](https://www.mikkipastel.com/content/images/2023/04/image-1-1.webp)
และ ConstraintSet
สุดท้ายคือแสดงดาวทั้งหมดสามอัน โดยจะมีดาวสีขาวขนาบซ้ายขวา และมีค่า alpha เป็น 1
<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"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/red_star"
app:layout_constraintTop_toBottomOf="@id/credits" />
<Constraint
android:id="@+id/red_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@id/left_star"
app:layout_constraintEnd_toStartOf="@id/right_star"
app: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"
app:layout_constraintStart_toEndOf="@id/red_star"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/credits" />
</ConstraintSet>
![](https://www.mikkipastel.com/content/images/2023/04/image-2.webp)
และไปส่วน Transition
นั้นเราก็ระบุ constraintSetStart
และ constraintSetEnd
แล้วใส่ OnSwipe
เข้าไป โดยมี attribute ที่สำคัญ คือ
touchAnchorId
[อันนี้ต้องใส่] view ที่เรา track การเคลื่อนไหวไว้touchAnchorSide
ด้านข้างของ target view ในการกวาดนิ้วเพื่อ swipe โดยตัว MotionLayout จะพยายามรักษาระยะห่างนะหว่างตัว anchor กับนิ้วของ user (เอ้ออออแปลจาก document แล้วงงๆเนอะ) สามารถใส่ค่าได้คือleft
,right
,top
, และbottom
เท่านั้นนะ (ใน codelab บทท้ายๆจะมีการอธิบายเพิ่ม แล้วแต่ดุลยพินิจแหละว่าอันไหนมันเหมาะกับงานของเรา)dragDirection
direction ของการ swipe ว่าจะให้เป็นบนล่างซ้ายขวาเนาะ ยังไม่มีตีลังกาให้นะจ๊ะ ค่าที่สามารถใส่ได้คือdragLeft
,dragRight
,dragUp
,dragDown
สามารถอ่านเพิ่มเติมได้ที่นี่
ในตัวอย่างนี้จะเป็นดังนี้นะ
<Transition
app:constraintSetStart="@+id/start"
app:constraintSetEnd="@+id/end">
<OnSwipe app:touchAnchorId="@id/red_star" />
</Transition>
![](https://www.mikkipastel.com/content/images/2020/09/475480a0ed97d029.gif)
จริงๆเราสามารถดู path debugging ได้นะ โดยการใส่ app:motionDebug="SHOW_PATH"
ใน MotionLayout ที่ layout ผลจะเป็นดังนี้
![](https://www.mikkipastel.com/content/images/2023/04/Untitled.webp)
![](https://www.mikkipastel.com/content/images/2023/04/Untitled--1-.webp)
![](https://www.mikkipastel.com/content/images/2023/04/Untitled--2-.webp)
- วงกลม คือสัญลักษณ์แทน view ใน
MotionLayout
- เส้นนี้เขาเรียกว่า Motion Path เป็นเส้นการเดินทางของ view
- มีอีกอันรูปเพชร เป็น
KeyPosition
เดี๋ยวเราจะเล่าถัดไป
![](https://www.mikkipastel.com/content/images/2023/04/image-4.webp)
มาแก้เส้น Path โดยเพิ่ม KeyFrameSet และ KeyPosition กันเถอะ
![](https://www.mikkipastel.com/content/images/2023/04/Untitled-1.webp)
![](https://www.mikkipastel.com/content/images/2023/04/Untitled--1--1.webp)
ไปเพิ่ม KeyFrameSet
ใน Transition
กันก่อน
<KeyFrameSet>
<KeyPosition
app:framePosition="50"
app:motionTarget="@id/moon"
app:keyPositionType="parentRelative"
app:percentY="0.5"/>
</KeyFrameSet>
![](https://www.mikkipastel.com/content/images/2023/04/image-5.png)
เราก็จะเห็นรูปเพชรงอกมา ตรงไหนกัน เห็นเป็นวงกลมสีส้มๆ555 มันไม่เพชรเว้ย
KeyPosition
เป็นบุตรหลานของ Transition
นำไปใช้ตอน transition โดย MotionLayout
จะไปคำนวณ path ในส่วนการเคลื่อนที่ในวัตถุที่เราต้องการ
attribute ที่สำคัญและจำเป็นมีดังนี้
framePosition
มีค่าระหว่าง 0-100 อยากให้มองเป็น % ก็ได้เนอะ โดยในที่นี้ set ค่าเป็น 50 คือจะอยู่กลางใจ ไม่ใช่กลางใจหน่ะนะ .......... อันนี้คิดจาก path นะเออ โดยค่าน้อยๆจะใกล้จุด start ส่วนค่าเยอะๆก็จะใกล้จุด endmotionTarget
view ที่ต้องการไปอยู่ใน position ตรงนั้นkeyPositionType
จะให้KeyPosition
อยู่ยังไง เป็น attribute ที่เกี่ยวกับ coordinate system โดยสามารถเลือกค่าเอามาใส่ได้ 3 ตัว คือ
1) parentRelative พิกัด (0, 0) อยู่ซ้ายบน ส่วน (1,1) อยู่ขวาล่าง ใช้เมื่อเราจะสร้าง animation ที่เคลื่อนผ่านกัน
![](https://www.mikkipastel.com/content/images/2023/04/image-6.webp)
2) deltaRelative พิกัด (0, 0) อยู่ซ้ายล่าง เป็นตำแหน่งของ view ตั้งต้น ส่วน (1,1) อยู่ขวาบน เป็นตั้งแหน่งของ view สิ้นสุด ใช้เมื่อต้องการควบคุมการเคลื่อนที่ในแนวนอนหรือแนวตั้งแยกกัน
![](https://www.mikkipastel.com/content/images/2023/04/image-7.webp)
3) pathRelative พิกัด (0, 0) เป็นตำแหน่งของ view ตั้งต้น และ (1,0) เป็นตำแหน่งของ view สิ้นสุด ใช้เมื่อตอนเร้งขึ้น ช้าลง หรือหยุก view ในระหว่างทางที่แสดง animation อันนี้เราอ่านแล้วก็มีความงงๆ แหะๆ
![](https://www.mikkipastel.com/content/images/2023/04/image-8.webp)
percentX
และpercentY
เป็นค่าในการแก้ path ของframePosition
มีค่าระหว่าง 0 - 1 ถ้าค่าpercentY
เท่ากับ 1 ก็คือเป็นเส้นตรง (default)
ส่วนประกอบใน path ที่เพิ่มขึ้นมาจะเป็นแบบรูปเพชร นั่นคือตำแหน่งของ KeyPosition
นั่นเอง
![](https://www.mikkipastel.com/content/images/2023/04/image-9-1.webp)
และเราสามารถกำหนด KeyPosition
ได้มากกว่า 1 จุด
เท่าที่ลองตามใน codelab เราสามารถกำหนดการเดินทาง (Path) ของ view ต่างๆ ได้ผ่าน KeyPosition
โดยเราสามารถกำหนดจุดและตำแหน่งที่เราต้องการ ได้ดังนี้
<KeyFrameSet>
<KeyPosition
app:framePosition="25"
app:motionTarget="@id/moon"
app:keyPositionType="parentRelative"
app:percentY="0.6"
app:percentX="0.1"/>
<KeyPosition
app:framePosition="50"
app:motionTarget="@id/moon"
app:keyPositionType="parentRelative"
app:percentY="0.5"
app:percentX="0.3"/>
<KeyPosition
app:framePosition="75"
app:motionTarget="@id/moon"
app:keyPositionType="parentRelative"
app:percentY="0.6"
app:percentX="0.1"/>
</KeyFrameSet>
และนี่คือผลที่ได้จ้า
![](https://www.mikkipastel.com/content/images/2023/04/image-10.webp)
เราจะให้ดวงจันทร์เราวิ่งไปเฉยๆไม่ได้ ต้องเต้นรำตีลังกาเสียหน่อยเยอะ
KeyAttribute
เอามาแสดง animation อื่นๆไม่ว่าจะเป็น
android:visibility
android:alpha
android:elevation
android:rotation
android:rotationX
android:rotationY
android:scaleX
android:scaleY
android:translationX
android:translationY
android:translationZ
ซึ่งก็จะคล้ายๆ ObjectAnimator
เนอะ
ตัวอย่างจ้า
![](https://www.mikkipastel.com/content/images/2020/09/db2a00ee5ddf5ecd.gif)
ก่อนอื่นเราอยากให้น้องดวงจันทร์ของเรานั้นหมุน และให้ขนาดใหญ่เป็น 2 เท่าเมื่อมาตรงกลางเนอะ
<KeyAttribute
app:framePosition="50"
app:motionTarget="@id/moon"
android:scaleY="2.0"
android:scaleX="2.0"
android:rotation="-360"/>
<KeyAttribute
app:framePosition="100"
app:motionTarget="@id/moon"
android:rotation="-720"/>
และเมื่อถึงตำแหน่ง framePosition
ที่ 85 ช่วย จะเริ่ม show text หล่ะ โดยเรา set alpha
= 0 ที่ตำแหน่งนั้น และพอถึง 100 ค่า alpha จะเป็น 1.0 ตามที่ set ไว้ใน Constraint
end
เนอะ
<KeyAttribute
app:framePosition="85"
app:motionTarget="@id/credits"
android:alpha="0.0"/>
จากนั้นทำการเปลี่ยนสีดวงจันทร์ โดยการเปลี่ยนสีจะใช้ CustomAttribute
ใส่ app:attributeName
เป็น "colorFilter"
และใส่สีโดย app:customColorValue
เอ้ออเอาจริงๆมันจะแปลกๆหน่อยอ่ะ
<KeyAttribute
app:framePosition="0"
app:motionTarget="@id/moon">
<CustomAttribute
app:attributeName="colorFilter"
app:customColorValue="#FFFFFF"/>
</KeyAttribute>
<KeyAttribute
app:framePosition="50"
app:motionTarget="@id/moon">
<CustomAttribute
app:attributeName="colorFilter"
app:customColorValue="#FFB612"/>
</KeyAttribute>
<KeyAttribute
app:framePosition="100"
app:motionTarget="@id/moon">
<CustomAttribute
app:attributeName="colorFilter"
app:customColorValue="#FFFFFF"/>
</KeyAttribute>
สุดท้าย สามารถทำ MotionLayout จากการเขียนโค้ดได้ด้วยนะ
เช่น เมื่อเราเลื่อนจอขึ้นไป ก็จะแสดง animation ที่ AppBarLayout
ให้เราดูด้วย ดังรูป
![](https://www.mikkipastel.com/content/images/2020/09/69fde2ef83df97c6.gif)
ก่อนอื่นทำการสร้าง layout กันก่อน โดยเจ้า MotionLayout
นั้นจะอยู่ภายใต้ AppBarLayout
นะ ซึ่งเขาจะอยู่ภายใต้ CoordinatorLayout
อีกทีนุง
ทำการเคลื่อนไหว MotionLayout
ด้วยโค้ด โดยเราจะเพิ่ม listener ของ AppBarLayout
ที่มีชื่อว่า OnOffsetChangedListener
โดยให้ progress ของ MotionLayout
เท่ากับ verticalOffset / appBarLayout.totalScrollRange.toFloat()
และ AppBarLayout
ไม่สามารถเปลี่ยนขนาดของ MotionLayout
ได้นะจ๊ะ
สรุปใน MotionScene
โครงสร้างจะเป็นแบบนี้เนอะ โดยชื่อไฟล์ก็ไปใส่ไว้ใน MotionLayout
ไปว่า motion:layoutDescription="@xml/{motion_scene_name}"
เนอะ
<MotionScene>
<Translation>
<OnSwipe/>
<OnClick/>
<KeyFrameSet>
<KeyPosition/>
<KeyAttribute/>
</KeyFrameSet>
</Translation>
<ConstraintSet android:id="@+id/start">
<Constraint/>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint/>
</ConstraintSet>
</MotionScene>
จบบล็อกแล้วเนอะ หวังว่าคนอ่านคงจะไม่มึนจนเกินไปเนอะ 555555 (ขำแห้ง)
ก่อนจากกันแปะของ Material Design Components ไว้ด้วย ว่าจะเขียนถึงแต่ขี้เกียจหล่ะ555
![](https://material.io/static/assets/result.png)
และ Video Series ของทาง Android Developer เกี่ยวกับ Motion Layout นี่แหละ มีทั้งหมด 10 ตอน ไปดูกันได้เลยยยย
ติดตามข่าวสารตามช่องทางต่าง ๆ และทุกช่องทางโดเนทกันไว้ที่นี่เลย
ติดตามข่าวสารแบบไว ๆ มาที่ Twitter เลย บางอย่างไม่มีในบล็อก และหน้าเพจนะ
สวัสดีจ้า ฝากเนื้อฝากตัวกับชาวทวิตเตอร์ด้วยน้าา
— Minseo | Stocker DAO (@mikkipastel) August 24, 2020