上篇文章,咱們已經完成了通信錄的列表。這篇文章介紹完成通信錄右側的索引條的功能。markdown
以前咱們已經作過了個人頁面的佈局,個人頁面上有一個列表和一個拍照按鈕,和咱們今天要實現的索引條佈局十分相似。個人頁面的佈局以下: 通信錄界面的佈局,和個人頁面的佈局都是使用一個Stack包含列表和其餘子視圖來實現。索引條是緊貼屏幕右側,而後裏面的子視圖是由上至下的。因此天然的會想到使用一個Positioned包含Column來實現。Positioned和Stack的組合咱們以前講過,這兩個組合起來使用,就和咱們iOS的約束佈局相似,能夠設置上左寬高等等。Column就更不用多說,咱們已經使用過不少次了。因此代碼以下圖所示:
網絡
寫到這裏咱們會發現,這個索引條還有不少的功能須要咱們來實現,仍是有點複雜的,若是代碼都寫在friends_page.dart裏會有點冗餘,咱們徹底能夠將這個索引條做爲一個獨立的Widget來實現。新建index_bar.dart文件,代碼以下: 閉包
當沒有觸摸到IndexBar的時候,默認是不展現背景色的,文字也是黑色的。當咱們開始點擊IndexBar的時候,顯示出背景色,而後文字也變成了白色。 實現這個功能,主要是要對GestureDetector
的兩個方法有所瞭解。onVerticalDragDown
方法會在手指觸摸IndexBar的時候就會被調用,onVerticalDragEnd
會在手指鬆開屏幕的時候調用。利用這兩個方法就能夠實現需求。代碼以下: 由於要對文字的顏色進行修改,因此初始化Text的時候,就須要使用變量_textColor;
函數
一樣是對GestureDetector
的一個手勢方法的使用,onVerticalDragUpdate
這個方法的調用時機,在手指移動的時候會不停的調用這個方法。這個方法有一個DragUpdateDetails
參數,它包含了手指所在的座標信息。不過是相對於整個屏幕的座標,能夠將它轉化爲相對於IndexBar的座標,而後經過計算能夠獲得咱們當前選中的是哪一個下標。代碼以下: 方法中的~/是flutter特有的運算符,意思是除後取整。而clamp()是對邊界狀況的處理,意思調用該函數的結果在它的兩個參數之間。佈局
這裏的回調,和OC裏面的block,Swift裏面的閉包都是一個意思。flutter裏面帶有下劃線的變量是私有的,外部沒法訪問的。因此對外暴露的參數,不能寫在_IndexBarState
類裏面,須要寫在IndexBar類裏面。聲明一個閉包(或者叫block)屬性,做爲必傳參數在初始化的時候傳入。 這樣,在
friends_page.dart
文件中初始化IndexBar
的時候,就須要傳入一個閉包。而後IndexBar
內部在onVerticalDragUpdate
的時候,調用這個閉包,就能夠將當前選中的下標回調給外部了。
這個時候,會發現一個小問題,就是點擊IndexBar的時候,回調沒有執行,只有在點擊並手指挪動的時候纔會執行。因此須要在
onVerticalDragDown
方法裏面也調用一次閉包。這時候若是直接將onVerticalDragUpdate
方法裏面的代碼複製到onVerticalDragDown
方法裏面確實沒有問題,可是會明顯的看到重複的代碼太多了。 因此能夠抽取一個方法,將重複的代碼放到一塊。
而後調用的時候就簡單多了。
性能
已經成功的實現了回調,可是從打印的結果來看,會發現一樣一個下標會被回調許屢次。這樣咱們滾動好友列表的時候會形成沒必要要的性能消耗。明明只須要滾動一次,結果卻滾動了無數次到同一個位置。因此這裏咱們須要優化一下,一個很天然的想法就是記錄一個_currentIndexLetter
,每次執行回調的時候,判斷回調的首字母是否和_currentIndexLetter
是否不一樣,若是是同樣的就沒有必要回調了,只有不一樣的時候,才執行回調。 代碼以下:
這樣回調的頻率就正常了。優化
可滾動的widget都有一個controller屬性,用於控制滾動條的行爲。controller屬性是一個ScrollController
對象。可使用它來實現指定滾動到某個位置,實現回到頂部等功能。 滾動好友列表須要一個新的對象ScrollController
實例,將它設置給ListView的controller
屬性,而後就能夠經過使用ScrollController
實例來操做ListView的滾動。
這裏暫時先將滾動的偏移設置爲固定值250,試試看效果。能夠看到當咱們點擊IndexBar的時候,ListView就會滾動到偏移爲250的地方。接下來就是處理滾動的實際偏移值了。spa
滾動的實際偏移,是根據咱們的數據源來計算的。由於咱們的cell的高度是肯定的,不顯示組頭的cell高度是54,有組頭的cell高度是54 + 30 = 84。使用首字母做爲key,計算出對應的偏移爲offset,而後使用Map(相似iOS中字典)記錄下來。因爲第一個是否是字母,而是搜索符號,而它對應的偏移也是固定的0。因此能夠在初始化Map的時候就指定好。而其餘的高度咱們在initState方法中計算。代碼以下: 3d
有了這個Map以後,咱們在IndexBar的回調方法中,就能夠根據IndexBar回調給咱們的首字母獲得對應的偏移值了。代碼以下: code
到這裏,咱們的IndexBar基本上就實現了滾動ListView的功能。可是滾動幾回以後就會發現一個小問題。。。滾動到底部的幾個組頭的時候,會出現ListView先將組頭滾動到指定位置,而後又滾回底部的狀況。緣由很好理解,後面的組頭內容不夠顯示一整個屏幕了。因此咱們這裏須要作下處理。這裏主要是對ListView的滾動的監聽,若是是在iOS中咱們會想去獲取滾動視圖的contentSize而後減去UITableView的高度,就是UITableView的最大的滾動範圍。而在Flutter中,這些都不須要咱們計算了。
若是須要獲取到ListView的一些滾動相關的信息,能夠將它包裹在NotificationListener
裏面,它有一個onNotification
屬性,是一個閉包,能夠回調給咱們一些滾動的相關信息。包含在閉包參數ScrollNotification note
裏面。準確來說滾動相關的信息包含在ScrollNotification
的屬性metrics
裏面。它包含當前滾動偏移值,能滾動的最大範圍(這就是咱們iOS中contentSize的高減去UITableView的高)等等信息。完整代碼以下: 將
_maxScrollExtent
定義爲一個屬性就行了。須要注意的是並不能給初始值爲0,不然沒有滾動ListView以前,使用IndexBar就沒法滾動ListView了。 至此,IndexBar滾動ListView的功能就實現了。
終於來到了最後一步,顯示咱們IndexBar的指示器。首先考慮的就是佈局。最初的IndexBar只有右側的下標一列。如今咱們左側須要一塊容器用來顯示咱們的指示器,因此IndexBar的根視圖應該考慮改成Row。指示器背景的不規則圖形可使用一張圖片展現,圖片已經準備好了。中間的文字,使用Text就夠了。先看下大概佈局代碼:
若是以爲位置不是很合適,能夠修改一下各自的寬度。而後是對指示器的顯示與隱藏作控制,指示器的顯示與隱藏的控制,應該說跟背景色的顯示隱藏是相似的。都是在手勢的那兩個方法裏面實現控制。使用一個bool變量來控制指示器的顯示與隱藏,在手勢的觸摸方法和離開方法裏面操做這個bool變量,而後setState()就能夠實現了指示器的顯示與隱藏了。而後是關於指示器的顯示文本的。這個文本就是咱們的_currentIndexLetter
,直接使用就行了。最後是關於如何控制整個IndexBar的上下位移的。經過對Alignment的使用,發現能夠控制IndexBar的上下位移。經過不斷的修改Alignment的y值會找到一個合適的y值指向第一個放大鏡,那麼-y就指向最後一個字母Z。我這裏試了幾回發現y=-1.13的時候,指示器剛恰好指向第一個放大鏡的位置。那麼如今的問題就是將1.13 * 2 = 2.26分紅_index_words.length - 1
份,而後根據選擇的下標,取得對應的Alignment的y值。當咱們選擇第一個的時候下標爲0,y值應該爲-1.13,當咱們選擇最後一個的時候下標爲_index_words.length - 1
,y值應該爲1.13。根據這些信息就能夠找到計算y值的公式。最終的代碼以下:
新增兩個變量_showIndicator
和_indicatorAlignmentY
。 使用這兩個變量還有
_currentIndexLetter
到這裏,咱們就終於實現了通信錄的IndexBar的封裝。下一節會介紹一些網絡請求了...