Remove corner clip, draw content behind system bars

This commit is contained in:
MM20 2022-06-11 21:12:01 +02:00
parent e565780015
commit c7c97a3866
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
5 changed files with 299 additions and 41 deletions

View File

@ -14,9 +14,11 @@ import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
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.blur
import androidx.compose.ui.geometry.Size import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.dp
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.WindowInsetsControllerCompat
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
@ -111,7 +113,6 @@ class LauncherActivity : BaseActivity() {
PullDownScaffold( PullDownScaffold(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.systemBarsPadding()
.graphicsLayer { .graphicsLayer {
scaleX = 0.5f + enterTransition.value * 0.5f scaleX = 0.5f + enterTransition.value * 0.5f
scaleY = 0.5f + enterTransition.value * 0.5f scaleY = 0.5f + enterTransition.value * 0.5f
@ -125,7 +126,6 @@ class LauncherActivity : BaseActivity() {
PagerScaffold( PagerScaffold(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.systemBarsPadding()
.graphicsLayer { .graphicsLayer {
scaleX = enterTransition.value scaleX = enterTransition.value
scaleY = enterTransition.value scaleY = enterTransition.value

View File

@ -22,21 +22,17 @@ import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
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.clip
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.google.accompanist.pager.ExperimentalPagerApi
import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.accompanist.systemuicontroller.rememberSystemUiController
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.ktx.toPixels import de.mm20.launcher2.ui.ktx.toPixels
@ -61,7 +57,6 @@ fun PagerScaffold(
) { ) {
val viewModel: LauncherScaffoldVM = viewModel() val viewModel: LauncherScaffoldVM = viewModel()
val searchVM: SearchVM = viewModel() val searchVM: SearchVM = viewModel()
val context = LocalContext.current
val isSearchOpen by viewModel.isSearchOpen.observeAsState(false) val isSearchOpen by viewModel.isSearchOpen.observeAsState(false)
val isWidgetEditMode by viewModel.isWidgetEditMode.observeAsState(false) val isWidgetEditMode by viewModel.isWidgetEditMode.observeAsState(false)
@ -70,6 +65,25 @@ fun PagerScaffold(
val searchScrollState = rememberScrollState() val searchScrollState = rememberScrollState()
val swipeableState = rememberSwipeableState(if (isSearchOpen) Page.Search else Page.Widgets) val swipeableState = rememberSwipeableState(if (isSearchOpen) Page.Search else Page.Widgets)
val showStatusBarScrim by remember {
derivedStateOf {
if (isSearchOpen) {
searchScrollState.value < searchScrollState.maxValue
} else {
widgetsScrollState.value > 0
}
}
}
val showNavBarScrim by remember {
derivedStateOf {
if (isSearchOpen) {
searchScrollState.value > 0
} else {
widgetsScrollState.value > 0 && widgetsScrollState.value < widgetsScrollState.maxValue
}
}
}
val isWidgetsScrollZero by remember { val isWidgetsScrollZero by remember {
derivedStateOf { derivedStateOf {
widgetsScrollState.value == 0 widgetsScrollState.value == 0
@ -79,11 +93,15 @@ fun PagerScaffold(
val systemUiController = rememberSystemUiController() val systemUiController = rememberSystemUiController()
val colorSurface = MaterialTheme.colorScheme.surface val colorSurface = MaterialTheme.colorScheme.surface
LaunchedEffect(isWidgetEditMode, darkStatusBarIcons, colorSurface) { LaunchedEffect(isWidgetEditMode, darkStatusBarIcons, colorSurface, showStatusBarScrim) {
if (isWidgetEditMode) { if (isWidgetEditMode) {
systemUiController.setStatusBarColor( systemUiController.setStatusBarColor(
colorSurface colorSurface
) )
} else if (showStatusBarScrim) {
systemUiController.setStatusBarColor(
colorSurface.copy(0.75f),
)
} else { } else {
systemUiController.setStatusBarColor( systemUiController.setStatusBarColor(
Color.Transparent, Color.Transparent,
@ -92,12 +110,18 @@ fun PagerScaffold(
} }
} }
LaunchedEffect(darkNavBarIcons) { LaunchedEffect(darkNavBarIcons, showNavBarScrim) {
systemUiController.setNavigationBarColor( if (showNavBarScrim) {
Color.Transparent, systemUiController.setNavigationBarColor(
darkIcons = darkNavBarIcons, colorSurface.copy(0.75f),
navigationBarContrastEnforced = false )
) } else {
systemUiController.setNavigationBarColor(
Color.Transparent,
darkIcons = darkNavBarIcons,
navigationBarContrastEnforced = false
)
}
} }
val blurWallpaper by remember { val blurWallpaper by remember {
@ -138,11 +162,11 @@ fun PagerScaffold(
} }
} }
val notificationDragThreshold = with(LocalDensity.current) {200.dp.toPx()} val notificationDragThreshold = with(LocalDensity.current) { 200.dp.toPx() }
val notificationShadeController = rememberNotificationShadeController() val notificationShadeController = rememberNotificationShadeController()
val nestedScrollConnection = remember { val nestedScrollConnection = remember {
object: NestedScrollConnection { object : NestedScrollConnection {
private var pullDownTotalY: Float? = 0f private var pullDownTotalY: Float? = 0f
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
val diff = widgetsScrollState.value - available.y val diff = widgetsScrollState.value - available.y
@ -171,6 +195,8 @@ fun PagerScaffold(
} }
} }
val insets = WindowInsets.systemBars.asPaddingValues()
Box( Box(
modifier = modifier modifier = modifier
) { ) {
@ -218,12 +244,12 @@ fun PagerScaffold(
val editModePadding by animateDpAsState(if (isWidgetEditMode) 56.dp else 0.dp) val editModePadding by animateDpAsState(if (isWidgetEditMode) 56.dp else 0.dp)
val clockPadding by animateDpAsState( val clockPadding by animateDpAsState(
if (isWidgetsScrollZero) 64.dp else 0.dp if (isWidgetsScrollZero) 64.dp + insets.calculateBottomPadding() else 0.dp
) )
val clockHeight by remember { val clockHeight by remember {
derivedStateOf { derivedStateOf {
height - (64.dp - clockPadding) height - (64.dp + insets.calculateTopPadding() + insets.calculateBottomPadding() - clockPadding)
} }
} }
@ -231,10 +257,10 @@ fun PagerScaffold(
modifier = Modifier modifier = Modifier
.requiredWidth(width) .requiredWidth(width)
.fillMaxHeight() .fillMaxHeight()
.padding(horizontal = 8.dp)
.clip(MaterialTheme.shapes.medium)
.nestedScroll(nestedScrollConnection) .nestedScroll(nestedScrollConnection)
.verticalScroll(widgetsScrollState) .verticalScroll(widgetsScrollState)
.systemBarsPadding()
.padding(horizontal = 8.dp)
.padding(top = 8.dp, bottom = 64.dp) .padding(top = 8.dp, bottom = 64.dp)
.padding(top = editModePadding), .padding(top = editModePadding),
clockHeight = { clockHeight }, clockHeight = { clockHeight },
@ -254,10 +280,10 @@ fun PagerScaffold(
modifier = Modifier modifier = Modifier
.requiredWidth(width) .requiredWidth(width)
.fillMaxHeight() .fillMaxHeight()
.padding(horizontal = 8.dp)
.clip(MaterialTheme.shapes.medium)
.verticalScroll(searchScrollState, reverseScrolling = true) .verticalScroll(searchScrollState, reverseScrolling = true)
.imePadding() .imePadding()
.systemBarsPadding()
.padding(horizontal = 8.dp)
.padding(top = 8.dp, bottom = 64.dp) .padding(top = 8.dp, bottom = 64.dp)
.padding(bottom = webSearchPadding), .padding(bottom = webSearchPadding),
reverse = true, reverse = true,
@ -302,6 +328,7 @@ fun PagerScaffold(
modifier = Modifier modifier = Modifier
.align(Alignment.BottomCenter) .align(Alignment.BottomCenter)
.padding(start = 8.dp, end = 8.dp, bottom = 8.dp) .padding(start = 8.dp, end = 8.dp, bottom = 8.dp)
.systemBarsPadding()
.imePadding() .imePadding()
.offset(y = widgetEditModeOffset), .offset(y = widgetEditModeOffset),
level = { searchBarLevel }, focused = focusSearchBar, onFocusChange = { level = { searchBarLevel }, focused = focusSearchBar, onFocusChange = {

View File

@ -1,6 +1,5 @@
package de.mm20.launcher2.ui.launcher package de.mm20.launcher2.ui.launcher
import android.util.Log
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateDpAsState
@ -19,7 +18,6 @@ import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
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.clip
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.TransformOrigin import androidx.compose.ui.graphics.TransformOrigin
@ -43,6 +41,7 @@ import de.mm20.launcher2.ui.launcher.search.SearchBarLevel
import de.mm20.launcher2.ui.launcher.search.SearchColumn import de.mm20.launcher2.ui.launcher.search.SearchColumn
import de.mm20.launcher2.ui.launcher.search.SearchVM import de.mm20.launcher2.ui.launcher.search.SearchVM
import de.mm20.launcher2.ui.launcher.widgets.WidgetColumn import de.mm20.launcher2.ui.launcher.widgets.WidgetColumn
import de.mm20.launcher2.ui.modifier.verticalFadingEdges
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -55,7 +54,6 @@ fun PullDownScaffold(
) { ) {
val viewModel: LauncherScaffoldVM = viewModel() val viewModel: LauncherScaffoldVM = viewModel()
val searchVM: SearchVM = viewModel() val searchVM: SearchVM = viewModel()
val context = LocalContext.current
val density = LocalDensity.current val density = LocalDensity.current
@ -67,12 +65,41 @@ fun PullDownScaffold(
val systemUiController = rememberSystemUiController() val systemUiController = rememberSystemUiController()
val isWidgetsScrollZero by remember {
derivedStateOf {
widgetsScrollState.value == 0
}
}
val showStatusBarScrim by remember {
derivedStateOf {
if (isSearchOpen) {
searchScrollState.value > 0
} else {
widgetsScrollState.value > 0
}
}
}
val showNavBarScrim by remember {
derivedStateOf {
if (isSearchOpen) {
searchScrollState.value < searchScrollState.maxValue
} else {
widgetsScrollState.value > 0 && widgetsScrollState.value < widgetsScrollState.maxValue
}
}
}
val colorSurface = MaterialTheme.colorScheme.surface val colorSurface = MaterialTheme.colorScheme.surface
LaunchedEffect(isWidgetEditMode, darkStatusBarIcons, colorSurface) { LaunchedEffect(isWidgetEditMode, darkStatusBarIcons, colorSurface, showStatusBarScrim) {
if (isWidgetEditMode) { if (isWidgetEditMode) {
systemUiController.setStatusBarColor( systemUiController.setStatusBarColor(
colorSurface colorSurface
) )
} else if (showStatusBarScrim) {
systemUiController.setStatusBarColor(
colorSurface.copy(0.75f),
)
} else { } else {
systemUiController.setStatusBarColor( systemUiController.setStatusBarColor(
Color.Transparent, Color.Transparent,
@ -81,12 +108,18 @@ fun PullDownScaffold(
} }
} }
LaunchedEffect(darkNavBarIcons) { LaunchedEffect(darkNavBarIcons, showNavBarScrim) {
systemUiController.setNavigationBarColor( if (showNavBarScrim) {
Color.Transparent, systemUiController.setNavigationBarColor(
darkIcons = darkNavBarIcons, colorSurface.copy(0.75f),
navigationBarContrastEnforced = false )
) } else {
systemUiController.setNavigationBarColor(
Color.Transparent,
darkIcons = darkNavBarIcons,
navigationBarContrastEnforced = false
)
}
} }
val offsetY = remember { mutableStateOf(0f) } val offsetY = remember { mutableStateOf(0f) }
@ -151,7 +184,7 @@ fun PullDownScaffold(
consumed consumed
} }
isSearchOpen && (offsetY.value < 0 || source == NestedScrollSource.Drag && newValue > searchScrollState.maxValue) -> { isSearchOpen && (offsetY.value < 0 || source == NestedScrollSource.Drag && newValue > searchScrollState.maxValue) -> {
val consumed = available.y - (value- searchScrollState.maxValue) val consumed = available.y - (value - searchScrollState.maxValue)
offsetY.value = (offsetY.value + (consumed * 0.5f)).coerceIn(-maxOffset, 0f) offsetY.value = (offsetY.value + (consumed * 0.5f)).coerceIn(-maxOffset, 0f)
consumed consumed
} }
@ -182,11 +215,13 @@ fun PullDownScaffold(
} }
} }
val insets = WindowInsets.systemBars.asPaddingValues()
Box( Box(
modifier = modifier modifier = modifier
.padding(horizontal = 8.dp) .verticalFadingEdges(
.clip(MaterialTheme.shapes.medium) top = insets.calculateTopPadding(),
amount = 0.85f
)
.nestedScroll(nestedScrollConnection) .nestedScroll(nestedScrollConnection)
.offset { IntOffset(0, offsetY.value.toInt()) }, .offset { IntOffset(0, offsetY.value.toInt()) },
contentAlignment = Alignment.TopCenter contentAlignment = Alignment.TopCenter
@ -230,12 +265,21 @@ fun PullDownScaffold(
.fillMaxWidth() .fillMaxWidth()
.requiredHeight(height) .requiredHeight(height)
.verticalScroll(searchScrollState) .verticalScroll(searchScrollState)
.padding(vertical = 8.dp) .systemBarsPadding()
.padding(8.dp)
.padding(top = 56.dp) .padding(top = 56.dp)
.padding(top = webSearchPadding) .padding(top = webSearchPadding)
.imePadding() .imePadding()
) )
val editModePadding by animateDpAsState(if (isWidgetEditMode) 56.dp else 0.dp) val editModePadding by animateDpAsState(if (isWidgetEditMode) 56.dp else 0.dp)
val clockPadding by animateDpAsState(
if (isWidgetsScrollZero) insets.calculateBottomPadding() else 0.dp
)
val clockHeight by remember {
derivedStateOf {
height - (insets.calculateTopPadding() + insets.calculateBottomPadding() - clockPadding)
}
}
WidgetColumn( WidgetColumn(
modifier = modifier =
Modifier Modifier
@ -248,9 +292,11 @@ fun PullDownScaffold(
.fillMaxWidth() .fillMaxWidth()
.requiredHeight(height) .requiredHeight(height)
.verticalScroll(widgetsScrollState) .verticalScroll(widgetsScrollState)
.padding(vertical = 8.dp) .systemBarsPadding()
.padding(8.dp)
.padding(top = editModePadding), .padding(top = editModePadding),
clockHeight = { height }, clockHeight = { clockHeight },
clockBottomPadding = { clockPadding },
editMode = isWidgetEditMode, editMode = isWidgetEditMode,
onEditModeChange = { onEditModeChange = {
viewModel.setWidgetEditMode(it) viewModel.setWidgetEditMode(it)
@ -268,6 +314,8 @@ fun PullDownScaffold(
exit = slideOut { IntOffset(0, -it.height) } exit = slideOut { IntOffset(0, -it.height) }
) { ) {
CenterAlignedTopAppBar( CenterAlignedTopAppBar(
modifier = Modifier
.systemBarsPadding(),
title = { title = {
Text(stringResource(R.string.menu_edit_widgets)) Text(stringResource(R.string.menu_edit_widgets))
}, },
@ -285,7 +333,7 @@ fun PullDownScaffold(
offsetY.value != 0f -> SearchBarLevel.Raised offsetY.value != 0f -> SearchBarLevel.Raised
isSearchOpen && searchScrollState.value == 0 -> SearchBarLevel.Active isSearchOpen && searchScrollState.value == 0 -> SearchBarLevel.Active
isSearchOpen && searchScrollState.value > 0 -> SearchBarLevel.Raised isSearchOpen && searchScrollState.value > 0 -> SearchBarLevel.Raised
widgetsScrollState.value > 0 -> SearchBarLevel.Raised !isWidgetsScrollZero -> SearchBarLevel.Raised
else -> SearchBarLevel.Resting else -> SearchBarLevel.Resting
} }
} }
@ -300,7 +348,8 @@ fun PullDownScaffold(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.wrapContentHeight() .wrapContentHeight()
.padding(vertical = 8.dp) .systemBarsPadding()
.padding(8.dp)
.offset { IntOffset(0, searchBarOffset.value.toInt()) } .offset { IntOffset(0, searchBarOffset.value.toInt()) }
.offset { .offset {
IntOffset( IntOffset(

View File

@ -0,0 +1,89 @@
package de.mm20.launcher2.ui.modifier
import androidx.annotation.FloatRange
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.BlendMode
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.LinearGradientShader
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlin.math.pow
fun Modifier.verticalFadingEdges(
enabled: Boolean = true,
top: Dp = 0.dp,
bottom: Dp = 0.dp,
/**
* How strong the fading effect should be.
* If 1, edges will be completely transparent.
* If 0, the modifier will have no effect at all.
*/
@FloatRange(from = 0.0, to = 1.0, fromInclusive = true, toInclusive = true) amount: Float = 1f
): Modifier {
if(!enabled) return this
if (top == 0.dp && bottom == 0.dp) return this
return this then drawWithContent {
val topColors = if (top > 0.dp) createColors(
1f - amount,
top.roundToPx() + 1,
) else emptyList()
val bottomColors = if (bottom > 0.dp) createColors(
1f - amount,
bottom.roundToPx() + 1,
reverse = true
) else emptyList()
val topSteps = if (top > 0.dp) createColorSteps(
size.height,
top.toPx() * 1.3f,
top.roundToPx() + 1
) else emptyList()
val bottomSteps = if (bottom > 0.dp) createColorSteps(
size.height,
bottom.toPx() * 1.3f,
bottom.roundToPx() + 1,
reverse = true
) else emptyList()
val paint = Paint().apply {
blendMode = BlendMode.DstIn
shader = LinearGradientShader(
Offset.Zero,
Offset(0f, size.height),
colors = topColors + bottomColors,
colorStops = topSteps + bottomSteps
)
}
drawContent()
drawIntoCanvas {
it.drawRect(
Rect(0f, 0f, size.width, size.height),
paint
)
}
}
}
private fun createColors(alpha: Float, steps: Int, reverse: Boolean = false): List<Color> {
val interval = 1f / (steps - 1)
return (0 until steps).map {
val x = interval * if (reverse) (steps - 1 - it) else it
val y = (1 - alpha) * (1f - (x - 1f).pow(2)) + alpha
Color.Black.copy(alpha = y)
}
}
private fun createColorSteps(height: Float, size: Float, steps: Int, reverse: Boolean = false): List<Float> {
val interval = 1f / (steps - 1)
return (0 until steps).map {
val x = interval * if (reverse) (steps - 1 - it) else it
if (reverse) 1 - (x * size / height) else (x * size / height)
}
}

View File

@ -0,0 +1,93 @@
package de.mm20.launcher2.ui.modifier
import androidx.annotation.FloatRange
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.LinearGradientShader
import androidx.compose.ui.graphics.Paint
import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlin.math.pow
fun Modifier.verticalScrims(
enabled: Boolean = true,
top: Dp = 0.dp,
bottom: Dp = 0.dp,
/**
* How strong the fading effect should be.
* If 1, edges will be completely transparent.
* If 0, the modifier will have no effect at all.
*/
@FloatRange(from = 0.0, to = 1.0, fromInclusive = true, toInclusive = true) amount: Float = 1f
): Modifier {
if (!enabled) return this
if (top == 0.dp && bottom == 0.dp) return this
return this then drawWithCache {
onDrawWithContent {
val topColors = if (top > 0.dp) createColors(
1f - amount,
top.roundToPx() + 1,
) else emptyList()
val bottomColors = if (bottom > 0.dp) createColors(
1f - amount,
bottom.roundToPx() + 1,
reverse = true
) else emptyList()
val topSteps = if (top > 0.dp) createColorSteps(
size.height,
top.toPx() * 1.3f,
top.roundToPx() + 1
) else emptyList()
val bottomSteps = if (bottom > 0.dp) createColorSteps(
size.height,
bottom.toPx() * 1.3f,
bottom.roundToPx() + 1,
reverse = true
) else emptyList()
val paint = Paint().apply {
shader = LinearGradientShader(
Offset.Zero,
Offset(0f, size.height),
colors = topColors + bottomColors,
colorStops = topSteps + bottomSteps
)
}
drawContent()
drawIntoCanvas {
it.drawRect(
Rect(0f, 0f, size.width, size.height),
paint
)
}
}
}
}
private fun createColors(alpha: Float, steps: Int, reverse: Boolean = false): List<Color> {
val interval = 1f / (steps - 1)
return (0 until steps).map {
val x = interval * if (reverse) (steps - 1 - it) else it
val y = 1f - ((1 - alpha) * (1f - (x - 1f).pow(2)) + alpha)
Color.Black.copy(alpha = y)
}
}
private fun createColorSteps(
height: Float,
size: Float,
steps: Int,
reverse: Boolean = false
): List<Float> {
val interval = 1f / (steps - 1)
return (0 until steps).map {
val x = interval * if (reverse) (steps - 1 - it) else it
if (reverse) 1 - (x * size / height) else (x * size / height)
}
}