@apexnative/navigation 1.0.0-alpha.5 → 1.0.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.md +1 -1
- package/android/CLAUDE.md +14 -12
- package/android/CORE.md +9 -6
- package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +36 -5
- package/android/src/main/java/dev/apexnative/navigation/options/BottomTabsOptions.kt +13 -8
- package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +3 -3
- package/android/src/main/java/dev/apexnative/navigation/react/ReloadCoordinator.kt +0 -2
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenEnvironment.kt +177 -0
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +12 -49
- package/android/src/main/java/dev/apexnative/navigation/react/ScrollEvents.kt +49 -128
- package/android/src/main/java/dev/apexnative/navigation/safearea/SafeAreaView.kt +6 -6
- package/android/src/main/java/dev/apexnative/navigation/utils/ViewExtensions.kt +0 -13
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/ArrangementController.kt +9 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsCollapseBehavior.kt +5 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +13 -10
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/CustomTabRowGeometry.kt +22 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/TabsRoom.kt +0 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentViewController.kt +7 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenSafeArea.kt +29 -11
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/externalcomponent/ExternalComponentViewController.kt +2 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +20 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +119 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +7 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetBack.kt +42 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +4 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ParentController.kt +3 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/PreparedFrame.kt +3 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackCommands.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +4 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +3 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ScrollEventListener.kt +6 -9
- package/android/src/main/java/dev/apexnative/navigation/views/animations/AnimationLayer.kt +73 -0
- package/android/src/main/java/dev/apexnative/navigation/views/animations/ValueInterpolation.kt +14 -0
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabsLayout.kt +61 -2
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/CustomTabRow.kt +72 -10
- package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +9 -2
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +11 -0
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt +318 -25
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +18 -20
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +17 -25
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +15 -22
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +12 -9
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/TextChangeAnimator.kt +84 -35
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +49 -40
- package/docs-md/architecture/transitions.md +4 -2
- package/docs-md/containers/modals.md +2 -0
- package/docs-md/containers/overlays.md +1 -1
- package/docs-md/containers/side-menu.md +1 -1
- package/docs-md/containers/tabs.md +2 -0
- package/docs-md/flows/bottom-sheets.md +1 -1
- package/docs-md/fundamentals/starting-the-app.md +1 -1
- package/docs-md/help/glossary.md +2 -2
- package/docs-md/help/troubleshooting.md +21 -0
- package/docs-md/index.json +12 -0
- package/docs-md/migration/from-expo-router.md +1 -1
- package/docs-md/migration/from-react-native-navigation.md +1 -1
- package/docs-md/migration/from-react-navigation.md +2 -2
- package/docs-md/screen-ui/back-and-gestures.md +14 -0
- package/docs-md/screen-ui/bar-buttons.md +4 -4
- package/docs-md/screen-ui/search.md +1 -1
- package/docs-md/screen-ui/system-ui.md +16 -11
- package/docs-md/screen-ui/toolbar-and-fab.md +2 -2
- package/docs-md/screen-ui/top-bar.md +8 -4
- package/ios/Animations/ANNFrameDriver.mm +2 -0
- package/ios/Animations/Elements/ANNSharedElementFlight.mm +70 -4
- package/ios/Animations/Interpolators/ANNInterpolate.h +2 -30
- package/ios/Animations/Interpolators/ANNTransformParts.h +48 -0
- package/ios/Animations/Interpolators/ANNTransformParts.mm +507 -0
- package/ios/Animations/Interpolators/ANNViewTracks.h +6 -4
- package/ios/Animations/Interpolators/ANNViewTracks.mm +49 -9
- package/ios/CLAUDE.md +17 -15
- package/ios/CORE.md +24 -20
- package/ios/Contract/ANNCommandRunner.h +8 -7
- package/ios/Contract/ANNCommandRunner.mm +21 -27
- package/ios/Contract/ANNLayoutNode.h +3 -3
- package/ios/Contract/ANNLayoutReader.h +3 -3
- package/ios/Core/ANNJavaScriptCalls.h +11 -12
- package/ios/Core/ANNJavaScriptCalls.mm +7 -16
- package/ios/Core/ANNReloadCoordinator.mm +4 -8
- package/ios/Core/ApexNavigation+Internal.h +1 -1
- package/ios/Layouts/Arrangement/ANNArrangementState.h +2 -17
- package/ios/Layouts/Arrangement/ANNArrangementState.mm +0 -38
- package/ios/Layouts/Arrangement/ANNArrangementViewController.mm +4 -4
- package/ios/Layouts/Screen/ANNComponentViewController.h +2 -2
- package/ios/Layouts/Screen/ANNComponentViewController.mm +9 -22
- package/ios/Layouts/Screen/ANNPreviewInteraction.h +7 -4
- package/ios/Layouts/Screen/ANNPreviewInteraction.mm +72 -10
- package/ios/Layouts/Screen/ANNScreenEnvironment.h +63 -0
- package/ios/Layouts/Screen/ANNScreenEnvironment.mm +181 -0
- package/ios/Layouts/Screen/ANNScreenRegions.h +3 -18
- package/ios/Layouts/Screen/ANNScreenRegions.mm +0 -38
- package/ios/Layouts/Screen/ANNScreenSafeArea.h +4 -9
- package/ios/Layouts/Screen/ANNScreenSafeArea.mm +14 -16
- package/ios/Layouts/Stack/ANNPreviewPush.h +7 -6
- package/ios/Layouts/Stack/ANNPreviewPush.mm +57 -5
- package/ios/Layouts/Tabs/ANNBottomTabsAccessoryView.h +12 -1
- package/ios/Layouts/Tabs/ANNBottomTabsAccessoryView.mm +23 -0
- package/ios/Layouts/Tabs/ANNBottomTabsController.mm +2 -1
- package/ios/Layouts/Tabs/ANNBottomTabsCustomRow.mm +8 -3
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.h +4 -0
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.mm +123 -36
- package/ios/Layouts/Tabs/ANNTabsDesign.h +6 -5
- package/ios/Layouts/Tabs/ANNTabsDesign.mm +9 -1
- package/ios/Layouts/Tabs/UITabBar+utils.h +5 -5
- package/ios/Layouts/Tabs/UITabBar+utils.mm +32 -13
- package/ios/Options/ANNBottomTabsOptions.h +2 -0
- package/ios/Options/ANNBottomTabsOptions.mm +1 -0
- package/ios/Options/ANNOptions.h +18 -0
- package/ios/Options/ANNOptions.mm +112 -1
- package/ios/Options/ANNOptionsReader.mm +110 -0
- package/ios/Options/Params/ANNParam.h +15 -0
- package/ios/Options/Params/ANNParam.mm +61 -0
- package/ios/Options/Params/Color.h +3 -0
- package/ios/Options/Params/Color.mm +21 -0
- package/ios/Options/Params/Image.h +4 -0
- package/ios/Options/Params/Image.mm +20 -0
- package/ios/React/ANNReactView.mm +6 -25
- package/ios/React/ANNSafeAreaViewComponentView.mm +26 -15
- package/ios/React/ANNSurfaceHostingView.h +6 -0
- package/ios/React/ANNSurfaceHostingView.mm +116 -0
- package/ios/TopBar/ANNBackButtonPresenter.mm +6 -5
- package/ios/TopBar/ANNBarAppearance.h +2 -1
- package/ios/TopBar/ANNBarAppearance.mm +111 -30
- package/ios/TopBar/ANNBarBackgroundComponent.mm +16 -0
- package/ios/TopBar/ANNBarButtonComponentView.h +4 -4
- package/ios/TopBar/ANNBarButtonComponentView.mm +1 -1
- package/ios/TopBar/ANNBarButtons.h +5 -3
- package/ios/TopBar/ANNBarButtons.mm +186 -17
- package/ios/TopBar/ANNLargeTitlePresenter.mm +24 -4
- package/ios/TopBar/ANNMainList.h +3 -0
- package/ios/TopBar/ANNMainList.mm +4 -0
- package/ios/TopBar/ANNScrollEdgeEffects.h +22 -6
- package/ios/TopBar/ANNScrollEdgeEffects.mm +123 -16
- package/ios/TopBar/ANNSearchPresenter.mm +2 -2
- package/ios/TopBar/ANNSearchResultsViewController.mm +5 -5
- package/ios/TopBar/ANNTopBarPresenter.h +3 -2
- package/ios/TopBar/ANNTopBarPresenter.mm +95 -22
- package/ios/TurboModules/ANNTurboModule.mm +20 -16
- package/lib/module/ARCHITECTURE.md +8 -4
- package/lib/module/api/hooks/useSafeAreaInsets.js +4 -3
- package/lib/module/api/hooks/useSafeAreaInsets.js.map +1 -1
- package/lib/module/api/options/OptionsChain.js +2 -2
- package/lib/module/engine/commands.js +21 -1
- package/lib/module/engine/commands.js.map +1 -1
- package/lib/module/engine/screens.js +29 -4
- package/lib/module/engine/screens.js.map +1 -1
- package/lib/typescript/api/app/createNavigation.d.ts +3 -3
- package/lib/typescript/api/hooks/useSafeAreaInsets.d.ts +4 -3
- package/lib/typescript/api/hooks/useSafeAreaInsets.d.ts.map +1 -1
- package/lib/typescript/api/options/OptionsChain.d.ts +2 -2
- package/lib/typescript/engine/commands.d.ts.map +1 -1
- package/lib/typescript/engine/screens.d.ts +20 -6
- package/lib/typescript/engine/screens.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/animations.d.ts +12 -7
- package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/bottomTabs.d.ts +18 -5
- package/lib/typescript/interfaces/options/bottomTabs.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/buttons.d.ts +8 -8
- package/lib/typescript/interfaces/options/system.d.ts +8 -5
- package/lib/typescript/interfaces/options/system.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/toolbar.d.ts +2 -2
- package/lib/typescript/interfaces/options/topBar.d.ts +32 -29
- package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ARCHITECTURE.md +8 -4
- package/src/api/app/createNavigation.ts +3 -3
- package/src/api/hooks/useSafeAreaInsets.ts +4 -3
- package/src/api/options/OptionsChain.ts +2 -2
- package/src/engine/commands.ts +30 -6
- package/src/engine/screens.tsx +29 -8
- package/src/interfaces/options/animations.ts +12 -7
- package/src/interfaces/options/bottomTabs.ts +18 -5
- package/src/interfaces/options/buttons.ts +8 -8
- package/src/interfaces/options/system.ts +8 -5
- package/src/interfaces/options/toolbar.ts +2 -2
- package/src/interfaces/options/topBar.ts +32 -29
- package/android/src/main/java/dev/apexnative/navigation/react/ArrangementStates.kt +0 -42
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenInsets.kt +0 -70
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/CustomTabRowPlacer.kt +0 -223
- package/ios/Layouts/Screen/ANNScreenInsets.h +0 -30
- package/ios/Layouts/Screen/ANNScreenInsets.mm +0 -70
|
@@ -14,15 +14,18 @@ import kotlin.math.min
|
|
|
14
14
|
import kotlin.math.roundToInt
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* A shared image of another kind (an `ImageView` that isn't React Native's):
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* back as it was at the end.
|
|
17
|
+
* A shared image of another kind (an `ImageView` that isn't React Native's): from where `from` is drawn to where it is
|
|
18
|
+
* drawn in its screen ([FlightGeometry]), unscaled and turned about its middle; its image matrix from `from`'s fit
|
|
19
|
+
* (`centerCrop`, `fitXY`, `fitCenter`, `centerInside`) to its own, the part seen from `from`'s size to its own, the
|
|
20
|
+
* corner radius of the parent that clips it (`borderRadius` with `overflow: hidden`) by its outline. Another fit: the
|
|
21
|
+
* pair moves without the change of its matrix, and a line goes to the log. Everything goes back as it was at the end.
|
|
22
|
+
* Main thread only.
|
|
22
23
|
*/
|
|
23
24
|
internal class ImageMatrixAnimator(
|
|
24
25
|
private val from: ImageView,
|
|
25
26
|
private val to: ImageView,
|
|
27
|
+
/** Where [to] was in its screen: what it flies to. */
|
|
28
|
+
private val toStandIn: View?,
|
|
26
29
|
private val layer: TransitionLayer,
|
|
27
30
|
/** The parents the views are clipped by in their screens: [to]'s is the one it had before the layer. */
|
|
28
31
|
private val fromParent: View?,
|
|
@@ -32,8 +35,7 @@ internal class ImageMatrixAnimator(
|
|
|
32
35
|
) {
|
|
33
36
|
/** The animator; none when [to] has no place in the layer. */
|
|
34
37
|
fun create(): Animator? {
|
|
35
|
-
val geometry = FlightGeometry.of(from, to, layer) ?: return null
|
|
36
|
-
val toRect = geometry.to
|
|
38
|
+
val geometry = FlightGeometry.of(from, to, toStandIn, layer) ?: return null
|
|
37
39
|
val fromFit = from.scaleType
|
|
38
40
|
val toFit = to.scaleType
|
|
39
41
|
// The image whose matrix changes; none when a fit isn't one of the four or there is no image to fit.
|
|
@@ -42,9 +44,10 @@ internal class ImageMatrixAnimator(
|
|
|
42
44
|
}
|
|
43
45
|
val fits = image != null
|
|
44
46
|
if (!fits) log("The image '$id' fits by ${fromFit.name} and ${toFit.name}: it moves without a change of fit")
|
|
45
|
-
val fromRadius = clipRadius(fromParent) * geometry.
|
|
46
|
-
val toRadius = clipRadius(toParent)
|
|
47
|
+
val fromRadius = clipRadius(fromParent) * geometry.from.scale
|
|
48
|
+
val toRadius = clipRadius(toParent) * geometry.to.scale
|
|
47
49
|
val outline = FlightOutline()
|
|
50
|
+
val moved = MovedProperties(to)
|
|
48
51
|
var savedProvider: ViewOutlineProvider? = null
|
|
49
52
|
var savedClip = false
|
|
50
53
|
val savedMatrix = Matrix(to.imageMatrix)
|
|
@@ -55,13 +58,11 @@ internal class ImageMatrixAnimator(
|
|
|
55
58
|
return MoveAnimator().apply {
|
|
56
59
|
fun frame(time: Float) {
|
|
57
60
|
val f = curve.getInterpolation(time)
|
|
58
|
-
val
|
|
59
|
-
val
|
|
60
|
-
val
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
to.translationX = left - toRect.left
|
|
64
|
-
to.translationY = top - toRect.top
|
|
61
|
+
val at = geometry.at(f, time)
|
|
62
|
+
val width = at.width.roundToInt()
|
|
63
|
+
val height = at.height.roundToInt()
|
|
64
|
+
layer.place(to, geometry.toLeft, geometry.toTop, width, height)
|
|
65
|
+
moved.show(geometry, at, width, height)
|
|
65
66
|
outline.width = width
|
|
66
67
|
outline.height = height
|
|
67
68
|
outline.radius = ValueInterpolation.number(fromRadius, toRadius, f, time)
|
|
@@ -92,10 +93,7 @@ internal class ImageMatrixAnimator(
|
|
|
92
93
|
}
|
|
93
94
|
to.outlineProvider = savedProvider
|
|
94
95
|
to.clipToOutline = savedClip
|
|
95
|
-
to.
|
|
96
|
-
to.translationY = 0f
|
|
97
|
-
layer.place(to, toRect.left.toInt(), toRect.top.toInt(), toRect.width().toInt(),
|
|
98
|
-
toRect.height().toInt())
|
|
96
|
+
moved.end(geometry, landed) { w, h -> layer.place(to, geometry.toLeft, geometry.toTop, w, h) }
|
|
99
97
|
}
|
|
100
98
|
}
|
|
101
99
|
}
|
package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt
CHANGED
|
@@ -11,49 +11,45 @@ import dev.apexnative.navigation.views.element.TransitionLayer
|
|
|
11
11
|
import kotlin.math.roundToInt
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* A shared React view without children moves itself: from `from`
|
|
15
|
-
* size and corner radius by an outline that grows from `from`'s (at the scale
|
|
16
|
-
* laid out at the larger of the two sizes
|
|
17
|
-
* color from `from`'s to its own in Lab, ending
|
|
18
|
-
*
|
|
14
|
+
* A shared React view without children moves itself: from where `from` is drawn to where it is drawn in its screen
|
|
15
|
+
* ([FlightGeometry]); its size and corner radius by an outline that grows from `from`'s (at the scale it is drawn at)
|
|
16
|
+
* to its own while it is laid out at the larger of the two sizes, unscaled and turned about the outline's middle; its
|
|
17
|
+
* own radius taken off meanwhile and put back as it was; its background color from `from`'s to its own in Lab, ending
|
|
18
|
+
* exactly on its own (`BackgroundStyleApplicator`). Main thread only.
|
|
19
19
|
*/
|
|
20
20
|
internal class PlainViewAnimator(
|
|
21
21
|
private val from: View,
|
|
22
22
|
private val to: View,
|
|
23
|
+
/** Where [to] was in its screen: what it flies to. */
|
|
24
|
+
private val toStandIn: View?,
|
|
23
25
|
private val layer: TransitionLayer,
|
|
24
26
|
) {
|
|
25
27
|
/** The animator; none when [to] has no place in the layer. */
|
|
26
28
|
fun create(): Animator? {
|
|
27
|
-
val geometry = FlightGeometry.of(from, to, layer) ?: return null
|
|
28
|
-
val fromRadius = from.borderRadius * geometry.
|
|
29
|
-
val toRadius = to.borderRadius
|
|
29
|
+
val geometry = FlightGeometry.of(from, to, toStandIn, layer) ?: return null
|
|
30
|
+
val fromRadius = from.borderRadius * geometry.from.scale
|
|
31
|
+
val toRadius = to.borderRadius * geometry.to.scale
|
|
30
32
|
val fromColor = BackgroundStyleApplicator.getBackgroundColor(from)
|
|
31
33
|
val toColor = BackgroundStyleApplicator.getBackgroundColor(to)
|
|
32
|
-
val fromRotation = from.rotation
|
|
33
|
-
val toRotation = to.rotation
|
|
34
34
|
val ownRadius = BackgroundStyleApplicator.getBorderRadius(to, RADIUS)
|
|
35
35
|
val outline = FlightOutline()
|
|
36
|
+
val moved = MovedProperties(to)
|
|
36
37
|
var savedProvider: ViewOutlineProvider? = null
|
|
37
38
|
var savedClip = false
|
|
38
39
|
val (width, height) = geometry.largest
|
|
39
|
-
val toRect = geometry.to
|
|
40
40
|
return MoveAnimator().apply {
|
|
41
41
|
fun frame(time: Float) {
|
|
42
42
|
val f = curve.getInterpolation(time)
|
|
43
|
-
val
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
to.translationY = top - toRect.top
|
|
47
|
-
val from = geometry.from
|
|
48
|
-
outline.width = ValueInterpolation.number(from.width(), toRect.width(), f, time).roundToInt()
|
|
49
|
-
outline.height = ValueInterpolation.number(from.height(), toRect.height(), f, time).roundToInt()
|
|
43
|
+
val at = geometry.at(f, time)
|
|
44
|
+
outline.width = at.width.roundToInt()
|
|
45
|
+
outline.height = at.height.roundToInt()
|
|
50
46
|
outline.radius = ValueInterpolation.number(fromRadius, toRadius, f, time)
|
|
47
|
+
moved.show(geometry, at, outline.width, outline.height)
|
|
51
48
|
to.invalidateOutline()
|
|
52
49
|
if (fromColor != toColor) {
|
|
53
50
|
val color = ValueInterpolation.color(fromColor, toColor, f, time)
|
|
54
51
|
BackgroundStyleApplicator.setBackgroundColor(to, color)
|
|
55
52
|
}
|
|
56
|
-
to.rotation = ValueInterpolation.number(fromRotation, toRotation, f, time)
|
|
57
53
|
}
|
|
58
54
|
onStart {
|
|
59
55
|
savedProvider = to.outlineProvider
|
|
@@ -61,21 +57,17 @@ internal class PlainViewAnimator(
|
|
|
61
57
|
if (ownRadius != null) BackgroundStyleApplicator.setBorderRadius(to, RADIUS, null)
|
|
62
58
|
to.outlineProvider = outline
|
|
63
59
|
to.clipToOutline = true
|
|
64
|
-
layer.place(to,
|
|
60
|
+
layer.place(to, geometry.toLeft, geometry.toTop, width, height)
|
|
65
61
|
frame(0f)
|
|
66
62
|
}
|
|
67
63
|
onFrame { frame(it) }
|
|
68
64
|
// A cancelled flight ends too: the view goes back whole.
|
|
69
65
|
onEnd {
|
|
70
|
-
to.translationX = 0f
|
|
71
|
-
to.translationY = 0f
|
|
72
|
-
to.rotation = toRotation
|
|
73
66
|
if (fromColor != toColor) BackgroundStyleApplicator.setBackgroundColor(to, toColor)
|
|
74
67
|
if (ownRadius != null) BackgroundStyleApplicator.setBorderRadius(to, RADIUS, ownRadius)
|
|
75
68
|
to.outlineProvider = savedProvider
|
|
76
69
|
to.clipToOutline = savedClip
|
|
77
|
-
layer.place(to,
|
|
78
|
-
toRect.height().toInt())
|
|
70
|
+
moved.end(geometry, landed) { w, h -> layer.place(to, geometry.toLeft, geometry.toTop, w, h) }
|
|
79
71
|
}
|
|
80
72
|
}
|
|
81
73
|
}
|
|
@@ -1,46 +1,43 @@
|
|
|
1
1
|
package dev.apexnative.navigation.views.element.animators
|
|
2
2
|
|
|
3
3
|
import android.animation.Animator
|
|
4
|
+
import android.view.View
|
|
4
5
|
import com.facebook.drawee.drawable.ScalingUtils
|
|
5
6
|
import com.facebook.react.views.image.ReactImageView
|
|
6
|
-
import dev.apexnative.navigation.views.animations.ValueInterpolation
|
|
7
7
|
import dev.apexnative.navigation.views.element.TransitionLayer
|
|
8
8
|
import kotlin.math.roundToInt
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* A shared React image:
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* A shared React image: from where `from` is drawn to where it is drawn in its screen ([FlightGeometry]), laid out at
|
|
12
|
+
* the size of each frame, unscaled and turned about its middle; its content by Fresco's scale type going from `from`'s
|
|
13
|
+
* fit to its own (`ScalingUtils.InterpolatingScaleType`); Fresco's own fade off meanwhile. Everything goes back as it
|
|
14
|
+
* was at the end. Main thread only.
|
|
15
15
|
*/
|
|
16
16
|
internal class ReactImageAnimator(
|
|
17
17
|
private val from: ReactImageView,
|
|
18
18
|
private val to: ReactImageView,
|
|
19
|
+
/** Where [to] was in its screen: what it flies to. */
|
|
20
|
+
private val toStandIn: View?,
|
|
19
21
|
private val layer: TransitionLayer,
|
|
20
22
|
) {
|
|
21
23
|
/** The animator; none when [to] has no place in the layer or no hierarchy. */
|
|
22
24
|
fun create(): Animator? {
|
|
23
25
|
if (!from.hasHierarchy() || !to.hasHierarchy()) return null
|
|
24
|
-
val geometry = FlightGeometry.of(from, to, layer) ?: return null
|
|
25
|
-
val toRect = geometry.to
|
|
26
|
+
val geometry = FlightGeometry.of(from, to, toStandIn, layer) ?: return null
|
|
26
27
|
val toType = to.hierarchy.actualImageScaleType ?: ScalingUtils.ScaleType.CENTER_CROP
|
|
27
28
|
val fromType = from.hierarchy.actualImageScaleType ?: ScalingUtils.ScaleType.CENTER_CROP
|
|
28
29
|
val scale = ScalingUtils.InterpolatingScaleType(fromType, toType)
|
|
29
|
-
val
|
|
30
|
-
val toRotation = to.rotation
|
|
30
|
+
val moved = MovedProperties(to)
|
|
31
31
|
var savedFade = 0
|
|
32
32
|
return MoveAnimator().apply {
|
|
33
33
|
fun frame(time: Float) {
|
|
34
34
|
val f = curve.getInterpolation(time)
|
|
35
|
-
val
|
|
36
|
-
val
|
|
37
|
-
val
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
to.translationX = left - toRect.left
|
|
41
|
-
to.translationY = top - toRect.top
|
|
35
|
+
val at = geometry.at(f, time)
|
|
36
|
+
val width = at.width.roundToInt()
|
|
37
|
+
val height = at.height.roundToInt()
|
|
38
|
+
layer.place(to, geometry.toLeft, geometry.toTop, width, height)
|
|
39
|
+
moved.show(geometry, at, width, height)
|
|
42
40
|
scale.setValue(if (time >= 1f) 1f else f)
|
|
43
|
-
to.rotation = ValueInterpolation.number(fromRotation, toRotation, f, time)
|
|
44
41
|
to.invalidate()
|
|
45
42
|
}
|
|
46
43
|
onStart {
|
|
@@ -53,11 +50,7 @@ internal class ReactImageAnimator(
|
|
|
53
50
|
onEnd {
|
|
54
51
|
to.hierarchy.setActualImageScaleType(toType)
|
|
55
52
|
to.hierarchy.fadeDuration = savedFade
|
|
56
|
-
to.
|
|
57
|
-
to.translationY = 0f
|
|
58
|
-
to.rotation = toRotation
|
|
59
|
-
layer.place(to, toRect.left.toInt(), toRect.top.toInt(), toRect.width().toInt(),
|
|
60
|
-
toRect.height().toInt())
|
|
53
|
+
moved.end(geometry, landed) { w, h -> layer.place(to, geometry.toLeft, geometry.toTop, w, h) }
|
|
61
54
|
}
|
|
62
55
|
}
|
|
63
56
|
}
|
|
@@ -29,13 +29,14 @@ internal object SharedAnimatorFactory {
|
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* The flight of the pair [from] → [to]; [to] is in [layer] at its margins, at its real alpha (the pictures are
|
|
32
|
-
* taken now). [toParent] is the parent [to] had in its screen
|
|
33
|
-
* change, a picture that can't be taken, no place).
|
|
32
|
+
* taken now). [toParent] is the parent [to] had in its screen, [toStandIn] the view in its place there. None: the
|
|
33
|
+
* pair doesn't fly (a text that didn't change, a picture that can't be taken, no place).
|
|
34
34
|
*/
|
|
35
35
|
fun create(
|
|
36
36
|
from: View,
|
|
37
37
|
to: View,
|
|
38
38
|
toParent: View?,
|
|
39
|
+
toStandIn: View?,
|
|
39
40
|
layer: TransitionLayer,
|
|
40
41
|
options: SharedElementTransitionOptions,
|
|
41
42
|
fallbackDuration: Long,
|
|
@@ -43,17 +44,19 @@ internal object SharedAnimatorFactory {
|
|
|
43
44
|
): Flight? {
|
|
44
45
|
val flight = when {
|
|
45
46
|
from is ReactTextView && to is ReactTextView -> pictures(
|
|
46
|
-
TextChangeAnimator(from, to).takeIf { it.shouldAnimate() }?.create(options),
|
|
47
|
+
TextChangeAnimator(from, to, toStandIn).takeIf { it.shouldAnimate() }?.create(options),
|
|
47
48
|
)
|
|
48
49
|
hasChildren(from) || hasChildren(to) -> pictures(
|
|
49
|
-
ViewChangeAnimator(from, to).takeIf { it.shouldAnimate() }?.create(options),
|
|
50
|
-
) ?: moves(PlainViewAnimator(from, to, layer).create())
|
|
51
|
-
from is ReactImageView && to is ReactImageView ->
|
|
50
|
+
ViewChangeAnimator(from, to, toStandIn).takeIf { it.shouldAnimate() }?.create(options),
|
|
51
|
+
) ?: moves(PlainViewAnimator(from, to, toStandIn, layer).create())
|
|
52
|
+
from is ReactImageView && to is ReactImageView ->
|
|
53
|
+
moves(ReactImageAnimator(from, to, toStandIn, layer).create())
|
|
52
54
|
from is ImageView && to is ImageView -> moves(
|
|
53
|
-
ImageMatrixAnimator(
|
|
54
|
-
.
|
|
55
|
+
ImageMatrixAnimator(
|
|
56
|
+
from, to, toStandIn, layer, from.parent as? View, toParent, options.toId.get(""), log,
|
|
57
|
+
).create(),
|
|
55
58
|
)
|
|
56
|
-
else -> moves(PlainViewAnimator(from, to, layer).create())
|
|
59
|
+
else -> moves(PlainViewAnimator(from, to, toStandIn, layer).create())
|
|
57
60
|
} ?: return null
|
|
58
61
|
val animator = flight.animator
|
|
59
62
|
(animator as? MoveAnimator)?.element = pairName(options.fromId.get(""), options.toId.get(""))
|
|
@@ -14,24 +14,30 @@ import android.widget.TextView
|
|
|
14
14
|
import com.facebook.react.views.text.ReactTextView
|
|
15
15
|
import dev.apexnative.navigation.options.SharedElementTransitionOptions
|
|
16
16
|
import dev.apexnative.navigation.utils.TextViewUtils
|
|
17
|
-
import dev.apexnative.navigation.
|
|
17
|
+
import dev.apexnative.navigation.views.animations.ValueInterpolation
|
|
18
|
+
import kotlin.math.abs
|
|
19
|
+
import kotlin.math.min
|
|
18
20
|
import kotlin.math.roundToInt
|
|
19
21
|
|
|
20
22
|
// The text moves as two pictures of itself, of the text that goes away and of the text that comes, the same text or
|
|
21
23
|
// another. Both keep their proportions, scaled by the font size from a point of the first line, and one turns into the
|
|
22
24
|
// other in a quarter of the move: at its start when the text grows, at its end when it shrinks, so the larger picture
|
|
23
|
-
// shows most of the way (as on iOS).
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
|
|
25
|
+
// shows most of the way (as on iOS). Each text is where it is drawn, at the scale and angle its parents and its own
|
|
26
|
+
// properties give it; the angle goes along the shorter arc (FlightGeometry). The pictures are drawing commands, not
|
|
27
|
+
// pixels: they stay sharp at any scale and keep the colors, emoji and background of the text. They are drawn in the
|
|
28
|
+
// overlay of the transition layer while `to` is hidden.
|
|
29
|
+
class TextChangeAnimator internal constructor(
|
|
30
|
+
from: View,
|
|
31
|
+
to: View,
|
|
32
|
+
// Where `to` was in its screen: what the text flies to.
|
|
33
|
+
private val toStandIn: View?,
|
|
34
|
+
) : PropertyAnimatorCreator<ReactTextView>(from, to, ReactTextView::class.java) {
|
|
27
35
|
private val pictures by lazy { pictures() }
|
|
28
36
|
|
|
29
37
|
override fun shouldAnimateProperty(fromChild: ReactTextView, toChild: ReactTextView): Boolean {
|
|
30
|
-
val
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
TextViewUtils.getTextSize(fromChild) != TextViewUtils.getTextSize(toChild) || fromPlace != toPlace
|
|
34
|
-
) && pictures != null
|
|
38
|
+
val (start, end) = pictures ?: return false
|
|
39
|
+
return fromChild.text.toString() != toChild.text.toString() ||
|
|
40
|
+
TextViewUtils.getTextSize(fromChild) != TextViewUtils.getTextSize(toChild) || start.drawnElsewhere(end)
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
override fun create(options: SharedElementTransitionOptions): Animator {
|
|
@@ -49,43 +55,63 @@ class TextChangeAnimator(from: View, to: View) : PropertyAnimatorCreator<ReactTe
|
|
|
49
55
|
drawable.update(it, curve.getInterpolation(it))
|
|
50
56
|
layer.invalidate()
|
|
51
57
|
}
|
|
58
|
+
// A flight that landed where `to` shows only in its screen keeps its last picture: the transition takes the
|
|
59
|
+
// layer away with it and shows `to` in its place.
|
|
60
|
+
val holds = FlightEnd.holdsAtEnd(to, toStandIn, layer)
|
|
52
61
|
onEnd {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
if (!holds || !landed) {
|
|
63
|
+
layer.overlay.remove(drawable)
|
|
64
|
+
layer.invalidate()
|
|
65
|
+
to.alpha = end.alpha
|
|
66
|
+
}
|
|
56
67
|
}
|
|
57
68
|
}
|
|
58
69
|
}
|
|
59
70
|
|
|
60
|
-
// `to` is in the transition layer already
|
|
61
|
-
// it is
|
|
62
|
-
// over itself); others from the start of the line, as on iOS: a centered
|
|
63
|
-
// start.
|
|
71
|
+
// `to` is in the transition layer already: the pictures are drawn in its coordinates. `from` is where it is drawn
|
|
72
|
+
// on the screen, `to` where it is drawn once back in its screen. Texts that line up the same way go from that point
|
|
73
|
+
// of their first lines (the same text lies over itself); others from the start of the line, as on iOS: a centered
|
|
74
|
+
// text no longer sticks out of a text at the start.
|
|
64
75
|
private fun pictures(): Pair<TextPicture, TextPicture>? {
|
|
65
|
-
|
|
66
|
-
val toPlace = to.layoutParams as? ViewGroup.MarginLayoutParams ?: return null
|
|
76
|
+
val layer = to.parent as? ViewGroup ?: return null
|
|
67
77
|
val fromLayout = (from as TextView).layout ?: return null
|
|
68
78
|
val toLayout = (to as TextView).layout ?: return null
|
|
69
79
|
val edge = lineEdge(fromLayout).takeIf { it == lineEdge(toLayout) } ?: LineEdge.START
|
|
70
|
-
val
|
|
71
|
-
|
|
72
|
-
val start = TextPicture.of(from, fromPlace.x, fromPlace.y, edge) ?: return null
|
|
73
|
-
val end = TextPicture.of(to, toPlace.leftMargin, toPlace.topMargin, edge) ?: return null
|
|
80
|
+
val start = TextPicture.of(from, LayerPlace.of(from, layer), edge) ?: return null
|
|
81
|
+
val end = TextPicture.of(to, LayerPlace.of(to, layer, toStandIn), edge) ?: return null
|
|
74
82
|
return start to end
|
|
75
83
|
}
|
|
76
84
|
}
|
|
77
85
|
|
|
78
|
-
// A text's picture, the point of its first line it is scaled from (in the view) and where that point is in the
|
|
79
|
-
// transition layer, its font size and opacity.
|
|
86
|
+
// A text's picture, the point of its first line it is scaled from (in the view) and where that point is drawn in the
|
|
87
|
+
// transition layer, its font size, the scale and angle it is drawn at, and its opacity.
|
|
80
88
|
private class TextPicture(
|
|
81
89
|
val picture: Picture,
|
|
82
90
|
val anchor: PointF,
|
|
83
91
|
val position: PointF,
|
|
84
92
|
val fontSize: Float,
|
|
93
|
+
val scale: Float,
|
|
94
|
+
val rotation: Float,
|
|
95
|
+
val mirrorX: Float,
|
|
96
|
+
val mirrorY: Float,
|
|
85
97
|
val alpha: Float
|
|
86
98
|
) {
|
|
99
|
+
/** The font size it shows at. */
|
|
100
|
+
val shownSize: Float get() = fontSize * scale
|
|
101
|
+
|
|
102
|
+
/** Whether [other] is drawn elsewhere, larger or smaller, or turned. */
|
|
103
|
+
fun drawnElsewhere(other: TextPicture): Boolean =
|
|
104
|
+
abs(position.x - other.position.x) > SAME_PLACE || abs(position.y - other.position.y) > SAME_PLACE ||
|
|
105
|
+
abs(scale - other.scale) > SAME_SCALE || abs(rotation - other.rotation) > SAME_ANGLE ||
|
|
106
|
+
mirrorX != other.mirrorX || mirrorY != other.mirrorY
|
|
107
|
+
|
|
87
108
|
companion object {
|
|
88
|
-
|
|
109
|
+
// Within half a pixel, a thousandth of the scale and a tenth of a degree: the same.
|
|
110
|
+
private const val SAME_PLACE = 0.5f
|
|
111
|
+
private const val SAME_SCALE = 0.001f
|
|
112
|
+
private const val SAME_ANGLE = 0.1f
|
|
113
|
+
|
|
114
|
+
fun of(view: TextView, place: LayerPlace, edge: LineEdge): TextPicture? {
|
|
89
115
|
val layout = view.layout ?: return null
|
|
90
116
|
val fontSize = TextViewUtils.getTextSize(view).takeIf { it > 0 } ?: view.textSize
|
|
91
117
|
if (view.width <= 0 || view.height <= 0 || layout.lineCount == 0 || fontSize <= 0) return null
|
|
@@ -95,7 +121,14 @@ private class TextPicture(
|
|
|
95
121
|
view.totalPaddingLeft + edgeX(layout, edge),
|
|
96
122
|
view.totalPaddingTop + layout.getLineBaseline(0).toFloat()
|
|
97
123
|
)
|
|
98
|
-
|
|
124
|
+
val position = place.point(anchor.x, anchor.y)
|
|
125
|
+
val scale = min(abs(place.scaleX), abs(place.scaleY))
|
|
126
|
+
return TextPicture(
|
|
127
|
+
picture, anchor, PointF(position[0], position[1]), fontSize, scale, place.rotation,
|
|
128
|
+
mirrorX = if (place.scaleX < 0) -1f else 1f,
|
|
129
|
+
mirrorY = if (place.scaleY < 0) -1f else 1f,
|
|
130
|
+
alpha = view.alpha,
|
|
131
|
+
)
|
|
99
132
|
}
|
|
100
133
|
|
|
101
134
|
// That point of the first line, in the direction of the text.
|
|
@@ -127,15 +160,19 @@ private fun lineEdge(layout: Layout): LineEdge {
|
|
|
127
160
|
}
|
|
128
161
|
}
|
|
129
162
|
|
|
130
|
-
// Both pictures where the move has brought the text: the font size and the point of the first
|
|
131
|
-
// to the end along the transition's curve, the pictures change over by the clock.
|
|
163
|
+
// Both pictures where the move has brought the text: the font size it shows at, its angle and the point of the first
|
|
164
|
+
// line go from the start to the end along the transition's curve, the pictures change over by the clock.
|
|
132
165
|
private class TextMorphDrawable(private val start: TextPicture, private val end: TextPicture) : Drawable() {
|
|
133
|
-
private val grows = end.
|
|
166
|
+
private val grows = end.shownSize >= start.shownSize
|
|
134
167
|
private var progress = 0f
|
|
168
|
+
private var time = 0f
|
|
135
169
|
private var changeOver = 0f
|
|
170
|
+
private var mirrorX = 1f
|
|
171
|
+
private var mirrorY = 1f
|
|
136
172
|
|
|
137
173
|
fun update(time: Float, progress: Float) {
|
|
138
174
|
this.progress = progress
|
|
175
|
+
this.time = time
|
|
139
176
|
val changeOverStart = if (grows) 0f else 1f - CHANGE_OVER
|
|
140
177
|
changeOver = ((time - changeOverStart) / CHANGE_OVER).coerceIn(0f, 1f)
|
|
141
178
|
}
|
|
@@ -143,18 +180,30 @@ private class TextMorphDrawable(private val start: TextPicture, private val end:
|
|
|
143
180
|
override fun draw(canvas: Canvas) {
|
|
144
181
|
val x = start.position.x + (end.position.x - start.position.x) * progress
|
|
145
182
|
val y = start.position.y + (end.position.y - start.position.y) * progress
|
|
146
|
-
val fontSize = start.
|
|
147
|
-
|
|
148
|
-
|
|
183
|
+
val fontSize = start.shownSize + (end.shownSize - start.shownSize) * progress
|
|
184
|
+
val rotation = ValueInterpolation.angle(start.rotation, end.rotation, progress, time)
|
|
185
|
+
mirrorX = ValueInterpolation.number(start.mirrorX, end.mirrorX, progress, time)
|
|
186
|
+
mirrorY = ValueInterpolation.number(start.mirrorY, end.mirrorY, progress, time)
|
|
187
|
+
draw(canvas, start, x, y, fontSize, rotation, 1f - changeOver)
|
|
188
|
+
draw(canvas, end, x, y, fontSize, rotation, changeOver)
|
|
149
189
|
}
|
|
150
190
|
|
|
151
|
-
private fun draw(
|
|
191
|
+
private fun draw(
|
|
192
|
+
canvas: Canvas,
|
|
193
|
+
text: TextPicture,
|
|
194
|
+
x: Float,
|
|
195
|
+
y: Float,
|
|
196
|
+
fontSize: Float,
|
|
197
|
+
rotation: Float,
|
|
198
|
+
shown: Float,
|
|
199
|
+
) {
|
|
152
200
|
val alpha = (shown * text.alpha * 255).roundToInt().coerceIn(0, 255)
|
|
153
201
|
if (alpha == 0) return
|
|
154
202
|
val scale = fontSize / text.fontSize
|
|
155
203
|
val count = canvas.save()
|
|
156
204
|
canvas.translate(x, y)
|
|
157
|
-
canvas.
|
|
205
|
+
canvas.rotate(rotation)
|
|
206
|
+
canvas.scale(scale * mirrorX, scale * mirrorY)
|
|
158
207
|
canvas.translate(-text.anchor.x, -text.anchor.y)
|
|
159
208
|
// The picture fades as a whole: its letters and background don't show through each other.
|
|
160
209
|
if (alpha < 255) {
|
|
@@ -14,17 +14,22 @@ import android.view.View
|
|
|
14
14
|
import android.view.ViewGroup
|
|
15
15
|
import com.facebook.react.views.view.ReactViewGroup
|
|
16
16
|
import dev.apexnative.navigation.options.SharedElementTransitionOptions
|
|
17
|
-
import dev.apexnative.navigation.utils.ViewUtils
|
|
18
17
|
import dev.apexnative.navigation.utils.borderRadius
|
|
19
|
-
import dev.apexnative.navigation.utils.computeInheritedScale
|
|
20
18
|
import kotlin.math.roundToInt
|
|
21
19
|
|
|
22
20
|
// A view with children moves as two pictures of itself, as on iOS: of the view that goes away and of the one that
|
|
23
|
-
// comes, each with its children. The copy goes from the
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
|
|
21
|
+
// comes, each with its children. The copy goes from where the one is drawn — its center, its size at the scale it is
|
|
22
|
+
// drawn at, its angle and corner radius — to where the other is drawn in its screen (FlightGeometry), the angle along
|
|
23
|
+
// the shorter arc; both pictures fill it, and the picture of the destination shows over the other on the way. The
|
|
24
|
+
// pictures are drawing commands: an avatar's letter grows sharp. They are drawn in the overlay of the transition layer
|
|
25
|
+
// while `to` is hidden, and nothing else moves the pair. A view without children moves itself, its outline growing, as
|
|
26
|
+
// before.
|
|
27
|
+
class ViewChangeAnimator internal constructor(
|
|
28
|
+
from: View,
|
|
29
|
+
to: View,
|
|
30
|
+
// Where `to` was in its screen: what the copy flies to.
|
|
31
|
+
private val toStandIn: View?,
|
|
32
|
+
) : PropertyAnimatorCreator<ReactViewGroup>(from, to, ReactViewGroup::class.java) {
|
|
28
33
|
private val pictures by lazy { pictures() }
|
|
29
34
|
|
|
30
35
|
override fun shouldAnimateProperty(fromChild: ReactViewGroup, toChild: ReactViewGroup): Boolean =
|
|
@@ -42,68 +47,72 @@ class ViewChangeAnimator(from: View, to: View) : PropertyAnimatorCreator<ReactVi
|
|
|
42
47
|
to.alpha = 0f
|
|
43
48
|
}
|
|
44
49
|
onFrame {
|
|
45
|
-
drawable.
|
|
50
|
+
drawable.update(it, curve.getInterpolation(it))
|
|
46
51
|
layer.invalidate()
|
|
47
52
|
}
|
|
53
|
+
// A flight that landed where `to` shows only in its screen keeps its last picture: the transition takes the
|
|
54
|
+
// layer away with it and shows `to` in its place.
|
|
55
|
+
val holds = FlightEnd.holdsAtEnd(to, toStandIn, layer)
|
|
48
56
|
onEnd {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
if (!holds || !landed) {
|
|
58
|
+
layer.overlay.remove(drawable)
|
|
59
|
+
layer.invalidate()
|
|
60
|
+
to.alpha = end.alpha
|
|
61
|
+
}
|
|
52
62
|
}
|
|
53
63
|
}
|
|
54
64
|
}
|
|
55
65
|
|
|
56
|
-
// `to` is in the transition layer already
|
|
57
|
-
//
|
|
66
|
+
// `to` is in the transition layer already: the pictures are drawn in its coordinates. `from` is where it is drawn
|
|
67
|
+
// on the screen, `to` where it is drawn once back in its screen.
|
|
58
68
|
private fun pictures(): Pair<ViewPicture, ViewPicture>? {
|
|
59
|
-
|
|
60
|
-
val
|
|
61
|
-
val
|
|
62
|
-
.apply { ViewUtils.getLocationOnScreen(to.parent as View).let { offset(-it.x, -it.y) } }
|
|
63
|
-
val (scaleX, scaleY) = computeInheritedScale(from)
|
|
64
|
-
val fromRect = RectF(0f, 0f, from.width * scaleX, from.height * scaleY).apply {
|
|
65
|
-
offset(fromPlace.x.toFloat(), fromPlace.y.toFloat())
|
|
66
|
-
}
|
|
67
|
-
val toRect = RectF(0f, 0f, to.width.toFloat(), to.height.toFloat()).apply {
|
|
68
|
-
offset(toPlace.leftMargin.toFloat(), toPlace.topMargin.toFloat())
|
|
69
|
-
}
|
|
70
|
-
val start = ViewPicture.of(from as ReactViewGroup, fromRect, scaleX) ?: return null
|
|
71
|
-
val end = ViewPicture.of(to as ReactViewGroup, toRect, 1f) ?: return null
|
|
69
|
+
val layer = to.parent as? ViewGroup ?: return null
|
|
70
|
+
val start = ViewPicture.of(from as ReactViewGroup, LayerPlace.of(from, layer)) ?: return null
|
|
71
|
+
val end = ViewPicture.of(to as ReactViewGroup, LayerPlace.of(to, layer, toStandIn)) ?: return null
|
|
72
72
|
return start to end
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
-
// A view's picture,
|
|
77
|
-
private class ViewPicture(val picture: Picture, val
|
|
76
|
+
// A view's picture, where it is drawn in the transition layer, its corner radius there and its opacity.
|
|
77
|
+
private class ViewPicture(val picture: Picture, val end: FlightEnd, val radius: Float, val alpha: Float) {
|
|
78
78
|
companion object {
|
|
79
|
-
fun of(view: ReactViewGroup,
|
|
79
|
+
fun of(view: ReactViewGroup, place: LayerPlace): ViewPicture? {
|
|
80
80
|
if (view.width <= 0 || view.height <= 0) return null
|
|
81
81
|
val picture = recordPicture(view) ?: return null
|
|
82
|
-
|
|
82
|
+
val end = place.end(view.width, view.height)
|
|
83
|
+
return ViewPicture(picture, end, view.borderRadius * end.scale, view.alpha)
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
// The copy where the move has brought it: its
|
|
88
|
-
// transition's curve, and so does the picture of the end over the picture of the start.
|
|
88
|
+
// The copy where the move has brought it: its center, size, angle and corner radius go from the start to the end along
|
|
89
|
+
// the transition's curve, and so does the picture of the end over the picture of the start.
|
|
89
90
|
private class ViewMorphDrawable(private val start: ViewPicture, private val end: ViewPicture) : Drawable() {
|
|
90
|
-
|
|
91
|
+
private val flight = FlightGeometry(start.end, end.end, 0, 0)
|
|
92
|
+
private var at = start.end
|
|
93
|
+
private var progress = 0f
|
|
91
94
|
private val rect = RectF()
|
|
92
95
|
private val maskPaint = Paint(Paint.ANTI_ALIAS_FLAG)
|
|
93
96
|
// What is drawn over the mask keeps only the mask's pixels: the corners stay smooth (a clip path on the hardware
|
|
94
97
|
// canvas is not anti-aliased).
|
|
95
98
|
private val contentPaint = Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN) }
|
|
96
99
|
|
|
100
|
+
/** The copy at [time] (the part of the time), [progress] the curve's value. */
|
|
101
|
+
fun update(time: Float, progress: Float) {
|
|
102
|
+
this.progress = progress
|
|
103
|
+
at = flight.at(progress, time)
|
|
104
|
+
}
|
|
105
|
+
|
|
97
106
|
override fun draw(canvas: Canvas) {
|
|
98
|
-
rect.set(
|
|
99
|
-
lerp(start.rect.left, end.rect.left),
|
|
100
|
-
lerp(start.rect.top, end.rect.top),
|
|
101
|
-
lerp(start.rect.right, end.rect.right),
|
|
102
|
-
lerp(start.rect.bottom, end.rect.bottom)
|
|
103
|
-
)
|
|
107
|
+
rect.set(-at.width / 2, -at.height / 2, at.width / 2, at.height / 2)
|
|
104
108
|
if (rect.isEmpty) return
|
|
105
109
|
val radius = lerp(start.radius, end.radius)
|
|
106
|
-
|
|
110
|
+
// The copy turns about its center, mirrored where a view is.
|
|
111
|
+
val count = canvas.save()
|
|
112
|
+
canvas.translate(at.centerX, at.centerY)
|
|
113
|
+
canvas.rotate(at.rotation)
|
|
114
|
+
canvas.scale(at.mirrorX, at.mirrorY)
|
|
115
|
+
canvas.saveLayer(rect, null)
|
|
107
116
|
canvas.drawRoundRect(rect, radius, radius, maskPaint)
|
|
108
117
|
canvas.saveLayer(rect, contentPaint)
|
|
109
118
|
draw(canvas, start, start.alpha)
|