mirror of
https://github.com/BetterAndroid/FlexiUI.git
synced 2025-09-09 03:54:23 +08:00
refactor: use SubcomposeLayout to rebuild Scaffold
This commit is contained in:
@@ -23,19 +23,19 @@
|
|||||||
|
|
||||||
package com.highcapable.flexiui.component
|
package com.highcapable.flexiui.component
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.safeDrawing
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.layout.SubcomposeLayout
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.highcapable.betterandroid.compose.extension.ui.ComponentPadding
|
import com.highcapable.betterandroid.compose.extension.ui.ComponentPadding
|
||||||
|
|
||||||
// TODO: re-made it by SubcomposeLayout.
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scaffold implements the basic Flexi UI visual layout structure.
|
* Scaffold implements the basic Flexi UI visual layout structure.
|
||||||
*
|
*
|
||||||
@@ -47,43 +47,127 @@ import com.highcapable.betterandroid.compose.extension.ui.ComponentPadding
|
|||||||
* @see NavigationBarColumn
|
* @see NavigationBarColumn
|
||||||
* @param modifier the [Modifier] to be applied to content.
|
* @param modifier the [Modifier] to be applied to content.
|
||||||
* @param colors the colors of content, default is [SurfaceDefaults.colors].
|
* @param colors the colors of content, default is [SurfaceDefaults.colors].
|
||||||
* @param padding the padding of content, default is [SurfaceDefaults.padding].
|
* @param padding the padding of content, default is [ScaffoldDefaults.padding].
|
||||||
* @param verticalArrangement the vertical arrangement of content, default is [Arrangement.Top].
|
|
||||||
* @param horizontalAlignment the horizontal alignment of content, default is [Alignment.Start].
|
|
||||||
* @param appBar the app bar on top of the screen, should typically be [PrimaryAppBar] or [SecondaryAppBar].
|
* @param appBar the app bar on top of the screen, should typically be [PrimaryAppBar] or [SecondaryAppBar].
|
||||||
* @param tab the tab below the app bar, should typically be [TabRow].
|
* @param tab the tab below the app bar, should typically be [TabRow].
|
||||||
* @param navigationBar the navigation bar on bottom of the screen, should typically be [NavigationBarRow] or [NavigationBarColumn].
|
* @param navigationBar the navigation bar on bottom of the screen, should typically be [NavigationBarRow] or [NavigationBarColumn].
|
||||||
* @param content the content of the screen.
|
* @param contentWindowInsets the window insets of content, default is [ScaffoldDefaults.contentWindowInsets].
|
||||||
|
* @param content the content of the screen. The lambda receives a [ComponentPadding] that should be applied to the content root via
|
||||||
|
* [Modifier.padding], if using Modifier.verticalScroll, apply this modifier to the child of the scroll, and not on the scroll itself.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun Scaffold(
|
fun Scaffold(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
colors: SurfaceColors = SurfaceDefaults.colors(),
|
colors: SurfaceColors = SurfaceDefaults.colors(),
|
||||||
padding: ComponentPadding = SurfaceDefaults.padding,
|
padding: ComponentPadding = ScaffoldDefaults.padding,
|
||||||
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
|
contentWindowInsets: WindowInsets = ScaffoldDefaults.contentWindowInsets,
|
||||||
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
|
|
||||||
appBar: @Composable () -> Unit = {},
|
appBar: @Composable () -> Unit = {},
|
||||||
tab: @Composable () -> Unit = {},
|
tab: @Composable () -> Unit = {},
|
||||||
navigationBar: @Composable () -> Unit = {},
|
navigationBar: @Composable () -> Unit = {},
|
||||||
content: @Composable () -> Unit
|
content: @Composable (innerPadding: ComponentPadding) -> Unit
|
||||||
) {
|
) {
|
||||||
// When out of the box, we no need to match the top padding, it should be provided by the action bar.
|
Surface(modifier = modifier, colors = colors, padding = ComponentPadding()) {
|
||||||
val outBoxPadding = padding.copy(top = 0.dp)
|
ScaffoldLayout(
|
||||||
// When in the box, we no need to match the start and end padding, it should be provided by the surface.
|
padding = padding,
|
||||||
val inBoxPadding = padding.copy(start = 0.dp, end = 0.dp)
|
contentWindowInsets = contentWindowInsets,
|
||||||
Surface(
|
appBar = appBar,
|
||||||
modifier = modifier,
|
tab = tab,
|
||||||
colors = colors,
|
navigationBar = navigationBar,
|
||||||
padding = outBoxPadding
|
content = content
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ScaffoldLayout(
|
||||||
|
padding: ComponentPadding,
|
||||||
|
contentWindowInsets: WindowInsets,
|
||||||
|
appBar: @Composable () -> Unit,
|
||||||
|
tab: @Composable () -> Unit,
|
||||||
|
navigationBar: @Composable () -> Unit,
|
||||||
|
content: @Composable (innerPadding: ComponentPadding) -> Unit
|
||||||
) {
|
) {
|
||||||
Column(
|
val density = LocalDensity.current
|
||||||
verticalArrangement = verticalArrangement,
|
val layoutDirection = LocalLayoutDirection.current
|
||||||
horizontalAlignment = horizontalAlignment
|
val leftInsets = with(density) { contentWindowInsets.getLeft(density, layoutDirection).toDp() }
|
||||||
) {
|
val topInsets = with(density) { contentWindowInsets.getTop(density).toDp() }
|
||||||
appBar()
|
val rightInsets = with(density) { contentWindowInsets.getRight(density, layoutDirection).toDp() }
|
||||||
tab()
|
val bottomInsets = with(density) { contentWindowInsets.getBottom(density).toDp() }
|
||||||
Box(modifier = Modifier.fillMaxSize().padding(inBoxPadding).weight(1f)) { content() }
|
val insetsPadding = padding.copy(
|
||||||
navigationBar()
|
start = padding.start + leftInsets,
|
||||||
|
// Top insets padding is override by [appBar].
|
||||||
|
top = topInsets,
|
||||||
|
end = padding.end + rightInsets,
|
||||||
|
bottom = padding.bottom + bottomInsets
|
||||||
|
)
|
||||||
|
SubcomposeLayout(modifier = Modifier.padding(insetsPadding)) { constraints ->
|
||||||
|
var currentY = 0
|
||||||
|
var navigationBarHeight = 0
|
||||||
|
val appBarPlaceables = subcompose(ScaffoldSlots.AppBar, appBar).map { it.measure(constraints) }
|
||||||
|
val tabPlaceables = subcompose(ScaffoldSlots.Tab, tab).map { it.measure(constraints) }
|
||||||
|
val navigationBarPlaceables = subcompose(ScaffoldSlots.NavigationBar, navigationBar).map { it.measure(constraints) }
|
||||||
|
// Inner content no need start and end padding.
|
||||||
|
val innerPadding = padding.copy(
|
||||||
|
start = 0.dp,
|
||||||
|
top = if (tabPlaceables.isNotEmpty()) padding.top else 0.dp,
|
||||||
|
end = 0.dp,
|
||||||
|
bottom = if (navigationBarPlaceables.isNotEmpty()) padding.bottom else 0.dp
|
||||||
|
)
|
||||||
|
// Measure [appBar], [tab] and [navigationBar] height.
|
||||||
|
appBarPlaceables.forEach { currentY += it.height }
|
||||||
|
tabPlaceables.forEach { currentY += it.height }
|
||||||
|
navigationBarPlaceables.forEach { navigationBarHeight += it.height }
|
||||||
|
// Measure content with [navigationBar] height.
|
||||||
|
val contentConstraints = constraints.copy(
|
||||||
|
// The maxHeight of content must be >= minHeight, if not will coerce to minHeight.
|
||||||
|
maxHeight = (constraints.maxHeight - currentY - navigationBarHeight).coerceAtLeast(constraints.minHeight)
|
||||||
|
)
|
||||||
|
val contentPlaceables = subcompose(ScaffoldSlots.Content) { content(innerPadding) }.map { it.measure(contentConstraints) }
|
||||||
|
layout(constraints.maxWidth, constraints.maxHeight) {
|
||||||
|
var placementY = 0
|
||||||
|
appBarPlaceables.forEach {
|
||||||
|
it.placeRelative(0, placementY)
|
||||||
|
placementY += it.height
|
||||||
|
}
|
||||||
|
tabPlaceables.forEach {
|
||||||
|
it.placeRelative(0, placementY)
|
||||||
|
placementY += it.height
|
||||||
|
}
|
||||||
|
contentPlaceables.forEach {
|
||||||
|
it.placeRelative(0, placementY)
|
||||||
|
placementY += it.height
|
||||||
|
}
|
||||||
|
var navigationBarY = constraints.maxHeight
|
||||||
|
navigationBarPlaceables.forEach {
|
||||||
|
navigationBarY -= it.height
|
||||||
|
it.placeRelative(0, navigationBarY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
private enum class ScaffoldSlots { AppBar, Tab, NavigationBar, Content }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defaults of scaffold.
|
||||||
|
*/
|
||||||
|
object ScaffoldDefaults {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the default padding of scaffold.
|
||||||
|
* @return [ComponentPadding]
|
||||||
|
*/
|
||||||
|
val padding: ComponentPadding
|
||||||
|
@Composable
|
||||||
|
@ReadOnlyComposable
|
||||||
|
get() = SurfaceDefaults.padding
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the default content window insets of scaffold.
|
||||||
|
* @return [WindowInsets]
|
||||||
|
*/
|
||||||
|
val contentWindowInsets: WindowInsets
|
||||||
|
@Composable
|
||||||
|
get() = WindowInsets.safeDrawing
|
||||||
|
}
|
Reference in New Issue
Block a user