| /* |
| * Copyright 2022 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package androidx.compose.ui.demos.focus |
| |
| import androidx.compose.foundation.ExperimentalFoundationApi |
| import androidx.compose.foundation.border |
| import androidx.compose.foundation.focusable |
| import androidx.compose.foundation.layout.Box |
| import androidx.compose.foundation.layout.BoxScope |
| import androidx.compose.foundation.layout.padding |
| import androidx.compose.foundation.layout.size |
| import androidx.compose.foundation.lazy.LazyColumn |
| import androidx.compose.foundation.lazy.LazyRow |
| import androidx.compose.foundation.lazy.rememberLazyListState |
| import androidx.compose.material.Text |
| import androidx.compose.runtime.Composable |
| import androidx.compose.runtime.DisposableEffect |
| import androidx.compose.runtime.getValue |
| import androidx.compose.runtime.mutableStateOf |
| import androidx.compose.runtime.remember |
| import androidx.compose.runtime.rememberCoroutineScope |
| import androidx.compose.runtime.setValue |
| import androidx.compose.ui.Modifier |
| import androidx.compose.ui.focus.FocusRequester |
| import androidx.compose.ui.focus.FocusRequester.Companion.Default |
| import androidx.compose.ui.focus.focusProperties |
| import androidx.compose.ui.focus.focusRequester |
| import androidx.compose.ui.focus.onFocusChanged |
| import androidx.compose.ui.graphics.Color |
| import androidx.compose.ui.layout.LocalPinnableContainer |
| import androidx.compose.ui.layout.PinnableContainer |
| import androidx.compose.ui.tooling.preview.Preview |
| import androidx.compose.ui.unit.dp |
| import kotlinx.coroutines.launch |
| |
| @OptIn(ExperimentalFoundationApi::class) |
| @Preview(widthDp = 200, heightDp = 400) |
| @Composable |
| fun LazyListChildFocusDemos() { |
| LazyColumn { |
| stickyHeader { Text("Default Compose Behavior") } |
| item { |
| LazyRow { |
| items(10) { |
| FocusableBox() |
| } |
| } |
| } |
| |
| stickyHeader { Text("Direct Focus to First Child") } |
| item { |
| val firstItem = remember { FocusRequester() } |
| val firstItemModifier = Modifier.focusRequester(firstItem) |
| val coroutineScope = rememberCoroutineScope() |
| val state = rememberLazyListState() |
| LazyRow( |
| Modifier |
| .onFocusChanged { |
| if (it.isFocused) coroutineScope.launch { |
| state.animateScrollToItem(0) |
| firstItem.requestFocus() |
| } |
| } |
| .focusable(), |
| state, |
| ) { |
| items(10) { |
| FocusableBox(if (it == 0) firstItemModifier else Modifier) |
| } |
| } |
| } |
| |
| stickyHeader { Text("Direct Focus to previously Focused Child") } |
| item { |
| var previouslyFocusedItem: FocusRequester? by remember { mutableStateOf(null) } |
| LazyRow( |
| Modifier |
| .focusProperties { |
| enter = { previouslyFocusedItem ?: Default } |
| } |
| ) { |
| items(10) { index -> |
| val focusRequester = remember(index) { FocusRequester() } |
| val pinnableContainer = LocalPinnableContainer.current |
| var pinnedHandle: PinnableContainer.PinnedHandle? = null |
| FocusableBox(Modifier |
| .onFocusChanged { |
| if (it.isFocused) { |
| previouslyFocusedItem = focusRequester |
| pinnedHandle = pinnableContainer?.pin() |
| } |
| } |
| .focusRequester(focusRequester) |
| ) |
| DisposableEffect(pinnableContainer) { |
| onDispose { |
| pinnedHandle?.release() |
| pinnedHandle = null |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| @Composable |
| private fun FocusableBox( |
| modifier: Modifier = Modifier, |
| content: @Composable BoxScope.() -> Unit = {} |
| ) { |
| var borderColor by remember { mutableStateOf(Color.Black) } |
| Box( |
| modifier = modifier |
| .size(100.dp) |
| .padding(2.dp) |
| .onFocusChanged { borderColor = if (it.isFocused) Color.Red else Color.Black } |
| .border(2.dp, borderColor) |
| .focusable(), |
| content = content |
| ) |
| } |