Dovydas
02/07/2025, 10:34 AMZach Klippenstein (he/him) [MOD]
02/07/2025, 4:42 PMDovydas
02/07/2025, 4:55 PMAppTabs.entries.forEachIndexed { index, tab ->
val pageOffset by remember(pagerState, index) {
derivedStateOf {
pagerState.calculateCurrentOffsetForPage(index).coerceIn(-1f, 1f)
}
}
TopBarTab(
label = tab.label.asString(),
currentOffsetForPage = pageOffset,
onTabClick = { scope.launch { pagerState.animateScrollToPage(tab.ordinal) } },
onOverflowText = { fontSize *= 0.99f },
fontSize = fontSize
)
}
@Composable
fun TopBarTab(
label: String,
currentOffsetForPage: Float,
onTabClick: () -> Unit,
fontSize: Float = 40f,
onOverflowText: () -> Unit,
) {
val fraction = abs(currentOffsetForPage)
val width = lerp(MAX_WIDTH, MIN_WIDTH, fraction)
val weight = lerp(MAX_WEIGHT, MIN_WEIGHT, fraction)
val textStyle = MaterialTheme.typography.headlineLarge.copy(
fontSize = fontSize.sp,
fontFamily = FontFamily(
Font(
APP_FONT,
variationSettings = FontVariation.Settings(
FontVariation.width(width),
FontVariation.weight(weight.toInt())
)
)
)
)
val cachedTextStyle by remember(width, weight, fontSize) {
derivedStateOf {
textStyle
}
}
Text(
text = label,
modifier = Modifier
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) { onTabClick() },
style = cachedTextStyle,
softWrap = false,
onTextLayout = {
if (it.didOverflowWidth) {
onOverflowText()
}
}
)
}
Zach Klippenstein (he/him) [MOD]
02/07/2025, 5:01 PMcalculateCurrentOffsetForPage
? If it's just doing math, and maps indices roughly 1-1 to offsets, it's not worth using derivedStateOf
.
onOverflowText = { fontSize *= 0.99f }It looks like this is trying to implement resizable text? This is a bad impl since it will require multiple frames to find the size that fits – you'll see the text animate smaller gradually, which is probably not what you want. Compose 1.8 has built-in resizable text, or before that you can use
TextMeasurer
.
val cachedTextStyle by remember(width, weight, fontSize) {
derivedStateOf {
textStyle
}
}This
derivedStateOf
is pointless – it's not reading any snapshot state, and even if it textStyle
were stored in a mutableStateOf
, you're already calculating it in the composition anyway, and not doing any further calculations on the state, so it serves absolutely no purpose. Please read https://medium.com/androiddevelopers/jetpack-compose-when-should-i-use-derivedstateof-63ce7954c11b.
Lastly, I don't know why the app is crashing, what is the exception and stack trace?Dovydas
02/07/2025, 6:44 PMTopBarTab
so the parent doesn't get recomposed when offsetChanges. Here is the modified code again:
@Composable
fun TopBarTab(
pagerState: PagerState,
index: Int,
label: String,
onTabClick: () -> Unit,
fontSize: Float = 40f,
onOverflowText: () -> Unit,
) {
val currentOffsetForPage = pagerState.calculateCurrentOffsetForPage(index).coerceIn(-1f, 1f)
val fraction = abs(currentOffsetForPage)
val width = lerp(MAX_WIDTH, MIN_WIDTH, fraction)
val weight = lerp(MAX_WEIGHT, MIN_WEIGHT, fraction)
val textStyle = MaterialTheme.typography.headlineLarge.copy(
fontSize = fontSize.sp,
fontFamily = FontFamily(
Font(
APP_FONT,
variationSettings = FontVariation.Settings(
FontVariation.width(width),
FontVariation.weight(weight.toInt())
)
)
)
)
Text(
text = label,
modifier = Modifier
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) { onTabClick() },
style = textStyle,
softWrap = false,
onTextLayout = {
if (it.didOverflowWidth) {
onOverflowText()
}
},
)
}
fun lerp(start: Float, stop: Float, fraction: Float): Float {
return start + (stop - start) * fraction
}
fun PagerState.calculateCurrentOffsetForPage(page: Int): Float {
println("calculated")
return (currentPage - page) + currentPageOffsetFraction
}
I also attached a video to show what it actually looks like. But currently, after swiping for just a bit, the app's memory usage goes up to 5GB.
Thank you again for the initial remarks, do you have any ideas on what else I could improve?Zach Klippenstein (he/him) [MOD]
02/07/2025, 6:59 PMDovydas
02/07/2025, 7:02 PMZach Klippenstein (he/him) [MOD]
02/07/2025, 7:03 PMZach Klippenstein (he/him) [MOD]
02/07/2025, 7:04 PMDovydas
02/07/2025, 7:05 PMZach Klippenstein (he/him) [MOD]
02/07/2025, 7:07 PMZach Klippenstein (he/him) [MOD]
02/07/2025, 7:07 PMDovydas
02/07/2025, 7:09 PMjava.lang.OutOfMemoryError: Failed to allocate a 1684643 byte allocation with 1013888 free bytes and 990KB until OOM, target footprint 268435456, growth limit 268435456
at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
at java.nio.DirectByteBuffer$MemoryRef.<init>(DirectByteBuffer.java:73)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:347)
at android.graphics.fonts.Font$Builder.createBuffer(Font.java:294)
at android.graphics.fonts.Font$Builder.<init>(Font.java:202)
at android.graphics.Typeface$Builder.<init>(Typeface.java:562)
at android.graphics.Typeface$Builder.<init>(Typeface.java:549)
at androidx.compose.ui.text.font.TypefaceBuilderCompat.createFromAssets(AndroidPreloadedFont.android.kt:186)
at androidx.compose.ui.text.font.AndroidAssetFont.doLoad$ui_text_release(AndroidPreloadedFont.android.kt:80)
at androidx.compose.ui.text.font.AndroidPreloadedFont.loadCached$ui_text_release(AndroidPreloadedFont.android.kt:52)
at androidx.compose.ui.text.font.AndroidPreloadedFontTypefaceLoader.loadBlocking(AndroidPreloadedFont.android.kt:61)
at androidx.compose.ui.text.font.AndroidFontLoader.loadBlocking(AndroidFontLoader.android.kt:37)
at androidx.compose.ui.text.font.AndroidFontLoader.loadBlocking(AndroidFontLoader.android.kt:31)
at androidx.compose.ui.text.font.FontListFontFamilyTypefaceAdapterKt.firstImmediatelyAvailable(FontListFontFamilyTypefaceAdapter.kt:198)
at androidx.compose.ui.text.font.FontListFontFamilyTypefaceAdapterKt.access$firstImmediatelyAvailable(FontListFontFamilyTypefaceAdapter.kt:1)
at androidx.compose.ui.text.font.FontListFontFamilyTypefaceAdapter.resolve(FontListFontFamilyTypefaceAdapter.kt:138)
at androidx.compose.ui.text.font.FontFamilyResolverImpl$resolve$result$1.invoke(FontFamilyResolver.kt:95)
at androidx.compose.ui.text.font.FontFamilyResolverImpl$resolve$result$1.invoke(FontFamilyResolver.kt:94)
at androidx.compose.ui.text.font.TypefaceRequestCache.runCached(FontFamilyResolver.kt:197)
at androidx.compose.ui.text.font.FontFamilyResolverImpl.resolve(FontFamilyResolver.kt:94)
at androidx.compose.ui.text.font.FontFamilyResolverImpl.resolve-DPcqOEQ(FontFamilyResolver.kt:80)
at androidx.compose.ui.text.platform.AndroidParagraphIntrinsics$resolveTypeface$1.invoke-DPcqOEQ(AndroidParagraphIntrinsics.android.kt:94)
at androidx.compose.ui.text.platform.AndroidParagraphIntrinsics$resolveTypeface$1.invoke(AndroidParagraphIntrinsics.android.kt:91)
at androidx.compose.ui.text.platform.extensions.TextPaintExtensions_androidKt.applySpanStyle(TextPaintExtensions.android.kt:62)
at androidx.compose.ui.text.platform.AndroidParagraphIntrinsics.<init>(AndroidParagraphIntrinsics.android.kt:107)
at androidx.compose.ui.text.platform.AndroidParagraphIntrinsics_androidKt.ActualParagraphIntrinsics(AndroidParagraphIntrinsics.android.kt:183)
at androidx.compose.ui.text.ParagraphIntrinsicsKt.ParagraphIntrinsics(ParagraphIntrinsics.kt:126)
at androidx.compose.ui.text.MultiParagraphIntrinsics.<init>(MultiParagraphIntrinsics.kt:106)
at androidx.compose.foundation.text.modifiers.MultiParagraphLayoutCache.setLayoutDirection(MultiParagraphLayoutCache.kt:282)
at androidx.compose.foundation.text.modifiers.MultiParagraphLayoutCache.layoutText-K40F9xA(MultiParagraphLayoutCache.kt:307)
at androidx.compose.foundation.text.modifiers.MultiParagraphLayoutCache.layoutWithConstraints-K40F9xA(MultiParagraphLayoutCache.kt:163)
at androidx.compose.foundation.text.modifiers.TextAnnotatedStringNode.measure-3p2s80s(TextAnnotatedStringNode.kt:419)
at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:190)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:359)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:358)
2025-02-07 20:07:48.159 23084-23084 AndroidRuntime com...developer.esm_vertretungsplan E at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2489) (Ask Gemini)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:460)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:244)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:124)
at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release(OwnerSnapshotObserver.kt:107)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:1914)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate.access$performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:39)
at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:745)
at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui_release(LayoutNode.kt:1138)
at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui_release$default(LayoutNode.kt:1131)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.doRemeasure-sdFAvZA(MeasureAndLayoutDelegate.kt:366)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.remeasureAndRelayoutIfNeeded(MeasureAndLayoutDelegate.kt:566)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.onlyRemeasureIfScheduled(MeasureAndLayoutDelegate.kt:667)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.forceMeasureTheSubtreeInternal(MeasureAndLayoutDelegate.kt:694)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.forceMeasureTheSubtreeInternal(MeasureAndLayoutDelegate.kt:701)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.forceMeasureTheSubtreeInternal(MeasureAndLayoutDelegate.kt:701)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.forceMeasureTheSubtreeInternal(MeasureAndLayoutDelegate.kt:701)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.forceMeasureTheSubtreeInternal(MeasureAndLayoutDelegate.kt:701)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.forceMeasureTheSubtreeInternal(MeasureAndLayoutDelegate.kt:701)
at androidx.compose.ui.node.MeasureAndLayoutDelegate.forceMeasureTheSubtreeInternal(MeasureAndLayoutDelegate.kt:701)
Zach Klippenstein (he/him) [MOD]
02/07/2025, 7:10 PMZach Klippenstein (he/him) [MOD]
02/07/2025, 7:12 PMZach Klippenstein (he/him) [MOD]
02/07/2025, 7:12 PMDovydas
02/07/2025, 7:15 PMZach Klippenstein (he/him) [MOD]
02/07/2025, 7:16 PMAndroidFontLoader
which indicates you're running this on android, so this is google's codeZach Klippenstein (he/him) [MOD]
02/07/2025, 7:16 PMZach Klippenstein (he/him) [MOD]
02/07/2025, 7:17 PMZach Klippenstein (he/him) [MOD]
02/07/2025, 7:17 PMDovydas
02/07/2025, 7:18 PMZach Klippenstein (he/him) [MOD]
02/07/2025, 7:18 PMDovydas
02/07/2025, 7:18 PMZach Klippenstein (he/him) [MOD]
02/07/2025, 7:18 PMDovydas
02/07/2025, 7:58 PMZach Klippenstein (he/him) [MOD]
02/07/2025, 8:00 PMDovydas
02/07/2025, 8:00 PMZach Klippenstein (he/him) [MOD]
02/07/2025, 8:00 PMDovydas
02/07/2025, 8:03 PMcomposeBom = "2024.04.01"
and CMP 1.8.0-alpha02
was build against Compose 1.8.0-alpha07
which was released December 11, 2024.Dovydas
02/07/2025, 8:04 PMDovydas
02/07/2025, 8:04 PMDovydas
02/07/2025, 9:06 PMZach Klippenstein (he/him) [MOD]
02/08/2025, 2:52 AMDovydas
02/08/2025, 11:19 AMZach Klippenstein (he/him) [MOD]
02/08/2025, 9:49 PM