Jetpack Compose 作为 Android 的新一代 UI 开发结构,提供了十分强壮的东西来构建用户界面。
今天,咱们就运用 Compose 来完结一个炫酷的验证码输入框!
开端的思路是用6个TextField
来完结
// 用于存储验证码的长度
val codeLength = 6
// 界说一个变量,用于存储验证码的值
val code = remember { mutableStateOf(TextFieldValue("\t\t\t\t\t\t")) }
// 界说一个变量,用于存储输入框的焦点恳求器列表
val focusRequesters = remember { List(codeLength) { FocusRequester() } }
// 界说一个函数,用于处理输入框的文本改变事情
fun onTextChanged(text: String, index: Int) {
// 更新验证码的值,将指定方位的字符替换为输入的文本
code.value = code.value.copy(
text = code.value.text.replaceRange(index, index + 1, text)
)
// 假如输入的文本不为空,而且不是最后一个输入框,那么恳求下一个输入框获取焦点
if (text.isNotEmpty() && index < codeLength - 1) {
focusRequesters[index + 1].requestFocus()
}else{
//删去返回上一个
val mIndex = if (index == 0) 0 else index - 1
focusRequesters[mIndex].requestFocus()
code.value.copy(selection = TextRange(1))
}
}
后来发现主动切换焦点处理逻辑 不高雅 不
用1个TextField
来完结才符合我的风格
后来突发奇想运用BasicTextField
的decorationBox
试试
decorationBox的效果
Jetpack Compose
中的 BasicTextField
有一个 decorationBox
特点,它的效果是:能够运用自界说组件去装修 BasicTextField
。
这样咱们就能够:
- 自界说输入框的背景色、边框等款式。
- 添加前缀或后缀图标。
- 在输入框输入或获取焦点时具有过渡效果。
- 完结各种自界说输入框效果,比方咱们完结的验证码输入框。
用简略易懂的话来说
decorationBox
特点给了咱们大量定制BasicTextField
款式和效果的自由度,咱们能够依据界面需求构建出各式各样的输入框组件。
相比之下 OutlinedTextField
和 TextField
等组件的定制空间就较小。所以,假如您要完结高度定制的输入框效果,BasicTextField
是一个很好的选择。
//省略亿些小细节
decorationBox = {
Row(){
for (i in 0 until 6) {
Text("9")
}
}
发现
太棒了,单个TextField
完结省去处理焦点的逻辑。
封装成简略易用的组件
@Composable
fun BaseVerificationCodeTextField(
modifier: Modifier = Modifier,
isError: Boolean = false,
codeLength :Int = 6,
fontSize : TextUnit = 21.sp,
onVerify: (String) -> Unit = {},
codeBox: @Composable RowScope.(codeLength: Int, index :Int ,code :String) -> Unit
) {
- 是否显现错误状况
isError
-
codeLength
操控验证码的个数,这里默认是 6 个。 -
fontSize
操控输入框内文本的巨细 -
onVerify
是一个回调函数,在完结所有输入框的输入时会被调用,参数是一个 String 表明终究输入的验证码。 -
codeBox
: 一个 composable 函数,用于自界说每个验证码框的款式。 它有三个参数:
- codeLength 验证码的个数,
- index: 当时验证码框的索引,从 0 开端。
- code:验证码
//存储文本输入的值
var text by remember { mutableStateOf("") }
//管理当时取得焦点的文本框
val focusManager = LocalFocusManager.current
//用于恳求焦点以显现软键盘
val focusRequester = remember { FocusRequester() }
//它操控软键盘的显现和躲藏。
val keyboardController = LocalSoftwareKeyboardController.current
//获取焦点
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
完结软键盘主动弹起
BasicTextField(
modifier = modifier
.focusRequester(focusRequester)//监听焦点
.onFocusChanged {
if (it.isFocused) {
//获取焦点后主动弹起软键盘
keyboardController?.show()
}
})
decorationBox
Row(
Modifier
.background(Color.Transparent)
.padding(horizontal = 12.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(6.dp)//能够操控子组件之间的距离
) {
for (i in 0 until codeLength) {
codeBox(i,text)
}
onValueChange
文本框内容改变的回调函数 onValueChange
咱们要 检查 输入的内容 的长度是否 <= codeLength(验证码的个数),且是否全部由数字组成。假如不满足,不做任何处理。
onValueChange = { newText ->
// 限制最大长度为6且只能输入数字
if (newText.length <= codeLength && newText.all { it.isDigit() }) {
text = newText
if (newText.length == codeLength) {
//输入完结后主动提交而且躲藏软件盘
onVerify(newText)
focusManager.clearFocus()
}
}
},
仿百度验证码输入框
运用刚刚封装好的 BaseVerificationCodeTextField
BaseVerificationCodeTextField(
onVerify = {
//输入完结的回调
}
) { codeLength, indxe, code ->
//开端大显神通
}
三种状况
能够发现验证码框框有三种状况
- 表明验证码现已彻底输入
- 表明验证码正在输入中,还未完结。
- 表明验证码没有开端输入。
private enum class CodeState {
ENTERED,//表明验证码现已彻底输入
INPUTTING,//表明验证码正在输入中,还未完结。
PENDING,//表明验证码没有开端输入。
}
isHasCode
变量,表明当时索引的验证码位是否现已被输入。
计算方法是索引 index
小于 code
的长度,即用户输入的验证码字符串的长度。
val isHasCode = indxe < code.length
val fontSize = (144 / codeLength).sp
val codeState = when {
isHasCode -> CodeState.ENTERED
//假如 index 等于 code 长度,即用户输入到当时位
(indxe == code.length) -> CodeState.INPUTTING
else -> CodeState.PENDING
}
依据这三种状况定制各种效果
颜色
val cardColor = when (codeState) {
CodeState.ENTERED -> Color(0xFF7593ff) //蓝色
CodeState.INPUTTING -> Color.White //白色
CodeState.PENDING -> Color(0xFFF5F5F5) //灰色
}
暗影高度
val elevation = when (codeState) {
CodeState.ENTERED -> 3.dp
CodeState.INPUTTING -> 6.dp
CodeState.PENDING -> 0.dp
}
Card(
Modifier
.size((baseSize / codeLength).dp),
colors = CardDefaults.cardColors(
containerColor = cardColor
),
elevation = CardDefaults.c(
defaultElevation = elevation,
),
) {
elevation 不收效
Card
的elevation
不收效 但是Card
的colors
收效了
我猜应该也许可能发生了这种状况:
- Card 设置了 elevation 和 colors, Compose 效果于这两个特点。
- 接着某个状况改变导致 Card 需求从头履行。
- Compose 首先会铲除 Card 现有的 elevation 和 colors 效果。
- 接着 Compose 又效果于咱们设置的新特点,应用新的 elevation 和 colors。
- 但此时,Card 的背景色已被铲除,所以新的 elevation 设置就不会收效了。
- 终究,只要 colors 新设置的背景色收效了
Jetpack Compose 中的 Key 的效果
仅有标识 Compose
树中某个节点。
当 Compose
树某个节点的 Key
发生改变时,Compose
会将原节点与新节点进行比较,决定是否需求从头履行该节点。
简略来说,Key
的首要效果是提高 Compose
树的履行效率。通过 Key
,Compose
能够准确判别哪些节点发生了改变,只需从头履行改变的节点,而保存那些 Key
未改变的节点。
正在输入时 _ 闪耀
val blinkInterval = 1000L //1秒闪耀一次
var isVisible by remember { mutableStateOf(true) }
LaunchedEffect(blinkInterval) {
while (true) {
isVisible = !isVisible
delay(blinkInterval)
}
}
CodeState.INPUTTING -> {
if (isVisible) {
Text(
"_", style = TextStyle(
fontSize = fontSize,
color = textColor,
textAlign = TextAlign.Center
)
)
}
}
效果图
完好代码
BaseVerificationCodeTextField.kt
@ExperimentalComposeUiApi
@Composable
fun BaseVerificationCodeTextField(
modifier: Modifier = Modifier,
codeLength: Int = 6,
onVerify: (String) -> Unit = {},
codeBox: @Composable RowScope.(codeLength: Int, index: Int, code: String) -> Unit
) {
//存储文本输入的值
var text by remember { mutableStateOf("") }
//管理当时取得焦点的文本框
val focusManager = LocalFocusManager.current
//用于恳求焦点以显现软键盘
val focusRequester = remember { FocusRequester() }
//它操控软键盘的显现和躲藏。
val keyboardController = LocalSoftwareKeyboardController.current
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
BasicTextField(
value = text,
singleLine = true,
onValueChange = { newText ->
// 限制最大长度为6且只能输入数字
if (newText.length <= codeLength && newText.all { it.isDigit() }) {
text = newText
if (newText.length == codeLength) {
onVerify(newText)
focusManager.clearFocus()
}
}
},
keyboardOptions = KeyboardOptions(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done
),
modifier = modifier
.padding(horizontal = 26.dp)
.fillMaxWidth()
.focusRequester(focusRequester)
.onFocusChanged {
if (it.isFocused) {
keyboardController?.show()
}
}
.wrapContentHeight(),
readOnly = false,
decorationBox = {
Row(
Modifier
.fillMaxWidth()
.background(Color.Transparent),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceAround
) {
for (i in 0 until codeLength) {
codeBox(codeLength, i, text)
}
}
}
)
}
VerificationCodeTextField.kt
private enum class CodeState {
ENTERED,//表明验证码现已彻底输入
INPUTTING,//表明验证码正在输入中,还未完结。
PENDING,//表明验证码没有开端输入。
}
@ExperimentalComposeUiApi
@Composable
fun VerificationCodeTextField(
modifier: Modifier = Modifier,
onVerify: (String) -> Unit = {}
) {
val baseSize = 276
BaseVerificationCodeTextField(
onVerify = onVerify
) { codeLength, indxe, code ->
// 判别当时方位是否有字符
val isHasCode = indxe < code.length
val fontSize = (144 / codeLength).sp
val codeState = when {
isHasCode -> CodeState.ENTERED
(indxe == code.length) -> CodeState.INPUTTING
else -> CodeState.PENDING
}
val cardColor = when (codeState) {
CodeState.ENTERED -> Color(0xFF466Eff)
CodeState.INPUTTING -> Color.White
CodeState.PENDING -> Color(0xFFF5F5F5)
}
val elevation = when (codeState) {
CodeState.ENTERED -> 3.dp
CodeState.INPUTTING -> 6.dp
CodeState.PENDING -> 0.dp
}
val textColor = when (codeState) {
CodeState.ENTERED -> Color.White
CodeState.INPUTTING -> Color.Gray
CodeState.PENDING -> Color.Gray
}
val blinkInterval = 1000L
var isVisible by remember { mutableStateOf(true) }
LaunchedEffect(blinkInterval) {
while (true) {
isVisible = !isVisible
delay(blinkInterval)
}
}
key(elevation) {
Card(
Modifier
.size((baseSize / codeLength).dp),
colors = CardDefaults.cardColors(
containerColor = cardColor
),
elevation = CardDefaults.cardElevation(
defaultElevation = elevation,
),
) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
when (codeState) {
CodeState.ENTERED -> {
Text(
code[indxe].toString(), style = TextStyle(
fontSize = fontSize,
color = textColor,
textAlign = TextAlign.Center
)
)
}
CodeState.INPUTTING -> {
if (isVisible) {
Text(
"_", style = TextStyle(
fontSize = fontSize,
color = textColor,
textAlign = TextAlign.Center
)
)
}
}
CodeState.PENDING -> {
// Text(
// "_", style = TextStyle(
// fontSize = fontSize,
// color = textColor,
// textAlign = TextAlign.Center
// )
// )
}
}
}
}
}
}
}
运用方法
Column(Modifier.background(WordsFairyTheme.colors.whiteBackground)) {
Spacer(modifier = Modifier.height(100.dp))
VerificationCodeTextField(Modifier){
ToastModel("验证码:$it").showToast()//
}
}
ToastModel 这篇文章有描述
Jetpack Compose完结的一个高雅的 Toast