Read the original article:ArkUI “Shared Element Transition” (Hero-style Animations & Flutter Hero)
Context
Flutter’s Hero animates the same visual element between two screens. In ArkTS/ArkUI you achieve the same effect with two patterns:
-
Across pages (Router):
sharedTransition(id, options) -
Within a single scene / Navigation:
geometryTransition(id)+getUIContext().animateTo(...)
Description
Goal: when going from a list (small thumbnail) to a detail view (large artwork), smoothly animate the same content between two UI states. In ArkUI, when two elements share the same id, their geometry (position/size) is animated; you can add timing curves and secondary effects like opacity.
Solution / Approach
1) Cross-page Hero with Router — sharedTransition
Mark the element on both pages with the same id; ArkUI drives the shared-element transition automatically.
ListPage.ets
import router from '@ohos.router';
@Entry
@Component
struct ListPage {
private items: number[] = Array.from({ length: 12 }, (_, i) => i);
build() {
Grid() {
ForEach(this.items, (idx: number) => {
GridItem() {
Image($r('app.media.ic_sample'))
.width(80).height(80).borderRadius(12)
.sharedTransition(`hero-${idx}`, { duration: 350, curve: Curve.EaseInOut })
.onClick(() => router.pushUrl({ url: 'pages/DetailPage', params: { id: idx } }))
}
}, i => i.toString())
}
.columnsTemplate('1fr 1fr 1fr')
.columnsGap(12).rowsGap(12)
.padding(16)
}
}
DetailPage.ets
import router from '@ohos.router';
@Entry
@Component
struct DetailPage {
@State id: number = 0;
aboutToAppear() {
const p = router.getParams() as Record<string, number>;
this.id = p?.id ?? 0;
}
build() {
Column({ space: 16 }) {
Image($r('app.media.ic_sample'))
.width('90%').aspectRatio(1).borderRadius(24)
.sharedTransition(`hero-${this.id}`, { duration: 350, curve: Curve.EaseInOut })
Button('Back').onClick(() => router.back())
}
.width('100%').height('100%')
.justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)
}
}
Tips
- The same id must be used on both pages (e.g.,
hero-7↔hero-7). -
optionssupportsduration,curve, and optionaldelay,type,zIndex,motionPath. - You can also define overall page transitions via
pageTransition—it works alongside the shared element.
2) Single-scene / Navigator Hero — geometryTransition + animateTo
Bind two elements with the same id and wrap the state change inside animateTo to get a fluid transition.
@Entry
@Component
struct GalleryHero {
@State showDetail: boolean = false
@State selected: number = -1
private items: number[] = Array.from({ length: 9 }, (_, i) => i)
build() {
Stack({ alignContent: Alignment.Center }) {
if (!this.showDetail) {
Grid() {
ForEach(this.items, (i: number) => {
GridItem() {
Image($r('app.media.ic_sample'))
.width(80).height(80).borderRadius(12)
.geometryTransition(`g-${i}`)
.transition(TransitionEffect.Opacity) // subtle fade
.onClick(() => {
getUIContext()?.animateTo({ duration: 350, curve: Curve.EaseInOut }, () => {
this.selected = i
this.showDetail = true
})
})
}
}, i => i.toString())
}
.columnsTemplate('1fr 1fr 1fr')
.columnsGap(12).rowsGap(12)
.padding(16)
} else {
Column({ space: 16 }) {
Image($r('app.media.ic_sample'))
.width('88%').aspectRatio(1).borderRadius(24)
.geometryTransition(`g-${this.selected}`)
.transition(TransitionEffect.Opacity)
Button('Back').onClick(() => {
getUIContext()?.animateTo({ duration: 350, curve: Curve.EaseInOut }, () => {
this.showDetail = false
})
})
}
.width('100%').height('100%')
.justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center)
}
}
.width('100%').height('100%')
}
}
Tips
- Timing/curve come from
animateTo(not fromgeometryTransition). - Keep visuals consistent (e.g., small
borderRadius(12)→ largeborderRadius(24)); geometry changes read nicely during the transition. - Add a lightweight effect like
TransitionEffect.Opacityto enhance perception.
Key Takeaways
- Two primary approaches:
sharedTransition(Router, cross-page) andgeometryTransition+animateTo(single scene/Navigation). - A matching id on both ends is mandatory for the shared element.
- Timing/curve: via
optionsonsharedTransition, or viaanimateToforgeometryTransition. - Position/size animate smoothly; you can pair with fades or page transitions.
- Works great for list → detail flows on phones and wearables.
- Keywords:
sharedTransition, geometryTransition, TransitionEffect, PageTransition, UIContext.animateTo
Additional Resources
https://developer.huawei.com/consumer/en/doc/harmonyos-guides/arkts-attribute-animation-apis-V14
https://docs.openharmony.cn/pages/v4.1/en/application-dev/ui/arkts-transition-overview.md
https://docs.openharmony.cn/pages/v4.1/en/application-dev/ui/arkts-shared-element-transition.md
https://developer.huawei.com/consumer/en/doc/harmonyos-guides/arkts-router-to-navigation
https://docs.flutter.dev/ui/animations/hero-animations
Top comments (0)