Animate launcher icon in return to home gesture animation
This commit is contained in:
parent
709a36fb31
commit
a9032735f2
@ -9,8 +9,9 @@ class LauncherActivity: SharedLauncherActivity(LauncherActivityMode.Launcher) {
|
||||
super.onNewIntent(intent)
|
||||
val navContract = intent?.let { GestureNavContract.fromIntent(it) }
|
||||
if (navContract != null) {
|
||||
homeTransitionManager.resolve(navContract)
|
||||
homeTransitionManager.resolve(navContract, window)
|
||||
} else {
|
||||
homeTransitionManager.clear()
|
||||
onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,9 @@
|
||||
package de.mm20.launcher2.ui.launcher
|
||||
|
||||
import android.app.WallpaperManager
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.foundation.background
|
||||
@ -16,34 +14,40 @@ import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.TransformOrigin
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.flowWithLifecycle
|
||||
import com.android.launcher3.GestureNavContract
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import de.mm20.launcher2.preferences.Settings
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.assistant.AssistantScaffold
|
||||
import de.mm20.launcher2.ui.base.BaseActivity
|
||||
import de.mm20.launcher2.ui.base.ProvideCurrentTime
|
||||
import de.mm20.launcher2.ui.base.ProvideSettings
|
||||
import de.mm20.launcher2.ui.component.NavBarEffects
|
||||
import de.mm20.launcher2.ui.ktx.animateTo
|
||||
import de.mm20.launcher2.ui.launcher.transitions.HomeTransition
|
||||
import de.mm20.launcher2.ui.launcher.transitions.HomeTransitionManager
|
||||
import de.mm20.launcher2.ui.launcher.transitions.LocalHomeTransitionManager
|
||||
import de.mm20.launcher2.ui.locals.LocalSnackbarHostState
|
||||
import de.mm20.launcher2.ui.locals.LocalWindowSize
|
||||
import de.mm20.launcher2.ui.theme.LauncherTheme
|
||||
import kotlin.math.pow
|
||||
|
||||
|
||||
abstract class SharedLauncherActivity(
|
||||
@ -86,15 +90,22 @@ abstract class SharedLauncherActivity(
|
||||
|
||||
val systemUiController = rememberSystemUiController()
|
||||
|
||||
val enterTransition = remember { mutableStateOf(1f) }
|
||||
val enterTransitionProgress = remember { mutableStateOf(1f) }
|
||||
var enterTransition by remember {
|
||||
mutableStateOf<HomeTransition?>(
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(null) {
|
||||
homeTransitionManager
|
||||
.currentTransition
|
||||
.flowWithLifecycle(lifecycle, Lifecycle.State.RESUMED)
|
||||
.collect {
|
||||
enterTransition.value = 0f
|
||||
enterTransition.animateTo(1f)
|
||||
enterTransitionProgress.value = 0f
|
||||
enterTransition = it
|
||||
enterTransitionProgress.animateTo(1f)
|
||||
enterTransition = null
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,29 +149,33 @@ abstract class SharedLauncherActivity(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.graphicsLayer {
|
||||
scaleX = 0.5f + enterTransition.value * 0.5f
|
||||
scaleY = 0.5f + enterTransition.value * 0.5f
|
||||
alpha = enterTransition.value
|
||||
scaleX =
|
||||
0.5f + enterTransitionProgress.value * 0.5f
|
||||
scaleY =
|
||||
0.5f + enterTransitionProgress.value * 0.5f
|
||||
alpha = enterTransitionProgress.value
|
||||
},
|
||||
darkStatusBarIcons = lightStatus,
|
||||
darkNavBarIcons = lightNav,
|
||||
)
|
||||
}
|
||||
|
||||
Settings.AppearanceSettings.Layout.Pager,
|
||||
Settings.AppearanceSettings.Layout.PagerReversed -> {
|
||||
PagerScaffold(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.graphicsLayer {
|
||||
scaleX = enterTransition.value
|
||||
scaleY = enterTransition.value
|
||||
alpha = enterTransition.value
|
||||
scaleX = enterTransitionProgress.value
|
||||
scaleY = enterTransitionProgress.value
|
||||
alpha = enterTransitionProgress.value
|
||||
},
|
||||
darkStatusBarIcons = lightStatus,
|
||||
darkNavBarIcons = lightNav,
|
||||
reverse = layout == Settings.AppearanceSettings.Layout.PagerReversed
|
||||
)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
@ -170,6 +185,25 @@ abstract class SharedLauncherActivity(
|
||||
.navigationBarsPadding()
|
||||
.imePadding()
|
||||
)
|
||||
enterTransition?.let {
|
||||
val dX = it.startBounds.center.x - it.targetBounds.center.x
|
||||
val dY = it.startBounds.center.y - it.targetBounds.center.y
|
||||
val s = (it.startBounds.minDimension / it.targetBounds.minDimension - 1f) * 0.5f
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.TopStart)
|
||||
.graphicsLayer {
|
||||
val p = (enterTransitionProgress.value).pow(2f)
|
||||
transformOrigin = TransformOrigin.Center
|
||||
translationX = it.targetBounds.left + dX * (1 - p)
|
||||
translationY = it.targetBounds.top + dY * (1 - p)
|
||||
alpha = enterTransitionProgress.value
|
||||
scaleX = 1f + s * (1 - p)
|
||||
scaleY = 1f + s * (1 - p)
|
||||
}) {
|
||||
it.icon?.invoke(Offset(dX, dY)) { enterTransitionProgress.value }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,7 +73,11 @@ fun GridItem(modifier: Modifier = Modifier, item: Searchable, showLabels: Boolea
|
||||
bounds.right > 0f && bounds.left < windowSize.width &&
|
||||
bounds.bottom > 0f && bounds.top < windowSize.height
|
||||
) {
|
||||
return@HandleHomeTransition HomeTransitionParams(bounds)
|
||||
return@HandleHomeTransition HomeTransitionParams(
|
||||
bounds
|
||||
) { _, _ ->
|
||||
ShapedLauncherIcon(size = LocalGridIconSize.current, icon = { icon })
|
||||
}
|
||||
}
|
||||
return@HandleHomeTransition null
|
||||
}
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
package de.mm20.launcher2.ui.launcher.transitions
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
|
||||
data class HomeTransition(
|
||||
val startBounds: Rect,
|
||||
val targetBounds: Rect,
|
||||
val icon: (@Composable (animVector: Offset, progress: () -> Float) -> Unit)? = null
|
||||
)
|
||||
@ -1,28 +1,42 @@
|
||||
package de.mm20.launcher2.ui.launcher.transitions
|
||||
|
||||
import android.view.Window
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.toAndroidRectF
|
||||
import com.android.launcher3.GestureNavContract
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
|
||||
class HomeTransitionManager {
|
||||
|
||||
val currentTransition = MutableSharedFlow<HomeTransitionParams?>(1)
|
||||
val currentTransition = MutableSharedFlow<HomeTransition?>(1)
|
||||
|
||||
private val handlers = mutableSetOf<HomeTransitionHandler>()
|
||||
|
||||
fun resolve(gestureNavContract: GestureNavContract) {
|
||||
fun resolve(gestureNavContract: GestureNavContract, window: Window) {
|
||||
for (handler in handlers) {
|
||||
val result = handler.handle(gestureNavContract)
|
||||
if (result != null) {
|
||||
gestureNavContract.sendEndPosition(result.targetBounds.toAndroidRectF())
|
||||
currentTransition.tryEmit(result)
|
||||
val startRect = Rect(Offset(0f, 0f), Size(window.decorView.width.toFloat(), window.decorView.height.toFloat()))
|
||||
val targetBounds = result.targetBounds
|
||||
gestureNavContract.sendEndPosition(targetBounds.toAndroidRectF())
|
||||
currentTransition.tryEmit(HomeTransition(
|
||||
startBounds = startRect,
|
||||
icon = result.icon,
|
||||
targetBounds = targetBounds,
|
||||
))
|
||||
return
|
||||
}
|
||||
}
|
||||
currentTransition.tryEmit(null)
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
currentTransition.tryEmit(null)
|
||||
}
|
||||
|
||||
fun registerHandler(handler: HomeTransitionHandler) {
|
||||
handlers.add(handler)
|
||||
}
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
package de.mm20.launcher2.ui.launcher.transitions
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
|
||||
fun interface HomeTransitionOffer {
|
||||
fun accept(targetBounds: Rect, icon: @Composable () -> Unit)
|
||||
}
|
||||
@ -2,10 +2,11 @@ package de.mm20.launcher2.ui.launcher.transitions
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
|
||||
@Stable
|
||||
data class HomeTransitionParams(
|
||||
val targetBounds: Rect,
|
||||
val icon: (@Composable () -> Unit)? = null
|
||||
val icon: (@Composable (animVector: Offset, progress: () -> Float) -> Unit)? = null
|
||||
)
|
||||
Loading…
x
Reference in New Issue
Block a user