You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Kotlin Compose实现适配手机与平板的响应式登录UI

使用单一Compose UI适配手机与平板横竖屏场景

现有面向手机开发的登录界面,因原Modifier设置仅适配手机,在平板上出现视图遮挡问题。需基于屏幕比例调整尺寸,保持UI样式不变,实现跨设备无差异适配。

当前代码

@Composable
fun TestLogin(
) {

val userNameFieldValue = remember { mutableStateOf("") }
val passwordFieldValue = remember { mutableStateOf("") }
val passwordVisibility = remember { mutableStateOf(false) }
val isLoginError = remember { mutableStateOf(false) }
val isInternetError = remember { mutableStateOf(false) }
val configuration = LocalConfiguration.current

Box(
    Modifier
        .fillMaxSize()
        .background(color = colorResource(id = R.color.white)),
    contentAlignment = Alignment.Center) {
    Card(
        shape = RoundedCornerShape(20.dp),
        modifier = Modifier
            .padding(start = 10.dp, end = 10.dp)
    ) {
        Column(
            modifier = Modifier
                .padding(start = 16.dp, end = 16.dp, bottom = 10.dp, top = 10.dp),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center,
        ) {

            Icon(
                painter = painterResource(id = R.drawable.ic_launcher_background),
                tint = Color.Unspecified,
                contentDescription = "success",
                modifier = Modifier
                    .align(Alignment.CenterHorizontally)
            )
            Text(
                text = "Welcome to the App \n Hi There",
                fontWeight = FontWeight.ExtraBold,
                fontSize = 32.sp,
                color = colorResource(R.color.black),
                modifier = Modifier
                    .padding(bottom = 24.dp)
                    .fillMaxWidth(),
                textAlign = TextAlign.Center
            )
            Text(
                text = "Login",
                fontWeight = FontWeight.Bold,
                fontSize = 22.sp,
                color = colorResource(id = R.color.black),
                modifier = Modifier
                    .padding(top = 50.dp, bottom = 24.dp)
                    .fillMaxWidth(),
                textAlign = TextAlign.Center
            )
            OutlinedTextField(
                modifier = Modifier
                    .padding(bottom = 40.dp)
                    .fillMaxWidth(),
                value = userNameFieldValue.value,
                textStyle = TextStyle(
                    fontSize = 16.sp,
                    fontWeight = FontWeight.Normal
                ),
                singleLine = true,
                onValueChange = {
                    isLoginError.value = false
                    userNameFieldValue.value = it
                },
                keyboardOptions = KeyboardOptions(
                    imeAction = ImeAction.Next
                ),
                enabled = true,
                label = { Text(text = "username") },
                colors = TextFieldDefaults.outlinedTextFieldColors(
                    focusedBorderColor = colorResource(id = R.color.black),
                    unfocusedBorderColor = colorResource(id = R.color.black),
                    cursorColor = colorResource(id = R.color.black)
                )

            )
            OutlinedTextField(
                modifier = Modifier
                    .padding(bottom = 32.dp)
                    .fillMaxWidth(),
                value = passwordFieldValue.value,
                textStyle = TextStyle(
                    fontSize = 16.sp,
                    fontWeight = FontWeight.Normal
                ),
                enabled = true,
                singleLine = true,
                onValueChange = {
                    isLoginError.value = false
                    passwordFieldValue.value = it
                },
                label = { Text(text = "password") },
                visualTransformation = if (passwordVisibility.value) {
                    VisualTransformation.None
                } else {
                    PasswordVisualTransformation()
                },
                keyboardOptions = KeyboardOptions(
                    keyboardType = KeyboardType.Password
                ),
                colors = TextFieldDefaults.outlinedTextFieldColors(
                    focusedBorderColor = colorResource(id = R.color.black),
                    unfocusedBorderColor = colorResource(id = R.color.black),
                    cursorColor = colorResource(id = R.color.black)
                )
            )
            Row(
                Modifier
                    .background(
                        colorResource(
                            id = getLoginButtonColor(
                                userNameFieldValue,
                                passwordFieldValue,
                                isInternetError
                            )
                        ),
                        RoundedCornerShape(5.dp)
                    )
                    .align(Alignment.CenterHorizontally)
                    .fillMaxWidth()
                    .padding(15.dp)
                    .clickable(
                        enabled = userNameFieldValue.value.isNotEmpty() && passwordFieldValue.value.isNotEmpty() && !isInternetError.value,
                        onClick = {}
                    ),
                content = {
                        CircularProgressIndicator(
                            modifier = Modifier
                                .padding(end = 10.dp)
                                .size(20.dp)
                                .align(Alignment.CenterVertically),
                            color = colorResource(id = R.color.white),
                            strokeWidth = 3.dp
                        )
                    Text(
                        "Login",
                        fontWeight = FontWeight.Medium,
                        fontSize = 14.sp,
                        textAlign = TextAlign.Center,
                        color = colorResource(id = getLoginButtonTextColor(userNameFieldValue, passwordFieldValue, isInternetError)))
                },
                horizontalArrangement = Arrangement.Center
            )
            Text(
                text = "Forgot Password",
                fontWeight = FontWeight.ExtraBold,
                color = colorResource(id = R.color.black),
                modifier = Modifier
                    .padding(top = 28.dp),
                textAlign = TextAlign.Center
            )
            Text(
                text = "Not a User ",
                fontWeight = FontWeight.ExtraBold,
                color = colorResource(id = R.color.black),
                modifier = Modifier
                    .padding(top = 24.dp)
                    .clickable { },
                textAlign = TextAlign.Center
            )
        }
    }
}
}


fun getLoginButtonColor(
    userNameFieldValue: MutableState<String>,
    passwordFieldValue: MutableState<String>,
    isInternetError: MutableState<Boolean>
): Int {
    return if(!isInternetError.value && userNameFieldValue.value.isNotEmpty() && passwordFieldValue.value.isNotEmpty()) R.color.black else R.color.purple_200
}

fun getLoginButtonTextColor(
    userNameFieldValue: MutableState<String>,
    passwordFieldValue: MutableState<String>,
    isInternetError: MutableState<Boolean>
): Int {
    return if(!isInternetError.value && userNameFieldValue.value.isNotEmpty() && passwordFieldValue.value.isNotEmpty()) R.color.white else R.color.white
}

@Preview
@Composable
fun previewView() {
    TestLogin()
}

当前显示状态

  • 手机端显示正常:
    手机端正常显示
  • 平板端视图被遮挡:
    平板端视图遮挡

适配解决方案

1. 限制Card最大宽度,避免大屏过度拉伸

给Card添加maxWidth修饰符,固定最大宽度为适合的数值(如400dp),同时让高度自适应内容,防止平板上Card过宽导致布局异常:

Card(
    shape = RoundedCornerShape(20.dp),
    modifier = Modifier
        .padding(10.dp)
        .maxWidth(400.dp)
        .wrapContentHeight()
)

2. 为Column添加滚动支持

在小屏幕竖屏或特殊场景下,确保内容可滚动不被遮挡,给Column添加verticalScroll修饰符:

Column(
    modifier = Modifier
        .padding(16.dp)
        .verticalScroll(rememberScrollState()),
    horizontalAlignment = Alignment.CenterHorizontally,
    verticalArrangement = Arrangement.Center,
)

3. 动态调整字体与间距(可选)

基于屏幕宽度判断是否为平板,动态调整字体大小和间距,让UI在不同设备上保持比例一致:

val isTablet = configuration.screenWidthDp > 600
val titleFontSize = if (isTablet) 36.sp else 32.sp
val sectionPadding = if (isTablet) 32.dp else 24.dp

将代码中固定的32.sp、24.dp等替换为这些动态变量即可。

完整适配后代码

@Composable
fun TestLogin(
) {
    val userNameFieldValue = remember { mutableStateOf("") }
    val passwordFieldValue = remember { mutableStateOf("") }
    val passwordVisibility = remember { mutableStateOf(false) }
    val isLoginError = remember { mutableStateOf(false) }
    val isInternetError = remember { mutableStateOf(false) }
    val configuration = LocalConfiguration.current
    val isTablet = configuration.screenWidthDp > 600
    val titleFontSize = if (isTablet) 36.sp else 32.sp
    val sectionPadding = if (isTablet) 32.dp else 24.dp

    Box(
        Modifier
            .fillMaxSize()
            .background(color = colorResource(id = R.color.white)),
        contentAlignment = Alignment.Center
    ) {
        Card(
            shape = RoundedCornerShape(20.dp),
            modifier = Modifier
                .padding(10.dp)
                .maxWidth(400.dp)
                .wrapContentHeight()
        ) {
            Column(
                modifier = Modifier
                    .padding(16.dp)
                    .verticalScroll(rememberScrollState()),
                horizontalAlignment = Alignment.CenterHorizontally,
                verticalArrangement = Arrangement.Center,
            ) {
                Icon(
                    painter = painterResource(id = R.drawable.ic_launcher_background),
                    tint = Color.Unspecified,
                    contentDescription = "App Icon",
                    modifier = Modifier.align(Alignment.CenterHorizontally)
                )
                Text(
                    text = "Welcome to the App \n Hi There",
                    fontWeight = FontWeight.ExtraBold,
                    fontSize = titleFontSize,
                    color = colorResource(R.color.black),
                    modifier = Modifier
                        .padding(bottom = sectionPadding)
                        .fillMaxWidth(),
                    textAlign = TextAlign.Center
                )
                Text(
                    text = "Login",
                    fontWeight = FontWeight.Bold,
                    fontSize = if (isTablet) 26.sp else 22.sp,
                    color = colorResource(id = R.color.black),
                    modifier = Modifier
                        .padding(top = if (isTablet) 60.dp else 50.dp, bottom = sectionPadding)
                        .fillMaxWidth(),
                    textAlign = TextAlign.Center
                )
                OutlinedTextField(
                    modifier = Modifier
                        .padding(bottom = if (isTablet) 48.dp else 40.dp)
                        .fillMaxWidth(),
                    value = userNameFieldValue.value,
                    textStyle = TextStyle(
                        fontSize = if (isTablet) 18.sp else 16.sp,
                        fontWeight = FontWeight.Normal
                    ),
                    singleLine = true,
                    onValueChange = {
                        isLoginError.value = false
                        userNameFieldValue.value = it
                    },
                    keyboardOptions = KeyboardOptions(
                        imeAction = ImeAction.Next
                    ),
                    enabled = true,
                    label = { Text(text = "username") },
                    colors = TextFieldDefaults.outlinedTextFieldColors(
                        focusedBorderColor = colorResource(id = R.color.black),
                        unfocusedBorderColor = colorResource(id = R.color.black),
                        cursorColor = colorResource(id = R.color.black)
                    )
                )
                OutlinedTextField(
                    modifier = Modifier
                        .padding(bottom = if (isTablet) 40.dp else 32.dp)
                        .fillMaxWidth(),
                    value = passwordFieldValue.value,
                    textStyle = TextStyle(
                        fontSize = if (isTablet) 18.sp else 16.sp,
                        fontWeight = FontWeight.Normal
                    ),
                    enabled = true,
                    singleLine = true,
                    onValueChange = {
                        isLoginError.value = false
                        passwordFieldValue.value = it
                    },
                    label = { Text(text = "password") },
                    visualTransformation = if (passwordVisibility.value) {
                        VisualTransformation.None
                    } else {
                        PasswordVisualTransformation()
                    },
                    keyboardOptions = KeyboardOptions(
                        keyboardType = KeyboardType.Password
                    ),
                    colors = TextFieldDefaults.outlinedTextFieldColors(
                        focusedBorderColor = colorResource(id = R.color.black),
                        unfocusedBorderColor = colorResource(id = R.color.black),
                        cursorColor = colorResource(id = R.color.black)
                    )
                )
                Row(
                    Modifier
                        .background(
                            colorResource(
                                id = getLoginButtonColor(
                                    userNameFieldValue,
                                    passwordFieldValue,
                                    isInternetError
                                )
                            ),
                            RoundedCornerShape(5.dp)
                        )
                        .align(Alignment.CenterHorizontally)
                        .fillMaxWidth()
                        .padding(if (isTablet) 18.dp else 15.dp)
                        .clickable(
                            enabled = userNameFieldValue.value.isNotEmpty() && passwordFieldValue.value.isNotEmpty() && !isInternetError.value,
                            onClick = {}
                        ),
                    content = {
                        CircularProgressIndicator(
                            modifier = Modifier
                                .padding(end = 10.dp)
                                .size(if (isTablet) 24.dp else 20.dp)
                                .align(Alignment.CenterVertically),
                            color = colorResource(id = R.color.white),
                            strokeWidth = 3.dp
                        )
                        Text(
                            "Login",
                            fontWeight = FontWeight.Medium,
                            fontSize = if (isTablet) 16.sp else 14.sp,
                            textAlign = TextAlign.Center,
                            color = colorResource(id = getLoginButtonTextColor(userNameFieldValue, passwordFieldValue, isInternetError))
                        )
                    },
                    horizontalArrangement = Arrangement.Center
                )
                Text(
                    text = "Forgot Password",
                    fontWeight = FontWeight.ExtraBold,
                    color = colorResource(id = R.color.black),
                    modifier = Modifier
                        .padding(top = if (isTablet) 36.dp else 28.dp),
                    textAlign = TextAlign.Center
                )
                Text(
                    text = "Not a User ",
                    fontWeight = FontWeight.ExtraBold,
                    color = colorResource(id = R.color.black),
                    modifier = Modifier
                        .padding(top = if (isTablet) 32.dp else 24.dp)
                        .padding(bottom = 16.dp)
                        .clickable { },
                    textAlign = TextAlign.Center
                )
            }
        }
    }
}

fun getLoginButtonColor(
    userNameFieldValue: MutableState<String>,
    passwordFieldValue: MutableState<String>,
    isInternetError: MutableState<Boolean>
): Int {
    return if (!isInternetError.value && userNameFieldValue.value.isNotEmpty() && passwordFieldValue.value.isNotEmpty()) R.color.black else R.color.purple_200
}

fun getLoginButtonTextColor(
    userNameFieldValue: MutableState<String>,
    passwordFieldValue: MutableState<String>,
    isInternetError: MutableState<Boolean>
): Int {
    return if (!isInternetError.value && userNameFieldValue.value.isNotEmpty() && passwordFieldValue.value.isNotEmpty()) R.color.white else R.color.white
}

@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 720)
@Composable
fun previewView() {
    TestLogin()
}

内容的提问来源于stack exchange,提问作者arun

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 19:52:03