重慶分公司,新征程啟航
為企業(yè)提供網(wǎng)站建設(shè)、域名注冊(cè)、服務(wù)器等服務(wù)
為企業(yè)提供網(wǎng)站建設(shè)、域名注冊(cè)、服務(wù)器等服務(wù)
原因:在flutter中,鍵盤彈起時(shí)系統(tǒng)會(huì)縮小Scaffold的高度并重建
創(chuàng)新互聯(lián)公司主要從事成都做網(wǎng)站、網(wǎng)站設(shè)計(jì)、網(wǎng)頁設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)泰山,10年網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):18982081108
1)把Scaffold的resizeToAvoidBottomInset屬性設(shè)置為false,這樣在鍵盤彈出時(shí)將不會(huì)resize
2)把寫死的高度改為 原高度 - MediaQuery.of(context).viewInsets.bottom ,鍵盤彈出時(shí)布局將重建,而這個(gè) MediaQuery.of(context).viewInsets.bottom 變量在鍵盤彈出前是0,鍵盤彈起后的就是鍵盤的高度
將輸入框放進(jìn)可滾動(dòng)的Widget中即可,當(dāng)輸入框獲取焦點(diǎn)后,系統(tǒng)會(huì)自動(dòng)將它滑動(dòng)到可視區(qū)域
講道理我起的好長的名字啊,不過文如上題,搜索到這里的兄弟應(yīng)該都知道我說的是啥情況,正好
~~
我這個(gè)方案可能有點(diǎn)笨拙TT,不過自測有效,有其它想法的老哥希望可以幫忙指點(diǎn)一下~
下面進(jìn)入正題
點(diǎn)進(jìn)源碼里面看,可以發(fā)現(xiàn)他直接繼承了StatelessWidget,那我們就直接看看build方法
可以看到,這里直接返回一個(gè)scrollable或者一個(gè)子節(jié)點(diǎn)是scrollable的InheritedWidget
scrollable是一個(gè)StatefulWidget,那我們就看看它的state
首先scrollable持有一個(gè)scrollposition對(duì)象,是通過其scrollcontroller構(gòu)建的
在其state的setCanDrag方法中,對(duì)其拖動(dòng)設(shè)置了一系列的監(jiān)聽
這里就可以看出來,當(dāng)拖動(dòng)觸發(fā)時(shí),就會(huì)通過當(dāng)前scrollable的position生成一個(gè)Drag/Hold對(duì)象,并調(diào)用相應(yīng)的方法 這個(gè)position有幾個(gè)子類,我們先隨便看一個(gè)實(shí)現(xiàn)
可以看到生成了一個(gè)ScrollDragController對(duì)象,當(dāng)手勢(shì)拖動(dòng)而調(diào)用這個(gè)對(duì)象的update方法時(shí)
可以看到直接調(diào)用其委托對(duì)象的applyUserOffset方法進(jìn)行偏移,而這個(gè)委托對(duì)象根據(jù)剛才的drag方法可以得知正是我們scrollable中的position
最后,由position通知其scrollcontext,也就是之前的scrollable進(jìn)行滑動(dòng)
具體的滑動(dòng)流程這里就不細(xì)說了,我們只是要知道這個(gè)事件是怎么傳遞的就好了,有興趣的老哥可以自行分析
NestedScrollView是一個(gè)statefulwidget,那我們就先看看它的build方法
先忽略其他奇奇怪怪的方法,我們發(fā)現(xiàn)在我們body的外面,包裹了一層PrimaryScrollController,同時(shí)它還持有innerController,這個(gè)innerController暫時(shí)先不管它是啥
還記不記得在最開始ScrollView的build方法中,生成Scrollable的時(shí)候,我們已經(jīng)見過這個(gè)PrimaryScrollController了,再回顧一下
再看看PrimaryScrollController.of(context)
可以看到,在生成scrollable的時(shí)候,在primary = true的情況下是會(huì)向上查找的,看看有沒有PrimaryScrollController,如果有的話,scrollable使用的controller實(shí)際就是nestedscrollview中的innerController了
而之前看過了,scrollable中的position就是scrollcontroller來生成的,那么在這種情況下:
實(shí)際上是生成了_NestedScrollPosition并返回給了body中的scrollable
構(gòu)造方法中有一個(gè)參數(shù)coordinator 暫時(shí)先不管
好了,下面我們?cè)诨仡^看剛才NestedScrollView的build方法,實(shí)際上是生成了一個(gè)_NestedScrollViewCustomScrollView,繼承自大名鼎鼎的CustomScrollView,它當(dāng)然也是scrollview啦,而我們傳給它的controller也是一個(gè)_NestedScrollController,不過叫做_outerController,和body中的不是同一個(gè)罷了,那么自然這個(gè)父scrollview的position也是_NestedScrollPosition。
下面我們按照之前的邏輯,當(dāng)拖動(dòng)開始時(shí),就會(huì)調(diào)用position.drag方法
可以看到,實(shí)際上吧方法交給了我們之前多次見到的coordinator來完成,那我們就簡單看一下吧
這里可以看到,他把返回的ScrollDragController的委托者設(shè)成了自己
那么自然在拖動(dòng)的時(shí)候,調(diào)用的就是coordinator的applyUseroffset方法了 我們分析一下
可以看到,在需要子列表滾動(dòng)時(shí),是對(duì)innerPositions中的所有position調(diào)用滑動(dòng)方法的
而這innerPositions中的position是怎么來的呢?跟蹤一下可以發(fā)現(xiàn)是在調(diào)用NestedScrollController的attach時(shí)添加進(jìn)來的,如下
因?yàn)橹拔覀兛吹竭^,子scrollable中的controller就是這個(gè)NestedScrollController,所以在updateopsition時(shí)會(huì)把舊的detach調(diào),把新生成的position attach進(jìn)來
另外,在dispose中也會(huì)detach
由此我們就知道啦,因?yàn)殚_啟了緩存后就不會(huì)調(diào)用劃出屏幕的頁面的dispose,自然所有子scrollable的position都存在nestedScrollController里面了,當(dāng)發(fā)生滑動(dòng)時(shí),遍歷調(diào)用positions數(shù)組,就導(dǎo)致屏幕外的列表也跟著滑動(dòng)了~
既然開啟了緩存,手動(dòng)dispose肯定是沒啥意義的,實(shí)際上我們只要在頁面切換過后把未顯示的position 給detach掉就好了。
然鵝,因?yàn)閒lutter不支持反射,子布局傳遞的position我們拿不到,nestedScrollController我們也不能直接拿到=。=
不過有一個(gè)對(duì)象我們之前見到過,scrollable就是通過他獲取controller的,而position則是傳給了獲取到的controller 就是PrimaryScrollController了,所以我打算在中間第三者插足,對(duì)傳遞Position的PrimaryScrollController進(jìn)行Hook
在使用的時(shí)候把子列表添加進(jìn)去,并設(shè)置對(duì)應(yīng)的GlobalKey。
然后監(jiān)聽Tab切換
以上是我的方案,有問題的話還希望老哥幫忙指正,也希望有其他思路的老哥指點(diǎn)一下~~
上一下Github項(xiàng)目地址 用Flutter寫的WanAndroid 其中用到了這個(gè)方案
= =
3
在Tree中從上往下高效傳遞數(shù)據(jù)的基類widget , 定義為:abstract class InheritedWidget extends ProxyWidget
Flutter的響應(yīng)式開發(fā)與React類似,數(shù)據(jù)都是自頂向下的。
假設(shè)有祖先組點(diǎn)A,中間經(jīng)過結(jié)點(diǎn)B, C,然后到結(jié)點(diǎn)D,D需要從A中獲取數(shù)據(jù)f,那按照自頂向下數(shù)據(jù)流轉(zhuǎn),f需要依次傳遞給B及C,最后才到C。這樣開發(fā)極為不靈活,成本也比較高。所有Flutter需要有跨結(jié)點(diǎn)(只能是祖先后代節(jié)點(diǎn),不能跨兄弟節(jié)點(diǎn))高效傳遞數(shù)據(jù)的方案。
大體意思如下:
InheritedWidget 是在樹中高效向下傳遞信息的基類部件;
調(diào)用[BuildContext.inheritFromWidgetOfExactType]方法可以從 BuildContext 中獲取到最近的 InheritedWidget 類型的實(shí)例;
在 InheritedWidget 類型的控件被引用,也就是調(diào)用過 inheritFromWidgetOfExactType 方法后,當(dāng) InheritedWidget 自身狀態(tài)改變時(shí),會(huì)導(dǎo)致引用了 InheritedWidget 類型的子控件重構(gòu)(rebuild)。
這里隨便定義一個(gè)人 Person 類。
創(chuàng)建一個(gè)類繼承 InheritedWidget,并實(shí)現(xiàn) updateShouldNotify 方法。
之前說到調(diào)用[BuildContext.inheritFromWidgetOfExactType]方法可以從 BuildContext 中獲取到最近的 InheritedWidget 類型的實(shí)例,所以此處定義一個(gè)靜態(tài)的 of 方法,通過傳入的 context 獲取到最近的 InheriedDataWidget 實(shí)例。
1.定義數(shù)據(jù)模型
這里隨便定義一個(gè) Person 類。
2.自定義 InheritedWidget 控件類
創(chuàng)建一個(gè)類繼承 InheritedWidget,并實(shí)現(xiàn) updateShouldNotify 方法。
之前說到調(diào)用[BuildContext.inheritFromWidgetOfExactType]方法可以從 BuildContext 中獲取到最近的 InheritedWidget 類型的實(shí)例,所以此處定義一個(gè)靜態(tài)的 of 方法,通過傳入的 context 獲取到最近的 InheriedDataWidget 實(shí)例。
3.InheriedDataWidget 的使用
InheriedDataWidget 使用起來也很簡單,它本身也是一個(gè)控件,只要在任意一個(gè)頁面的子控件調(diào)用其構(gòu)造方法就行,這里我們定義一個(gè)形如的 Widget 樹。
WidgetA 是一個(gè) StatefulWidget 類型的控件,可以調(diào)用 setState 刷新,如果是繼承人 Stateless 類型的控件,那我們也可以通過 Stream 或者其他方式刷新數(shù)據(jù),感興趣的請(qǐng)看[什么是 Stream? Dart
WidgetA1_1 類
WidgetA1_2 類
WidgetA1_3 類
當(dāng)我們點(diǎn)擊 floatingActionButton 的時(shí)候,WidgetA1, WidgetA1_1, WidgetA1_2 的控件都會(huì)更新 Person 的信息,而且每點(diǎn) floatingActionButton 一次, 當(dāng)我們點(diǎn)擊 floatingActionButton 的時(shí)候,WidgetA1, WidgetA1_1, WidgetA1_2 的控件都會(huì)更新 Person 的信息,而且每點(diǎn) floatingActionButton 一次,都會(huì)輸出:
如果我們?cè)噲D在和 WidgetA 的同一層級(jí)的兄弟節(jié)點(diǎn)去訪問 InheriedDataWidget 的 Person 數(shù)據(jù),是不行的,因?yàn)楦腹?jié)點(diǎn)中并沒有插入 InheriedDataWidget。
把 WidgetB 和 WidgetA 保持同一節(jié)點(diǎn)
這也體現(xiàn)了 Inheried(遺傳) 這一單詞的特性,遺傳只存在于父子。兄弟不存在遺傳的關(guān)系。
這種數(shù)據(jù)共享的方式在某些場景還是很有用的,就比如說全局主題,字體大小,字體顏色的變更,只要在 App 根層級(jí)共享出這些配置數(shù)據(jù),然后在觸發(fā)數(shù)據(jù)改變之后,所有引用到這些共享數(shù)據(jù)的地方都會(huì)刷新,這換主題,字體是不是就很輕松,事實(shí)上 Theme.of(context).primaryColor 之流就是這么干的。
以上就是有關(guān)InheritedWidget的使用。
自己也是從事Android開發(fā)5年有余了;整理了一些Android開發(fā)技術(shù)核心筆記和面經(jīng)題綱,有關(guān)更多Android開發(fā)進(jìn)階技術(shù)資料、面經(jīng)題綱、核心技術(shù)筆記; 想要進(jìn)階自己、拿高薪的同學(xué)請(qǐng)私信我回復(fù)“核心筆記”或“面試”領(lǐng)取!
狀態(tài)可變的 widget 。
通過其類的定義能夠看到 StatefulWidget 配置 StatefulElement 。
State 是 StatefulWidget 的內(nèi)部邏輯與狀態(tài),由 StatefulWidget 的 createState 創(chuàng)建。
StatefulWidget 實(shí)例本身是不可變的, 但是 StatefulWidget 將其可變的狀態(tài),存儲(chǔ)在與之關(guān)聯(lián)的 State 對(duì)象中。
不管什么時(shí)候,只要在樹中 mount 一個(gè)新的 StatefulElement ,必然需要注入一個(gè) StatefulWidget ,注入一個(gè) StatefulWidget 時(shí), framework 都會(huì)調(diào)用一次 createState 方法。
其實(shí),在 StatefulElement 構(gòu)造的時(shí)候,就會(huì)調(diào)用 createState ,創(chuàng)建 _state 對(duì)象,( _state 是 StatefulElement 的變量)并且在 StatefulElement 的初始化方法中為 _state 關(guān)聯(lián)當(dāng)前的 StatefulElement 和用以配置 StatefulElement 的 StatefulWidget 。
StatefulElement 初始化方法如下:
這意味著如果 StatefulWidget 被插入到樹中的多個(gè)位置,則會(huì)有多個(gè) State 對(duì)象分別與它們關(guān)聯(lián)。
關(guān)于此類的定義如下:
描述: 重寫此方法以執(zhí)行初始化。
場景: 如果 State 的 build 方法依賴于本身可以改變狀態(tài)的對(duì)象時(shí)。(例如 ChangeNotifier 或 Stream ,或者可以訂閱并接收通知的其他對(duì)象)正確的方式是:
注意點(diǎn): 此方法中不能使用 BuildContext.dependOnInheritedWidgetOfExactType 。但是此方法被調(diào)用后會(huì)立即調(diào)用 didChangeDependencies ,在 didChangeDependencies 可以使用 BuildContext.dependOnInheritedWidgetOfExactType 。
調(diào)用時(shí)機(jī): StatefulElement ,首次插入樹中時(shí)會(huì)調(diào)用此方法,在 build 方法調(diào)用之前調(diào)用。
描述: StatefulElement 通過此方法返回的 widget 并通過調(diào)用 updateChild 來更新自己。
調(diào)用時(shí)機(jī): framework 調(diào)用此方法的幾個(gè)不同的場景如下:
描述: StatefulElement 存在,并且符合 Widget.canUpdate 的情況下對(duì) StatefulWidget 進(jìn)行更新。
調(diào)用時(shí)機(jī): 不論何時(shí)只要 StatefulElement 的配置 widget 改變的時(shí)候就會(huì)調(diào)用。
注意: didUpdateWidget 方法最終會(huì)調(diào)用 build 方法,因此在此方法中調(diào)用 setState 是多余的。如果重寫此方法,請(qǐng)確保調(diào)用 super.didUpdateWidget(oldWidget) 。
調(diào)用時(shí)機(jī): 當(dāng)此 State 對(duì)象的依賴項(xiàng)( InheritedWidget )更改時(shí)調(diào)用。
描述: 用于開發(fā)階段 hot reload 。
調(diào)用時(shí)機(jī): hot reload 時(shí)調(diào)用,調(diào)用后 build 方法也將被調(diào)用。無需在此方法中做任何操作。
調(diào)用時(shí)機(jī): 當(dāng) StatefulElement 從樹中移除的時(shí)候會(huì)調(diào)用。
調(diào)用時(shí)機(jī): 當(dāng) StatefulElement 從樹中 unmount 的時(shí)候會(huì)調(diào)用。
StatefulWidget 用以配置 StatefulElement ,但在這兩者之間的 State 承接了 StatefulElement 的生命周期,而 StatefulWidget 僅僅只是連接了 State 與 StatefulElement 的不可變的實(shí)例,因此 StatefulWidget 的生命周期,依賴于 StatefulElement ,而 State 卻是其最簡單直接的體現(xiàn)形式。
為了能更好的理解 StatefulWidget 的生命周期,我畫了一張關(guān)于 State 、 StatefulElement 、 Component 、 Element 的關(guān)系圖。