Floating Menu

This commit is contained in:
tom5079
2021-09-17 17:22:41 +09:00
parent cfe6a814d4
commit 29aefa4197
2 changed files with 120 additions and 13 deletions

View File

@@ -31,11 +31,17 @@ import androidx.compose.material.Text
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.DateRange import androidx.compose.material.icons.filled.DateRange
import androidx.compose.material.icons.filled.Science 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 androidx.recyclerview.widget.LinearLayoutManager
import com.google.accompanist.appcompattheme.AppCompatTheme import com.google.accompanist.appcompattheme.AppCompatTheme
import org.kodein.di.DIAware import org.kodein.di.DIAware
import org.kodein.di.android.closestDI import org.kodein.di.android.closestDI
import xyz.quaver.pupil.databinding.ReaderActivityBinding 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.MultipleFloatingActionButton
import xyz.quaver.pupil.ui.composable.SubFabItem import xyz.quaver.pupil.ui.composable.SubFabItem
import xyz.quaver.pupil.ui.viewmodel.ReaderViewModel import xyz.quaver.pupil.ui.viewmodel.ReaderViewModel
@@ -43,15 +49,14 @@ import xyz.quaver.pupil.ui.viewmodel.ReaderViewModel
class ReaderActivity : ComponentActivity(), DIAware { class ReaderActivity : ComponentActivity(), DIAware {
override val di by closestDI() override val di by closestDI()
private var menu: Menu? = null
private lateinit var bindiddng: ReaderActivityBinding
private val model: ReaderViewModel by viewModels() private val model: ReaderViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setContent {
var isFABExpanded by remember { mutableStateOf(FloatingActionButtonState.COLLAPSED) }
AppCompatTheme { AppCompatTheme {
Scaffold( Scaffold(
topBar = { topBar = {
@@ -60,10 +65,15 @@ class ReaderActivity : ComponentActivity(), DIAware {
) )
}, },
floatingActionButton = { floatingActionButton = {
MultipleFloatingActionButton(items = listOf( MultipleFloatingActionButton(
SubFabItem(Icons.Default.Science, "Testing"), items = listOf(
SubFabItem(Icons.Default.DateRange, "EY") // TODO
)) ),
targetState = isFABExpanded,
onStateChanged = {
isFABExpanded = it
}
)
} }
) { ) {

View File

@@ -1,6 +1,8 @@
package xyz.quaver.pupil.ui.composable 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.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape 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.PlayArrow
import androidx.compose.material.icons.filled.Stop import androidx.compose.material.icons.filled.Stop
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.graphics.vector.ImageVector
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
enum class FloatingActionButtonState(val isExpanded: Boolean) { enum class FloatingActionButtonState(val isExpanded: Boolean) {
@@ -41,6 +48,9 @@ data class SubFabItem(
fun MiniFloatingActionButton( fun MiniFloatingActionButton(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
item: SubFabItem, item: SubFabItem,
buttonScale: Float = 1f,
labelAlpha: Float = 1f,
labelOffset: Dp = 0.dp,
onClick: ((SubFabItem) -> Unit)? = null onClick: ((SubFabItem) -> Unit)? = null
) { ) {
Row( Row(
@@ -53,15 +63,20 @@ fun MiniFloatingActionButton(
item.label?.let { label -> item.label?.let { label ->
Surface( Surface(
modifier = Modifier
.alpha(labelAlpha)
.offset(x = labelOffset),
shape = RoundedCornerShape(4.dp), shape = RoundedCornerShape(4.dp),
elevation = elevation.elevation(interactionSource).value elevation = elevation.elevation(interactionSource).value
) { ) {
Text(modifier = Modifier.padding(4.dp), text = label) Text(modifier = Modifier.padding(8.dp, 4.dp), text = label)
} }
} }
FloatingActionButton( FloatingActionButton(
modifier = Modifier.size(32.dp), modifier = Modifier
.size(32.dp)
.scale(buttonScale),
onClick = { onClick?.invoke(item) }, onClick = { onClick?.invoke(item) },
elevation = elevation, elevation = elevation,
interactionSource = interactionSource interactionSource = interactionSource
@@ -90,12 +105,94 @@ fun MultipleFloatingActionButton(
) { ) {
val transition = updateTransition(targetState = targetState, label = "expand") 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( Column(
horizontalAlignment = Alignment.End, horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.spacedBy(12.dp) verticalArrangement = Arrangement.spacedBy(12.dp)
) { ) {
items.forEach { item -> items.forEachIndexed { index, item ->
MiniFloatingActionButton(modifier = Modifier.padding(end = 12.dp),item = 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) onItemClick?.invoke(it)
} }
} }
@@ -103,7 +200,7 @@ fun MultipleFloatingActionButton(
FloatingActionButton(onClick = { FloatingActionButton(onClick = {
onStateChanged?.invoke(!targetState) onStateChanged?.invoke(!targetState)
}) { }) {
Icon(imageVector = fabIcon, contentDescription = null) Icon(modifier = Modifier.rotate(rotation), imageVector = fabIcon, contentDescription = null)
} }
} }
} }