FocusRequester 与 FocusManager 在 Compose 中,可以通过 FocusRequester 与 FocusManager 这两个对象可以主动在代码中控制焦点获取和取消焦点,其中FocusRequester可以用来获取焦点,通过调用它的requestFocus()方法来实现,而 FocusManager可以用来取消焦点(以及移动焦点),通过调用它的clearFocus() 方法来实现。(不知道 Compose 官方的设计初衷是什么,这里为什么要搞两个对象。。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 @Composable fun FocusControlExample () { var text by remember { mutableStateOf("" ) } var hasFocus by remember { mutableStateOf(false ) } val focusRequester = remember { FocusRequester() } val focusManager = LocalFocusManager.current Column(horizontalAlignment = Alignment.CenterHorizontally) { TextField( value = text, onValueChange = { text = it }, Modifier .focusRequester(focusRequester) .onFocusChanged { hasFocus = it.isFocused }, placeholder = { Text("请输入..." ) }, textStyle = TextStyle(fontSize = 20. sp), colors = TextFieldDefaults.textFieldColors( backgroundColor = Color.Transparent ), ) Button( onClick = { if (!hasFocus) { focusRequester.requestFocus() } else { focusManager.clearFocus() } } ) { Text(if (!hasFocus) "Focus" else "UnFocus" ) } } }
这里有一个非常重要的点需要注意:在调用 focusRequester.requestFocus() 之前必须保证已经调用Modifier.focusRequester()为组件绑定过了FocusRequester,否则将会导致应用崩溃。
另外这里使用了 Modifier.onFocusChanged修饰符来获取焦点状态,它的lambda中可以获取到一个FocusState参数,FocusState.isFocused可以判断当前组件是否获取焦点,而FocusState.hasFocus则可以判断是否包含获取焦点的child组件。
进入页面后输入框自动弹出软键盘 既然 focusRequester 可以主动获取焦点,对于TextField组件,那么我们理所当然地可以在进入其Composable重组作用域时,就通过一个副作用Api来启动协程 进行焦点获取,那么软键盘自然也就自动弹出了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 @Composable fun AutoFocusExample () { var text by remember { mutableStateOf("" ) } var hasFocus by remember { mutableStateOf(false ) } val focusRequester = remember { FocusRequester() } LaunchedEffect(Unit ) { if (!hasFocus) focusRequester.requestFocus() } Column(horizontalAlignment = Alignment.CenterHorizontally) { TextField( value = text, onValueChange = { text = it }, modifier = Modifier .focusRequester(focusRequester) .onFocusChanged { hasFocus = it.isFocused }, textStyle = TextStyle(fontSize = 20. sp), colors = TextFieldDefaults.textFieldColors( backgroundColor = Color.Transparent ), ) } }
捕获焦点 研究了一下,在Compose中捕获焦点 跟获取到焦点 是两个概念,使用还是通过 focusRequester 的两个方法 captureFocus() 和 freeFocus() ,具体区别可以看下面代码与运行效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 @Composable fun CaptureFocusExample () { Column(horizontalAlignment = Alignment.CenterHorizontally) { val focusRequester = remember { FocusRequester() } var text by remember { mutableStateOf("" ) } TextField( value = text, onValueChange = { text = it }, placeholder = { Text(text = "TextField1" )}, modifier = Modifier.focusRequester(focusRequester), textStyle = TextStyle(fontSize = 20. sp), colors = TextFieldDefaults.textFieldColors( backgroundColor = Color.Transparent ), ) val focusRequester2 = remember { FocusRequester() } var value by remember { mutableStateOf("" ) } var borderColor by remember { mutableStateOf(Color.Transparent) } TextField( value = value, onValueChange = { value = it.apply { if (length > 5 ) focusRequester2.captureFocus() else focusRequester2.freeFocus() } }, placeholder = { Text(text = "TextField2" )}, colors = TextFieldDefaults.textFieldColors( backgroundColor = Color.Transparent ), modifier = Modifier .border(2. dp, borderColor) .focusRequester(focusRequester2) .onFocusChanged { borderColor = if (it.isCaptured) Color.Red else Color.Transparent } ) val focusManager = LocalFocusManager.current Button(onClick = { focusManager.clearFocus() }) { Text("clearFocus" ) } Button(onClick = { focusRequester.requestFocus() }) { Text("TextField1 requestFocus" ) } } }
可以看到在捕获焦点以后,任何其他组件均不能获取焦点(包括clearFocus()也不会起作用),直到当前组件调用freeFocus()释放焦点。 相当于当前组件锁定了焦点模式。
这里为每个TextField组件都绑定了一个 FocusRequester 对象,每个 FocusRequester 对象控制各自绑定的组件的焦点获取。而 FocusManager 使用一个就可以了,因此 FocusManager的获取是通过 LocalFocusManager.current 它获取到的是一个范围内的单例 。
通过 SoftwareKeyboardController 控制软键盘显示与隐藏 前面通过 FocusManager.clearFocus() 方法可以取消组件的焦点,对于TextField组件,如果失去焦点,那么软键盘也会自动隐藏。这里 Compose 中还有另一种方式来专门控制键盘的显示与隐藏,就是通过SoftwareKeyboardController 来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 @OptIn(ExperimentalComposeUiApi::class, ExperimentalLayoutApi::class) @Composable fun KeyboardControllerExample () { var text by remember { mutableStateOf("" ) } var hasFocus by remember { mutableStateOf(false ) } val focusRequester = remember { FocusRequester() } val keyboardController = LocalSoftwareKeyboardController.current val isImeVisible = WindowInsets.isImeVisible Column(horizontalAlignment = Alignment.CenterHorizontally) { TextField( value = text, onValueChange = { text = it }, Modifier .focusRequester(focusRequester) .onFocusChanged { hasFocus = it.isFocused }, placeholder = { Text("请输入..." ) }, textStyle = TextStyle(fontSize = 20. sp), colors = TextFieldDefaults.textFieldColors( backgroundColor = Color.Transparent ), ) Button( onClick = { if (!hasFocus) { focusRequester.requestFocus() } else { if (isImeVisible) { keyboardController?.hide() } else { keyboardController?.show() } } } ) { Text( if (!hasFocus) "request Focus" else { if (isImeVisible) "hide keyboard" else "show keyboard" } ) } val focusManager = LocalFocusManager.current Button(onClick = { focusManager.clearFocus() }) { Text(text = "clear Focus" ) } } }
需要注意的是,软键盘的显示与隐藏的前提是:输入框组件已经获取到了焦点。如果输入框都没有获取到焦点,主动调用显示软键盘不会有任何效果,因为它不知道要往哪里输入。另外这里使用了 Compose 提供的一个很方便的api WindowInsets.isImeVisible 它可以判断软键盘的显示状态。
在面向传统View体系的开发中,一般我们在项目中应该都会有相应的工具类来专门处理软键盘的显示与隐藏,那么能不能直接复用以前的工具类代码呢?答案是可以。例如我们以前传统的开发中是通过context.getSystemService(Context.INPUT_METHOD_SERVICE) 拿到 InputMethodManager对象,再通过该对象的方法来操作软键盘,如果你的项目中以前有这样的代码,那么依然可以拿来直接使用。
例如之前有如下封装:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 fun showKeyboard (context: Context ) { if (context is Activity) { context.apply{ val inputManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager runOnUiThread { currentFocus?.let { inputManager.showSoftInput(it, 0 ) } } } } } fun hideKeyboard (context: Context ) { if (context is Activity) { context.apply{ val inputManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager runOnUiThread { currentFocus?.let { inputManager.hideSoftInputFromWindow(it.windowToken, InputMethodManager.HIDE_NOT_ALWAYS) window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) } } } } }
那么就可以如下调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 @OptIn(ExperimentalLayoutApi::class) @Composable fun KeyboardControllerOldWayExample () { var text by remember { mutableStateOf("" ) } var hasFocus by remember { mutableStateOf(false ) } val focusRequester = remember { FocusRequester() } val context = LocalContext.current val isImeVisible = WindowInsets.isImeVisible Column(horizontalAlignment = Alignment.CenterHorizontally) { TextField( value = text, onValueChange = { text = it }, Modifier .focusRequester(focusRequester) .onFocusChanged { hasFocus = it.isFocused }, placeholder = { Text("请输入..." ) }, textStyle = TextStyle(fontSize = 20. sp), colors = TextFieldDefaults.textFieldColors( backgroundColor = Color.Transparent ), ) Button( onClick = { if (!hasFocus) { focusRequester.requestFocus() } else { if (isImeVisible) { hideKeyboard(context) } else { showKeyboard(context) } } } ) { Text( if (!hasFocus) "request Focus" else { if (isImeVisible) "hide keyboard" else "show keyboard" } ) } val focusManager = LocalFocusManager.current Button(onClick = { focusManager.clearFocus() }) { Text(text = "clear Focus" ) } } }
运行效果跟前面通过SoftwareKeyboardController 实现的效果一模一样。
在其他组件上应用 FocusRequester 注意到 FocusRequester是通过Modifier修饰符来提供绑定的,因此它并不是仅仅针对 TextField 输入框组件的,任何组件上都可以使用它来控制获取焦点。
下面是使用Box组件获取焦点的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @Composable fun FocusBoxSample () { val boxFocusRequester = remember { FocusRequester() } val boxFocusRequester2 = remember { FocusRequester() } var color by remember { mutableStateOf(Color.Black) } var color2 by remember { mutableStateOf(Color.Black) } val focusManager = LocalFocusManager.current Column(horizontalAlignment = Alignment.CenterHorizontally) { Box( Modifier .size(100. dp) .clickable { boxFocusRequester.requestFocus() } .border(2. dp, color) .focusRequester(boxFocusRequester) .onFocusChanged { color = if (it.isFocused) Color.Red else Color.Black } .focusable() ) Spacer(modifier = Modifier.height(20. dp)) Box( Modifier .size(100. dp) .clickable { boxFocusRequester2.requestFocus() } .border(2. dp, color2) .focusRequester(boxFocusRequester2) .onFocusChanged { color2 = if (it.isFocused) Color.Red else Color.Black } .focusable() ) Button(onClick = { focusManager.clearFocus() }) { Text(text = "ClearFocus" ) } } }
需要注意的是,为了保证生效,在Modifier链中的focusRequester 和 onFocusChanged 必须在 focusable() 之前调用。
通过FocusRequester.createRefs()创建多个FocusRequester 由于每个需要进行焦点控制的组件都需要绑定一个FocusRequester对象,如果一个一个创建太麻烦了,因此 Compose 提供了 FocusRequester.createRefs() 可以通过解构语法来一次性创建多个FocusRequester对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @OptIn(ExperimentalComposeUiApi::class) @Composable fun CreateFocusRequesterRefsExample () { val (item1, item2, item3, item4) = remember { FocusRequester.createRefs() } val requesterList = listOf(item1, item2, item3, item4) Column(horizontalAlignment = Alignment.CenterHorizontally) { repeat(4 ) { index -> var color by remember { mutableStateOf(Color.Black) } Box( Modifier .size(100. dp) .clickable { requesterList[index].requestFocus() } .border(2. dp, color) .focusRequester(requesterList[index]) .onFocusChanged { color = if (it.isFocused) Color.Red else Color.Black } .focusable() ) Spacer(modifier = Modifier.height(20. dp)) } } }
既然是解构语法,那么一定是通过提供 component1....componentN语法来实现的,那么 FocusRequester.createRefs() 最多能一次性创建多少个呢? 可以看一下源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class FocusRequester { ..... companion object { ..... object FocusRequesterFactory { operator fun component1 () = FocusRequester() operator fun component2 () = FocusRequester() operator fun component3 () = FocusRequester() operator fun component4 () = FocusRequester() operator fun component5 () = FocusRequester() operator fun component6 () = FocusRequester() operator fun component7 () = FocusRequester() operator fun component8 () = FocusRequester() operator fun component9 () = FocusRequester() operator fun component10 () = FocusRequester() operator fun component11 () = FocusRequester() operator fun component12 () = FocusRequester() operator fun component13 () = FocusRequester() operator fun component14 () = FocusRequester() operator fun component15 () = FocusRequester() operator fun component16 () = FocusRequester() } fun createRefs () = FocusRequesterFactory } }
可知FocusRequester.createRefs() 最多只能创建16个FocusRequester对象。再多就只能自己for循环创建了。
通过 FocusManager.moveFocus() 控制焦点移动方向 当需要在多个组件之间来回切换焦点获取状态时,可以通过FocusManager.moveFocus()方法来实现,它可以传一个FocusDirection参数来指定焦点移动的方向,代码中直接调用到的FocusDirection是一个伴生对象,它主要有以下几个值:
一个可以左右上下移动焦点的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @Composable fun MoveFocusExample () { val focusManager = LocalFocusManager.current val list = ('A' ..'Z' ).toList() Column(horizontalAlignment = Alignment.CenterHorizontally) { LazyVerticalGrid( modifier = Modifier .weight(1f ) .focusable(), columns = GridCells.Fixed(4 ), verticalArrangement = Arrangement.spacedBy(8. dp), horizontalArrangement = Arrangement.spacedBy(8. dp), contentPadding = PaddingValues(5. dp) ) { itemsIndexed(list) { index, item -> val boxFocusRequester = remember { FocusRequester() } var color by remember { mutableStateOf(Color.Black) } Box( Modifier .clip(RoundedCornerShape(5. dp)) .size(80. dp) .clickable { boxFocusRequester.requestFocus() } .border(3. dp, color) .focusRequester(boxFocusRequester) .onFocusChanged { color = if (it.isFocused) Color.Red else Color.Black } .focusable(), contentAlignment = Alignment.Center ) { Text("$index $item " , fontSize = 22. sp) } } } Button(onClick = { focusManager.moveFocus(FocusDirection.Up) }) { Text("Up" ) } Row { Button(onClick = { focusManager.moveFocus(FocusDirection.Left) }) { Text("Left" ) } Spacer(modifier = Modifier.width(50. dp)) Button(onClick = { focusManager.moveFocus(FocusDirection.Right) }) { Text("Right" ) } } Button(onClick = { focusManager.moveFocus(FocusDirection.Down) }) { Text("Down" ) } } }
(提示:如果是模拟器可以通过电脑键盘的方向键来控制焦点移动,而如果是真机还可以通过连接蓝牙键盘后按蓝牙键盘上的方向键来控制焦点移动)
通过 focusProperties 自定义焦点移动方向 通过 Modifier.focusProperties 我们可以自定义每个焦点方向的下一个焦点位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 @OptIn(ExperimentalComposeUiApi::class) @Composable fun CustomFocusOrderByFocusPropertiesExample () { val focusManager = LocalFocusManager.current Column(Modifier.fillMaxSize(), Arrangement.SpaceEvenly) { val (item1, item2, item3, item4) = remember { FocusRequester.createRefs() } Row(Modifier.fillMaxWidth(), Arrangement.SpaceEvenly) { MyFocusBox( size = 140. dp, focusRequester = item1, focusProperties = { next = item2 right = item2 down = item3 previous = item4 } ) { isFocused -> Text( text = "item1: \n next = item2 \n right = item2 \n down = item3 \n previous = item4" , color = if (isFocused) Color.Red else Color.Black, fontSize = 16. sp ) } MyFocusBox( size = 140. dp, focusRequester = item2, focusProperties = { next = item3 right = item1 down = item4 previous = item1 } ) { isFocused -> Text( text = "item2: \n next = item3 \n right = item1 \n down = item4 \n previous = item1" , color = if (isFocused) Color.Red else Color.Black, fontSize = 16. sp ) } } Row(Modifier.fillMaxWidth(), Arrangement.SpaceEvenly) { MyFocusBox( size = 140. dp, focusRequester = item3, focusProperties = { next = item4 right = item4 up = item1 previous = item2 } ) { isFocused -> Text( text = "item3: \n next = item4 \n right = item4 \n up = item1 \n previous = item2" , color = if (isFocused) Color.Red else Color.Black, fontSize = 16. sp ) } MyFocusBox( size = 140. dp, focusRequester = item4, focusProperties = { next = item1 left = item3 up = item2 previous = item3 } ) { isFocused -> Text( text = "item4: \n next = item1 \n left = item3 \n up = item2 \n previous = item3" , color = if (isFocused) Color.Red else Color.Black, fontSize = 16. sp ) } } FlowRow(mainAxisSpacing = 10. dp) { Button(onClick = { focusManager.moveFocus(FocusDirection.Left) }) { Text("Left" ) } Button(onClick = { focusManager.moveFocus(FocusDirection.Right) }) { Text("Right" ) } Button(onClick = { focusManager.moveFocus(FocusDirection.Up) }) { Text("Up" ) } Button(onClick = { focusManager.moveFocus(FocusDirection.Down) }) { Text("Down" ) } Button(onClick = { focusManager.moveFocus(FocusDirection.Next) }) { Text("Next" ) } Button(onClick = { focusManager.moveFocus(FocusDirection.Previous) }) { Text("Previous" ) } } } } @Composable fun MyFocusBox ( size: Dp = 100. dp, focusRequester: FocusRequester = remember { FocusRequester() }, focusProperties: FocusProperties.() -> Unit = {}, content: (@Composable BoxScope.(Boolean ) -> Unit )? = null ) { var color by remember { mutableStateOf(Color.Black) } var isFocused by remember { mutableStateOf(false ) } Box( Modifier .size(size) .clickable { focusRequester.requestFocus() } .border(3. dp, color) .padding(8. dp) .focusRequester(focusRequester) .focusProperties { focusProperties() } .onFocusChanged { focusState -> color = if (focusState.isFocused) Color.Red else Color.Black isFocused = focusState.isFocused } .focusable() ) { content?.let { content(isFocused) } } }
拦截某个方向的焦点移动 如果需要拦截某个方向的焦点移动,只需将对应方向的值设为FocusRequester.Cancel即可,如上面代码中,将第二个Row组件中的第一个Box组件的up修改为 up = FocusRequester.Cancel 则可以拦截up方向的焦点移动,效果如下:
可以看到,从第一个Box向下移动到下面的Box组件后,再执行FocusManager.moveFocus(FocusDirection.Up)方法无法生效了。
FocusDirection.Enter FocusDirection.Enter指从获取焦点的组件上执行FocusManager.moveFocus(FocusDirection.Enter)可直接进入到下一个获取焦点的组件中,至于下一个是哪个组件获取焦点,同样可以通过在当前组件上应用 Modifier.focusProperties 然后指定其enter参数为要进入的FocusRequester即可。
下面代码中,如果Row组件获取焦点后,执行focusManager.moveFocus(FocusDirection.Enter)时,会跳入第二个Box组件获取焦点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 @OptIn(ExperimentalComposeUiApi::class) @Composable fun FocusEnterExample () { val rowFocusRequester = remember { FocusRequester() } var color by remember { mutableStateOf(Color.Black) } val item2 = remember { FocusRequester() } Column(horizontalAlignment = Alignment.CenterHorizontally) { Row( Modifier .clickable { rowFocusRequester.requestFocus() } .border(3. dp, color) .padding(30. dp) .focusRequester(rowFocusRequester) .focusProperties { enter = { item2 } } .onFocusChanged { color = if (it.isFocused) Color.Red else Color.Black } .focusable(), horizontalArrangement = Arrangement.spacedBy(10. dp) ) { MyFocusBox() MyFocusBox(focusRequester = item2) MyFocusBox() } val focusManager = LocalFocusManager.current Button(onClick = { focusManager.moveFocus(FocusDirection.Enter) }) { Text("Enter" ) } } } @Composable fun MyFocusBox ( size: Dp = 100. dp, focusRequester: FocusRequester = remember { FocusRequester() }, focusProperties: FocusProperties.() -> Unit = {}, content: (@Composable BoxScope.(Boolean ) -> Unit )? = null ) { var color by remember { mutableStateOf(Color.Black) } var isFocused by remember { mutableStateOf(false ) } Box( Modifier .size(size) .clickable { focusRequester.requestFocus() } .border(3. dp, color) .padding(8. dp) .focusRequester(focusRequester) .focusProperties { focusProperties() } .onFocusChanged { focusState -> color = if (focusState.isFocused) Color.Red else Color.Black isFocused = focusState.isFocused } .focusable() ) { content?.let { content(isFocused) } } }
FocusDirection.Exit 跟FocusDirection.Enter相反,FocusDirection.Exit是指从获取焦点的组件上执行FocusManager.moveFocus(FocusDirection.Exit)时会跳转到下一个获取焦点的组件,同样通过 focusProperties 参数指定。
下面代码中,当第一个Row中的任何一个Box获取焦点后,执行FocusManager.moveFocus(FocusDirection.Exit)时,都会跳转到第二个Row中的第二个Box组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 @OptIn(ExperimentalComposeUiApi::class) @Composable fun FocusExitSample () { val focusRequester = remember { FocusRequester() } var color by remember { mutableStateOf(Color.Black) } val nextItem = remember { FocusRequester() } Column( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(10. dp) ) { Row( Modifier .clickable { focusRequester.requestFocus() } .border(3. dp, color) .padding(15. dp) .focusRequester(focusRequester) .onFocusChanged { color = if (it.isFocused) Color.Red else Color.Black } .focusable(), horizontalArrangement = Arrangement.spacedBy(10. dp) ) { MyFocusBox(focusProperties = { exit = { nextItem } }) MyFocusBox(focusProperties = { exit = { nextItem } }) MyFocusBox(focusProperties = { exit = { nextItem } }) } Row(Modifier.focusable(), horizontalArrangement = Arrangement.spacedBy(10. dp)) { MyFocusBox() MyFocusBox(focusRequester = nextItem) } val focusManager = LocalFocusManager.current Button(onClick = { focusManager.moveFocus(FocusDirection.Exit) }) { Text("Exit" ) } } } @Composable fun MyFocusBox ( size: Dp = 100. dp, focusRequester: FocusRequester = remember { FocusRequester() }, focusProperties: FocusProperties.() -> Unit = {}, content: (@Composable BoxScope.(Boolean ) -> Unit )? = null ) { var color by remember { mutableStateOf(Color.Black) } var isFocused by remember { mutableStateOf(false ) } Box( Modifier .size(size) .clickable { focusRequester.requestFocus() } .border(3. dp, color) .padding(8. dp) .focusRequester(focusRequester) .focusProperties { focusProperties() } .onFocusChanged { focusState -> color = if (focusState.isFocused) Color.Red else Color.Black isFocused = focusState.isFocused } .focusable() ) { content?.let { content(isFocused) } } }
作为对比,可以把 focusProperties = { exit = { nextItem } } 去掉,查看正常效果有何不同: