自定義 Compose 的 TextField,實現(xiàn)各種酷炫的文本框效果

前言

在 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

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末厨诸,一起剝皮案震驚了整個濱河市镶殷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌微酬,老刑警劉巖绘趋,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異颗管,居然都是意外死亡陷遮,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門垦江,熙熙樓的掌柜王于貴愁眉苦臉地迎上來帽馋,“玉大人,你說我怎么就攤上這事比吭〔绺” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵梗逮,是天一觀的道長项秉。 經常有香客問我,道長慷彤,這世上最難降的妖魔是什么娄蔼? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮底哗,結果婚禮上岁诉,老公的妹妹穿的比我還像新娘。我一直安慰自己跋选,他們只是感情好涕癣,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著前标,像睡著了一般坠韩。 火紅的嫁衣襯著肌膚如雪距潘。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天只搁,我揣著相機與錄音音比,去河邊找鬼。 笑死氢惋,一個胖子當著我的面吹牛洞翩,可吹牛的內容都是我干的。 我是一名探鬼主播焰望,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼骚亿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了熊赖?” 一聲冷哼從身側響起来屠,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎秫舌,沒想到半個月后的妖,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡足陨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年嫂粟,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片墨缘。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡星虹,死狀恐怖,靈堂內的尸體忽然破棺而出镊讼,到底是詐尸還是另有隱情宽涌,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布蝶棋,位于F島的核電站卸亮,受9級特大地震影響,放射性物質發(fā)生泄漏玩裙。R本人自食惡果不足惜兼贸,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望吃溅。 院中可真熱鬧溶诞,春花似錦、人聲如沸决侈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至枉圃,卻和暖如春功茴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背讯蒲。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工痊土, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留肄扎,地道東北人墨林。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像犯祠,于是被迫代替她去往敵國和親旭等。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

推薦閱讀更多精彩內容