在學(xué)習(xí)Flutter的過程中經(jīng)常會(huì)遇到一些小坑,由于目前Flutter還不夠完善,有些問題還是要我們自己想辦法解決的,下面就分享一個(gè)關(guān)于TextField的問題,如有錯(cuò)誤,請(qǐng)指出。
首先先上個(gè)被軟鍵盤遮蔽的問題
TextField彈出系統(tǒng)軟鍵盤時(shí),如果被遮蔽是不會(huì)自動(dòng)上移的。這對(duì)于開發(fā)慣了原生的同學(xué)來說簡(jiǎn)直是不能原諒的,網(wǎng)上有一些很復(fù)雜的自定義插件方案可以完美解決,其實(shí)我們偷懶的話只需要在最外層嵌套一個(gè)ScrollView組件里就可以了,無論是ListView還是SingleChildScrollView,這樣基本解決了被遮蔽的問題,如果還需要控制滑動(dòng)多少則需要對(duì)ScrollView的controller進(jìn)行設(shè)置了。
開胃菜吃完了,現(xiàn)在進(jìn)入正餐
在一些業(yè)務(wù)場(chǎng)景里我們有時(shí)候是不希望彈出系統(tǒng)的軟鍵盤的,比如輸入密碼、搜索等情況,但是Flutter并沒有給我們提供這樣的屬性,那我們要如何做到呢?
首先,我們先觀察一下TextField是怎么彈出鍵盤的。從最直觀的手機(jī)界面看,當(dāng)TextField獲得焦點(diǎn)時(shí)彈出了,失去焦點(diǎn)時(shí)消失了,而且彈出的鍵盤是系統(tǒng)原生的鍵盤。現(xiàn)在我們猜測(cè)一下,他是通過焦點(diǎn)監(jiān)聽來決定系統(tǒng)鍵盤狀態(tài)的,帶著這2個(gè)猜想我們看看TextField的代碼是怎么寫的。
由于源碼比較多,我就直接上圖了

隨便定義一個(gè)TextField組件點(diǎn)進(jìn)去,來到text_field.dart文件中,構(gòu)造函數(shù)中一大串設(shè)置屬性,不急,先看下這個(gè)類有什么繼承,實(shí)現(xiàn)什么的

哦,很好,很簡(jiǎn)單,那么我們可以往下找他的_state類了

繼續(xù)點(diǎn)進(jìn)_TextFieldState類中,一路翻下來,看到了很多私有方法,還有生命周期的重寫,我們先不關(guān)心里面的邏輯,先找到最重要的build方法

嗯,很長(zhǎng),在return里找到最里層的child

點(diǎn)過去

這里他建了一個(gè)EditableText類,有點(diǎn)熟悉的名字了,點(diǎn)過去,來到editable_text.dart文件中

focusNode通常是flutter里處理焦點(diǎn)的,是不是和我們觀察界面時(shí)總結(jié)的焦點(diǎn)監(jiān)聽有點(diǎn)聯(lián)系呢?帶著疑問,我們追蹤一下這個(gè)屬性,最后我們?cè)赺state類里找到了

PS:這里插隊(duì)一條小知識(shí)

在追蹤的過程里發(fā)現(xiàn)了這個(gè),回過去看了一下
class EditableTextState extends State<EditableText> with AutomaticKeepAliveClientMixin<EditableText>, WidgetsBindingObserver, TickerProviderStateMixin<EditableText> implements TextInputClient, TextSelectionDelegate {}
這是mixin了AutomaticKeepAliveClientMixin需要重寫的方法,這樣用來保證輸入框在有焦點(diǎn)時(shí)狀態(tài)不會(huì)被UI刷新時(shí)重置,是不是聯(lián)想到了TabBarView在切換時(shí)又被重置的問題了~方法已經(jīng)教你了,剩下的你懂的
繼續(xù)看焦點(diǎn)的問題,從widget類中拿到了focusNode對(duì)象,進(jìn)行了焦點(diǎn)監(jiān)聽,通過_handleFocusChanged方法實(shí)現(xiàn)

這么接地氣的方法名,大概猜出來了吧,繼續(xù)點(diǎn)...

當(dāng)獲得焦點(diǎn)的時(shí)候open當(dāng)沒焦點(diǎn)時(shí)close,我們是想阻止他在獲得焦點(diǎn)時(shí)不彈鍵盤,所以我們只需要看open這個(gè)方法

上面一堆我們也不知道啥意思,我們也看不懂,但是他最后都會(huì)調(diào)用show方法,是不是show鍵盤呢??我們過去看看
/// An interface for interacting with a text input control.
///
/// See also:
///
/// * [TextInput.attach]
class TextInputConnection {
TextInputConnection._(this._client)
: assert(_client != null),
_id = _nextId++;
static int _nextId = 1;
final int _id;
final TextInputClient _client;
/// Whether this connection is currently interacting with the text input control.
bool get attached => _clientHandler._currentConnection == this;
/// Requests that the text input control become visible.
void show() {
assert(attached);
SystemChannels.textInput.invokeMethod<void>('TextInput.show');
}
/// Requests that the text input control change its internal state to match the given state.
void setEditingState(TextEditingValue value) {
assert(attached);
SystemChannels.textInput.invokeMethod<void>(
'TextInput.setEditingState',
value.toJSON(),
);
}
/// Stop interacting with the text input control.
///
/// After calling this method, the text input control might disappear if no
/// other client attaches to it within this animation frame.
void close() {
if (attached) {
SystemChannels.textInput.invokeMethod<void>('TextInput.clearClient');
_clientHandler
.._currentConnection = null
.._scheduleHide();
}
assert(!attached);
}
}
這是text_input.dart文件里的TextInputConnection類,一共就這么多內(nèi)容,從我們點(diǎn)過來的show方法可以看出他調(diào)用了SystemChannels.textInput里的方法,看到System是不是菊花一緊,這是要和系統(tǒng)打交道了嗎??點(diǎn)過去看看吧

這個(gè)方法在platform_channel.dart文件下,看過原生混合開發(fā)的同學(xué)大概都猜出了,這是在和原生系統(tǒng)進(jìn)行交互了,到這一步我們觀察UI表現(xiàn)時(shí)的2個(gè)猜想都被證實(shí)了。
看到這里已經(jīng)有同學(xué)知道怎么做了,要禁止彈出鍵盤只需要把show方法里的調(diào)用給禁掉就好啦
和java不同的是,flutter的源碼是可以直接修改的,你在源碼文件中隨便敲個(gè)字就會(huì)彈出

選擇一個(gè)你想要的方式就可以直接修改源碼了,我們現(xiàn)在選擇第一個(gè),再把show方法里的實(shí)現(xiàn)屏蔽掉來驗(yàn)證下是不是真的可以不彈出鍵盤
/// Requests that the text input control become visible.
void show() {
assert(attached);
// SystemChannels.textInput.invokeMethod<void>('TextInput.show');
}
測(cè)試發(fā)現(xiàn)真的不彈了!但是我不是想讓所有輸入框都不彈鍵盤啊,我的登陸框怎么辦??難道我要把這么多文件復(fù)制出來自定義一個(gè)輸入框?qū)iT用來不彈??當(dāng)然這是可以的,但是這一點(diǎn)也不優(yōu)雅!
通過上面這么多源碼的分析,我們大概已經(jīng)知道了flutter是怎么控制的了,那么我們可以自己定義一個(gè)屬性給TextField,按著上面的流程一步一步傳遞過去,用來控制最終的show方法,我們想彈就彈,不想彈就禁止,一勞永逸。
最后的調(diào)用代碼:
void show({bool needshow = true}) {
assert(attached);
if(needshow){
SystemChannels.textInput.invokeMethod<void>('TextInput.show');}else {close();}
}
注意需要在else的邏輯里調(diào)用原本類中的close()方法,這樣就算一個(gè)要彈鍵盤,一個(gè)不要彈鍵盤的輸入框在一個(gè)界面也不會(huì)出問題啦