前言
在 Compose 中如果我們想要實現(xiàn)文本輸入框的話芬萍,一般都是使用 Material 包中的 TextField
或者 OutlinedTextField
尤揣。
但是因為這兩個組件都是屬于 Material 包中的,自然是需要符合 Material 設計規(guī)范的柬祠,這也就會導致使用他們會喪失很多靈活性北戏。
如果我們想自己實現(xiàn)一些不符合 Material 規(guī)范但是很酷炫的效果,亦或是其他設計風格漫蛔,那繼續(xù)使用 TextField
或者 OutlinedTextField
將會變得非常痛苦嗜愈,甚至沒法實現(xiàn)示罗。
好在,Compose 提供了一個名為 BasicTextField
的組件芝硬,這個組件比上面兩個級別更低(上面兩個位于 androidx.compose.material
包蚜点,而它位于 androidx.compose.foundation.text
包),相比于他們有著極大的靈活性拌阴。其實上述兩個組件都是對 BasicTextField
的封裝绍绘。
下面,我們就以仿寫一個微信的搜索框為例講解如何實現(xiàn)使用 BasicTextField
迟赃。
開始
分析布局
在開始之前我們先分析一下微信的搜索框是什么樣子的陪拘。
這是沒有輸入內容時:
這是輸入內容后:
可以看到,在沒有輸入內容前纤壁,輸入框有一個前導圖標顯示搜索左刽,中間輸入框中有一個淺色的占位字符,最后有一個后置圖標顯示語音輸入酌媒。
而輸入內容后欠痴,占位字符清除,后置圖標更改為清除圖標秒咨。
這么一分析喇辽,好像沒啥難度啊,直接用 OutlinedTextField
完全可以實現(xiàn)嘛雨席。
是嗎菩咨?那我們先嘗試直接用 OutlinedTextField
仿寫一下試試。
直接使用 OutlinedTextField
根據(jù)上面的分析陡厘,無非就是一個 OutlinedTextField
加上前導圖標還有后置圖標抽米,以及占位字符而已嘛,所以我們很容易就能編寫出這樣的代碼:
var inputText by remember { mutableStateOf("") }
OutlinedTextField(
value = inputText,
onValueChange = {
inputText = it
},
leadingIcon = {
Icon(imageVector = Icons.Outlined.Search, contentDescription = null)
},
trailingIcon = {
if (inputText.isNotEmpty()) Icon(imageVector = Icons.Outlined.Close, contentDescription = null)
else Icon(imageVector = Icons.Outlined.Mic, contentDescription = null)
},
placeholder = {
Text(text = "搜索")
},
)
其中后置圖標通過 inputText.isNotEmpty()
判斷輸入內容是否為空糙置,如果為空則顯示麥克風圖標云茸,不為空則顯示清除圖標,運行效果如下:
咋一看好像沒啥問題罢低,仔細一看發(fā)現(xiàn)好像不對勁查辩。
對了胖笛,是輸入框背景顏色不對勁网持,而且微信的輸入框是有圓角的,那就改一下吧长踊。
首先是加上圓角功舀,添加參數(shù):
shape = RoundedCornerShape(8.dp)
然后改一下背景顏色,這里我們通過重新指定一個 colors 顏色配置文件并修改其中的 backgroundColor
字段實現(xiàn)修改背景顏色:
colors = TextFieldDefaults.outlinedTextFieldColors(
backgroundColor = Color.White
)
修改完成身弊,再次運行:
這下好像對味了辟汰?不對列敲!還是不對勁,首先微信的輸入框是沒有邊框的帖汞;其次在微信中即使輸入框拿到焦點邊框也不會變色戴而;另外微信的后置語音圖標是黑色的,不是灰色的翩蘸。
那么我們再改一改所意。
首先是語音輸入圖標顏色,這個沒什么難度催首,使用 tint
參數(shù)重新著色即可:
Icon(imageVector = Icons.Outlined.Mic, contentDescription = null, tint = Color.Black)
接下來是去掉邊框扶踊,這個就不好弄了。
我看了一圈文檔郎任,發(fā)現(xiàn)沒有提供設置邊框尺寸的地方秧耗,又看了一下源碼,果然舶治,邊框尺寸被直接寫死了:
調用 OutlinedTextField
后分井,會調用到 TextFieldImpl
函數(shù),并在其中通過 TextFieldTransitionScope.Transition
獲取到邊框寬度霉猛。
而 TextFieldTransitionScope.Transition
中對邊框寬度的定義如下:
val indicatorWidth by transition.animateDp(
label = "IndicatorWidth",
transitionSpec = { tween(durationMillis = AnimationDuration) }
) {
when (it) {
InputPhase.Focused -> IndicatorFocusedWidth
InputPhase.UnfocusedEmpty -> IndicatorUnfocusedWidth
InputPhase.UnfocusedNotEmpty -> IndicatorUnfocusedWidth
}
}
可以看到這里是定義的一個動畫杂抽,但是不要緊,我們只需要關心動畫完成后最終的寬度值是多少就行韩脏,查看上面兩個個常量值:
private val IndicatorUnfocusedWidth = 1.dp
private val IndicatorFocusedWidth = 2.dp
可以看到缩麸,在持有焦點時的寬度是 2 dp,沒有焦點時是 1 dp赡矢。
不過杭朱,既然無法自己定義邊框寬度,那我們改一下顏色總可以了吧吹散?把邊框顏色改成和背景顏色一樣弧械,約等于沒有邊框嘛。
改邊框顏色依舊是修改 colors
顏色配置信息空民,這里需要把聚焦和失焦時的顏色都改成白色:
colors = TextFieldDefaults.outlinedTextFieldColors(
backgroundColor = Color.White,
focusedBorderColor = Color.White,
unfocusedBorderColor = Color.White
)
最終完整代碼:
var inputText by remember { mutableStateOf("") }
OutlinedTextField(
value = inputText,
onValueChange = {
inputText = it
},
leadingIcon = {
Icon(imageVector = Icons.Outlined.Search, contentDescription = null)
},
trailingIcon = {
if (inputText.isNotEmpty()) Icon(imageVector = Icons.Outlined.Close, contentDescription = null)
else Icon(imageVector = Icons.Outlined.Mic, contentDescription = null, tint = Color.Black)
},
placeholder = {
Text(text = "搜索")
},
shape = RoundedCornerShape(8.dp),
colors = TextFieldDefaults.outlinedTextFieldColors(
backgroundColor = Color.White,
focusedBorderColor = Color.White,
unfocusedBorderColor = Color.White
)
)
現(xiàn)在再來看看效果:
好像差不多了欸刃唐?哈哈,你再仔細看看界轩。
發(fā)現(xiàn)問題了嗎画饥?
沒錯,雖然大體上是像了浊猾,但是顯然文本和圖標相對于輸入框的邊距不對勁啊抖甘。
又是翻了一圈文檔和源碼,并沒有發(fā)現(xiàn)設置邊距的地方葫慎,算了衔彻,太麻煩了薇宠,我們還是使用 BasicTextField
自定義一個吧。
使用 BasicTextField 自定義
BasicTextField
的參數(shù)和 OutlinedTextField
大差不差:
但是它多了一個關鍵參數(shù) decorationBox
艰额,得益于這個參數(shù)澄港,我們可以為所欲為了。
根據(jù)文檔介紹:
decorationBox - Composable lambda that allows to add decorations around text field, such as icon, placeholder, helper messages or similar, and automatically increase the hit target area of the text field. To allow you to control the placement of the inner text field relative to your decorations, the text field implementation will pass in a framework-controlled composable parameter “innerTextField” to the decorationBox lambda you provide. You must call innerTextField exactly once.
簡單來說就是這個參數(shù)是一個作用域為 Composable 且?guī)в袇?shù) innerTextField
的匿名函數(shù)柄沮。
而 innerTextField
也是一個 Composable 的匿名函數(shù)慢睡,并且它就是輸入框的實現(xiàn)函數(shù)。
也就是說铡溪,我們可以在 decorationBox
中通過自定義 innerTextField
的調用位置等方式實現(xiàn)自定義自己需要的文本框的目的漂辐。
需要注意的是徒像,正如上面說的夺英,innerTextField
是輸入框的實現(xiàn)就漾,所以我們必須并且也只能調用一次這個函數(shù)凤覆,不然我們的組件里面就沒有輸入框了领虹。
依舊是實現(xiàn)上述的微信搜索框捆探,我們可以這樣寫:
var inputText by remember { mutableStateOf("") }
BasicTextField(
value = inputText,
onValueChange = {
inputText = it
},
decorationBox = { innerTextField ->
Box {
Surface(
// border = BorderStroke(1.dp, Color.Gray),
shape = RoundedCornerShape(8.dp)
) {
Row(
modifier = Modifier.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(imageVector = Icons.Outlined.Search, contentDescription = null, tint = Color(0x88000000))
Box(modifier = Modifier.padding(start = 4.dp, end = 4.dp)) {
if (inputText.isEmpty()) Text(text = "搜索", color = Color(0x88000000))
innerTextField()
}
if (inputText.isNotEmpty()) Icon(imageVector = Icons.Outlined.Close, contentDescription = null, tint = Color(0x88000000))
else Icon(imageVector = Icons.Outlined.Mic, contentDescription = null, tint = Color(0xFF000000))
}
}
}
}
)
其他地方沒什么好說的揍移,我們來重點分析 decorationBox
的內容只损。
首先滑肉,我們的根組件選擇了 Surface
包各,這是 Material 中的組件之一,官方稱之為 “平面”靶庙,簡單來說就是可以把它包含的內容以統(tǒng)一的樣式配置(例如邊框问畅、陰影、圓角等)放到同一個“平面”內六荒。
因為我們需要給輸入框加上圓角护姆,所以選擇它做根組件,并設置了 8dp 的圓角 shape = RoundedCornerShape(8.dp)
掏击。
因為輸入框的三個主要組件:前置圖標卵皂、輸入框(占位字符)、后置圖標是水平排列的砚亭,所以接下來用了一個 Row
灯变,并設置垂直對齊方式為居中 verticalAlignment = Alignment.CenterVertically
。
然后根據(jù)需求設置前置圖標捅膘,后置圖標添祸,以及配置顏色和邊距等這里就不過多贅述了,重點需要注意占位文本和輸入框(innerTextField()
)的擺放篓跛。
因為占位文本和輸入框實際上應該是屬于同一個位置的膝捞,雖然在輸入框有內容后就不會顯示占位文本了,但是我們依舊需要把他們放到 Box
中愧沟,即堆疊到同一個位置蔬咬,否則將會變成這樣:
沒看出區(qū)別?仔細看光標沐寺,輸入框已經被擠到占位文本之后了林艘。
加上 Box
后效果如下:
這樣看起來是不是對味多了?
總結
我們通過模仿微信搜索框的方式講解了如何使用 BasicTextField
自定義文本輸入框效果混坞。
當然狐援,這里只是拋磚引玉,只是簡單的介紹了使用方法究孕,并沒有做什么酷炫的組件啥酱,但是知道了如何使用 BasicTextField
想要實現(xiàn)什么酷炫的輸入框效果那還不是手到擒來?
作者:equationl
鏈接:https://juejin.cn/post/7154261690379403277