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.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
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user