我正在处理三个页面:StartPage、LoginPage 和 SignupPage,并且我正在将它们集成到 HorizontalPager 中。
在 SignupPage 中,我在 textField 上使用 requestFocus。
导航流程是“开始”->“登录”,但在转换到“登录页面”时遇到此错误,尽管 focusRequester 是在“注册”上定义的。根据我的研究,发生这种情况的原因是 HorizontalPager,准备下一页,尽管它在屏幕上不可见。
那么我如何仅在 SignupPage 可见时触发 requestFocus,并在 SignupPage 不可见时禁用它。
java.lang.IllegalStateException:
FocusRequester is not initialized. Here are some possible fixes:
1. Remember the FocusRequester: val focusRequester = remember { FocusRequester() }
2. Did you forget to add a Modifier.focusRequester() ?
3. Are you attempting to request focus during composition? Focus requests should be made in
response to some event. Eg Modifier.clickable { focusRequester.requestFocus() }
这是 MyNavigation.kt
@Composable
fun MyAppNavigation(
modifier: Modifier, authViewModel: AuthViewModel
) {
val pages = registerInfoList.size
val pagerState = rememberPagerState(pageCount = { pages })
val scope = rememberCoroutineScope()
Column(
modifier = modifier.fillMaxSize()
) {
HorizontalPager(
state = pagerState, userScrollEnabled = false
) { page ->
when (page) {
0 -> StartPage(modifier = modifier,
toSignUp = {
scope.launch {
pagerState.animateScrollToPage(2)
}
}, toLogin = {
scope.launch {
pagerState.animateScrollToPage(1)
}
}, authViewModel = authViewModel
)
1 -> LoginPage(modifier = modifier,
toHome = {
scope.launch {
pagerState.animateScrollToPage(3)
}
}, toSignUp = {
scope.launch {
pagerState.animateScrollToPage(2)
}
},
toStart = {
scope.launch {
pagerState.animateScrollToPage(0)
}
}, authViewModel = authViewModel
)
2 -> SignupPage(
modifier = modifier,
toStart = {
scope.launch {
pagerState.animateScrollToPage(0)
}
},
toHome = {
scope.launch {
pagerState.animateScrollToPage(3)
}
},
authViewModel = authViewModel
)
......
}
}
}
}
登录页面.kt
@OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterial3Api::class)
@Composable
fun LoginPage(
modifier: Modifier = Modifier,
toSignUp: () -> Unit,
toHome: () -> Unit,
toStart: () -> Unit,
authViewModel: AuthViewModel
) {
...................
if (shouldShowDialog.value) {
AlertDialog(shouldShowDialog = shouldShowDialog, message = errorMessage)
}
val authState = authViewModel.authState.observeAsState()
LocalContext.current
LaunchedEffect(authState.value) {
when (authState.value) {
is AuthState.Authenticated -> toHome()
is AuthState.Error.InvalidCredentialsError -> {
errorMessage = (authState.value as AuthState.Error.InvalidCredentialsError).message
shouldShowDialog.value = true
}
is AuthState.Error.NetworkError -> {
errorMessage = (authState.value as AuthState.Error.NetworkError).message
}
else -> Unit
}
}
Scaffold(...................
) { innerPadding ->
Column(
.......
) {
Text(
............
)
Spacer(modifier = Modifier.size(4.dp))
TextField(............)
Spacer(modifier = Modifier.size(32.dp))
Text(.............)
Spacer(modifier = Modifier.size(4.dp))
TextField(.........)
Text(
..........
)
Spacer(modifier = Modifier.height(32.dp))
var colorButton by remember { mutableStateOf(Color.White) }
Button(..........)
Spacer(modifier = Modifier.height(8.dp))
TextButton(.......)
}
}
}
注册页面.kt
@Composable
fun SignupPage(
modifier: Modifier = Modifier,
toStart: () -> Unit,
toHome: () -> Unit,
authViewModel: AuthViewModel
) {
.........
// Added FocusRequesters for each TextField
val emailFocusRequester = remember { FocusRequester() }
val passwordFocusRequester = remember { FocusRequester() }
val userNameFocusRequester = remember { FocusRequester() }
val keyboardController = LocalSoftwareKeyboardController.current
val context = LocalContext.current
LaunchedEffect(authState.value) {
when (authState.value) {
is AuthState.Authenticated -> toHome()
is AuthState.Error.NetworkError -> Toast.makeText(
context,
(authState.value as AuthState.Error.NetworkError).message, Toast.LENGTH_SHORT
).show()
else -> Unit
}
}
// Updated LaunchedEffect to handle focus and keyboard visibility
LaunchedEffect(pagerState.currentPage) {//------> this where the crash happen
when (pagerState.currentPage) {
0 -> emailFocusRequester.requestFocus()
1 -> passwordFocusRequester.requestFocus()
2 -> keyboardController?.hide()
3 -> keyboardController?.hide() // No TextField on this page
4 -> userNameFocusRequester.requestFocus()
}
// Delay to ensure the focus change is processed
delay(100)
if (pagerState.currentPage in listOf(0, 1, 4)) {
keyboardController?.show()
} else {
keyboardController?.hide()
}
}
Scaffold(
..........
) { innerPadding ->
Column(
..................
) {
HorizontalPager(
state = pagerState,
userScrollEnabled = false
) { index ->
Column(
...............
) {
Text(
..................
)
Spacer(modifier = Modifier.size(4.dp))
when (index) {
0 -> // email and name
TextField(
modifier = Modifier
.fillMaxWidth()
.focusRequester(emailFocusRequester),
)
1 -> {
TextField(modifier = Modifier
.fillMaxWidth()
.focusRequester(passwordFocusRequester))
2 -> {
DatePicker { date ->
dateOfBirth = date.let {
SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()).format(it)
}
}
}
3 -> {
ExposedDropdownMenuBox(............)
4 -> // email and name
TextField(
modifier = Modifier
.fillMaxWidth()
.focusRequester(userNameFocusRequester),
)
Spacer(modifier = Modifier.size(4.dp))
Text(
....................
)
}
}
Spacer(modifier = Modifier.height(16.dp))
Button(..............)
}
}
}
这是我当前的解决方案,不确定这是否符合最佳实践。
val lifecycleOwner = LocalLifecycleOwner.current
val lifecycleState by lifecycleOwner.lifecycle.currentStateFlow.collectAsState()
LaunchedEffect(key1 = lifecycleState, key2 = pagerState.currentPage) {
when (lifecycleState) {
Lifecycle.State.DESTROYED -> {
keyboardController?.hide()
}
Lifecycle.State.INITIALIZED -> {
keyboardController?.hide()
}
Lifecycle.State.CREATED -> {
when (pagerState.currentPage) {
0 -> emailFocusRequester.requestFocus()
1 -> passwordFocusRequester.requestFocus()
2 -> keyboardController?.hide()
3 -> keyboardController?.hide() // No TextField on this page
4 -> userNameFocusRequester.requestFocus()
}
// Delay to ensure the focus change is processed
delay(100)
if (pagerState.currentPage in listOf(0, 1, 4)) {
keyboardController?.show()
} else {
keyboardController?.hide()
}
}
Lifecycle.State.STARTED -> {
when (pagerState.currentPage) {
0 -> emailFocusRequester.requestFocus()
1 -> passwordFocusRequester.requestFocus()
2 -> keyboardController?.hide()
3 -> keyboardController?.hide() // No TextField on this page
4 -> userNameFocusRequester.requestFocus()
}
// Delay to ensure the focus change is processed
delay(100)
if (pagerState.currentPage in listOf(0, 1, 4)) {
keyboardController?.show()
} else {
keyboardController?.hide()
}
}
Lifecycle.State.RESUMED -> {
when (pagerState.currentPage) {
0 -> emailFocusRequester.requestFocus()
1 -> passwordFocusRequester.requestFocus()
2 -> keyboardController?.hide()
3 -> keyboardController?.hide() // No TextField on this page
4 -> userNameFocusRequester.requestFocus()
}
// Delay to ensure the focus change is processed
delay(100)
if (pagerState.currentPage in listOf(0, 1, 4)) {
keyboardController?.show()
} else {
keyboardController?.hide()
}
}
}
}
仅当可组合项组合时才会调用
Modifier.focusRequester(...)
。
等待是相当不精确的。
您可以等待更长时间,也可以只编写如下所示的代码:
val onFocusReady: ()->Unit={}
when(...){
...->onFocusReady={someFocusRequester.requestFocus()}
}
when(...){
...->TextField(modifier = Modifier
.fillMaxWidth()
.focusRequester(passwordFocusRequester))
onFocusReady()
}
这将确保焦点在您需要时被初始化。 希望这有帮助。