Improve pager scaffold performance

This commit is contained in:
MM20 2022-05-16 21:24:19 +02:00
parent c922d62228
commit 764aa3e587
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
2 changed files with 100 additions and 59 deletions

View File

@ -7,38 +7,46 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.slideIn import androidx.compose.animation.slideIn
import androidx.compose.animation.slideOut import androidx.compose.animation.slideOut
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.LocalOverScrollConfiguration
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.FractionalThreshold
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Done import androidx.compose.material.icons.rounded.Done
import androidx.compose.material.rememberSwipeableState
import androidx.compose.material.swipeable
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* 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.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalContext 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.IntSize
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.pager.ExperimentalPagerApi
import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.rememberPagerState
import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.accompanist.systemuicontroller.rememberSystemUiController
import de.mm20.launcher2.ktx.isAtLeastApiLevel import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.ktx.toPixels
import de.mm20.launcher2.ui.launcher.search.SearchBar import de.mm20.launcher2.ui.launcher.search.SearchBar
import de.mm20.launcher2.ui.launcher.search.SearchBarLevel 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 kotlin.math.roundToInt
@OptIn(ExperimentalPagerApi::class) @OptIn(
ExperimentalPagerApi::class, ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class,
ExperimentalFoundationApi::class
)
@Composable @Composable
fun PagerScaffold( fun PagerScaffold(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -52,9 +60,9 @@ fun PagerScaffold(
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)
val pagerState = rememberPagerState()
val widgetsScrollState = rememberScrollState() val widgetsScrollState = rememberScrollState()
val searchScrollState = rememberScrollState() val searchScrollState = rememberScrollState()
val swipeableState = rememberSwipeableState(if (isSearchOpen) Page.Search else Page.Widgets)
val isWidgetsScrollZero by remember { val isWidgetsScrollZero by remember {
derivedStateOf { derivedStateOf {
@ -88,8 +96,8 @@ fun PagerScaffold(
val blurWallpaper by remember { val blurWallpaper by remember {
derivedStateOf { derivedStateOf {
isSearchOpen || pagerState.currentPage == 0 && pagerState.currentPageOffset > 0.5f || isSearchOpen || swipeableState.progress.to == Page.Widgets && swipeableState.progress.fraction <= 0.5f ||
pagerState.currentPage == 1 && pagerState.currentPageOffset <= 0.5f || swipeableState.progress.to == Page.Search && swipeableState.progress.fraction > 0.5f ||
!isWidgetsScrollZero !isWidgetsScrollZero
} }
} }
@ -97,6 +105,7 @@ fun PagerScaffold(
val density = LocalDensity.current val density = LocalDensity.current
LaunchedEffect(blurWallpaper) { LaunchedEffect(blurWallpaper) {
if (!isAtLeastApiLevel(31)) return@LaunchedEffect if (!isAtLeastApiLevel(31)) return@LaunchedEffect
(context as Activity).window.attributes = context.window.attributes.also { (context as Activity).window.attributes = context.window.attributes.also {
@ -110,16 +119,16 @@ fun PagerScaffold(
} }
} }
val currentPage = pagerState.currentPage val currentPage = swipeableState.currentValue
LaunchedEffect(currentPage) { LaunchedEffect(currentPage) {
if (currentPage == 1) viewModel.openSearch() if (currentPage == Page.Search) viewModel.openSearch()
else viewModel.closeSearch() else viewModel.closeSearch()
} }
LaunchedEffect(isSearchOpen) { LaunchedEffect(isSearchOpen) {
if (isSearchOpen) pagerState.animateScrollToPage(1) if (isSearchOpen) swipeableState.animateTo(Page.Search)
else { else {
pagerState.animateScrollToPage(0) swipeableState.animateTo(Page.Widgets)
searchVM.search("") searchVM.search("")
} }
} }
@ -136,64 +145,93 @@ fun PagerScaffold(
} }
} }
Box( Box(
modifier = modifier modifier = modifier
) { ) {
HorizontalPager( BoxWithConstraints(
count = 2, modifier = Modifier.fillMaxWidth()
state = pagerState,
modifier = Modifier
.fillMaxSize(),
userScrollEnabled = !isWidgetEditMode
) { ) {
if (it == 1) { val height by remember {
val websearches by searchVM.websearchResults.observeAsState(emptyList()) derivedStateOf { maxHeight }
val webSearchPadding by animateDpAsState( }
if (websearches.isEmpty()) 0.dp else 48.dp val width by remember {
) derivedStateOf { maxWidth }
SearchColumn(
modifier = Modifier
.fillMaxSize()
.verticalScroll(searchScrollState, reverseScrolling = true)
.imePadding()
.padding(start = 8.dp, end = 8.dp, top = 8.dp, bottom = 64.dp)
.padding(bottom = webSearchPadding),
reverse = true,
)
} }
if (it == 0) {
val editModePadding by animateDpAsState(if (isWidgetEditMode) 56.dp else 0.dp)
val clockPadding by animateDpAsState( val widthPx = width.toPixels()
if (isWidgetsScrollZero) 64.dp else 0.dp
)
var size by remember { mutableStateOf(IntSize.Zero) }
val clockHeight by remember {
derivedStateOf {
with(density) { size.height.toDp() } - (64.dp - clockPadding)
}
}
WidgetColumn( CompositionLocalProvider(
LocalOverScrollConfiguration provides null
) {
Row(
modifier = Modifier modifier = Modifier
.fillMaxSize() .requiredWidth(width * 2)
.onSizeChanged { .fillMaxHeight()
size = it .swipeable(
swipeableState,
orientation = Orientation.Horizontal,
anchors = mapOf(
-widthPx / 2f to Page.Search,
widthPx / 2f to Page.Widgets,
),
thresholds = { _, _ ->
FractionalThreshold(0.5f)
},
enabled = !isWidgetEditMode
)
.offset {
IntOffset(swipeableState.offset.value.roundToInt(), 0)
}
) {
val editModePadding by animateDpAsState(if (isWidgetEditMode) 56.dp else 0.dp)
val clockPadding by animateDpAsState(
if (isWidgetsScrollZero) 64.dp else 0.dp
)
val clockHeight by remember {
derivedStateOf {
height - (64.dp - clockPadding)
} }
.verticalScroll(widgetsScrollState)
.padding(start = 8.dp, end = 8.dp, top = 8.dp, bottom = 64.dp)
.padding(top = editModePadding),
clockHeight = { clockHeight },
clockBottomPadding = { clockPadding },
editMode = isWidgetEditMode,
onEditModeChange = {
viewModel.setWidgetEditMode(it)
} }
)
WidgetColumn(
modifier = Modifier
.requiredWidth(width)
.fillMaxHeight()
.verticalScroll(widgetsScrollState)
.padding(start = 8.dp, end = 8.dp, top = 8.dp, bottom = 64.dp)
.padding(top = editModePadding),
clockHeight = { clockHeight },
clockBottomPadding = { clockPadding },
editMode = isWidgetEditMode,
onEditModeChange = {
viewModel.setWidgetEditMode(it)
}
)
val websearches by searchVM.websearchResults.observeAsState(emptyList())
val webSearchPadding by animateDpAsState(
if (websearches.isEmpty()) 0.dp else 48.dp
)
SearchColumn(
modifier = Modifier
.requiredWidth(width)
.fillMaxHeight()
.verticalScroll(searchScrollState, reverseScrolling = true)
.imePadding()
.padding(start = 8.dp, end = 8.dp, top = 8.dp, bottom = 64.dp)
.padding(bottom = webSearchPadding),
reverse = true,
)
}
} }
} }
AnimatedVisibility(visible = isWidgetEditMode, AnimatedVisibility(visible = isWidgetEditMode,
@ -215,7 +253,7 @@ fun PagerScaffold(
val searchBarLevel by remember { val searchBarLevel by remember {
derivedStateOf { derivedStateOf {
when { when {
pagerState.isScrollInProgress -> SearchBarLevel.Raised swipeableState.direction != 0f -> SearchBarLevel.Raised
!isSearchOpen && isWidgetsScrollZero -> SearchBarLevel.Resting !isSearchOpen && isWidgetsScrollZero -> SearchBarLevel.Resting
isSearchOpen && searchScrollState.value == 0 -> SearchBarLevel.Active isSearchOpen && searchScrollState.value == 0 -> SearchBarLevel.Active
else -> SearchBarLevel.Raised else -> SearchBarLevel.Raised
@ -242,5 +280,9 @@ fun PagerScaffold(
reverse = true reverse = true
) )
} }
}
private enum class Page {
Widgets,
Search
} }

View File

@ -216,7 +216,6 @@ fun PullDownScaffold(
val height by remember { val height by remember {
derivedStateOf { maxHeight } derivedStateOf { maxHeight }
} }
Log.d("MM20", "PullDownScaffold recompose, $maxHeight")
CompositionLocalProvider( CompositionLocalProvider(
LocalOverScrollConfiguration provides null LocalOverScrollConfiguration provides null
) { ) {