From 29aefa41970b7e7f8e849ed62e42dd308f3c91e5 Mon Sep 17 00:00:00 2001 From: tom5079 Date: Fri, 17 Sep 2021 17:22:41 +0900 Subject: [PATCH] Floating Menu --- .../xyz/quaver/pupil/ui/ReaderActivity.kt | 24 ++-- .../MultipleFloatingActionButton.kt | 109 +++++++++++++++++- 2 files changed, 120 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt index 0d9552c9..33cd1b0e 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt @@ -31,11 +31,17 @@ import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.DateRange import androidx.compose.material.icons.filled.Science +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.res.stringResource import androidx.recyclerview.widget.LinearLayoutManager import com.google.accompanist.appcompattheme.AppCompatTheme import org.kodein.di.DIAware import org.kodein.di.android.closestDI import xyz.quaver.pupil.databinding.ReaderActivityBinding +import xyz.quaver.pupil.ui.composable.FloatingActionButtonState import xyz.quaver.pupil.ui.composable.MultipleFloatingActionButton import xyz.quaver.pupil.ui.composable.SubFabItem import xyz.quaver.pupil.ui.viewmodel.ReaderViewModel @@ -43,15 +49,14 @@ import xyz.quaver.pupil.ui.viewmodel.ReaderViewModel class ReaderActivity : ComponentActivity(), DIAware { override val di by closestDI() - private var menu: Menu? = null - - private lateinit var bindiddng: ReaderActivityBinding private val model: ReaderViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { + var isFABExpanded by remember { mutableStateOf(FloatingActionButtonState.COLLAPSED) } + AppCompatTheme { Scaffold( topBar = { @@ -60,10 +65,15 @@ class ReaderActivity : ComponentActivity(), DIAware { ) }, floatingActionButton = { - MultipleFloatingActionButton(items = listOf( - SubFabItem(Icons.Default.Science, "Testing"), - SubFabItem(Icons.Default.DateRange, "EY") - )) + MultipleFloatingActionButton( + items = listOf( + // TODO + ), + targetState = isFABExpanded, + onStateChanged = { + isFABExpanded = it + } + ) } ) { diff --git a/app/src/main/java/xyz/quaver/pupil/ui/composable/MultipleFloatingActionButton.kt b/app/src/main/java/xyz/quaver/pupil/ui/composable/MultipleFloatingActionButton.kt index 32b5d6ca..839e87fb 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/composable/MultipleFloatingActionButton.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/composable/MultipleFloatingActionButton.kt @@ -1,6 +1,8 @@ package xyz.quaver.pupil.ui.composable -import androidx.compose.animation.core.updateTransition +import android.view.animation.AccelerateInterpolator +import android.view.animation.OvershootInterpolator +import androidx.compose.animation.core.* import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape @@ -10,13 +12,18 @@ import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.Stop import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp enum class FloatingActionButtonState(val isExpanded: Boolean) { @@ -41,6 +48,9 @@ data class SubFabItem( fun MiniFloatingActionButton( modifier: Modifier = Modifier, item: SubFabItem, + buttonScale: Float = 1f, + labelAlpha: Float = 1f, + labelOffset: Dp = 0.dp, onClick: ((SubFabItem) -> Unit)? = null ) { Row( @@ -53,15 +63,20 @@ fun MiniFloatingActionButton( item.label?.let { label -> Surface( + modifier = Modifier + .alpha(labelAlpha) + .offset(x = labelOffset), shape = RoundedCornerShape(4.dp), elevation = elevation.elevation(interactionSource).value ) { - Text(modifier = Modifier.padding(4.dp), text = label) + Text(modifier = Modifier.padding(8.dp, 4.dp), text = label) } } FloatingActionButton( - modifier = Modifier.size(32.dp), + modifier = Modifier + .size(32.dp) + .scale(buttonScale), onClick = { onClick?.invoke(item) }, elevation = elevation, interactionSource = interactionSource @@ -90,12 +105,94 @@ fun MultipleFloatingActionButton( ) { val transition = updateTransition(targetState = targetState, label = "expand") + val rotation by transition.animateFloat( + label = "FABRotation", + transitionSpec = { + spring( + dampingRatio = Spring.DampingRatioMediumBouncy, + stiffness = Spring.StiffnessMedium + ) + }) { state -> + when (state) { + FloatingActionButtonState.COLLAPSED -> 0f + FloatingActionButtonState.EXPANDED -> 45f + } + } + Column( horizontalAlignment = Alignment.End, verticalArrangement = Arrangement.spacedBy(12.dp) ) { - items.forEach { item -> - MiniFloatingActionButton(modifier = Modifier.padding(end = 12.dp),item = item) { + items.forEachIndexed { index, item -> + val delay = when (targetState) { + FloatingActionButtonState.COLLAPSED -> index + FloatingActionButtonState.EXPANDED -> (items.size - index) + } * 50 + + val buttonScale by transition.animateFloat( + label = "miniFAB scale", + transitionSpec = { + tween( + durationMillis = 100, + delayMillis = delay + ) + } + ) { state -> + when (state) { + FloatingActionButtonState.COLLAPSED -> 0f + FloatingActionButtonState.EXPANDED -> 1f + } + } + + val labelAlpha by transition.animateFloat( + label = "miniFAB alpha", + transitionSpec = { + tween( + durationMillis = 150, + delayMillis = delay, + ) + } + ) { state -> + when (state) { + FloatingActionButtonState.COLLAPSED -> 0f + FloatingActionButtonState.EXPANDED -> 1f + } + } + + val labelOffset by transition.animateDp( + label = "miniFAB offset", + transitionSpec = { + keyframes { + durationMillis = 200 + delayMillis = delay + + when (targetState) { + FloatingActionButtonState.COLLAPSED -> { + 0.dp at 0 + 64.dp at 200 + } + FloatingActionButtonState.EXPANDED -> { + 64.dp at 0 + (-4).dp at 150 with LinearEasing + 0.dp at 200 with FastOutLinearInEasing + } + } + } + } + ) { state -> + when (state) { + FloatingActionButtonState.COLLAPSED -> 64.dp + FloatingActionButtonState.EXPANDED -> 0.dp + } + } + + MiniFloatingActionButton( + modifier = Modifier.padding(end = 12.dp), + item = item, + buttonScale = buttonScale, + labelAlpha = labelAlpha, + labelOffset = labelOffset + ) { onItemClick?.invoke(it) } } @@ -103,7 +200,7 @@ fun MultipleFloatingActionButton( FloatingActionButton(onClick = { onStateChanged?.invoke(!targetState) }) { - Icon(imageVector = fabIcon, contentDescription = null) + Icon(modifier = Modifier.rotate(rotation), imageVector = fabIcon, contentDescription = null) } } } \ No newline at end of file