@apexnative/navigation 1.0.0-alpha.4 → 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 +38 -0
- package/README.md +1 -1
- package/android/CLAUDE.md +23 -14
- package/android/CORE.md +32 -19
- package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +60 -13
- package/android/src/main/java/dev/apexnative/navigation/options/AnimationsOptions.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/BottomTabsOptions.kt +13 -8
- package/android/src/main/java/dev/apexnative/navigation/options/HardwareBackButtonOptions.kt +9 -1
- package/android/src/main/java/dev/apexnative/navigation/options/StackAnimationOptions.kt +40 -2
- package/android/src/main/java/dev/apexnative/navigation/react/CommandAnswer.kt +28 -13
- package/android/src/main/java/dev/apexnative/navigation/react/Hinge.kt +7 -4
- package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +3 -3
- package/android/src/main/java/dev/apexnative/navigation/react/ReactView.kt +39 -20
- package/android/src/main/java/dev/apexnative/navigation/react/ReloadCoordinator.kt +7 -6
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenEnvironment.kt +177 -0
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +19 -51
- package/android/src/main/java/dev/apexnative/navigation/react/ScrollEvents.kt +49 -128
- package/android/src/main/java/dev/apexnative/navigation/react/events/AppearanceDispatcher.kt +30 -39
- package/android/src/main/java/dev/apexnative/navigation/react/events/EventEmitter.kt +34 -7
- package/android/src/main/java/dev/apexnative/navigation/react/events/NavigationState.kt +154 -0
- package/android/src/main/java/dev/apexnative/navigation/safearea/SafeAreaView.kt +12 -10
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotScreenView.kt +12 -12
- 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 +10 -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 +22 -21
- 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 +12 -9
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ContentReadiness.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenReadiness.kt +103 -56
- 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/BackCallbackCheck.kt +50 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackState.kt +45 -14
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +25 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ParentController.kt +3 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitViewController.kt +3 -0
- 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 +13 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +3 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/ScreenSearch.kt +3 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +2 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/BarComponents.kt +5 -51
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +4 -9
- 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/CustomBottomTabItemView.kt +0 -5
- 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/cli/doctor/checks/splash.js +4 -22
- package/cli/splash/index.js +3 -1
- package/docs-md/architecture/transitions.md +4 -2
- package/docs-md/containers/modals.md +4 -2
- 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/guides/cli.md +1 -2
- package/docs-md/help/glossary.md +2 -2
- package/docs-md/help/troubleshooting.md +23 -2
- 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 +36 -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/docs-md/start/installation.md +1 -1
- 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 +30 -26
- package/ios/Contract/ANNCommandAnswer.h +9 -2
- package/ios/Contract/ANNCommandAnswer.mm +25 -12
- 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/ANNAppearanceDispatcher.h +5 -4
- package/ios/Core/ANNAppearanceDispatcher.mm +12 -1
- package/ios/Core/ANNJavaScriptCalls.h +11 -12
- package/ios/Core/ANNJavaScriptCalls.mm +7 -16
- package/ios/Core/ANNNavigationState.h +4 -6
- package/ios/Core/ANNNavigationState.mm +0 -4
- 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/Root/ANNSplashViewController.h +5 -3
- package/ios/Layouts/Root/ANNSplashViewController.mm +147 -0
- 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 +142 -27
- 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/commands/commands.js +54 -6
- package/lib/module/api/commands/commands.js.map +1 -1
- 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/places.js +8 -0
- package/lib/module/engine/places.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/commands/commands.d.ts.map +1 -1
- package/lib/typescript/api/commands/presentation.d.ts +4 -3
- package/lib/typescript/api/commands/presentation.d.ts.map +1 -1
- 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/places.d.ts +2 -0
- package/lib/typescript/engine/places.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.d.ts +1 -1
- package/lib/typescript/interfaces/Options.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/animations.d.ts +30 -9
- 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/stack.d.ts +9 -0
- package/lib/typescript/interfaces/options/stack.d.ts.map +1 -1
- 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/commands/commands.ts +48 -7
- package/src/api/commands/presentation.ts +4 -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/places.ts +9 -0
- package/src/engine/screens.tsx +29 -8
- package/src/interfaces/Options.ts +1 -0
- package/src/interfaces/options/animations.ts +31 -9
- package/src/interfaces/options/bottomTabs.ts +18 -5
- package/src/interfaces/options/buttons.ts +8 -8
- package/src/interfaces/options/stack.ts +9 -0
- 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/react/events/NavigationStateReporter.kt +0 -62
- package/android/src/main/java/dev/apexnative/navigation/react/events/StateHold.kt +0 -62
- 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
package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabsLayout.kt
CHANGED
|
@@ -10,13 +10,15 @@ import androidx.annotation.RestrictTo
|
|
|
10
10
|
import androidx.core.view.ViewCompat
|
|
11
11
|
import androidx.core.view.WindowInsetsCompat
|
|
12
12
|
import dev.apexnative.navigation.utils.MaterialTheme
|
|
13
|
+
import dev.apexnative.navigation.viewcontrollers.bottomtabs.CustomTabRowGeometry
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* The view of bottom tabs: the view of each tab made so far fills it, the selected one visible and the others invisible
|
|
16
17
|
* but laid out, so a switch shows a tab at once and its screens render; the bar's [container] lies over them at the
|
|
17
18
|
* bottom, the module's bar ([bar], `R.id.bottomTabs`) in it. The container's distance from the bottom ([barOffset]:
|
|
18
19
|
* `bottomMargin`, and the navigation bar under a compact bar) is this view's own inset, not a layout parameter a root
|
|
19
|
-
* may ignore. The
|
|
20
|
+
* may ignore. The custom [row], when the tabs have one, lies right after the container and over the bar. The window
|
|
21
|
+
* insets go on to every child as they came. Main thread only.
|
|
20
22
|
*/
|
|
21
23
|
@SuppressLint("ViewConstructor")
|
|
22
24
|
@RestrictTo(RestrictTo.Scope.LIBRARY)
|
|
@@ -34,8 +36,25 @@ class BottomTabsLayout(context: Context) : FrameLayout(context) {
|
|
|
34
36
|
/** The window insets reached this view: the bar's room and the screens' room may have changed. */
|
|
35
37
|
internal var onInsets: (() -> Unit)? = null
|
|
36
38
|
|
|
37
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* The custom row of the tabs, when every tab draws its cell (`bottomTab.component`): a child right after the
|
|
41
|
+
* container, laid out over the bar, going with the container.
|
|
42
|
+
*/
|
|
38
43
|
internal var row: CustomTabRow? = null
|
|
44
|
+
set(value) {
|
|
45
|
+
if (field === value) return
|
|
46
|
+
field?.let {
|
|
47
|
+
removeView(it)
|
|
48
|
+
it.container = null
|
|
49
|
+
}
|
|
50
|
+
field = value
|
|
51
|
+
if (value == null) return
|
|
52
|
+
(value.parent as? ViewGroup)?.removeView(value)
|
|
53
|
+
value.container = container
|
|
54
|
+
value.place = null
|
|
55
|
+
// Its frame is absolute: the layout below puts it in its parameters.
|
|
56
|
+
addView(value, indexOfChild(container) + 1, LayoutParams(0, 0, Gravity.TOP or Gravity.LEFT))
|
|
57
|
+
}
|
|
39
58
|
|
|
40
59
|
/** The container's distance from the bottom, in pixels. */
|
|
41
60
|
internal var barOffset = 0
|
|
@@ -59,6 +78,16 @@ class BottomTabsLayout(context: Context) : FrameLayout(context) {
|
|
|
59
78
|
}
|
|
60
79
|
}
|
|
61
80
|
|
|
81
|
+
/**
|
|
82
|
+
* How far the container goes down to leave the screen whole: from the top of what the bar shows — the container, or
|
|
83
|
+
* the custom row that rises above it — to this view's bottom, in pixels.
|
|
84
|
+
*/
|
|
85
|
+
internal val hideDistance: Int
|
|
86
|
+
get() {
|
|
87
|
+
val rowTop = row?.takeIf { it.visibility != View.GONE }?.top ?: container.top
|
|
88
|
+
return (height - minOf(container.top, rowTop)).coerceAtLeast(0)
|
|
89
|
+
}
|
|
90
|
+
|
|
62
91
|
/** [view] of a tab joins under the bar, invisible until its tab shows. */
|
|
63
92
|
internal fun addTab(view: View) {
|
|
64
93
|
if (view.parent === this) return
|
|
@@ -85,5 +114,35 @@ class BottomTabsLayout(context: Context) : FrameLayout(context) {
|
|
|
85
114
|
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
86
115
|
super.onLayout(changed, left, top, right, bottom)
|
|
87
116
|
if (barOffset != 0) container.offsetTopAndBottom(-barOffset)
|
|
117
|
+
row?.let(::placeRow)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// The row over the bar where the bar rests (its container's motion the row takes before each frame), when its place
|
|
121
|
+
// changed: laid out there at once, and kept in its parameters, so the next passes measure it and put it there the
|
|
122
|
+
// same way. A hidden bar keeps the row where it was.
|
|
123
|
+
private fun placeRow(row: CustomTabRow) {
|
|
124
|
+
if (container.visibility == View.GONE || bar.width == 0) return
|
|
125
|
+
val barLeft = container.left + bar.left
|
|
126
|
+
val barTop = container.top + bar.top
|
|
127
|
+
val place = row.placeOver(
|
|
128
|
+
barLeft = barLeft,
|
|
129
|
+
barRight = barLeft + bar.width,
|
|
130
|
+
barBottom = barTop + bar.height,
|
|
131
|
+
barHeight = bar.height,
|
|
132
|
+
tabsHeight = height,
|
|
133
|
+
navigationBar = navigationBarInset,
|
|
134
|
+
)
|
|
135
|
+
if (!CustomTabRowGeometry.needsPlacing(row.place, place)) return
|
|
136
|
+
row.place = place
|
|
137
|
+
val params = row.layoutParams as LayoutParams
|
|
138
|
+
params.width = place.right - place.left
|
|
139
|
+
params.height = place.bottom - place.top
|
|
140
|
+
params.leftMargin = place.left
|
|
141
|
+
params.topMargin = place.top
|
|
142
|
+
row.measure(
|
|
143
|
+
MeasureSpec.makeMeasureSpec(params.width, MeasureSpec.EXACTLY),
|
|
144
|
+
MeasureSpec.makeMeasureSpec(params.height, MeasureSpec.EXACTLY),
|
|
145
|
+
)
|
|
146
|
+
row.layout(place.left, place.top, place.right, place.bottom)
|
|
88
147
|
}
|
|
89
148
|
}
|
package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/CustomBottomTabItemView.kt
CHANGED
|
@@ -22,9 +22,6 @@ open class CustomBottomTabItemView(context: Context) : FrameLayout(context) {
|
|
|
22
22
|
private var state: TabCellState? = null
|
|
23
23
|
private var destroyed = false
|
|
24
24
|
|
|
25
|
-
/** Runs once as the cell stops: what was kept for its content's id goes. */
|
|
26
|
-
internal var onDestroyed: (() -> Unit)? = null
|
|
27
|
-
|
|
28
25
|
/** The content the cell shows, filling it, made with [state] as its first props. */
|
|
29
26
|
internal fun show(view: IReactView, state: TabCellState) {
|
|
30
27
|
content = view
|
|
@@ -68,8 +65,6 @@ open class CustomBottomTabItemView(context: Context) : FrameLayout(context) {
|
|
|
68
65
|
didDisappear()
|
|
69
66
|
destroyed = true
|
|
70
67
|
content?.destroy()
|
|
71
|
-
onDestroyed?.invoke()
|
|
72
|
-
onDestroyed = null
|
|
73
68
|
}
|
|
74
69
|
|
|
75
70
|
/** The props of [state] as React Native takes them. */
|
|
@@ -11,18 +11,21 @@ import android.view.MotionEvent
|
|
|
11
11
|
import android.view.View
|
|
12
12
|
import android.view.ViewGroup
|
|
13
13
|
import android.view.ViewOutlineProvider
|
|
14
|
+
import android.view.ViewTreeObserver
|
|
14
15
|
import android.widget.FrameLayout
|
|
15
16
|
import android.widget.LinearLayout
|
|
16
17
|
import dev.apexnative.navigation.customrow.BottomTabsCustomRowOptions
|
|
17
18
|
import dev.apexnative.navigation.utils.TestIds
|
|
19
|
+
import dev.apexnative.navigation.utils.UiUtils
|
|
18
20
|
import dev.apexnative.navigation.viewcontrollers.bottomtabs.CustomTabRowGeometry
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
|
-
* The custom row of bottom tabs on Android (`bottomTabs.customRow`): a container over the bar,
|
|
22
|
-
* that holds the tabs' cells in one row of equal widths in tab order. It owns its
|
|
23
|
-
* the touches: each cell's place takes them, the React elements in the cell don't, and a tap
|
|
24
|
-
* goes to [onPress] or [onLongPress] with the tab's index.
|
|
25
|
-
*
|
|
23
|
+
* The custom row of bottom tabs on Android (`bottomTabs.customRow`): a container over the bar, a child of the tabs'
|
|
24
|
+
* view right after the bar's container, that holds the tabs' cells in one row of equal widths in tab order. It owns its
|
|
25
|
+
* look, its visibility and the touches: each cell's place takes them, the React elements in the cell don't, and a tap
|
|
26
|
+
* or a long press of a place goes to [onPress] or [onLongPress] with the tab's index. The tabs' view lays it out over
|
|
27
|
+
* the bar ([placeOver]); before each frame it goes with the bar's [container]. What it looks like is its tabs'. Main
|
|
28
|
+
* thread only.
|
|
26
29
|
*/
|
|
27
30
|
@SuppressLint("ViewConstructor")
|
|
28
31
|
internal class CustomTabRow(context: Context) : FrameLayout(context) {
|
|
@@ -42,11 +45,23 @@ internal class CustomTabRow(context: Context) : FrameLayout(context) {
|
|
|
42
45
|
/** A cell's place was long-pressed. */
|
|
43
46
|
var onLongPress: ((Int) -> Unit)? = null
|
|
44
47
|
|
|
45
|
-
/** `bottomTabs.customRow` of the tabs, resolved:
|
|
48
|
+
/** `bottomTabs.customRow` of the tabs, resolved: its height and margins say where the row lies. */
|
|
46
49
|
var options = BottomTabsCustomRowOptions()
|
|
47
50
|
|
|
48
51
|
/**
|
|
49
|
-
*
|
|
52
|
+
* The bar's container the row goes with: its motion (a ride, a slide, the hide on scroll), its alpha, its height
|
|
53
|
+
* over the screens and whether it shows. The tabs' view sets it as the row joins it.
|
|
54
|
+
*/
|
|
55
|
+
var container: View? = null
|
|
56
|
+
|
|
57
|
+
// Before each frame of the window the row is in: it goes with the container.
|
|
58
|
+
private val following = ViewTreeObserver.OnPreDrawListener {
|
|
59
|
+
container?.let(::follow)
|
|
60
|
+
true
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Where the tabs' view put the row last, in it; none before it did. The frame holds `bottomMargin` below the
|
|
50
65
|
* content: the row draws, clips, casts its shadow, lays its cells and takes touches only over the content, and the
|
|
51
66
|
* margin below lets the touches through to what is under it.
|
|
52
67
|
*/
|
|
@@ -117,10 +132,57 @@ internal class CustomTabRow(context: Context) : FrameLayout(context) {
|
|
|
117
132
|
return super.dispatchTouchEvent(event)
|
|
118
133
|
}
|
|
119
134
|
|
|
120
|
-
/**
|
|
121
|
-
|
|
122
|
-
|
|
135
|
+
/**
|
|
136
|
+
* Where the row lies over the bar whose edges are [barLeft], [barRight] and [barBottom] and whose height is
|
|
137
|
+
* [barHeight], in the tabs' view of [tabsHeight] with the navigation bar's [navigationBar] at its bottom: its
|
|
138
|
+
* height and margins from [options].
|
|
139
|
+
*/
|
|
140
|
+
fun placeOver(barLeft: Int, barRight: Int, barBottom: Int, barHeight: Int, tabsHeight: Int, navigationBar: Int) =
|
|
141
|
+
CustomTabRowGeometry.inTabs(
|
|
142
|
+
barLeft = barLeft,
|
|
143
|
+
barRight = barRight,
|
|
144
|
+
barBottom = barBottom,
|
|
145
|
+
tabsHeight = tabsHeight,
|
|
146
|
+
navigationBar = navigationBar,
|
|
147
|
+
margin = UiUtils.dpToPixels(
|
|
148
|
+
context,
|
|
149
|
+
options.horizontalMargin.get(CustomTabRowGeometry.HORIZONTAL_MARGIN_DP.toDouble()),
|
|
150
|
+
),
|
|
151
|
+
content = CustomTabRowGeometry.content(
|
|
152
|
+
barHeight = barHeight,
|
|
153
|
+
height = options.height.get(null)?.let { UiUtils.dpToPixels(context, it) },
|
|
154
|
+
extra = UiUtils.dpToPixels(context, CustomTabRowGeometry.EXTRA_HEIGHT_DP.toDouble()),
|
|
155
|
+
),
|
|
156
|
+
bottomMargin = UiUtils.dpToPixels(context, options.bottomMargin.get(0.0)),
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
override fun onAttachedToWindow() {
|
|
160
|
+
super.onAttachedToWindow()
|
|
161
|
+
viewTreeObserver.addOnPreDrawListener(following)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
override fun onDetachedFromWindow() {
|
|
165
|
+
viewTreeObserver.removeOnPreDrawListener(following)
|
|
166
|
+
super.onDetachedFromWindow()
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// The row hides with the bar and takes its motion and its alpha (the tabs' own alpha and motion, and their
|
|
170
|
+
// parents', the row has as their child); it stands as high as the container, and leaves accessibility while its
|
|
171
|
+
// tabs wait to show.
|
|
172
|
+
private fun follow(container: View) {
|
|
173
|
+
val wanted = if (container.visibility != View.GONE) View.VISIBLE else View.GONE
|
|
123
174
|
if (visibility != wanted) visibility = wanted
|
|
175
|
+
if (alpha != container.alpha) alpha = container.alpha
|
|
176
|
+
if (translationX != container.translationX) translationX = container.translationX
|
|
177
|
+
if (translationY != container.translationY) translationY = container.translationY
|
|
178
|
+
val lift = CustomTabRowGeometry.lift(container.z, elevation)
|
|
179
|
+
if (translationZ != lift) translationZ = lift
|
|
180
|
+
val mode = if (hiddenFromAccessibility()) {
|
|
181
|
+
View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
|
|
182
|
+
} else {
|
|
183
|
+
View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
|
|
184
|
+
}
|
|
185
|
+
if (importantForAccessibility != mode) importantForAccessibility = mode
|
|
124
186
|
}
|
|
125
187
|
|
|
126
188
|
/** Draws [look]; nothing changes when it is the one drawn. */
|
|
@@ -15,6 +15,7 @@ import dev.apexnative.navigation.react.events.AppearanceDispatcher
|
|
|
15
15
|
import dev.apexnative.navigation.utils.DevelopmentLog
|
|
16
16
|
import dev.apexnative.navigation.utils.NavigationLog
|
|
17
17
|
import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
|
|
18
|
+
import dev.apexnative.navigation.views.animations.AnimationLayer
|
|
18
19
|
import dev.apexnative.navigation.views.animations.SideAnimator
|
|
19
20
|
import dev.apexnative.navigation.views.element.animators.SharedAnimatorFactory
|
|
20
21
|
|
|
@@ -161,9 +162,15 @@ internal class ElementTransition private constructor(private val layer: Transiti
|
|
|
161
162
|
}
|
|
162
163
|
}
|
|
163
164
|
|
|
165
|
+
// An element with views in it fades through a hardware layer only where its alpha would draw it into an offscreen
|
|
166
|
+
// buffer at every frame (AnimationLayer: its rendering overlaps). React's views overlap only with
|
|
167
|
+
// `needsOffscreenAlphaCompositing`, so most elements fade without one and nothing they draw outside their bounds (a
|
|
168
|
+
// box shadow) is clipped. A view without children fades its own drawing at no extra cost.
|
|
164
169
|
private fun addElement(options: ElementTransitionOptions, item: Moved) {
|
|
165
170
|
val base = SideAnimator.Base(xOffset = item.originX.toFloat(), yOffset = item.originY.toFloat())
|
|
166
|
-
SideAnimator.build(item.view, options, base)
|
|
171
|
+
val animator = SideAnimator.build(item.view, options, base) ?: return
|
|
172
|
+
if (item.view is ViewGroup) AnimationLayer.during(item.view, animator)
|
|
173
|
+
animators += animator
|
|
167
174
|
}
|
|
168
175
|
|
|
169
176
|
// The pair's flight: `to` hidden until it starts, `from` hidden from its start; false when it doesn't fly.
|
|
@@ -176,7 +183,7 @@ internal class ElementTransition private constructor(private val layer: Transiti
|
|
|
176
183
|
): Boolean {
|
|
177
184
|
val to = item.view
|
|
178
185
|
val flight = SharedAnimatorFactory.create(
|
|
179
|
-
from, to, toParent, layer, options, fallbackDuration, DevelopmentLog::warn,
|
|
186
|
+
from, to, toParent, item.stub, layer, options, fallbackDuration, DevelopmentLog::warn,
|
|
180
187
|
) ?: return false
|
|
181
188
|
val alpha = item.values[ALPHA]
|
|
182
189
|
val fromAlpha = from.alpha
|
package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt
CHANGED
|
@@ -45,6 +45,13 @@ internal class MoveAnimator : ValueAnimator() {
|
|
|
45
45
|
private val frames = ArrayList<(Float) -> Unit>()
|
|
46
46
|
private val ends = ArrayList<() -> Unit>()
|
|
47
47
|
private var failed = false
|
|
48
|
+
private var cancelled = false
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Whether the flight ran to its end: it wasn't cancelled and nothing in it failed. Read at its end; a flight that
|
|
52
|
+
* landed may keep showing its last frame until the transition gives its views back.
|
|
53
|
+
*/
|
|
54
|
+
val landed: Boolean get() = !cancelled
|
|
48
55
|
|
|
49
56
|
init {
|
|
50
57
|
setFloatValues(0f, 1f)
|
|
@@ -52,6 +59,10 @@ internal class MoveAnimator : ValueAnimator() {
|
|
|
52
59
|
addListener(object : AnimatorListenerAdapter() {
|
|
53
60
|
override fun onAnimationStart(animation: Animator) = guarded { for (start in starts) start() }
|
|
54
61
|
|
|
62
|
+
override fun onAnimationCancel(animation: Animator) {
|
|
63
|
+
cancelled = true
|
|
64
|
+
}
|
|
65
|
+
|
|
55
66
|
override fun onAnimationEnd(animation: Animator) {
|
|
56
67
|
for (end in ends) elementStep(element) { end() }
|
|
57
68
|
}
|
package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt
CHANGED
|
@@ -1,46 +1,339 @@
|
|
|
1
1
|
package dev.apexnative.navigation.views.element.animators
|
|
2
2
|
|
|
3
|
-
import android.graphics.RectF
|
|
4
3
|
import android.view.View
|
|
5
4
|
import android.view.ViewGroup
|
|
6
|
-
import dev.apexnative.navigation.
|
|
7
|
-
import
|
|
8
|
-
import
|
|
5
|
+
import dev.apexnative.navigation.views.animations.ValueInterpolation
|
|
6
|
+
import kotlin.math.abs
|
|
7
|
+
import kotlin.math.cos
|
|
8
|
+
import kotlin.math.min
|
|
9
|
+
import kotlin.math.roundToInt
|
|
10
|
+
import kotlin.math.sin
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
+
* How a view lies in its parent and is drawn there: its frame's corner, its translation, scale and rotation about its
|
|
14
|
+
* pivot, and its parent's scroll. What `View.getMatrix` does, the scale first, without the tilts (`rotationX`,
|
|
15
|
+
* `rotationY`) but for a half turn about x or y: React Native draws a mirror that way ([of]). Pixels and degrees,
|
|
16
|
+
* clockwise.
|
|
17
|
+
*/
|
|
18
|
+
internal class ViewPlacement(
|
|
19
|
+
val left: Float,
|
|
20
|
+
val top: Float,
|
|
21
|
+
val translationX: Float = 0f,
|
|
22
|
+
val translationY: Float = 0f,
|
|
23
|
+
val scaleX: Float = 1f,
|
|
24
|
+
val scaleY: Float = 1f,
|
|
25
|
+
val rotation: Float = 0f,
|
|
26
|
+
val pivotX: Float = 0f,
|
|
27
|
+
val pivotY: Float = 0f,
|
|
28
|
+
val parentScrollX: Float = 0f,
|
|
29
|
+
val parentScrollY: Float = 0f,
|
|
30
|
+
) {
|
|
31
|
+
/** The point [x], [y] of the view in its parent's coordinates. */
|
|
32
|
+
fun toParent(x: Float, y: Float): FloatArray {
|
|
33
|
+
val dx = (x - pivotX) * scaleX
|
|
34
|
+
val dy = (y - pivotY) * scaleY
|
|
35
|
+
val radians = Math.toRadians(rotation.toDouble())
|
|
36
|
+
val cos = cos(radians).toFloat()
|
|
37
|
+
val sin = sin(radians).toFloat()
|
|
38
|
+
return floatArrayOf(
|
|
39
|
+
dx * cos - dy * sin + pivotX + translationX + left - parentScrollX,
|
|
40
|
+
dx * sin + dy * cos + pivotY + translationY + top - parentScrollY,
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Whether the view shows mirrored: one of its scales is negative. */
|
|
45
|
+
val mirrors: Boolean get() = scaleX * scaleY < 0
|
|
46
|
+
|
|
47
|
+
companion object {
|
|
48
|
+
// How close to half a turn a tilt counts as a flip, in degrees.
|
|
49
|
+
private const val FLIP_TOLERANCE = 0.5f
|
|
50
|
+
private const val HALF_TURN = 180f
|
|
51
|
+
|
|
52
|
+
/** [view] as it lies now; [frameOf] the view whose frame and parent it takes (a stand-in where it was). */
|
|
53
|
+
fun of(view: View, frameOf: View = view): ViewPlacement {
|
|
54
|
+
val parent = frameOf.parent as? View
|
|
55
|
+
return withTilts(
|
|
56
|
+
left = frameOf.left.toFloat(),
|
|
57
|
+
top = frameOf.top.toFloat(),
|
|
58
|
+
translationX = view.translationX,
|
|
59
|
+
translationY = view.translationY,
|
|
60
|
+
scaleX = view.scaleX,
|
|
61
|
+
scaleY = view.scaleY,
|
|
62
|
+
rotation = view.rotation,
|
|
63
|
+
rotationX = view.rotationX,
|
|
64
|
+
rotationY = view.rotationY,
|
|
65
|
+
pivotX = view.pivotX,
|
|
66
|
+
pivotY = view.pivotY,
|
|
67
|
+
parentScrollX = parent?.scrollX?.toFloat() ?: 0f,
|
|
68
|
+
parentScrollY = parent?.scrollY?.toFloat() ?: 0f,
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The placement of a view with these properties. React Native splits a mirror (`scaleX: -1`) into both scales
|
|
74
|
+
* negated and half a turn about x: a view turns about z after its scale and tilts about y, then x, last, and
|
|
75
|
+
* half a turn about x flips y after the turn, which is the same as flipping y first and turning the other way.
|
|
76
|
+
* Other tilts are left out.
|
|
77
|
+
*/
|
|
78
|
+
fun withTilts(
|
|
79
|
+
left: Float,
|
|
80
|
+
top: Float,
|
|
81
|
+
translationX: Float,
|
|
82
|
+
translationY: Float,
|
|
83
|
+
scaleX: Float,
|
|
84
|
+
scaleY: Float,
|
|
85
|
+
rotation: Float,
|
|
86
|
+
rotationX: Float,
|
|
87
|
+
rotationY: Float,
|
|
88
|
+
pivotX: Float,
|
|
89
|
+
pivotY: Float,
|
|
90
|
+
parentScrollX: Float,
|
|
91
|
+
parentScrollY: Float,
|
|
92
|
+
): ViewPlacement {
|
|
93
|
+
val flipsY = abs(abs(rotationX) - HALF_TURN) < FLIP_TOLERANCE
|
|
94
|
+
val flipsX = abs(abs(rotationY) - HALF_TURN) < FLIP_TOLERANCE
|
|
95
|
+
return ViewPlacement(
|
|
96
|
+
left = left,
|
|
97
|
+
top = top,
|
|
98
|
+
translationX = translationX,
|
|
99
|
+
translationY = translationY,
|
|
100
|
+
scaleX = if (flipsX) -scaleX else scaleX,
|
|
101
|
+
scaleY = if (flipsY) -scaleY else scaleY,
|
|
102
|
+
rotation = if (flipsX != flipsY) -rotation else rotation,
|
|
103
|
+
pivotX = pivotX,
|
|
104
|
+
pivotY = pivotY,
|
|
105
|
+
parentScrollX = parentScrollX,
|
|
106
|
+
parentScrollY = parentScrollY,
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Where the points of a view are drawn in a transition's layer: through its own placement and each parent's ([chain],
|
|
114
|
+
* the view first, up to the top view of the window but not it), then moved by [offsetX], [offsetY] (where the top view
|
|
115
|
+
* 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.
|
|
117
|
+
*/
|
|
118
|
+
internal class LayerPlace(
|
|
119
|
+
private val chain: List<ViewPlacement>,
|
|
120
|
+
private val offsetX: Float,
|
|
121
|
+
private val offsetY: Float,
|
|
122
|
+
) {
|
|
123
|
+
/** The angle the view is drawn at, degrees, clockwise: a mirrored parent turns what is in it the other way. */
|
|
124
|
+
val rotation: Float = chain.fold(0f) { inner, placement ->
|
|
125
|
+
placement.rotation + if (placement.mirrors) -inner else inner
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** The scale it is drawn at along its own x (a mirror is negative). */
|
|
129
|
+
val scaleX: Float = chain.fold(1f) { product, placement -> product * placement.scaleX }
|
|
130
|
+
|
|
131
|
+
/** The scale it is drawn at along its own y. */
|
|
132
|
+
val scaleY: Float = chain.fold(1f) { product, placement -> product * placement.scaleY }
|
|
133
|
+
|
|
134
|
+
/** The point [x], [y] of the view in the layer. */
|
|
135
|
+
fun point(x: Float, y: Float): FloatArray {
|
|
136
|
+
var point = floatArrayOf(x, y)
|
|
137
|
+
for (placement in chain) point = placement.toParent(point[0], point[1])
|
|
138
|
+
return floatArrayOf(point[0] + offsetX, point[1] + offsetY)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** The flight's end of a view of [width] × [height] drawn here. */
|
|
142
|
+
fun end(width: Int, height: Int): FlightEnd {
|
|
143
|
+
val center = point(width / 2f, height / 2f)
|
|
144
|
+
return FlightEnd(
|
|
145
|
+
centerX = center[0],
|
|
146
|
+
centerY = center[1],
|
|
147
|
+
width = width * abs(scaleX),
|
|
148
|
+
height = height * abs(scaleY),
|
|
149
|
+
rotation = rotation,
|
|
150
|
+
mirrorX = if (scaleX < 0) -1f else 1f,
|
|
151
|
+
mirrorY = if (scaleY < 0) -1f else 1f,
|
|
152
|
+
scale = min(abs(scaleX), abs(scaleY)),
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
companion object {
|
|
157
|
+
/**
|
|
158
|
+
* Where [view] is drawn in [layer]. With [standIn], [view] counts as lying where that one lies (its frame and
|
|
159
|
+
* parents) with its own properties: an element that moved into the layer, at the place it goes back to.
|
|
160
|
+
*/
|
|
161
|
+
fun of(view: View, layer: View, standIn: View? = null): LayerPlace {
|
|
162
|
+
val placed = standIn ?: view
|
|
163
|
+
val chain = arrayListOf(ViewPlacement.of(view, placed))
|
|
164
|
+
var parent = placed.parent as? View
|
|
165
|
+
while (parent != null) {
|
|
166
|
+
val above = parent.parent as? View ?: break
|
|
167
|
+
chain += ViewPlacement.of(parent)
|
|
168
|
+
parent = above
|
|
169
|
+
}
|
|
170
|
+
// 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 ?: placed
|
|
172
|
+
val screen = IntArray(2)
|
|
173
|
+
base.getLocationOnScreen(screen)
|
|
174
|
+
val origin = IntArray(2)
|
|
175
|
+
layer.getLocationOnScreen(origin)
|
|
176
|
+
return LayerPlace(chain, (screen[0] - origin[0]).toFloat(), (screen[1] - origin[1]).toFloat())
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* One end of a shared element's flight in its layer: where its center is drawn, the size it shows (its own at the
|
|
183
|
+
* scale of its chain), its angle, whether it shows mirrored, and its scale (what its corners shrink by). Pixels.
|
|
184
|
+
*/
|
|
185
|
+
internal class FlightEnd(
|
|
186
|
+
val centerX: Float,
|
|
187
|
+
val centerY: Float,
|
|
188
|
+
val width: Float,
|
|
189
|
+
val height: Float,
|
|
190
|
+
val rotation: Float,
|
|
191
|
+
val mirrorX: Float = 1f,
|
|
192
|
+
val mirrorY: Float = 1f,
|
|
193
|
+
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
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Where a shared element flies in its transition's layer: from where `from` is drawn to where `to` is drawn once it
|
|
220
|
+
* is back in its screen, both through their own and their parents' translation, scale and rotation. The center, the
|
|
221
|
+
* size and the mirror go evenly, the angle along the shorter arc (as on iOS). `to` lies in the layer at its margins
|
|
222
|
+
* ([toLeft], [toTop]) meanwhile.
|
|
13
223
|
*/
|
|
14
224
|
internal class FlightGeometry(
|
|
15
|
-
|
|
16
|
-
val
|
|
17
|
-
|
|
18
|
-
val
|
|
19
|
-
/**
|
|
20
|
-
val
|
|
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,
|
|
21
231
|
) {
|
|
22
232
|
/** The width and height `to` is laid out at meanwhile: the larger of the two sizes. */
|
|
23
233
|
val largest: Pair<Int, Int>
|
|
24
|
-
get() = maxOf(from.width
|
|
234
|
+
get() = maxOf(from.width, to.width).roundToInt() to maxOf(from.height, to.height).roundToInt()
|
|
235
|
+
|
|
236
|
+
/** The flight at [fraction] of the way (the curve's value), [time] the part of the time. */
|
|
237
|
+
fun at(fraction: Float, time: Float): FlightEnd = FlightEnd(
|
|
238
|
+
centerX = ValueInterpolation.number(from.centerX, to.centerX, fraction, time),
|
|
239
|
+
centerY = ValueInterpolation.number(from.centerY, to.centerY, fraction, time),
|
|
240
|
+
width = ValueInterpolation.number(from.width, to.width, fraction, time),
|
|
241
|
+
height = ValueInterpolation.number(from.height, to.height, fraction, time),
|
|
242
|
+
rotation = ValueInterpolation.angle(from.rotation, to.rotation, fraction, time),
|
|
243
|
+
mirrorX = ValueInterpolation.number(from.mirrorX, to.mirrorX, fraction, time),
|
|
244
|
+
mirrorY = ValueInterpolation.number(from.mirrorY, to.mirrorY, fraction, time),
|
|
245
|
+
scale = ValueInterpolation.number(from.scale, to.scale, fraction, time),
|
|
246
|
+
)
|
|
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
|
+
)
|
|
25
259
|
|
|
26
260
|
companion object {
|
|
27
|
-
/**
|
|
28
|
-
|
|
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? {
|
|
29
266
|
val place = to.layoutParams as? ViewGroup.MarginLayoutParams ?: return null
|
|
30
|
-
val
|
|
31
|
-
val
|
|
32
|
-
val (
|
|
33
|
-
|
|
34
|
-
val top = (onScreen.y - origin.y).toFloat()
|
|
35
|
-
val fromRect = RectF(left, top, left + from.width * scaleX, top + from.height * scaleY)
|
|
36
|
-
val toLeft = place.leftMargin.toFloat()
|
|
37
|
-
val toTop = place.topMargin.toFloat()
|
|
38
|
-
val toRect = RectF(toLeft, toTop, toLeft + to.width, toTop + to.height)
|
|
39
|
-
return FlightGeometry(fromRect, toRect, scaleX)
|
|
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)
|
|
40
271
|
}
|
|
41
272
|
}
|
|
42
273
|
}
|
|
43
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
|
+
|
|
44
337
|
/** An outline of the size and corners a flying view shows meanwhile: its own bounds are larger. */
|
|
45
338
|
internal class FlightOutline : android.view.ViewOutlineProvider() {
|
|
46
339
|
var width = 0
|