Jetpack compose: Top App Bar pinnedScrollBehavior not changing color on scroll from text focus events - kotlin

Okay, so this is a bit of a specific issue, but hopefully someone understands what's happening:
I'm using a Jetpack Compose Material 3 CenterAlignedTopAppBar, with TopAppBarDefaults.pinnedScrollBehavior to make it so that the color of the appbar changes when I scroll down on nested content.
It works! In most cases. However, one of my screens has a large text field, that when clicked causes the content to scroll down by itself (to focus on the text field)—and that seems to confuse the appbar, which doesn't change color. It will only change color if I manually scroll up and down.
Relevant code:
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold(
contentWindowInsets = EmptyWindowInsets,
modifier = Modifier
.fillMaxHeight()
.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
// just a wrapper around CenterAlignTopAppBar
StandardTopAppBar(scrollBehavior = scrollBehavior)
},
content = { innerPadding ->
// I've also tried with LazyColumn and see the same behavior
Column(
Modifier
.padding(innerPadding)
.padding(start = 10.dp, end = 10.dp)
.fillMaxHeight()
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(10.dp),
) {
tl;dr; manually scrolling changes the appbar color, but scrolling caused by clicking into a text field that scrolls into view does not. Any idea how I could fix this?

Probably not quite what you are looking for unfortunately, but I had a similar problem. My TopAppBar color was also stuck, but when navigating between different screens. I fixed it by assigning the TopAppBar a different scrollBehaviour depending on the screen.
val scrollBehavior1 = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val scrollBehavior2 = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
In TopAppBar arguments:
scrollBehavior =
when (currentRoute) {
Screens.ExampleScreen1.route -> scrollBehavior1
Screens.ExampleScreen2.route -> scrollBehavior2
else -> null
}
Hopefully this helps you in one way or another.

Related

Custom CollapsingTopAppBar Jetpack Compose

The essence of the problem is that I want to write my own version of the AppBar that would include content as another Compose function. After looking at the source code of the current CollapsingTopAppBar implementation, I saw the following lines:
#Composable
private fun TwoRowsTopAppBar(
...
scrollBehavior: TopAppBarScrollBehavior?
) {
...
val pinnedHeightPx: Float = 64.dp
val maxHeightPx: Float = 152.dp
LocalDensity.current.run {
pinnedHeightPx = pinnedHeight.toPx()
maxHeightPx = maxHeight.toPx()
}
// Sets the app bar's height offset limit to hide just the bottom title area and keep top title
// visible when collapsed.
SideEffect {
if (scrollBehavior?.state?.heightOffsetLimit != pinnedHeightPx - maxHeightPx) {
scrollBehavior?.state?.heightOffsetLimit = pinnedHeightPx - maxHeightPx
}
}
...
Surface(...) {
Column {
TopAppBarLayout(
...
heightPx = pinnedHeightPx
...
)
TopAppBarLayout(
...
heightPx = maxHeightPx - pinnedHeightPx + (scrollBehavior?.state?.heightOffset
?: 0f),
...
)
}
}
}
As I understand it, scrollBehavior is used to handle the collapse and expansion behavior. In the current implementation, just constant values are put in heightOffsetLimit. And since I need my appbar implementation to be able to contain content of any size, I need to somehow know the size of this content in advance and put this value in heightOffsetLimit.
I have already written the code for my AppBar, so that it also contains content. But since I can't pass the height value of the content to scrollBehavior, the AppBar doesn't collapse to the end.
you need to calculate the height that the appbar will have before drawing it into the screen. I have followed this issue and solved my problem with the last solution. hope it helps:
Get height of element Jetpack Compose
use the content you can put (ex. an image or a huge text) as the MainContent
use your appbar as the DependentContent and use the size given in lambda to give the height to your appbar
finally set placeMainContent false as I believe you don't need to draw the image (or any other composable) directly in a box
and you will good to go

Why did the border radius disappear for an RecyclerView item with a custom background?

I try to figure out Android development, and sometimes I have beginner question. For now I faced with issue creating the RecyclerView items with different background color. I created a simple
RecyclerView items list First I set one background (light green) for all items.
Then I decided to set a separate background for each item. Here's how I did it in adapter file:
override fun onBindViewHolder(holder: ChapterListAdapter.ViewHolder, position: Int) {
holder.chapter_item.text = chapter_titles[position]
holder.chapter_details.text = chapter_descrs[position]
holder.chapter_image.setImageResource(chapter_images[position])
when(position){
0 -> holder.chapter_card.setBackgroundColor(Color.parseColor("#ff5668"))
1 -> holder.chapter_card.setBackgroundColor(Color.parseColor("#41d5e2"))
2 -> holder.chapter_card.setBackgroundColor(Color.parseColor("#4d53e0"))
3 -> holder.chapter_card.setBackgroundColor(Color.parseColor("#ff8e36"))
}
}
And it works, it may not be the right way to do it, but it works. However, there is one problem. In the screenshot, you can see that the last item has a border radius. I set the cardCornerRadius value for the element in card_layout.xml And for some reason, when I assign a custom color for an item, this value disappears. This can be seen in the screenshot. The last element with a light green background has a border radius (I did not assign a custom background color value to this element) and the first four elements that have a custom color assigned do not have a border radius.
Please tell me why this is happening and how to fix it. I need to keep the border radius for all elements.
I really glad I fond the error by myself. For setting the item background color I used setBackgroundColor but it's not correct in my case. In my case I have to use setCardBackgroundColor because I setting background for the CardView. Now my code looks:
override fun onBindViewHolder(holder: ChapterListAdapter.ViewHolder, position: Int) {
holder.chapter_item.text = chapter_titles[position]
holder.chapter_details.text = chapter_descrs[position]
holder.chapter_image.setImageResource(chapter_images[position])
when(position){
0 -> {
holder.chapter_card.setCardBackgroundColor(Color.parseColor("#ff5668"))
}
1 -> {
holder.chapter_card.setCardBackgroundColor(Color.parseColor("#41d5e2"))
}
2 -> {
holder.chapter_card.setCardBackgroundColor(Color.parseColor("#4d53e0"))
}
3 -> {
holder.chapter_card.setCardBackgroundColor(Color.parseColor("#ff8e36"))
}
}
}
And it works well.

Why scrollable modifier doesn't scroll view content?

I am trying to get scrolling to work on a Column where the number of entries may exceed the height of the window.
I'm currently using Compose 1.1.0-rc03 and at the moment I'm only trying to get it working on desktop.
I reduced the problem to this:
#Composable
fun App() {
val optionsScrollState = rememberScrollState()
Row(modifier = Modifier.fillMaxSize()) {
// Left column
Column(
modifier = Modifier
.scrollable(optionsScrollState, Orientation.Vertical)
.width(240.dp)
.fillMaxHeight()
) {
(1..100).forEach { i -> Text("Row $i") }
}
}
}
But this doesn't scroll, or at least, not with the mousewheel. Maybe there's another way to scroll that isn't immediately apparent to me.
How do I make this work?
The docs on scrollable say that I might have to manage the state myself. So is using rememberScrollState() not enough?
I found some existing questions about disabling scrolling on columns, but they were always talking about LazyColumn which I'm not using here.
You're using a wrong modifier. From documentation:
The scrollable modifier differs from the scroll modifiers in that scrollable detects the scroll gestures, but does not offset its contents.
If you're interested how Modifier.scrollable should be used, you can find an example in the same documentation chapter.
You can use Modifier.verticalScroll instead, which will give you the expected behavior.
Also consider switching to LazyColumn, which already has built-in scrolling as well as lazy cell loading.

How to clear focus of BasicTextField upon clicking somewhere else in Compose Multiplatform?

I have a BasicTextField in Jetbrains Compose Multiplatform for desktop. When I click on it, the TextField gets focus and becomes editable. However, when I click somewhere else in my application, the focus is not lost and the field is still editable as if I just clicked on it.
I know this behavior is normal and intended. Nonetheless, I want to the TextField to become unfocused when the user clicks somewhere else, regardless of it being a clickable or non-clickable composable.
How do I achieve this?
This is one way I've done it in the past.
val keyboardController = LocalSoftwareKeyboardController.current
val focusManager = LocalFocusManager.current
val interactionSource = remember { MutableInteractionSource() }
Then I made my parent layout clickable.
Box(modifier = Modifier
.clickable(
interactionSource = interactionSource,
indication = null // this gets rid of the ripple effect
) {
keyboardController?.hide()
focusManager.clearFocus(true)
}

Clickable box makes its "non-clickable parent box" clickable as well?

Here is the code snippet:
Box(
Modifier
.size(48.dp, 48.dp)
.background(Color.Transparent)
.align(Alignment.CenterEnd)
)
{
Box(
Modifier
.size(height = 35.636363.dp, width = 41.818181.dp)
.align(Alignment.CenterEnd)
.clip(RoundedCornerShape(topStart = 20.dp, bottomStart = 20.dp))
.clickable(onClick = {showDialogFragment.value = true})
.background(Color.Red)
)
}
Whenever i click on parent box (the one which is transparent) it activates click event of its only child. Why so? Current behavior meets my needs, but i dont understand why it happens.
According to Material Guidelines, the minimum touch size is 48.dp.
Since 1.1.0-alpha03, when you add touch detection to a view with a size smaller than this value, tracking will work on the enlarged frame.
Added minimum touch target size to ViewConfiguration for use in semantics and pointer input to ensure accessibility.
Just out of curiosity, you can check the extended touch padding for the current view using pointerInput modifier, which lies under all touch processing in Compose:
.pointerInput(Unit) {
println("$extendedTouchPadding")
}