Floating Menu
This commit is contained in:
@@ -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
|
||||
}
|
||||
)
|
||||
}
|
||||
) {
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user