@apexnative/navigation 1.0.0-alpha.6 → 1.0.0-alpha.8
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/ApexNavigation.podspec +2 -1
- package/CHANGELOG.md +15 -0
- package/android/CLAUDE.md +19 -10
- package/android/CORE.md +3 -2
- package/android/consumer-rules.pro +6 -0
- package/android/src/main/java/dev/apexnative/navigation/options/BottomTabOptions.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/options/ButtonOptions.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/options/LargeTitleOptions.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/options/LayoutAnimation.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/options/OptionKey.kt +18 -0
- package/android/src/main/java/dev/apexnative/navigation/options/OptionPaths.kt +91 -0
- package/android/src/main/java/dev/apexnative/navigation/options/SheetOptions.kt +7 -4
- package/android/src/main/java/dev/apexnative/navigation/options/TitleOptions.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/TopBarAppearanceOptions.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/options/TopBarOptions.kt +16 -7
- package/android/src/main/java/dev/apexnative/navigation/react/ContentHeights.kt +95 -0
- package/android/src/main/java/dev/apexnative/navigation/react/ContentMounts.kt +66 -0
- package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +4 -0
- package/android/src/main/java/dev/apexnative/navigation/react/OptionsCommands.kt +41 -3
- package/android/src/main/java/dev/apexnative/navigation/react/ReactView.kt +16 -1
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotScreenView.kt +27 -7
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotView.kt +12 -1
- package/android/src/main/java/dev/apexnative/navigation/utils/MaterialTheme.kt +5 -6
- package/android/src/main/java/dev/apexnative/navigation/utils/SystemAnimations.kt +36 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +329 -55
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +26 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetStops.kt +60 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuMotion.kt +8 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/BackPreview.kt +32 -4
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +46 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +15 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +7 -4
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabMotion.kt +142 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt +215 -43
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +25 -26
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/BarRowTransition.kt +3 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/ButtonPlaces.kt +11 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +86 -39
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarVisibility.kt +9 -2
- package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +124 -181
- package/android/src/main/java/dev/apexnative/navigation/views/element/TargetsWait.kt +8 -1
- package/android/src/main/java/dev/apexnative/navigation/views/element/TransitionLayer.kt +37 -112
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +56 -3
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt +170 -153
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMorph.kt +199 -0
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +55 -45
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/{TextChangeAnimator.kt → TextMorph.kt} +79 -129
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewMorph.kt +108 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabCopy.kt +85 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabMenu.kt +13 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TitleBar.kt +5 -24
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TopBar.kt +51 -34
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonComponentCell.kt +6 -6
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonView.kt +113 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarMenu.kt +219 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonBar.kt +121 -175
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonMenus.kt +34 -64
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonRow.kt +216 -0
- package/android/src/main/jni/ANNContentHeights.cpp +82 -0
- package/android/src/main/res/drawable/apex_navigation_ic_arrow_right.xml +14 -0
- package/android/src/main/res/layout/apex_navigation_menu_header.xml +19 -0
- package/android/src/main/res/layout/apex_navigation_menu_item.xml +68 -0
- package/android/src/main/res/values/styles.xml +1 -1
- package/android/src/main/res/values-night/styles.xml +1 -1
- package/cli/doctor/checks/app.js +16 -1
- package/cli/project.js +4 -0
- package/cli/setup/index.js +5 -0
- package/cli/setup/steps/metro.js +46 -0
- package/docs-md/architecture/native-screens.md +4 -0
- package/docs-md/architecture/theme-and-context.md +131 -12
- package/docs-md/architecture/transitions.md +43 -4
- package/docs-md/flows/bottom-sheets.md +4 -2
- package/docs-md/fundamentals/screens-and-props.md +9 -1
- package/docs-md/guides/cli.md +3 -1
- package/docs-md/help/troubleshooting.md +1 -1
- package/docs-md/index.json +29 -1
- package/docs-md/screen-ui/back-and-gestures.md +3 -1
- package/docs-md/screen-ui/bar-buttons.md +1 -1
- package/docs-md/screen-ui/toolbar-and-fab.md +2 -0
- package/docs-md/start/installation.md +16 -1
- package/ios/Animations/ANNFrameDriver.h +55 -15
- package/ios/Animations/ANNFrameDriver.mm +214 -60
- package/ios/Animations/ANNTransitionAnimator.h +26 -5
- package/ios/Animations/ANNTransitionAnimator.mm +49 -3
- package/ios/Animations/ANNTransitionPlan.h +2 -2
- package/ios/Animations/ANNTransitionPlan.mm +10 -0
- package/ios/Animations/Elements/ANNElementFinder.h +2 -2
- package/ios/Animations/Elements/ANNElementFinder.mm +2 -2
- package/ios/Animations/Elements/ANNElementTransition.h +3 -3
- package/ios/Animations/Elements/ANNElementTransition.mm +2 -2
- package/ios/Animations/Elements/ANNSharedElementFlight.h +3 -3
- package/ios/Animations/Elements/ANNSharedElementFlight.mm +181 -43
- package/ios/Animations/Interpolators/ANNViewTracks.h +6 -5
- package/ios/Animations/Interpolators/ANNViewTracks.mm +51 -21
- package/ios/CLAUDE.md +19 -8
- package/ios/CORE.md +1 -1
- package/ios/Core/ANNAppDelegate.mm +0 -2
- package/ios/Core/ANNDeepLinkQueue.h +6 -5
- package/ios/Core/ANNDeepLinkQueue.mm +22 -10
- package/ios/Core/ANNSceneDelegate.mm +2 -2
- package/ios/Core/ANNUtils.h +7 -0
- package/ios/Core/ANNUtils.mm +10 -0
- package/ios/Core/ApexNavigation.mm +6 -2
- package/ios/Layouts/Overlay/ANNOverlayCoordinator.mm +1 -7
- package/ios/Layouts/Root/ANNRootCoordinator.mm +45 -15
- package/ios/Layouts/Screen/ANNComponentViewController.mm +4 -1
- package/ios/Layouts/Stack/ANNStackAnimatingDelegate.h +11 -2
- package/ios/Layouts/Stack/ANNStackAnimatingDelegate.mm +14 -7
- package/ios/Layouts/Stack/ANNStackController.h +8 -0
- package/ios/Layouts/Stack/ANNStackController.mm +153 -11
- package/ios/Layouts/Stack/ANNSwipePopInteraction.h +26 -0
- package/ios/Layouts/Stack/ANNSwipePopInteraction.mm +185 -0
- package/ios/Layouts/Stack/InteractivePopGestureDelegate.mm +28 -4
- package/ios/Layouts/Tabs/ANNTabsPreloadWindow.mm +2 -4
- package/ios/Options/ANNAnimationOptions.h +2 -1
- package/ios/Options/ANNAnimationOptions.mm +1 -1
- package/ios/Options/ANNOptions.h +7 -0
- package/ios/Options/ANNOptions.mm +45 -0
- package/ios/Options/ANNOptionsReader.mm +2 -4
- package/ios/Options/ANNStackPopAnimationOptions.h +13 -0
- package/ios/Options/ANNStackPopAnimationOptions.mm +11 -0
- package/ios/Options/Params/ColorParser.h +4 -3
- package/ios/Options/Params/ColorParser.mm +22 -4
- package/ios/React/ANNSurfaceHostingView.h +8 -0
- package/ios/React/ANNSurfaceHostingView.mm +28 -1
- package/ios/React/Slots/ANNSlotHost.mm +1 -3
- package/ios/React/Slots/ANNSlotScreenView.mm +8 -2
- package/ios/TurboModules/ANNTurboModule.mm +46 -0
- package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.cpp +20 -0
- package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.h +5 -14
- package/lib/module/ARCHITECTURE.md +9 -3
- package/lib/module/adapters/NativeANNTurboModule.js.map +1 -1
- package/lib/module/api/app/AppRegistry.js +14 -2
- package/lib/module/api/app/AppRegistry.js.map +1 -1
- package/lib/module/api/app/createNavigation.js +188 -66
- package/lib/module/api/app/createNavigation.js.map +1 -1
- package/lib/module/api/events/navigationEvents.js +6 -2
- package/lib/module/api/events/navigationEvents.js.map +1 -1
- package/lib/module/api/linking/linking.js +11 -0
- package/lib/module/api/linking/linking.js.map +1 -1
- package/lib/module/api/screens/defineScreen.js +45 -3
- package/lib/module/api/screens/defineScreen.js.map +1 -1
- package/lib/module/api/screens/nativeScreens.js +31 -6
- package/lib/module/api/screens/nativeScreens.js.map +1 -1
- package/lib/module/api/screens/registerScreens.js +193 -35
- package/lib/module/api/screens/registerScreens.js.map +1 -1
- package/lib/module/api/theme/theme.js +5 -0
- package/lib/module/api/theme/theme.js.map +1 -1
- package/lib/module/engine/commands.js +21 -1
- package/lib/module/engine/commands.js.map +1 -1
- package/lib/module/engine/layout.js +7 -2
- package/lib/module/engine/layout.js.map +1 -1
- package/lib/module/engine/nativeScreens.js +9 -0
- package/lib/module/engine/nativeScreens.js.map +1 -1
- package/lib/module/engine/options.js +5 -4
- package/lib/module/engine/options.js.map +1 -1
- package/lib/module/engine/optionsChange.js +102 -0
- package/lib/module/engine/optionsChange.js.map +1 -0
- package/lib/module/engine/refresh.js +85 -0
- package/lib/module/engine/refresh.js.map +1 -0
- package/lib/module/engine/rememberedOptions.js +60 -0
- package/lib/module/engine/rememberedOptions.js.map +1 -1
- package/lib/module/engine/screens.js +26 -0
- package/lib/module/engine/screens.js.map +1 -1
- package/lib/typescript/adapters/NativeANNTurboModule.d.ts +7 -0
- package/lib/typescript/adapters/NativeANNTurboModule.d.ts.map +1 -1
- package/lib/typescript/api/app/AppRegistry.d.ts.map +1 -1
- package/lib/typescript/api/app/createNavigation.d.ts +5 -0
- package/lib/typescript/api/app/createNavigation.d.ts.map +1 -1
- package/lib/typescript/api/events/navigationEvents.d.ts +2 -2
- package/lib/typescript/api/events/navigationEvents.d.ts.map +1 -1
- package/lib/typescript/api/index.d.ts +1 -1
- package/lib/typescript/api/index.d.ts.map +1 -1
- package/lib/typescript/api/linking/linking.d.ts +8 -0
- package/lib/typescript/api/linking/linking.d.ts.map +1 -1
- package/lib/typescript/api/screens/defineScreen.d.ts +23 -4
- package/lib/typescript/api/screens/defineScreen.d.ts.map +1 -1
- package/lib/typescript/api/screens/nativeScreens.d.ts +8 -1
- package/lib/typescript/api/screens/nativeScreens.d.ts.map +1 -1
- package/lib/typescript/api/screens/registerScreens.d.ts.map +1 -1
- package/lib/typescript/api/theme/theme.d.ts +2 -0
- package/lib/typescript/api/theme/theme.d.ts.map +1 -1
- package/lib/typescript/engine/commands.d.ts +5 -0
- package/lib/typescript/engine/commands.d.ts.map +1 -1
- package/lib/typescript/engine/layout.d.ts.map +1 -1
- package/lib/typescript/engine/nativeScreens.d.ts +5 -0
- package/lib/typescript/engine/nativeScreens.d.ts.map +1 -1
- package/lib/typescript/engine/optionsChange.d.ts +28 -0
- package/lib/typescript/engine/optionsChange.d.ts.map +1 -0
- package/lib/typescript/engine/refresh.d.ts +35 -0
- package/lib/typescript/engine/refresh.d.ts.map +1 -0
- package/lib/typescript/engine/rememberedOptions.d.ts +13 -0
- package/lib/typescript/engine/rememberedOptions.d.ts.map +1 -1
- package/lib/typescript/engine/screens.d.ts +7 -0
- package/lib/typescript/engine/screens.d.ts.map +1 -1
- package/lib/typescript/interfaces/ComponentEvents.d.ts +2 -2
- package/lib/typescript/interfaces/options/animations.d.ts +40 -15
- package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/bottomTabs.d.ts +2 -2
- package/lib/typescript/interfaces/options/buttons.d.ts +2 -2
- package/lib/typescript/interfaces/options/modals.d.ts +6 -7
- package/lib/typescript/interfaces/options/modals.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/stack.d.ts +8 -3
- package/lib/typescript/interfaces/options/stack.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/topBar.d.ts +14 -0
- package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
- package/metro/acceptPlugin.js +57 -0
- package/metro/babelTransformer.js +45 -0
- package/metro/index.js +36 -0
- package/package.json +3 -1
- package/src/ARCHITECTURE.md +9 -3
- package/src/adapters/NativeANNTurboModule.ts +7 -0
- package/src/api/app/AppRegistry.ts +16 -2
- package/src/api/app/createNavigation.ts +198 -66
- package/src/api/events/navigationEvents.ts +9 -3
- package/src/api/index.ts +1 -0
- package/src/api/linking/linking.ts +22 -1
- package/src/api/screens/defineScreen.ts +49 -5
- package/src/api/screens/nativeScreens.ts +40 -4
- package/src/api/screens/registerScreens.tsx +185 -42
- package/src/api/theme/theme.ts +5 -0
- package/src/engine/commands.ts +21 -0
- package/src/engine/layout.ts +11 -2
- package/src/engine/nativeScreens.ts +10 -1
- package/src/engine/options.ts +6 -8
- package/src/engine/optionsChange.ts +107 -0
- package/src/engine/refresh.ts +97 -0
- package/src/engine/rememberedOptions.ts +70 -0
- package/src/engine/screens.tsx +24 -0
- package/src/interfaces/ComponentEvents.ts +2 -2
- package/src/interfaces/options/animations.ts +40 -15
- package/src/interfaces/options/bottomTabs.ts +2 -2
- package/src/interfaces/options/buttons.ts +2 -2
- package/src/interfaces/options/modals.ts +6 -7
- package/src/interfaces/options/stack.ts +8 -3
- package/src/interfaces/options/topBar.ts +14 -0
- package/android/src/main/java/dev/apexnative/navigation/views/element/ElementStub.kt +0 -31
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +0 -134
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +0 -78
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PropertyAnimatorCreator.kt +0 -33
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +0 -57
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +0 -146
|
@@ -2,45 +2,35 @@ package dev.apexnative.navigation.views.element
|
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
4
|
import android.content.Context
|
|
5
|
-
import android.graphics.
|
|
6
|
-
import android.graphics.Rect
|
|
5
|
+
import android.graphics.Canvas
|
|
7
6
|
import android.view.MotionEvent
|
|
8
7
|
import android.view.View
|
|
9
8
|
import android.view.ViewGroup
|
|
9
|
+
import dev.apexnative.navigation.views.element.animators.FlightPicture
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* The layer of a transition's elements: full size over the screens' common parent (a stack's
|
|
13
|
-
* the modals layer and the overlays layer), there only while the transition runs.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* accessibility doesn't see it. Main thread only.
|
|
12
|
+
* The layer the copies of a transition's shared elements fly in: full size over the screens' common parent (a stack's
|
|
13
|
+
* view; for a modal, between the modals layer and the overlays layer), there only while the transition runs. It holds
|
|
14
|
+
* no views: it draws the pictures of the flights ([FlightPicture]) in the order they are added, each while it is shown,
|
|
15
|
+
* on every frame while one of them is [FlightPicture.live]. A picture whose drawing fails draws no more, and its
|
|
16
|
+
* [FlightPicture.onFailed] runs after the frame. It takes no touches, accessibility doesn't see it. Main thread only.
|
|
17
17
|
*/
|
|
18
18
|
@SuppressLint("ViewConstructor")
|
|
19
|
-
internal class TransitionLayer(context: Context) :
|
|
20
|
-
|
|
21
|
-
private val reactFrames = HashMap<View, Rect>()
|
|
19
|
+
internal class TransitionLayer(context: Context) : View(context) {
|
|
20
|
+
private val pictures = ArrayList<FlightPicture>()
|
|
22
21
|
|
|
23
22
|
init {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
|
|
23
|
+
importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
|
|
24
|
+
setWillNotDraw(false)
|
|
27
25
|
}
|
|
28
26
|
|
|
29
|
-
/** The layer's top left corner on the screen, in pixels: what the views in it are placed from. */
|
|
30
|
-
val originOnScreen: Point
|
|
31
|
-
get() {
|
|
32
|
-
val location = IntArray(2)
|
|
33
|
-
getLocationOnScreen(location)
|
|
34
|
-
return Point(location[0], location[1])
|
|
35
|
-
}
|
|
36
|
-
|
|
37
27
|
/**
|
|
38
|
-
* Joins [parent] at [index] (its top without one), laid out at once at the parent's size:
|
|
39
|
-
*
|
|
28
|
+
* Joins [parent] at [index] (its top without one), laid out at once at the parent's size: the places of the views
|
|
29
|
+
* are measured from it on the frame the transition starts.
|
|
40
30
|
*/
|
|
41
31
|
fun attachTo(parent: ViewGroup, index: Int = parent.childCount) {
|
|
42
32
|
// The parent turns these into its own kind of params.
|
|
43
|
-
parent.addView(this, index.coerceIn(0, parent.childCount), LayoutParams(MATCH, MATCH))
|
|
33
|
+
parent.addView(this, index.coerceIn(0, parent.childCount), ViewGroup.LayoutParams(MATCH, MATCH))
|
|
44
34
|
measure(
|
|
45
35
|
MeasureSpec.makeMeasureSpec(parent.width, MeasureSpec.EXACTLY),
|
|
46
36
|
MeasureSpec.makeMeasureSpec(parent.height, MeasureSpec.EXACTLY),
|
|
@@ -48,51 +38,10 @@ internal class TransitionLayer(context: Context) : ViewGroup(context) {
|
|
|
48
38
|
layout(0, 0, parent.width, parent.height)
|
|
49
39
|
}
|
|
50
40
|
|
|
51
|
-
/** [
|
|
52
|
-
fun
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
topMargin = top
|
|
56
|
-
}
|
|
57
|
-
addView(view, params)
|
|
58
|
-
layOut(view, left, top, width, height)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** [view], in the layer, lies at [left], [top] at the size [width] × [height] from now on. */
|
|
62
|
-
fun place(view: View, left: Int, top: Int, width: Int, height: Int) {
|
|
63
|
-
noteReactFrame(view)
|
|
64
|
-
(view.layoutParams as? MarginLayoutParams)?.let {
|
|
65
|
-
it.leftMargin = left
|
|
66
|
-
it.topMargin = top
|
|
67
|
-
it.width = width
|
|
68
|
-
it.height = height
|
|
69
|
-
}
|
|
70
|
-
layOut(view, left, top, width, height)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
private fun layOut(view: View, left: Int, top: Int, width: Int, height: Int) {
|
|
74
|
-
view.measure(
|
|
75
|
-
MeasureSpec.makeMeasureSpec(width.coerceAtLeast(0), MeasureSpec.EXACTLY),
|
|
76
|
-
MeasureSpec.makeMeasureSpec(height.coerceAtLeast(0), MeasureSpec.EXACTLY),
|
|
77
|
-
)
|
|
78
|
-
view.layout(left, top, left + width, top + height)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* The newest frame React gave [view] while it was in the layer, in its parent's coordinates; none when React
|
|
83
|
-
* didn't lay it out meanwhile. A frame other than the one the layer gave it can only be React's.
|
|
84
|
-
*/
|
|
85
|
-
fun frameFromReact(view: View): Rect? {
|
|
86
|
-
noteReactFrame(view)
|
|
87
|
-
return reactFrames.remove(view)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// The view's frame isn't where the layer put it: React laid it out since.
|
|
91
|
-
private fun noteReactFrame(view: View) {
|
|
92
|
-
val params = view.layoutParams as? MarginLayoutParams ?: return
|
|
93
|
-
val placed = view.left == params.leftMargin && view.top == params.topMargin &&
|
|
94
|
-
view.width == params.width && view.height == params.height
|
|
95
|
-
if (!placed) reactFrames[view] = Rect(view.left, view.top, view.right, view.bottom)
|
|
41
|
+
/** [picture] is drawn over those added before it. */
|
|
42
|
+
fun add(picture: FlightPicture) {
|
|
43
|
+
pictures += picture
|
|
44
|
+
invalidate()
|
|
96
45
|
}
|
|
97
46
|
|
|
98
47
|
/** The layer leaves its parent. */
|
|
@@ -100,53 +49,29 @@ internal class TransitionLayer(context: Context) : ViewGroup(context) {
|
|
|
100
49
|
(parent as? ViewGroup)?.removeView(this)
|
|
101
50
|
}
|
|
102
51
|
|
|
103
|
-
override fun
|
|
104
|
-
|
|
105
|
-
for (
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
val child = getChildAt(index)
|
|
118
|
-
val params = child.layoutParams as? MarginLayoutParams ?: continue
|
|
119
|
-
noteReactFrame(child)
|
|
120
|
-
child.layout(
|
|
121
|
-
params.leftMargin,
|
|
122
|
-
params.topMargin,
|
|
123
|
-
params.leftMargin + child.measuredWidth,
|
|
124
|
-
params.topMargin + child.measuredHeight,
|
|
125
|
-
)
|
|
52
|
+
override fun onDraw(canvas: Canvas) {
|
|
53
|
+
var live = false
|
|
54
|
+
for (picture in pictures) {
|
|
55
|
+
if (!picture.shown) continue
|
|
56
|
+
live = live || picture.live
|
|
57
|
+
val count = canvas.save()
|
|
58
|
+
try {
|
|
59
|
+
picture.draw(canvas)
|
|
60
|
+
} catch (e: RuntimeException) {
|
|
61
|
+
picture.shown = false
|
|
62
|
+
post { picture.onFailed(e) }
|
|
63
|
+
} finally {
|
|
64
|
+
canvas.restoreToCount(count)
|
|
65
|
+
}
|
|
126
66
|
}
|
|
67
|
+
// A landed copy gets no more frames from its flight: one that changes by itself asks for them.
|
|
68
|
+
if (live) postInvalidateOnAnimation()
|
|
127
69
|
}
|
|
128
70
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
override fun generateDefaultLayoutParams(): LayoutParams = MarginLayoutParams(0, 0)
|
|
132
|
-
|
|
133
|
-
override fun generateLayoutParams(params: LayoutParams?): LayoutParams = MarginLayoutParams(params)
|
|
134
|
-
|
|
71
|
+
@SuppressLint("ClickableViewAccessibility")
|
|
135
72
|
override fun dispatchTouchEvent(event: MotionEvent?): Boolean = false
|
|
136
73
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
companion object {
|
|
140
|
-
private const val MATCH = LayoutParams.MATCH_PARENT
|
|
141
|
-
|
|
142
|
-
/** Whether [view] is in a transition's layer now (an element of another transition). */
|
|
143
|
-
fun holds(view: View): Boolean {
|
|
144
|
-
var parent = view.parent
|
|
145
|
-
while (parent != null) {
|
|
146
|
-
if (parent is TransitionLayer) return true
|
|
147
|
-
parent = parent.parent
|
|
148
|
-
}
|
|
149
|
-
return false
|
|
150
|
-
}
|
|
74
|
+
private companion object {
|
|
75
|
+
const val MATCH = ViewGroup.LayoutParams.MATCH_PARENT
|
|
151
76
|
}
|
|
152
77
|
}
|
package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt
CHANGED
|
@@ -4,7 +4,12 @@ import android.animation.Animator
|
|
|
4
4
|
import android.animation.AnimatorListenerAdapter
|
|
5
5
|
import android.animation.TimeInterpolator
|
|
6
6
|
import android.animation.ValueAnimator
|
|
7
|
+
import android.graphics.Canvas
|
|
8
|
+
import android.graphics.Paint
|
|
7
9
|
import android.graphics.Picture
|
|
10
|
+
import android.graphics.PorterDuff
|
|
11
|
+
import android.graphics.PorterDuffXfermode
|
|
12
|
+
import android.graphics.RectF
|
|
8
13
|
import android.util.Log
|
|
9
14
|
import android.view.View
|
|
10
15
|
import android.view.animation.LinearInterpolator
|
|
@@ -13,8 +18,7 @@ import dev.apexnative.navigation.views.element.elementStep
|
|
|
13
18
|
|
|
14
19
|
/**
|
|
15
20
|
* The view's own drawing with its children, as drawing commands: sharp at any scale. Its opacity and place belong to
|
|
16
|
-
* the parent. Recording can fail (a hardware bitmap in an image
|
|
17
|
-
* pictures.
|
|
21
|
+
* the parent. Recording can fail (a hardware bitmap in an image before Android 9): the pair then doesn't fly.
|
|
18
22
|
*/
|
|
19
23
|
internal fun recordPicture(view: View): Picture? = try {
|
|
20
24
|
Picture().also {
|
|
@@ -26,10 +30,59 @@ internal fun recordPicture(view: View): Picture? = try {
|
|
|
26
30
|
}
|
|
27
31
|
}
|
|
28
32
|
} catch (e: RuntimeException) {
|
|
29
|
-
Log.w(NavigationLog.TAG, "Element transition: no picture of the element
|
|
33
|
+
Log.w(NavigationLog.TAG, "[ApexNavigation] Element transition: no picture of the element", e)
|
|
30
34
|
null
|
|
31
35
|
}
|
|
32
36
|
|
|
37
|
+
/**
|
|
38
|
+
* What a shared pair's flight draws in its transition's layer, in the layer's coordinates: the copy where the flight
|
|
39
|
+
* has brought it ([update]). The layer draws it while it is [shown]: from the start of the flight, and after a landing
|
|
40
|
+
* until the layer goes.
|
|
41
|
+
*/
|
|
42
|
+
internal abstract class FlightPicture {
|
|
43
|
+
/** Whether the layer draws it now. */
|
|
44
|
+
var shown = false
|
|
45
|
+
|
|
46
|
+
/** Whether what it draws changes by itself (an animated image): the layer draws it on every frame while shown. */
|
|
47
|
+
open val live: Boolean get() = false
|
|
48
|
+
|
|
49
|
+
/** What runs after its drawing failed: it is drawn no more, and the views of its pair show again. */
|
|
50
|
+
var onFailed: (Exception) -> Unit = {}
|
|
51
|
+
|
|
52
|
+
/** The copy at [time] (the part of the time), [progress] the curve's value. */
|
|
53
|
+
abstract fun update(time: Float, progress: Float)
|
|
54
|
+
|
|
55
|
+
/** The copy, on the layer's canvas; the layer saves and restores the canvas around it. */
|
|
56
|
+
abstract fun draw(canvas: Canvas)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The cut of a copy whose views' parents hide part of them ([FlightMask]): it keeps, of the mask the copy draws on its
|
|
61
|
+
* layer, only the part that shows. A layer of that part's rounded rect laid over the mask (`DST_IN`), not a clip path:
|
|
62
|
+
* its edges stay smooth.
|
|
63
|
+
*/
|
|
64
|
+
internal class MaskCut(private val mask: FlightMask) {
|
|
65
|
+
private val values = FloatArray(FlightMask.SIZE)
|
|
66
|
+
private val rect = RectF()
|
|
67
|
+
private val shape = Paint(Paint.ANTI_ALIAS_FLAG)
|
|
68
|
+
private val cut = Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_IN) }
|
|
69
|
+
|
|
70
|
+
/** The cut at [time] (the part of the time), [progress] the curve's value. */
|
|
71
|
+
fun update(time: Float, progress: Float) = mask.at(progress, time, values)
|
|
72
|
+
|
|
73
|
+
/** Cuts the mask drawn so far on the copy's layer of [bounds], in the copy's coordinates (0, 0 its center). */
|
|
74
|
+
fun draw(canvas: Canvas, bounds: RectF) {
|
|
75
|
+
rect.set(values[0], values[1], values[2], values[3])
|
|
76
|
+
canvas.saveLayer(bounds, cut)
|
|
77
|
+
if (!rect.isEmpty) canvas.drawRoundRect(rect, values[RADIUS], values[RADIUS], shape)
|
|
78
|
+
canvas.restore()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private companion object {
|
|
82
|
+
const val RADIUS = 4
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
33
86
|
// Runs in linear time and keeps the curve it is given for the move: an element moves along the transition's curve,
|
|
34
87
|
// while what has to follow the clock (the pictures of a text changing over) can. Its start and its frames each run in
|
|
35
88
|
// [elementStep]: one that fails stops the element — it is cancelled, so its ends put back what it changed and the
|
package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt
CHANGED
|
@@ -2,11 +2,13 @@ package dev.apexnative.navigation.views.element.animators
|
|
|
2
2
|
|
|
3
3
|
import android.view.View
|
|
4
4
|
import android.view.ViewGroup
|
|
5
|
+
import com.facebook.react.uimanager.ReactOverflowView
|
|
6
|
+
import dev.apexnative.navigation.utils.borderRadius
|
|
5
7
|
import dev.apexnative.navigation.views.animations.ValueInterpolation
|
|
6
8
|
import kotlin.math.abs
|
|
7
9
|
import kotlin.math.cos
|
|
10
|
+
import kotlin.math.max
|
|
8
11
|
import kotlin.math.min
|
|
9
|
-
import kotlin.math.roundToInt
|
|
10
12
|
import kotlin.math.sin
|
|
11
13
|
|
|
12
14
|
/**
|
|
@@ -41,6 +43,23 @@ internal class ViewPlacement(
|
|
|
41
43
|
)
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
/**
|
|
47
|
+
* The point [x], [y] of the parent in the view's coordinates: [toParent] undone. None when a scale of 0 flattens
|
|
48
|
+
* the view.
|
|
49
|
+
*/
|
|
50
|
+
fun toChild(x: Float, y: Float): FloatArray? {
|
|
51
|
+
if (scaleX == 0f || scaleY == 0f) return null
|
|
52
|
+
val dx = x - (pivotX + translationX + left - parentScrollX)
|
|
53
|
+
val dy = y - (pivotY + translationY + top - parentScrollY)
|
|
54
|
+
val radians = Math.toRadians(rotation.toDouble())
|
|
55
|
+
val cos = cos(radians).toFloat()
|
|
56
|
+
val sin = sin(radians).toFloat()
|
|
57
|
+
return floatArrayOf(
|
|
58
|
+
(dx * cos + dy * sin) / scaleX + pivotX,
|
|
59
|
+
(-dx * sin + dy * cos) / scaleY + pivotY,
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
44
63
|
/** Whether the view shows mirrored: one of its scales is negative. */
|
|
45
64
|
val mirrors: Boolean get() = scaleX * scaleY < 0
|
|
46
65
|
|
|
@@ -49,12 +68,12 @@ internal class ViewPlacement(
|
|
|
49
68
|
private const val FLIP_TOLERANCE = 0.5f
|
|
50
69
|
private const val HALF_TURN = 180f
|
|
51
70
|
|
|
52
|
-
/** [view] as it lies now
|
|
53
|
-
fun of(view: View
|
|
54
|
-
val parent =
|
|
71
|
+
/** [view] as it lies now. */
|
|
72
|
+
fun of(view: View): ViewPlacement {
|
|
73
|
+
val parent = view.parent as? View
|
|
55
74
|
return withTilts(
|
|
56
|
-
left =
|
|
57
|
-
top =
|
|
75
|
+
left = view.left.toFloat(),
|
|
76
|
+
top = view.top.toFloat(),
|
|
58
77
|
translationX = view.translationX,
|
|
59
78
|
translationY = view.translationY,
|
|
60
79
|
scaleX = view.scaleX,
|
|
@@ -113,12 +132,13 @@ internal class ViewPlacement(
|
|
|
113
132
|
* Where the points of a view are drawn in a transition's layer: through its own placement and each parent's ([chain],
|
|
114
133
|
* the view first, up to the top view of the window but not it), then moved by [offsetX], [offsetY] (where the top view
|
|
115
134
|
* is from the layer's corner). The angle and the scale are those of the whole chain: a turned or scaled parent turns
|
|
116
|
-
* or scales what is in it.
|
|
135
|
+
* or scales what is in it. [clips] are the parents that cut what the view draws, the innermost first.
|
|
117
136
|
*/
|
|
118
137
|
internal class LayerPlace(
|
|
119
138
|
private val chain: List<ViewPlacement>,
|
|
120
139
|
private val offsetX: Float,
|
|
121
140
|
private val offsetY: Float,
|
|
141
|
+
private val clips: List<Clip> = emptyList(),
|
|
122
142
|
) {
|
|
123
143
|
/** The angle the view is drawn at, degrees, clockwise: a mirrored parent turns what is in it the other way. */
|
|
124
144
|
val rotation: Float = chain.fold(0f) { inner, placement ->
|
|
@@ -153,28 +173,158 @@ internal class LayerPlace(
|
|
|
153
173
|
)
|
|
154
174
|
}
|
|
155
175
|
|
|
176
|
+
/**
|
|
177
|
+
* The part of a view of [width] × [height] drawn here that its [clips] leave visible, as iOS works it out: the
|
|
178
|
+
* view's bounds cut by the bounds of each clip, brought into the view's coordinates (the box around them where a
|
|
179
|
+
* turn tilts them), with the corners of the last clip that cut something. Uncut, it is the whole view with its own
|
|
180
|
+
* [cornerRadius] (in its pixels).
|
|
181
|
+
*/
|
|
182
|
+
fun visible(width: Int, height: Int, cornerRadius: Float): VisiblePart {
|
|
183
|
+
val w = width.toFloat()
|
|
184
|
+
val h = height.toFloat()
|
|
185
|
+
val visible = floatArrayOf(0f, 0f, w, h)
|
|
186
|
+
var radius = 0f
|
|
187
|
+
for (clip in clips) {
|
|
188
|
+
val cut = intersection(visible, boundsInView(clip))
|
|
189
|
+
if (!cut.contentEquals(visible)) radius = clip.radius
|
|
190
|
+
cut.copyInto(visible)
|
|
191
|
+
}
|
|
192
|
+
val clipped = abs(visible[0]) > SAME_RECT || abs(visible[1]) > SAME_RECT ||
|
|
193
|
+
abs(visible[2] - w) > SAME_RECT || abs(visible[3] - h) > SAME_RECT
|
|
194
|
+
val shown = if (clipped) visible else floatArrayOf(0f, 0f, w, h)
|
|
195
|
+
val scaleX = abs(scaleX)
|
|
196
|
+
val scaleY = abs(scaleY)
|
|
197
|
+
return VisiblePart(
|
|
198
|
+
left = (shown[0] - w / 2) * scaleX,
|
|
199
|
+
top = (shown[1] - h / 2) * scaleY,
|
|
200
|
+
right = (shown[2] - w / 2) * scaleX,
|
|
201
|
+
bottom = (shown[3] - h / 2) * scaleY,
|
|
202
|
+
radius = (if (clipped) radius else cornerRadius) * min(scaleX, scaleY),
|
|
203
|
+
clipped = clipped,
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// The bounds of [clip] in the view's coordinates: the box around its corners brought down the chain. Empty when a
|
|
208
|
+
// placement on the way flattens the view.
|
|
209
|
+
private fun boundsInView(clip: Clip): FloatArray {
|
|
210
|
+
val box = floatArrayOf(Float.MAX_VALUE, Float.MAX_VALUE, -Float.MAX_VALUE, -Float.MAX_VALUE)
|
|
211
|
+
for (corner in 0 until CORNERS) {
|
|
212
|
+
var point: FloatArray? = floatArrayOf(
|
|
213
|
+
if (corner % 2 == 0) 0f else clip.width,
|
|
214
|
+
if (corner < 2) 0f else clip.height,
|
|
215
|
+
)
|
|
216
|
+
for (level in clip.level - 1 downTo 0) point = point?.let { chain[level].toChild(it[0], it[1]) }
|
|
217
|
+
point ?: return FloatArray(RECT)
|
|
218
|
+
box[0] = min(box[0], point[0])
|
|
219
|
+
box[1] = min(box[1], point[1])
|
|
220
|
+
box[2] = max(box[2], point[0])
|
|
221
|
+
box[3] = max(box[3], point[1])
|
|
222
|
+
}
|
|
223
|
+
return box
|
|
224
|
+
}
|
|
225
|
+
|
|
156
226
|
companion object {
|
|
227
|
+
private const val CORNERS = 4
|
|
228
|
+
private const val RECT = 4
|
|
229
|
+
|
|
230
|
+
// How close two rects count as the same, in pixels: a visible part that is the whole view needs no mask.
|
|
231
|
+
private const val SAME_RECT = 0.5f
|
|
232
|
+
private const val VISIBLE = "visible"
|
|
233
|
+
|
|
157
234
|
/**
|
|
158
|
-
* Where [view] is drawn in [layer]. With [
|
|
159
|
-
*
|
|
235
|
+
* Where [view] is drawn in [layer]. With [clipRoot] (the view of its screen), the parents up to it that cut
|
|
236
|
+
* what they draw are its clips: as on iOS, the parents above the screen don't count.
|
|
160
237
|
*/
|
|
161
|
-
fun of(view: View, layer: View,
|
|
162
|
-
val
|
|
163
|
-
val
|
|
164
|
-
var
|
|
238
|
+
fun of(view: View, layer: View, clipRoot: View? = null): LayerPlace {
|
|
239
|
+
val chain = arrayListOf(ViewPlacement.of(view))
|
|
240
|
+
val clips = ArrayList<Clip>()
|
|
241
|
+
var clipping = clipRoot != null
|
|
242
|
+
var parent = view.parent as? View
|
|
165
243
|
while (parent != null) {
|
|
244
|
+
if (clipping) {
|
|
245
|
+
clipOf(parent, chain.size)?.let(clips::add)
|
|
246
|
+
clipping = parent !== clipRoot
|
|
247
|
+
}
|
|
166
248
|
val above = parent.parent as? View ?: break
|
|
167
249
|
chain += ViewPlacement.of(parent)
|
|
168
250
|
parent = above
|
|
169
251
|
}
|
|
170
252
|
// The window's top view (no parent view) lies where it is on the screen, with no properties of its own.
|
|
171
|
-
val base = parent ?:
|
|
253
|
+
val base = parent ?: view
|
|
172
254
|
val screen = IntArray(2)
|
|
173
255
|
base.getLocationOnScreen(screen)
|
|
174
256
|
val origin = IntArray(2)
|
|
175
257
|
layer.getLocationOnScreen(origin)
|
|
176
|
-
return LayerPlace(chain, (screen[0] - origin[0]).toFloat(), (screen[1] - origin[1]).toFloat())
|
|
258
|
+
return LayerPlace(chain, (screen[0] - origin[0]).toFloat(), (screen[1] - origin[1]).toFloat(), clips)
|
|
177
259
|
}
|
|
260
|
+
|
|
261
|
+
/** The rect [a] cut by [b] (left, top, right, bottom); an empty one at 0, 0 when they don't meet, as on iOS. */
|
|
262
|
+
internal fun intersection(a: FloatArray, b: FloatArray): FloatArray {
|
|
263
|
+
val left = max(a[0], b[0])
|
|
264
|
+
val top = max(a[1], b[1])
|
|
265
|
+
val right = min(a[2], b[2])
|
|
266
|
+
val bottom = min(a[3], b[3])
|
|
267
|
+
return if (left < right && top < bottom) floatArrayOf(left, top, right, bottom) else FloatArray(RECT)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// The clip of [parent], at [level] of the chain, when it cuts what it draws to its bounds: a React Native view
|
|
271
|
+
// whose `overflow` isn't visible (a scroll view's is `scroll` unless set) with its corners, and any view whose
|
|
272
|
+
// own parent keeps its children in (`clipChildren`: Android cuts each child to its bounds, square), as the
|
|
273
|
+
// screen's own views do. React Native's views let their children out, so their `overflow` alone counts.
|
|
274
|
+
private fun clipOf(parent: View, level: Int): Clip? {
|
|
275
|
+
val overflows = parent is ReactOverflowView && parent.overflow.let { it != null && it != VISIBLE }
|
|
276
|
+
val keptIn = (parent.parent as? ViewGroup)?.clipChildren == true
|
|
277
|
+
if (!overflows && !keptIn) return null
|
|
278
|
+
val radius = if (overflows) parent.borderRadius else 0f
|
|
279
|
+
return Clip(level, parent.width.toFloat(), parent.height.toFloat(), radius)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* A parent that cuts what a view draws to its bounds: its [level] in the view's chain (1 the view's parent: its
|
|
286
|
+
* coordinates come down to the view's through the placements before it), its size and its corner radius. Pixels.
|
|
287
|
+
*/
|
|
288
|
+
internal class Clip(val level: Int, val width: Float, val height: Float, val radius: Float = 0f)
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* The part of a shared element's copy that shows at one end of its flight: left, top, right, bottom from the copy's
|
|
292
|
+
* center at the size it is drawn at, and its corner radius there. [clipped]: a parent cuts the view; else it is the
|
|
293
|
+
* whole copy with its own corners.
|
|
294
|
+
*/
|
|
295
|
+
internal class VisiblePart(
|
|
296
|
+
val left: Float,
|
|
297
|
+
val top: Float,
|
|
298
|
+
val right: Float,
|
|
299
|
+
val bottom: Float,
|
|
300
|
+
val radius: Float,
|
|
301
|
+
val clipped: Boolean,
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* The mask of a copy cut by its views' parents, as on iOS: a rounded rect that goes from the part of the copy that
|
|
306
|
+
* shows at one end to the part that shows at the other, evenly.
|
|
307
|
+
*/
|
|
308
|
+
internal class FlightMask private constructor(private val from: VisiblePart, private val to: VisiblePart) {
|
|
309
|
+
/**
|
|
310
|
+
* Fills [out] with the mask at [fraction] of the way (the curve's value), [time] the part of the time: left, top,
|
|
311
|
+
* right, bottom from the copy's center, then the corner radius.
|
|
312
|
+
*/
|
|
313
|
+
fun at(fraction: Float, time: Float, out: FloatArray) {
|
|
314
|
+
out[0] = ValueInterpolation.number(from.left, to.left, fraction, time)
|
|
315
|
+
out[1] = ValueInterpolation.number(from.top, to.top, fraction, time)
|
|
316
|
+
out[2] = ValueInterpolation.number(from.right, to.right, fraction, time)
|
|
317
|
+
out[3] = ValueInterpolation.number(from.bottom, to.bottom, fraction, time)
|
|
318
|
+
out[4] = ValueInterpolation.number(from.radius, to.radius, fraction, time)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
companion object {
|
|
322
|
+
/** The values [at] fills. */
|
|
323
|
+
const val SIZE = 5
|
|
324
|
+
|
|
325
|
+
/** The mask between [from] and [to]; none when neither is cut, and the copy shows whole. */
|
|
326
|
+
fun of(from: VisiblePart, to: VisiblePart): FlightMask? =
|
|
327
|
+
if (from.clipped || to.clipped) FlightMask(from, to) else null
|
|
178
328
|
}
|
|
179
329
|
}
|
|
180
330
|
|
|
@@ -191,48 +341,14 @@ internal class FlightEnd(
|
|
|
191
341
|
val mirrorX: Float = 1f,
|
|
192
342
|
val mirrorY: Float = 1f,
|
|
193
343
|
val scale: Float = 1f,
|
|
194
|
-
)
|
|
195
|
-
/** Whether [other] is drawn at the same place, size, angle and mirror: within half a pixel and 0.1°. */
|
|
196
|
-
fun sameAs(other: FlightEnd): Boolean =
|
|
197
|
-
abs(centerX - other.centerX) < SAME_PLACE && abs(centerY - other.centerY) < SAME_PLACE &&
|
|
198
|
-
abs(width - other.width) < SAME_PLACE && abs(height - other.height) < SAME_PLACE &&
|
|
199
|
-
abs(rotation - other.rotation) < SAME_ANGLE && mirrorX == other.mirrorX && mirrorY == other.mirrorY
|
|
200
|
-
|
|
201
|
-
companion object {
|
|
202
|
-
private const val SAME_PLACE = 0.5f
|
|
203
|
-
private const val SAME_ANGLE = 0.1f
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Whether `to` shows elsewhere in its screen than in the layer with its own properties alone ([standIn] is
|
|
207
|
-
* where it was): a parent of its scales or turns it. A flight that landed then keeps it where it lands until
|
|
208
|
-
* the transition gives it back, or it would jump to its place in the layer meanwhile.
|
|
209
|
-
*/
|
|
210
|
-
fun holdsAtEnd(to: View, standIn: View?, layer: View): Boolean {
|
|
211
|
-
if (standIn == null) return false
|
|
212
|
-
val inScreen = LayerPlace.of(to, layer, standIn).end(to.width, to.height)
|
|
213
|
-
return !inScreen.sameAs(LayerPlace.of(to, layer).end(to.width, to.height))
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
344
|
+
)
|
|
217
345
|
|
|
218
346
|
/**
|
|
219
|
-
* Where a shared element flies in its transition's layer: from where `from` is drawn to where `to` is drawn
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
* ([toLeft], [toTop]) meanwhile.
|
|
347
|
+
* Where a shared element flies in its transition's layer: from where `from` is drawn to where `to` is drawn in its
|
|
348
|
+
* screen, both through their own and their parents' translation, scale and rotation. The center, the size and the
|
|
349
|
+
* mirror go evenly, the angle along the shorter arc (as on iOS).
|
|
223
350
|
*/
|
|
224
|
-
internal class FlightGeometry(
|
|
225
|
-
val from: FlightEnd,
|
|
226
|
-
val to: FlightEnd,
|
|
227
|
-
val toLeft: Int,
|
|
228
|
-
val toTop: Int,
|
|
229
|
-
/** Whether `to` keeps the flight's last frame at the end ([FlightEnd.holdsAtEnd]). */
|
|
230
|
-
val holdsAtEnd: Boolean = false,
|
|
231
|
-
) {
|
|
232
|
-
/** The width and height `to` is laid out at meanwhile: the larger of the two sizes. */
|
|
233
|
-
val largest: Pair<Int, Int>
|
|
234
|
-
get() = maxOf(from.width, to.width).roundToInt() to maxOf(from.height, to.height).roundToInt()
|
|
235
|
-
|
|
351
|
+
internal class FlightGeometry(val from: FlightEnd, val to: FlightEnd) {
|
|
236
352
|
/** The flight at [fraction] of the way (the curve's value), [time] the part of the time. */
|
|
237
353
|
fun at(fraction: Float, time: Float): FlightEnd = FlightEnd(
|
|
238
354
|
centerX = ValueInterpolation.number(from.centerX, to.centerX, fraction, time),
|
|
@@ -244,103 +360,4 @@ internal class FlightGeometry(
|
|
|
244
360
|
mirrorY = ValueInterpolation.number(from.mirrorY, to.mirrorY, fraction, time),
|
|
245
361
|
scale = ValueInterpolation.number(from.scale, to.scale, fraction, time),
|
|
246
362
|
)
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* The translation and pivot that draw a view laid out at its margins with [width] × [height] (or showing that part
|
|
250
|
-
* of a larger layout from its corner) at [frame]'s center and angle, unscaled: x, y of the translation, then of
|
|
251
|
-
* the pivot.
|
|
252
|
-
*/
|
|
253
|
-
fun moveOf(frame: FlightEnd, width: Int, height: Int): FloatArray = floatArrayOf(
|
|
254
|
-
frame.centerX - (toLeft + width / 2f),
|
|
255
|
-
frame.centerY - (toTop + height / 2f),
|
|
256
|
-
width / 2f,
|
|
257
|
-
height / 2f,
|
|
258
|
-
)
|
|
259
|
-
|
|
260
|
-
companion object {
|
|
261
|
-
/**
|
|
262
|
-
* The flight of [from] to [to], which is in [layer] already in place of [toStandIn] (the stub where it was);
|
|
263
|
-
* none when [to] has no place there.
|
|
264
|
-
*/
|
|
265
|
-
fun of(from: View, to: View, toStandIn: View?, layer: View): FlightGeometry? {
|
|
266
|
-
val place = to.layoutParams as? ViewGroup.MarginLayoutParams ?: return null
|
|
267
|
-
val fromEnd = LayerPlace.of(from, layer).end(from.width, from.height)
|
|
268
|
-
val toEnd = LayerPlace.of(to, layer, toStandIn).end(to.width, to.height)
|
|
269
|
-
val holds = FlightEnd.holdsAtEnd(to, toStandIn, layer)
|
|
270
|
-
return FlightGeometry(fromEnd, toEnd, place.leftMargin, place.topMargin, holds)
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* The properties a moving `to` is drawn with in the layer when its flight starts, put back when it ends: the
|
|
277
|
-
* flight draws it unscaled (but mirrored where an end is) and untilted at the flight's angle about the middle of what
|
|
278
|
-
* shows.
|
|
279
|
-
*/
|
|
280
|
-
internal class MovedProperties(private val view: View) {
|
|
281
|
-
private val translationX = view.translationX
|
|
282
|
-
private val translationY = view.translationY
|
|
283
|
-
private val scaleX = view.scaleX
|
|
284
|
-
private val scaleY = view.scaleY
|
|
285
|
-
private val rotation = view.rotation
|
|
286
|
-
private val rotationX = view.rotationX
|
|
287
|
-
private val rotationY = view.rotationY
|
|
288
|
-
private val pivotX = view.pivotX
|
|
289
|
-
private val pivotY = view.pivotY
|
|
290
|
-
private val ownWidth = view.width
|
|
291
|
-
private val ownHeight = view.height
|
|
292
|
-
|
|
293
|
-
/** [view] at [frame], laid out at [width] × [height] from its margins (or showing that part from its corner). */
|
|
294
|
-
fun show(geometry: FlightGeometry, frame: FlightEnd, width: Int, height: Int) {
|
|
295
|
-
val move = geometry.moveOf(frame, width, height)
|
|
296
|
-
view.pivotX = move[2]
|
|
297
|
-
view.pivotY = move[3]
|
|
298
|
-
view.translationX = move[0]
|
|
299
|
-
view.translationY = move[1]
|
|
300
|
-
view.scaleX = frame.mirrorX
|
|
301
|
-
view.scaleY = frame.mirrorY
|
|
302
|
-
view.rotation = frame.rotation
|
|
303
|
-
view.rotationX = 0f
|
|
304
|
-
view.rotationY = 0f
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* [view] at the end of a flight that [landed]: kept at the flight's last frame, laid out by [layOut] at the size it
|
|
309
|
-
* shows, when the geometry holds it there; else as it was at the start, laid out by [layOut] at its own size.
|
|
310
|
-
*/
|
|
311
|
-
fun end(geometry: FlightGeometry, landed: Boolean, layOut: (width: Int, height: Int) -> Unit) {
|
|
312
|
-
if (geometry.holdsAtEnd && landed) {
|
|
313
|
-
val width = geometry.to.width.roundToInt()
|
|
314
|
-
val height = geometry.to.height.roundToInt()
|
|
315
|
-
layOut(width, height)
|
|
316
|
-
show(geometry, geometry.to, width, height)
|
|
317
|
-
} else {
|
|
318
|
-
restore()
|
|
319
|
-
layOut(ownWidth, ownHeight)
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/** [view] as it was at the start. */
|
|
324
|
-
fun restore() {
|
|
325
|
-
view.translationX = translationX
|
|
326
|
-
view.translationY = translationY
|
|
327
|
-
view.scaleX = scaleX
|
|
328
|
-
view.scaleY = scaleY
|
|
329
|
-
view.rotation = rotation
|
|
330
|
-
view.rotationX = rotationX
|
|
331
|
-
view.rotationY = rotationY
|
|
332
|
-
view.pivotX = pivotX
|
|
333
|
-
view.pivotY = pivotY
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/** An outline of the size and corners a flying view shows meanwhile: its own bounds are larger. */
|
|
338
|
-
internal class FlightOutline : android.view.ViewOutlineProvider() {
|
|
339
|
-
var width = 0
|
|
340
|
-
var height = 0
|
|
341
|
-
var radius = 0f
|
|
342
|
-
|
|
343
|
-
override fun getOutline(view: View, outline: android.graphics.Outline) {
|
|
344
|
-
outline.setRoundRect(0, 0, width, height, radius)
|
|
345
|
-
}
|
|
346
363
|
}
|