@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
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
package dev.apexnative.navigation.views.element.animators
|
|
2
|
+
|
|
3
|
+
import android.graphics.Canvas
|
|
4
|
+
import android.graphics.Color
|
|
5
|
+
import android.graphics.Paint
|
|
6
|
+
import android.graphics.PorterDuff
|
|
7
|
+
import android.graphics.PorterDuffXfermode
|
|
8
|
+
import android.graphics.RectF
|
|
9
|
+
import android.graphics.drawable.Drawable
|
|
10
|
+
import android.view.View
|
|
11
|
+
import android.widget.ImageView
|
|
12
|
+
import com.facebook.react.uimanager.BackgroundStyleApplicator
|
|
13
|
+
import com.facebook.react.uimanager.style.LogicalEdge
|
|
14
|
+
import com.facebook.react.views.image.ReactImageView
|
|
15
|
+
import com.facebook.react.views.view.ReactViewGroup
|
|
16
|
+
import dev.apexnative.navigation.utils.borderRadius
|
|
17
|
+
import kotlin.math.abs
|
|
18
|
+
import kotlin.math.roundToInt
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A shared image flies as its image, fitted all the way: the copy goes from where `from` is drawn to where `to` is
|
|
22
|
+
* drawn in its screen ([FlightGeometry]), clipped to its corners, and the image of `to` in it goes from the part and
|
|
23
|
+
* place `from` shows its image at (its fit, `resizeMode`) to those of `to`, keeping its proportions where both fits
|
|
24
|
+
* do. The image is `to`'s own drawable, drawn on the layer's canvas and only read: an animated image plays on. The
|
|
25
|
+
* corners are the image's own radius, else that of a parent that clips it (`overflow: hidden`) and that it fills.
|
|
26
|
+
* Where a parent cuts the view, only the part that shows flies, as with a view ([ViewMorph]). A pair whose views draw
|
|
27
|
+
* more than their images (a background, a border) or whose images differ in proportions flies as pictures of the views
|
|
28
|
+
* ([ViewMorph]).
|
|
29
|
+
*/
|
|
30
|
+
internal class ImageMorph private constructor(
|
|
31
|
+
private val start: ImageEnd,
|
|
32
|
+
private val end: ImageEnd,
|
|
33
|
+
private val image: Drawable,
|
|
34
|
+
// Where the image's drawable draws the image, in the coordinates it draws in.
|
|
35
|
+
private val drawn: RectF,
|
|
36
|
+
) : FlightPicture() {
|
|
37
|
+
private val flight = FlightGeometry(start.end, end.end)
|
|
38
|
+
private val cut = FlightMask.of(start.visible, end.visible)?.let(::MaskCut)
|
|
39
|
+
private var at = start.end
|
|
40
|
+
private var progress = 0f
|
|
41
|
+
private val rect = RectF()
|
|
42
|
+
private val imageRect = FloatArray(RECT)
|
|
43
|
+
private val maskPaint = Paint(Paint.ANTI_ALIAS_FLAG)
|
|
44
|
+
// What is drawn over the mask keeps only the mask's pixels: the corners stay smooth.
|
|
45
|
+
private val contentPaint = Paint().apply { xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN) }
|
|
46
|
+
|
|
47
|
+
override val live: Boolean get() = true
|
|
48
|
+
|
|
49
|
+
override fun update(time: Float, progress: Float) {
|
|
50
|
+
this.progress = progress
|
|
51
|
+
at = flight.at(progress, time)
|
|
52
|
+
for (i in 0 until RECT) imageRect[i] = lerp(start.image[i], end.image[i])
|
|
53
|
+
cut?.update(time, progress)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
override fun draw(canvas: Canvas) {
|
|
57
|
+
rect.set(-at.width / 2, -at.height / 2, at.width / 2, at.height / 2)
|
|
58
|
+
val imageWidth = imageRect[2] - imageRect[0]
|
|
59
|
+
val imageHeight = imageRect[3] - imageRect[1]
|
|
60
|
+
if (rect.isEmpty || imageWidth <= 0 || imageHeight <= 0 || drawn.isEmpty) return
|
|
61
|
+
val alpha = (lerp(start.alpha, end.alpha) * 255).roundToInt().coerceIn(0, 255)
|
|
62
|
+
if (alpha == 0) return
|
|
63
|
+
val radius = lerp(start.radius, end.radius)
|
|
64
|
+
// The copy turns about its center, mirrored where a view is.
|
|
65
|
+
canvas.translate(at.centerX, at.centerY)
|
|
66
|
+
canvas.rotate(at.rotation)
|
|
67
|
+
canvas.scale(at.mirrorX, at.mirrorY)
|
|
68
|
+
canvas.saveLayer(rect, null)
|
|
69
|
+
canvas.drawRoundRect(rect, radius, radius, maskPaint)
|
|
70
|
+
cut?.draw(canvas, rect)
|
|
71
|
+
contentPaint.alpha = alpha
|
|
72
|
+
canvas.saveLayer(rect, contentPaint)
|
|
73
|
+
canvas.translate(imageRect[0], imageRect[1])
|
|
74
|
+
canvas.scale(imageWidth / drawn.width(), imageHeight / drawn.height())
|
|
75
|
+
canvas.translate(-drawn.left, -drawn.top)
|
|
76
|
+
image.draw(canvas)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private fun lerp(from: Float, to: Float) = from + (to - from) * progress
|
|
80
|
+
|
|
81
|
+
// One end of the flight: where the view is drawn, where its image shows in it (left, top, right, bottom from the
|
|
82
|
+
// view's center, at the size it is drawn at), its corner radius there, its opacity and the part of it that shows.
|
|
83
|
+
private class ImageEnd(
|
|
84
|
+
val end: FlightEnd,
|
|
85
|
+
val image: FloatArray,
|
|
86
|
+
val radius: Float,
|
|
87
|
+
val alpha: Float,
|
|
88
|
+
val visible: VisiblePart,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
companion object {
|
|
92
|
+
private const val RECT = 4
|
|
93
|
+
|
|
94
|
+
// How far from its parent's size an image still fills it, in pixels.
|
|
95
|
+
private const val FILLS = 1
|
|
96
|
+
private const val HIDDEN = "hidden"
|
|
97
|
+
|
|
98
|
+
// How far the proportions of two images may differ for one to stand for the other, in parts of one.
|
|
99
|
+
private const val SAME_ASPECT = 0.02f
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The morph of [from] into [to] in [layer], each cut by its parents up to the view of its screen ([fromRoot],
|
|
103
|
+
* [toRoot]); none when an image isn't there to fit, the images differ in proportions, or a view draws more
|
|
104
|
+
* than its image.
|
|
105
|
+
*/
|
|
106
|
+
fun of(from: ImageView, to: ImageView, fromRoot: View?, toRoot: View?, layer: View): ImageMorph? {
|
|
107
|
+
if (!onlyImage(from) || !onlyImage(to)) return null
|
|
108
|
+
val image = to.drawable ?: return null
|
|
109
|
+
val drawn = drawnRect(to) ?: return null
|
|
110
|
+
val fromDrawn = drawnRect(from) ?: return null
|
|
111
|
+
if (!sameAspect(fromDrawn.width(), fromDrawn.height(), drawn.width(), drawn.height())) return null
|
|
112
|
+
val start = endOf(from, layer, fromRoot) ?: return null
|
|
113
|
+
val end = endOf(to, layer, toRoot) ?: return null
|
|
114
|
+
return ImageMorph(start, end, image, drawn)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Whether an image of [width] × [height] has the proportions of one of [otherWidth] × [otherHeight]. */
|
|
118
|
+
internal fun sameAspect(width: Float, height: Float, otherWidth: Float, otherHeight: Float): Boolean {
|
|
119
|
+
if (width <= 0f || height <= 0f || otherWidth <= 0f || otherHeight <= 0f) return false
|
|
120
|
+
val aspect = width / height
|
|
121
|
+
val other = otherWidth / otherHeight
|
|
122
|
+
return abs(aspect - other) <= SAME_ASPECT * other
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// The view draws nothing but its image: no background color, no border (React Native draws them as the
|
|
126
|
+
// view's background, around the image).
|
|
127
|
+
private fun onlyImage(view: View): Boolean = try {
|
|
128
|
+
val background = BackgroundStyleApplicator.getBackgroundColor(view)
|
|
129
|
+
(background == null || Color.alpha(background) == 0) && LogicalEdge.entries.none {
|
|
130
|
+
(BackgroundStyleApplicator.getBorderWidth(view, it) ?: 0f) > 0f
|
|
131
|
+
}
|
|
132
|
+
} catch (e: RuntimeException) {
|
|
133
|
+
false
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private fun endOf(view: ImageView, layer: View, root: View?): ImageEnd? {
|
|
137
|
+
if (view.width <= 0 || view.height <= 0) return null
|
|
138
|
+
val shown = shownRect(view) ?: return null
|
|
139
|
+
val place = LayerPlace.of(view, layer, root)
|
|
140
|
+
val end = place.end(view.width, view.height)
|
|
141
|
+
val image = imageFromCenter(
|
|
142
|
+
floatArrayOf(shown.left, shown.top, shown.right, shown.bottom),
|
|
143
|
+
view.width, view.height, end.width, end.height,
|
|
144
|
+
)
|
|
145
|
+
val radius = cornerRadius(view)
|
|
146
|
+
return ImageEnd(
|
|
147
|
+
end, image, radius * end.scale, view.alpha, place.visible(view.width, view.height, radius),
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Where the drawable draws the image when the view draws it: Fresco's image where its fit puts it in the
|
|
152
|
+
// drawable, another image at its drawable's bounds (the view's matrix comes on top).
|
|
153
|
+
private fun drawnRect(view: ImageView): RectF? {
|
|
154
|
+
val rect = if (view is ReactImageView) {
|
|
155
|
+
if (!view.hasHierarchy()) return null
|
|
156
|
+
RectF().also { view.hierarchy.getActualImageBounds(it) }
|
|
157
|
+
} else {
|
|
158
|
+
RectF(view.drawable?.bounds ?: return null)
|
|
159
|
+
}
|
|
160
|
+
return rect.takeUnless { it.isEmpty }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Where the image shows in the view, in its pixels.
|
|
164
|
+
private fun shownRect(view: ImageView): RectF? {
|
|
165
|
+
val rect = drawnRect(view) ?: return null
|
|
166
|
+
if (view !is ReactImageView) view.imageMatrix.mapRect(rect)
|
|
167
|
+
rect.offset(view.paddingLeft.toFloat(), view.paddingTop.toFloat())
|
|
168
|
+
return rect
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private fun cornerRadius(view: ImageView): Float {
|
|
172
|
+
view.borderRadius.takeIf { it > 0f }?.let { return it }
|
|
173
|
+
val parent = view.parent as? ReactViewGroup ?: return 0f
|
|
174
|
+
val fills = abs(parent.width - view.width) <= FILLS && abs(parent.height - view.height) <= FILLS
|
|
175
|
+
return if (fills && parent.overflow == HIDDEN) parent.borderRadius else 0f
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* The rect [shown] (left, top, right, bottom) of a view of [width] × [height], from the view's center, at the
|
|
180
|
+
* size [shownWidth] × [shownHeight] the view is drawn at.
|
|
181
|
+
*/
|
|
182
|
+
internal fun imageFromCenter(
|
|
183
|
+
shown: FloatArray,
|
|
184
|
+
width: Int,
|
|
185
|
+
height: Int,
|
|
186
|
+
shownWidth: Float,
|
|
187
|
+
shownHeight: Float,
|
|
188
|
+
): FloatArray {
|
|
189
|
+
val scaleX = shownWidth / width
|
|
190
|
+
val scaleY = shownHeight / height
|
|
191
|
+
return floatArrayOf(
|
|
192
|
+
(shown[0] - width / 2f) * scaleX,
|
|
193
|
+
(shown[1] - height / 2f) * scaleY,
|
|
194
|
+
(shown[2] - width / 2f) * scaleX,
|
|
195
|
+
(shown[3] - height / 2f) * scaleY,
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -1,75 +1,85 @@
|
|
|
1
1
|
package dev.apexnative.navigation.views.element.animators
|
|
2
2
|
|
|
3
|
-
import android.animation.Animator
|
|
4
3
|
import android.view.View
|
|
5
4
|
import android.widget.ImageView
|
|
6
|
-
import com.facebook.react.views.image.ReactImageView
|
|
7
5
|
import com.facebook.react.views.text.ReactTextView
|
|
8
|
-
import com.facebook.react.views.view.ReactViewGroup
|
|
9
6
|
import dev.apexnative.navigation.options.SharedElementTransitionOptions
|
|
10
7
|
import dev.apexnative.navigation.views.animations.AnimationRules
|
|
11
8
|
import dev.apexnative.navigation.views.animations.Curves
|
|
12
|
-
import dev.apexnative.navigation.views.element.
|
|
9
|
+
import dev.apexnative.navigation.views.element.elementSkipped
|
|
13
10
|
import dev.apexnative.navigation.views.element.pairName
|
|
14
11
|
|
|
15
12
|
/**
|
|
16
|
-
* The
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* Main thread only.
|
|
13
|
+
* The flight of a shared pair, as on iOS: a copy drawn in the transition's layer, chosen by the kinds of the two views,
|
|
14
|
+
* by explicit classes, never by reflection — texts ([TextMorph]), images ([ImageMorph]; one without an image to fit
|
|
15
|
+
* flies as a view), any other views ([ViewMorph]). The views themselves are neither moved nor changed. The animator
|
|
16
|
+
* gets the pair's duration (else the fallback, the screen's fade), its delay and its curve here, each of its own (a
|
|
17
|
+
* spring in real time); an `AnimatorSet` around it never sets them. Main thread only.
|
|
22
18
|
*/
|
|
23
19
|
internal object SharedAnimatorFactory {
|
|
24
|
-
/**
|
|
25
|
-
|
|
26
|
-
* modules), or by moving `to` itself, which shows from the start of the flight.
|
|
27
|
-
*/
|
|
28
|
-
class Flight(val animator: Animator, val drawsPictures: Boolean)
|
|
20
|
+
/** A pair's animator and the copy it moves. */
|
|
21
|
+
class Flight(val animator: MoveAnimator, val picture: FlightPicture)
|
|
29
22
|
|
|
30
23
|
/**
|
|
31
|
-
* The flight of the pair [from] → [to]
|
|
32
|
-
*
|
|
33
|
-
* pair doesn't fly
|
|
24
|
+
* The flight of the pair [from] → [to] in [layer], its pictures taken now; a view or an image shows only the part
|
|
25
|
+
* its parents up to the view of its screen ([fromRoot], [toRoot]) leave visible, a text shows whole, as on iOS.
|
|
26
|
+
* None: the pair doesn't fly — a text that doesn't change, or, with a line in [log], a view without a size or a
|
|
27
|
+
* picture that can't be taken.
|
|
34
28
|
*/
|
|
35
29
|
fun create(
|
|
36
30
|
from: View,
|
|
37
31
|
to: View,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
layer:
|
|
32
|
+
fromRoot: View?,
|
|
33
|
+
toRoot: View?,
|
|
34
|
+
layer: View,
|
|
41
35
|
options: SharedElementTransitionOptions,
|
|
42
36
|
fallbackDuration: Long,
|
|
43
37
|
log: (String) -> Unit,
|
|
44
38
|
): Flight? {
|
|
45
|
-
val
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
39
|
+
val name = pairName(options.fromId.get(""), options.toId.get(""))
|
|
40
|
+
if (from.width <= 0 || from.height <= 0 || to.width <= 0 || to.height <= 0) {
|
|
41
|
+
elementSkipped(name, "a view of the pair has no size, it isn't laid out", log)
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
44
|
+
val picture = if (from is ReactTextView && to is ReactTextView) {
|
|
45
|
+
val text = TextMorph.of(from, to, layer) ?: return noPicture(name, log)
|
|
46
|
+
if (!text.changes) return null
|
|
47
|
+
text
|
|
48
|
+
} else {
|
|
49
|
+
val image = if (from is ImageView && to is ImageView) {
|
|
50
|
+
ImageMorph.of(from, to, fromRoot, toRoot, layer)
|
|
51
|
+
} else {
|
|
52
|
+
null
|
|
53
|
+
}
|
|
54
|
+
image ?: ViewMorph.of(from, to, fromRoot, toRoot, layer) ?: return noPicture(name, log)
|
|
55
|
+
}
|
|
56
|
+
val animator = MoveAnimator().apply {
|
|
57
|
+
element = name
|
|
58
|
+
onStart {
|
|
59
|
+
picture.shown = true
|
|
60
|
+
picture.update(0f, curve.getInterpolation(0f))
|
|
61
|
+
layer.invalidate()
|
|
62
|
+
}
|
|
63
|
+
onFrame {
|
|
64
|
+
picture.update(it, curve.getInterpolation(it))
|
|
65
|
+
layer.invalidate()
|
|
66
|
+
}
|
|
67
|
+
// A flight that landed keeps its last frame until the transition takes the layer away: the screens may
|
|
68
|
+
// still move. A stopped one draws nothing more.
|
|
69
|
+
onEnd {
|
|
70
|
+
if (!landed) picture.shown = false
|
|
71
|
+
layer.invalidate()
|
|
72
|
+
}
|
|
73
|
+
}
|
|
63
74
|
val duration = AnimationRules.durationOf(options.duration.get(null), fallbackDuration)
|
|
64
75
|
animator.duration = duration
|
|
65
76
|
animator.startDelay = AnimationRules.delayOf(options.startDelay.get(null))
|
|
66
77
|
animator.interpolator = Curves.of(options.interpolation, duration)
|
|
67
|
-
return
|
|
78
|
+
return Flight(animator, picture)
|
|
68
79
|
}
|
|
69
80
|
|
|
70
|
-
private fun
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
private fun hasChildren(view: View): Boolean = view is ReactViewGroup && view.childCount > 0
|
|
81
|
+
private fun noPicture(name: String, log: (String) -> Unit): Flight? {
|
|
82
|
+
elementSkipped(name, "no picture of a view of the pair can be taken", log)
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
75
85
|
}
|
|
@@ -1,91 +1,105 @@
|
|
|
1
1
|
package dev.apexnative.navigation.views.element.animators
|
|
2
2
|
|
|
3
|
-
import android.animation.Animator
|
|
4
3
|
import android.graphics.Canvas
|
|
5
|
-
import android.graphics.ColorFilter
|
|
6
4
|
import android.graphics.Picture
|
|
7
|
-
import android.graphics.PixelFormat
|
|
8
5
|
import android.graphics.PointF
|
|
9
|
-
import android.graphics.drawable.Drawable
|
|
10
6
|
import android.text.Layout
|
|
11
7
|
import android.view.View
|
|
12
|
-
import android.view.ViewGroup
|
|
13
8
|
import android.widget.TextView
|
|
14
9
|
import com.facebook.react.views.text.ReactTextView
|
|
15
|
-
import dev.apexnative.navigation.options.SharedElementTransitionOptions
|
|
16
10
|
import dev.apexnative.navigation.utils.TextViewUtils
|
|
17
11
|
import dev.apexnative.navigation.views.animations.ValueInterpolation
|
|
18
12
|
import kotlin.math.abs
|
|
19
13
|
import kotlin.math.min
|
|
20
14
|
import kotlin.math.roundToInt
|
|
21
15
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
)
|
|
35
|
-
private val pictures by lazy { pictures() }
|
|
16
|
+
/**
|
|
17
|
+
* The text moves as two pictures of itself, of the text that goes away and of the text that comes, the same text or
|
|
18
|
+
* another. Both keep their proportions, scaled by the font size from a point of the first line, and one turns into the
|
|
19
|
+
* other in a quarter of the move: at its start when the text grows, at its end when it shrinks, so the larger picture
|
|
20
|
+
* shows most of the way (as on iOS). Each text is where it is drawn, at the scale and angle its parents and its own
|
|
21
|
+
* properties give it; the angle goes along the shorter arc ([FlightGeometry]). The pictures are drawing commands, not
|
|
22
|
+
* pixels: they stay sharp at any scale and keep the colors, emoji and background of the text.
|
|
23
|
+
*/
|
|
24
|
+
internal class TextMorph private constructor(private val start: TextPicture, private val end: TextPicture) :
|
|
25
|
+
FlightPicture() {
|
|
26
|
+
/** Whether the text changes on the way: another text, another size, or drawn elsewhere. */
|
|
27
|
+
val changes: Boolean
|
|
28
|
+
get() = start.text != end.text || start.fontSize != end.fontSize || start.drawnElsewhere(end)
|
|
36
29
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
30
|
+
private val grows = end.shownSize >= start.shownSize
|
|
31
|
+
private var progress = 0f
|
|
32
|
+
private var time = 0f
|
|
33
|
+
private var changeOver = 0f
|
|
34
|
+
private var mirrorX = 1f
|
|
35
|
+
private var mirrorY = 1f
|
|
36
|
+
|
|
37
|
+
override fun update(time: Float, progress: Float) {
|
|
38
|
+
this.progress = progress
|
|
39
|
+
this.time = time
|
|
40
|
+
val changeOverStart = if (grows) 0f else 1f - CHANGE_OVER
|
|
41
|
+
changeOver = ((time - changeOverStart) / CHANGE_OVER).coerceIn(0f, 1f)
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
override fun
|
|
44
|
-
val
|
|
45
|
-
val
|
|
46
|
-
val
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
44
|
+
override fun draw(canvas: Canvas) {
|
|
45
|
+
val x = start.position.x + (end.position.x - start.position.x) * progress
|
|
46
|
+
val y = start.position.y + (end.position.y - start.position.y) * progress
|
|
47
|
+
val fontSize = start.shownSize + (end.shownSize - start.shownSize) * progress
|
|
48
|
+
val rotation = ValueInterpolation.angle(start.rotation, end.rotation, progress, time)
|
|
49
|
+
mirrorX = ValueInterpolation.number(start.mirrorX, end.mirrorX, progress, time)
|
|
50
|
+
mirrorY = ValueInterpolation.number(start.mirrorY, end.mirrorY, progress, time)
|
|
51
|
+
draw(canvas, start, x, y, fontSize, rotation, 1f - changeOver)
|
|
52
|
+
draw(canvas, end, x, y, fontSize, rotation, changeOver)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private fun draw(
|
|
56
|
+
canvas: Canvas,
|
|
57
|
+
text: TextPicture,
|
|
58
|
+
x: Float,
|
|
59
|
+
y: Float,
|
|
60
|
+
fontSize: Float,
|
|
61
|
+
rotation: Float,
|
|
62
|
+
shown: Float,
|
|
63
|
+
) {
|
|
64
|
+
val alpha = (shown * text.alpha * 255).roundToInt().coerceIn(0, 255)
|
|
65
|
+
if (alpha == 0) return
|
|
66
|
+
val scale = fontSize / text.fontSize
|
|
67
|
+
val count = canvas.save()
|
|
68
|
+
canvas.translate(x, y)
|
|
69
|
+
canvas.rotate(rotation)
|
|
70
|
+
canvas.scale(scale * mirrorX, scale * mirrorY)
|
|
71
|
+
canvas.translate(-text.anchor.x, -text.anchor.y)
|
|
72
|
+
// The picture fades as a whole: its letters and background don't show through each other.
|
|
73
|
+
if (alpha < 255) {
|
|
74
|
+
canvas.saveLayerAlpha(0f, 0f, text.picture.width.toFloat(), text.picture.height.toFloat(), alpha)
|
|
68
75
|
}
|
|
76
|
+
canvas.drawPicture(text.picture)
|
|
77
|
+
canvas.restoreToCount(count)
|
|
69
78
|
}
|
|
70
79
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
companion object {
|
|
81
|
+
private const val CHANGE_OVER = 0.25f
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The morph of [from] into [to] in [layer]; none when a text isn't laid out or its picture can't be taken.
|
|
85
|
+
* Texts that line up the same way go from that point of their first lines (the same text lies over itself);
|
|
86
|
+
* others from the start of the line, as on iOS: a centered text no longer sticks out of a text at the start.
|
|
87
|
+
*/
|
|
88
|
+
fun of(from: ReactTextView, to: ReactTextView, layer: View): TextMorph? {
|
|
89
|
+
val fromLayout = from.layout ?: return null
|
|
90
|
+
val toLayout = to.layout ?: return null
|
|
91
|
+
val edge = lineEdge(fromLayout).takeIf { it == lineEdge(toLayout) } ?: LineEdge.START
|
|
92
|
+
val start = TextPicture.of(from, LayerPlace.of(from, layer), edge) ?: return null
|
|
93
|
+
val end = TextPicture.of(to, LayerPlace.of(to, layer), edge) ?: return null
|
|
94
|
+
return TextMorph(start, end)
|
|
95
|
+
}
|
|
83
96
|
}
|
|
84
97
|
}
|
|
85
98
|
|
|
86
|
-
// A text's picture, the point of its first line it is scaled from (in the view) and where that point is
|
|
87
|
-
// transition layer, its font size, the scale and angle it is drawn at, and its opacity.
|
|
99
|
+
// A text's picture and its text, the point of its first line it is scaled from (in the view) and where that point is
|
|
100
|
+
// drawn in the transition layer, its font size, the scale and angle it is drawn at, and its opacity.
|
|
88
101
|
private class TextPicture(
|
|
102
|
+
val text: String,
|
|
89
103
|
val picture: Picture,
|
|
90
104
|
val anchor: PointF,
|
|
91
105
|
val position: PointF,
|
|
@@ -124,7 +138,8 @@ private class TextPicture(
|
|
|
124
138
|
val position = place.point(anchor.x, anchor.y)
|
|
125
139
|
val scale = min(abs(place.scaleX), abs(place.scaleY))
|
|
126
140
|
return TextPicture(
|
|
127
|
-
picture, anchor, PointF(position[0], position[1]), fontSize, scale,
|
|
141
|
+
view.text.toString(), picture, anchor, PointF(position[0], position[1]), fontSize, scale,
|
|
142
|
+
place.rotation,
|
|
128
143
|
mirrorX = if (place.scaleX < 0) -1f else 1f,
|
|
129
144
|
mirrorY = if (place.scaleY < 0) -1f else 1f,
|
|
130
145
|
alpha = view.alpha,
|
|
@@ -159,68 +174,3 @@ private fun lineEdge(layout: Layout): LineEdge {
|
|
|
159
174
|
else -> LineEdge.START
|
|
160
175
|
}
|
|
161
176
|
}
|
|
162
|
-
|
|
163
|
-
// Both pictures where the move has brought the text: the font size it shows at, its angle and the point of the first
|
|
164
|
-
// line go from the start to the end along the transition's curve, the pictures change over by the clock.
|
|
165
|
-
private class TextMorphDrawable(private val start: TextPicture, private val end: TextPicture) : Drawable() {
|
|
166
|
-
private val grows = end.shownSize >= start.shownSize
|
|
167
|
-
private var progress = 0f
|
|
168
|
-
private var time = 0f
|
|
169
|
-
private var changeOver = 0f
|
|
170
|
-
private var mirrorX = 1f
|
|
171
|
-
private var mirrorY = 1f
|
|
172
|
-
|
|
173
|
-
fun update(time: Float, progress: Float) {
|
|
174
|
-
this.progress = progress
|
|
175
|
-
this.time = time
|
|
176
|
-
val changeOverStart = if (grows) 0f else 1f - CHANGE_OVER
|
|
177
|
-
changeOver = ((time - changeOverStart) / CHANGE_OVER).coerceIn(0f, 1f)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
override fun draw(canvas: Canvas) {
|
|
181
|
-
val x = start.position.x + (end.position.x - start.position.x) * progress
|
|
182
|
-
val y = start.position.y + (end.position.y - start.position.y) * progress
|
|
183
|
-
val fontSize = start.shownSize + (end.shownSize - start.shownSize) * progress
|
|
184
|
-
val rotation = ValueInterpolation.angle(start.rotation, end.rotation, progress, time)
|
|
185
|
-
mirrorX = ValueInterpolation.number(start.mirrorX, end.mirrorX, progress, time)
|
|
186
|
-
mirrorY = ValueInterpolation.number(start.mirrorY, end.mirrorY, progress, time)
|
|
187
|
-
draw(canvas, start, x, y, fontSize, rotation, 1f - changeOver)
|
|
188
|
-
draw(canvas, end, x, y, fontSize, rotation, changeOver)
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
private fun draw(
|
|
192
|
-
canvas: Canvas,
|
|
193
|
-
text: TextPicture,
|
|
194
|
-
x: Float,
|
|
195
|
-
y: Float,
|
|
196
|
-
fontSize: Float,
|
|
197
|
-
rotation: Float,
|
|
198
|
-
shown: Float,
|
|
199
|
-
) {
|
|
200
|
-
val alpha = (shown * text.alpha * 255).roundToInt().coerceIn(0, 255)
|
|
201
|
-
if (alpha == 0) return
|
|
202
|
-
val scale = fontSize / text.fontSize
|
|
203
|
-
val count = canvas.save()
|
|
204
|
-
canvas.translate(x, y)
|
|
205
|
-
canvas.rotate(rotation)
|
|
206
|
-
canvas.scale(scale * mirrorX, scale * mirrorY)
|
|
207
|
-
canvas.translate(-text.anchor.x, -text.anchor.y)
|
|
208
|
-
// The picture fades as a whole: its letters and background don't show through each other.
|
|
209
|
-
if (alpha < 255) {
|
|
210
|
-
canvas.saveLayerAlpha(0f, 0f, text.picture.width.toFloat(), text.picture.height.toFloat(), alpha)
|
|
211
|
-
}
|
|
212
|
-
canvas.drawPicture(text.picture)
|
|
213
|
-
canvas.restoreToCount(count)
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
override fun setAlpha(alpha: Int) = Unit
|
|
217
|
-
|
|
218
|
-
override fun setColorFilter(colorFilter: ColorFilter?) = Unit
|
|
219
|
-
|
|
220
|
-
@Deprecated("Deprecated in Java")
|
|
221
|
-
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
|
|
222
|
-
|
|
223
|
-
private companion object {
|
|
224
|
-
const val CHANGE_OVER = 0.25f
|
|
225
|
-
}
|
|
226
|
-
}
|