สาระสำคัญ
Hero Animation คือเทคนิคแอนิเมชั่น UI ที่องค์ประกอบร่วมเคลื่อนที่ไปยังหน้าจอปลายทางอย่างลื่นไหล โดยรักษาตัวตนไว้ ชื่อสะท้อนการเปรียบเทียบของ “ฮีโร่” — องค์ประกอบสำคัญที่ผ่านการเปลี่ยนองแบบมีบทเรียกระหว่างหน้าจอ ดึงดูดความสนใจของผู้ใช้ ต่างจากการเปลี่ยนองมาตรฐาน Hero Animation ไม่ทำลายการเชื่อมต่อทางสายตา ผู้ใช้จะเห็นวิถีต่อเนื่องขององค์ประกอบ ซึ่งช่วยให้เข้าใจลำดับชั้นการนำทางได้ดีขึ้น
แนวคิดของ Hero Animation ถูกกำหนดไว้ใน Material Design เป็นคำแนะนำสำหรับหน้าจอรายการ-รายละเอียด และการ์ด ขั้นตอนวิธีการแอนิเมชั่นประกอบด้วยสามขั้นตอน: บนหน้าจอต้นทาง องค์ประกอบจะอูกเน้นและเรียบเรียงในตำแหน่งเดิมสัมบูรณ์; ระหว่างการเปลี่ยนอง มันจะเปลี่ยนขนาด เคลื่อนที่ และเปลี่ยนรูปร่างตามตัวระบุปลายทาง; บนหน้าจอสุดท้าย องค์ประกอบจะถูกฝังไว้ในเลเอาร์ทใหม่ ตามข้อมูลจาก Material Design 3 การเปลี่ยนองฮีโร่ที่ใช้งานอย่างถูกต้องช่วยลดจำนวนการกดปุ่ม “กลับ” ผิดพลาดได้ 22%
ใน Flutter การเปลี่ยนองฮีโร่ใช้งานผ่านวิเจ็ต Hero ซึ่งรับพารามิเตอร์ที่จำเป็น tag — ตัวระบุเฉพาะใจ Flutter จะค้นหาคู่ของวิเจ็ต Hero ที่มี tag เดียวกันบนเส้นทางต้นทางและปลายทางโดยอัตโนมัติ และเริ่มแอนิเมชั่น Hero ไม่ต้องการจัดการแอนิเมชั่นด้วยตนเอง — เฟรมเวิร์กเป็นผู้คำนวณวิถีและใช้การประมวลผ่านออบเจกต์ FlightShuttle ภายใน
FlightShuttle เป็น “กระสวยส่ง” ที่มองไม่เห็น ซึ่งในขณะที่เปลี่ยนอง จะสร้างสำเนาข้อนทับของวิเจ็ต Hero วางไว้ในระบบพิกัดสัมบูรณ์ และเปลี่ยนแปลง (scale, translate, rotate) จากตำแหน่งเริ่มต้นไปยังตำแหน่งสุดท้าย โค้งที่กำหนดเองสามารถส่งไปยัง FlightShuttle ผ่านคุณสมบัติ flightShuttleBuilder ซึ่งช่วยควบคุมวิถีการบิน: บินเป็นส่วนโค้ง กระเด้นขอบ หรือช้าลงก่อนถึงเป้าหมาย ใน Flutter 3.16+ Hero รองรับแอนิเมชั่นโดยรักษา clipRect และ borderRadius ให้ผลของการ์ดที่มีมุมกลมแปลงเป็นหน้าจอสี่เหลี่ยม
ใน SwiftUI สิ่งที่เทียบเท่ากับ Hero Animation คือตัวแก้ไข matchedGeometryEffect ที่เปิดตัวใน iOS 14 มันเชื่อมโยงสององค์ประกอผ่านตัวระบุ (id) และ namespace ต่างจาก Flutter ที่ Hero สกัดการเปลี่ยนองโดยอัตโนมัติ SwiftUI ต้องการประกาศ namespace อย่างชัดเจน และใช้ตัวแก้ไขกับทั้งสองส่วนของฉาก ไม่ว่าจะเป็นต้นทางหรือปลายทาง ภายในลำดับชั้นเดียวกันหรือผ่าน GeometryReader
แอนิเมชั่น matchedGeometryEffect รองรับการประมวลตำแหน่ง ขนาด การหมุน และรัศมีมุม สำหรับความลื่นไหล จะระบุ Animation ใน withAnimation SwiftUI ทำการแทรกค่ากลางระหว่างสถานะโดยอัตโนมัติตามโค้งเรียบระนุก easeInOut ใน iOS 17+ มีการรองรับ matchedTransitionSource สำหรับ TabView และ NavigationStack ซึ่งเป็นอนาล็อกของระบบของ Hero Animation ที่มีวิถีที่กำหนดไว้ล่วงหน้าสำหรับการเปลี่ยนองมาตรฐาน
ใน Android Hero Animation เรียกว่า Shared Element Transition และถูกนำไปใช้ผ่าน ActivityOptions.makeSceneTransitionAnimation หรือ FragmentTransaction.addSharedElement แต่ละองค์ประกอบร่วมจะถูกทำเครื่องหมายด้วยแอตทริบิวตร์ transitionName ใน XML หรือผ่านโค้ด และ Android จะจัดการแอนิเมชั่นระหว่างกิจกรรม ร่มจาก Android 5.0 (API 21) การเปลี่ยนองได้กลายเป็นส่วนหนึ่งของเฟรมเวิร์ก ก่อนหน้านั้นต้องเขียนแอนิเมชั่นแบบกำหนดเองโดยใช้ ValueAnimator
ใน Jetpack Compose Shared Element Transition ถูกนำไปใช้ผ่านตัวแก้ไข Modifier.sharedElement และ Modifier.sharedBounds ร่วมกับ AnimatedContent หรือ SharedTransitionLayout (ใช้งานได้ตั้งแต่ Compose BOM 2024.01) ต่างจากระบบ View Compose จะวาดรูปองค์ประกอบใหม่แบบขี้เกียจ ดังนั้นองค์ประกอบร่วมจึงถูกเก็บไว้ในหม่วนความจำใน CompositionLocal Compose รองรับการเปลี่ยนองจากรายการไปยังรายละเอียดโดยรักษา scrollOffset ซึ่งเป็นความสามารถสำคัญสำหรับการเปลี่ยนอง Hero ตามข้อมูล Google I/O 2024 Compose Shared Element Transition เร็วกว่ารุ่น View ถึง 15% เนื่องจากการจัดการ RenderNode โดยตรง
ตัวอย่างพื้นฐานของ Hero Animation ใน Flutter: ภาพย่อบนหน้าจอรายการและภาพเต็มจอบนหน้าจอรายละเอียด แท็ก image-hero เชื่อมโยงสองวิเจ็ต และ FlightShuttle จะเปลี่ยนองจากการ์ดไปสู่เต็มจอโดยอัตโนมัติ
import 'package:flutter/material.dart';
class ListScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: [
Hero(
tag: 'image-hero',
child: GestureDetector(
onTap: () {
Navigator.push(context,
MaterialPageRoute(
builder: (_) => DetailScreen(),
),
);
},
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Image.network(
'https://picsum.photos/200',
width: 100, height: 100,
fit: BoxFit.cover,
),
),
),
),
],
),
);
}
}
class DetailScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Detail')),
body: Center(
child: Hero(
tag: 'image-hero',
child: Image.network(
'https://picsum.photos/800',
width: 300, height: 400,
fit: BoxFit.cover,
),
),
),
);
}
}
ประเด็นสำคัญคือการใช้ แท็กเดียวกัน (image-hero) บนทั้งสองหน้าจอ Flutter จับคู่วิเจ็ต Hero โดยแท็กระหว่างการเปลี่ยนองเส้นทาง หากแท็กแตกต่างกัน แอนิเมชั่นจะไม่เริ่มขึ้น — องค์ประกอบจะปรากฏบนหน้าจอใหม่โดยไม่มีการเปลี่ยนอง สามารถใช้ชนิดข้อมูลใดๆ ก็ได้สำหรับแท็ก แต่สตริงเป็นตัวเลือกที่น่าเชื่อถือที่สุด
ตัวอย่างของ Hero Animation ใน SwiftUI กับ NavigationStack ตัวแก้ไข matchedGeometryEffect เชื่อมโยงภาพในรายการและหน้าจอรายละเอียดผ่าน namespace และ withAnimation จะเริ่มการเปลี่ยนองที่ลื่นไหล
import SwiftUI
struct HeroExampleView: View {
@Namespace private var heroNamespace
@State private var isDetailShown = false
var body: some View {
VStack {
if isDetailShown {
DetailView(namespace: heroNamespace)
.transition(.identity)
} else {
ListView(namespace: heroNamespace)
.transition(.identity)
}
}
.animation(Animation.easeInOut(duration: 0.4),
value: isDetailShown)
}
}
struct ListView: View {
let namespace: Namespace.ID
var body: some View {
Image("thumbnail")
.resizable()
.frame(width: 100, height: 100)
.cornerRadius(12)
.matchedGeometryEffect(id: "hero", in: namespace)
}
}
struct DetailView: View {
let namespace: Namespace.ID
var body: some View {
Image("thumbnail")
.resizable()
.frame(width: 300, height: 400)
.cornerRadius(0)
.matchedGeometryEffect(id: "hero", in: namespace)
}
}
@Namespace สร้างชื่อวงสำหรับตัวระบุ matchedGeometryEffect องค์ประกอบทั้งสองใช้ id เดียวกัน (“hero”) และ namespace ที่ใช้ร่วมกัน เมื่อ isDetailShown เปลี่ยนแปลง SwiftUI จะเปลี่ยนแปลงขนาด ตำแหน่ง และรัศมีมุมระหว่างสองมุมมอง สร้างผลของฮีโร่ สำหรับ NavigationLink มาตรฐานกับ iOS 16+ ให้ใช้ navigationTransition(.hero)
ตัวอย่างของ Shared Element Transition ใน Jetpack Compose กับ SharedTransitionLayout ตัวแก้ไข sharedElement จะเปลี่ยนององค์ประกอบร่วมระหว่างหน้าจอในระหว่างการนำทาง
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun HeroExample() {
SharedTransitionLayout {
var isDetail by remember { mutableStateOf(false) }
AnimatedContent(
targetState = isDetail,
transitionSpec = {
fadeIn() togetherWith fadeOut()
}
) { detail ->
if (detail) {
Box(
modifier = Modifier
.fillMaxSize()
.sharedElement(
state = rememberSharedContentState(key = "hero"),
animatedVisibilityScope = this
)
) {
AsyncImage(
model = "https://picsum.photos/800",
contentDescription = null,
modifier = Modifier.fillMaxSize()
)
}
} else {
Box(
modifier = Modifier
.size(100.dp)
.sharedElement(
state = rememberSharedContentState(key = "hero"),
animatedVisibilityScope = this
)
) {
AsyncImage(
model = "https://picsum.photos/200",
contentDescription = null,
modifier = Modifier.fillMaxSize()
)
}
}
}
}
}
Compose SharedTransitionLayout (Compose BOM 2024.01+) ห่อหุ้มลำดับชั้น และตัวแก้ไข sharedElement ที่มีคีย์ “hero” เชื่อมโยงองค์ประกอบเริ่มต้นและตัวสุดท้าย AnimatedContent เปลี่ยนสถานะ และ Compose จะเปลี่ยนองขนาด ตำแหน่ง และรูปร่างของภาพโดยอัตโนมัติ ข้อดีสำคัญคือการรองรับแอนิเมชั่น bounds และ clipBounds ซึ่งให้ผลของการมุมกลมของการ์ดระหว่างการเปลี่ยนอง
คำถามที่พบบ่อย
ใช่ Hero Animation ใช้งานได้บนทั้งสองแพลตฟอร์ม iOS ใช้ matchedGeometryEffect ใน SwiftUI หรือ UINavigationController.transitioningDelegate Android ใช้ ActivityOptions.makeSceneTransitionAnimation หรือ Shared Element Transition ใน Jetpack Compose กับ Modifier.sharedElement Flutter นำไปใช้ผ่านวิเจ็ต Hero ซึ่งทำงานเหมือนกันบนทั้งสองแพลตฟอร์ม
ได้ Hero Animation รองรับ องค์ประกอบร่วมหลายตัว ใน Flutter ระบุวิเจ็ต Hero หลายตัวโดยใช้แท็กที่แตกต่างกัน ใน SwiftUI ใช้ matchedGeometryEffect กับตัวระบุทีแตกต่างกันบนแต่ละองค์ประกอบ แอนิเมชั่นทั้งหมดทำงานแบบขนาน — แต่ละองค์ประกอบเดินทางตามวิถีของตัวเอง
ประสิทธิภาพขึ้นอยู่กับการนำไปใช้ FlightShuttle ของ Flutter ประมวลผลภาพผ่าน RenderObject ทำให้ได้ 60fps matchedGeometryEffect ของ SwiftUI เป็นเวกเตอร์ สำหรับภาพขนาดใหญ่ ให้ใช้ precacheImage ใน Flutter หรือ prefetch ใน SwiftUI — ซึ่งจะกำจัดความล่าช้าในการเปลี่ยนองครั้งแรก Android Compose ต้องการโหลดแบบรอผ่าน AsyncImage
การเปลี่ยนอง push มาตรฐานจะเลื่อนทั้งหน้าจอ สร้างภาพลวงของกองการ์ด Hero Animation ยึดความสนใจทางสายตา ไว้กับองค์ประกอบเดียว — ผู้ใช้จะเห็นการเคลื่อนที่ต่อเนื่องของมัน ซึ่งช่วยให้เข้าใจความสัมพันธ์ระหว่างหน้าจอได้ดีขึ้น Push เหมาะสำหรับการนำทางแบบลำดับชั้น Hero เหมาะสำหรับการเน้นเนื้อหา
ไม่ Hero Animation ถูกนำไปใช้โดยเครื่องมือในตัว ใน Flutter คือวิเจ็ต Hero จากแพ็กเกจ flutter/material.dart ใน SwiftUI คือตัวแก้ไข matchedGeometryEffect ใน Android Jetpack Compose คือ SharedTransitionLayout และ Modifier.sharedElement จาก Compose มาตรฐาน ไลบรารีเพิ่มเติมจำเป็นสำหรับวิถีที่ไม่ได้มาตรฐานหรือการประมวลที่ซับซ้อน
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม