小菜在業務開發過程中會自定義 Slider 滑動條,而在自定義之前,小菜先簡單了解一下 Flutter 自帶的 Slider ;
創新互聯專注為客戶提供全方位的互聯網綜合服務,包含不限于成都網站建設、網站設計、福州網絡推廣、微信小程序、福州網絡營銷、福州企業策劃、福州品牌公關、搜索引擎seo、人物專訪、企業宣傳片、企業代運營等,從售前售中售后,我們都將竭誠為您服務,您的肯定,是我們最大的嘉獎;創新互聯為所有大學生創業者提供福州建站搭建服務,24小時服務熱線:18980820575,官方網址:m.kartarina.com
簡單分析源碼可得, Slider 是一個有狀態的 StatefulWidget 組件,屬性也很清晰易懂,其中滑動過程中對應的 value 值和 onChanged 回調是必須參數;
value 未滑動過程中對應的值,在 min 和 max 之間; onChanged 是在滑動過程中回調,當 onChanged 為 null 或 value 所在的 min 和 max 集合范圍為空時, Slider 禁止滑動;
min 和 max 為滑動條范圍,而 value 的取值范圍是在 min 和 max 之間,無論 value 為正還是負,均需要在 min 和 max 之間;
activeColor 為滑動條已滑動過的顏色; inactiveColor 為滑動條中未滑動的顏色;兩者均可以在 SliderTheme 中設置;
label 為滑動條滑動到某一節點的標簽文案; divisions 是把 min 和 max 等分為 divisions 份數;只有在 divisions 生效時, label 才會展示;
onChangeStart 和 onChangeEnd 分別對應滑動過程中 value 值何時開始更改或何時完成更改時對應的回調;
Slider 的主題效果可以通過 SliderTheme 或 ThemeData.sliderTheme 中獲取更新,相較于 Slider 只提供已滑動和未滑動顏色效果,屬性粒度更細;
activeTrackColor 和 inactiveTrackColor 分別對應 Slider 已滑動過和未滑動過的軌道顏色;
thumbColor 對應滑動按鈕顏色,而 overlayColor 對應滑動按鈕映射的疊層顏色,通常設置為半透明狀態; overlayShape 對應疊層樣式;
valueIndicatorColor 對應 label 氣泡顏色; valueIndicatorShape 對應氣泡內文字屬性; valueIndicatorShape 對應氣泡樣式,可以再此進行自定義氣泡;
activeTickMarkColor 對應已選中刻度顏色; inactiveTickMarkColor 對應未選中刻度顏色; tickMarkShape 對應刻度樣式;
trackHeight 為 Slider 軌道高度; trackShape 對應軌道樣式,主要再此處進行自定義樣式;
對于不可滑動狀態, SliderThemeData 提供了對應屬性;
Slider 案例源碼
小菜本節暫未涉及自定義滑動條樣式,對于底層的 Slider 了解還不夠深入;如有錯誤,請多多指導!
源碼分析:
分析源碼可得,TextField 是有狀態 StatefulWidget,有豐富的屬性,自定義化較高,實踐中需要合理利用各種回調;
1、光標的相關屬性;cursorColor 為光標顏色,cursorWidth 為光標寬度,cursorRadius 為光標圓角;其中 Radius 提供了 circle 圓角和 elliptical 非圓角兩種;
2、textAlign 為文字起始位置,可根據業務光標居左/居右/居中等;注意只是文字開始方向;textDirection 問文字內容方向,從左向右或從右向左;
3、maxLength 為字符長度,設置時默認是展示一行,且右下角有編輯長度與整體長度對比;與 maxLengthEnforced 配合,maxLengthEnforced 為 true 時達到最大字符長度后不可編輯;為 false 時可繼續編輯展示有差別;
4、設置 maxLength 之后右下角默認有字符計數器,設置 TextField.noMaxLength 即可只展示輸入字符數;
5、maxLines 為允許展現的最大行數,在使用 maxLength 時內容超過一行不會自動換行,因為默認 maxLines=1,此時設置為 null 或固定展示行數即可自動換行;區別在于 null 會展示多行,而 maxLines 最多只展示到設置行數;
6、obscureText 是否隱藏編輯內容,常見的密碼格式;
7、enableInteractiveSelection 長按是否出現【剪切/復制/粘貼】菜單;不可為空;
8、keyboardAppearance 為鍵盤亮度,包括 Brightness.dark/light 兩種,但僅限于 iOS 設備;
9、textCapitalization 文字大小寫;理論上 sentences 為每句話第一個字母大寫;characters為每個字母大寫;words 為每個單詞首字母大寫;但該屬性僅限于 text keybord,和尚在本地更換多種方式并未實現,有待研究;
10、keyboardType 為鍵盤類型,和尚理解整體分為數字鍵盤和字母鍵盤等;根據設置的鍵盤類型,鍵盤會有差別;
a. 數字鍵盤
--1-- datetime 鍵盤上可隨時訪問 : 和 /;
--2-- phone 鍵盤上可隨時訪問 # 和 *;
--3-- number 鍵盤上可隨時訪問 + - * /
b. 字母鍵盤
--1-- emailAddress 鍵盤上可隨時訪問 @ 和 .;
--2-- url 鍵盤上可隨時訪問 / 和 .;
--3-- multiline 適用于多行文本換行;
--4-- text 默認字母鍵盤;
11、textInputAction 通常為鍵盤右下角操作類型,類型眾多,建議多多嘗試;
12、autofocus 是否自動獲取焦點,進入頁面優先獲取焦點,并彈出鍵盤,若頁面中有多個 TextField 設置 autofocus 為 true 則優先獲取第一個焦點;
13、focusNode 手動獲取焦點,可配合鍵盤輸入等減少用戶操作次數,直接獲取下一個 TextField 焦點;
14、enabled 設為 false 之后 TextField 為不可編輯狀態;
15、decoration 為邊框修飾,可以借此來調整 TextField 展示效果;可以設置前置圖標,后置圖片,邊框屬性,內容屬性等,會在后續集中嘗試;若要完全刪除裝飾,將 decoration 設置為空即可;
16、inputFormatters 為格式驗證,例如原生 Android 中通常會限制輸入手機號或其他特殊字符,在 Flutter 中也可以借此來進行格式限制,包括正則表達式;使用時需要引入 package:flutter/services.dart;
a. LengthLimitingTextInputFormatter 限制最長字符;
b. WhitelistingTextInputFormatter 僅允許輸入白名單中字符;如 digitsOnly 僅支持數字 [0-9];
c. BlacklistingTextInputFormatter 防止輸入黑名單中字符;如 singleLineFormatter 強制輸入單行;
分析源碼 RegExp("[/]") 可以設置正則表達式;
17、onChanged 文本內容變更時回調,可實時監聽 TextField 輸入內容;
18、controller 文本控制器,監聽輸入內容回調;
19、onTap 點擊 TextField時回調;
20、onEditingComplete 在提交內容時回調,通常是點擊回車按鍵時回調;
21、onSubmit 在提交時回調,不可與 onEditingComplete 同時使用,區別在于 onSubmit 是帶返回值的回調;
問題小結:
當 TextField 設置 enableInteractiveSelection 屬性后長按會出現菜單,默認為英文,可通過設置 Flutter 國際化來處理;
(1)在 pubspec.yaml 中集成 flutter_localizations;
2)在 MaterialApp 中設置本地化代理和支持的語言類型;
(1)將 maxLength 設置為 null 僅使用 LengthLimitingTextInputFormatter 限制最長字符;
(2)設置 InputDecoration 中 decoration 屬性為空;但是底部有空余,只是隱藏而并非消失;
Flutter Dio源碼分析(一)--Dio介紹
Flutter Dio源碼分析(二)--HttpClient、Http、Dio對比
Flutter Dio源碼分析(三)--深度剖析
Flutter Dio源碼分析(四)--封裝
Flutter Dio源碼分析(一)--Dio介紹視頻教程
Flutter Dio源碼分析(二)--HttpClient、Http、Dio對比視頻教程
Flutter Dio源碼分析(三)--深度剖析視頻教程
Flutter Dio源碼分析(四)--封裝視頻教程
github倉庫地址
本文會手把手教你該怎么去封裝一個類庫,平時在我們的工作中都是拿著別人的造好的輪子在使用,這篇文章將帶你怎么去自己造輪子,以后再碰到別的類庫需要對其進行封裝的時候提供一個的思路和方法。
在前面的文章中,我們對 Dio 的基本使用、請求庫對比、源碼分析,我們知道 Dio 的使用非常的簡單,那為什么還需要進行封裝呢?有兩點如下:
當組件庫方法發生重要改變需要遷移的時候如果有多處地方用到,那么需要對使用到的每個文件都進行修改,非常的繁瑣而且很容易出問題。
當不需要 Dio 庫的時候,我們可以隨時方便切換到別的網絡請求庫,當然 Dio 目前內置支持使用第三方庫的適配器。
因為一個應用程序基本都是統一的配置方式,所以我們可以針對 攔截器 、 轉換器 、 緩存 、 統一處理錯誤 、 代理配置 、 證書校驗 等多個配置進行統一管理。
因為我們的應用程序在每個頁面中都會用到網絡請求,那么如果我們每次請求的時候都去實例化一個 Dio ,無非是增加了系統不必要的開銷,而使用單例模式對象一旦創建每次訪問都是同一個對象,不需要再次實例化該類的對象。
這是通過靜態變量的私有構造器來創建的單例模式
我們對 超時時間 、 響應時間 、 BaseUrl 進行統一設置
因為不管是 get() 還是 post() 請求, Dio 內部最終都會調用 request 方法,只是傳入的 method 不一樣,所以我們這里定義一個枚舉類型在一個方法中進行處理
我們已經把 Restful API 風格簡化成了一個方法,通過 DioMethod 來標明不同的請求方式。在我們平時開發的過程中,需要在請求前、響應前、錯誤時對某一些接口做特殊的處理,那我們就需要用到攔截器。 Dio 為我們提供了自定義攔截器功能,很容易輕松的實現對請求、響應、錯誤時進行攔截
我們發現雖然 Dio 框架已經封裝了一個 DioError 類庫,但如果需要對返回的錯誤進行統一彈窗處理或者路由跳轉等就只能自定義了
在我們發送請求的時候會碰到幾種情況,比如需要對非open開頭的接口自動加上一些特定的參數,獲取需要在請求頭增加統一的 token
在我們請求接口前可以對響應數據進行一些基礎的處理,比如對響應的結果進行自定義封裝,還可以針對單獨的 url 做特殊處理等。
我們看了轉換器的介紹,發現和攔截器的功能差不多,那為什么還要存在轉換器,有兩點:
執行流程: 請求攔截器 請求轉換器 發起請求 響應轉換器 響應攔截器 最終結果 。
只會被用于 'PUT'、 'POST'、 'PATCH'方法,因為只有這些方法才可以攜帶請求體(request body)
會被用于所有請求方法的返回數據。
在開發過程中,客戶端和服務器打交道的時候,往往會用一個 token 來做校驗,因為每個公司處理刷新token的邏輯都不一樣,我這里舉一個簡單的例子
為什么我們需要有取消請求的功能,如果當我們的頁面在發送請求時,用戶主動退出當前界面或者app應用程序退出的時候數據還沒有響應,那我們就需要取消該網絡請求,防止不必要的錯誤。
由 服務器生成 的 一小段文本信息 ,發送給瀏覽器,瀏覽器把 cookie 以kv形式保存到本地 某個目錄下的文本文件內,下一次請求同一網站時會把該 cookie 發送給服務器。
cookie 的使用需要用到兩個第三方組件 dio_cookie_manager 和 cookie_jar
因為在我們平時的開發過程中,會碰到一種情況,在進行網絡請求時,我們希望能正常訪問到上次的數據,對于用戶的體驗比較好,而不是展示一個空白的頁面,該緩存主要是 《Flutter實戰》網絡接口緩存 提供參考。
我們在程序退出后內存緩存將會消失,所以我們用 shared_preferences 進行磁盤緩存數據。
在我們用flutter進行抓包的時候需要配置 Dio 代理。由 DefaultHttpClientAdapter 提供了一個 onHttpClientCreate 回調來設置底層 HttpClient 的代理。
用于驗證正在訪問的網站是否真實。提供安全性,因為證書和域名綁定,并且由根證書機構簽名確認。
日志打印主要是幫助我們開發時進行輔助排錯
最近公司做技術分享寫的文章的demo
Flutter中的InheritedWidget狀態管理
1.InheritedWidget是什么?
InheritedWidget是Flutter中非常重要的一個功能型組件,它提供了一種數據在widget樹中從上到下傳遞、共享的方式,比如我們在應用的根widget中通過InheritedWidget共享了一個數據,那么我們便可以在任意子widget中來獲取該共享的數據!這個特性在一些需要在widget樹中共享數據的場景中非常方便!如Flutter SDK中正是通過InheritedWidget來共享應用主題(Theme)和Locale (當前語言環境)信息的。
InheritedWidget和React中的context功能類似,和逐級傳遞數據相比,它們能實現組件跨級傳遞數據。InheritedWidget的在widget樹中數據傳遞方向是從上到下的,這和通知Notification的傳遞方向正好相反。
2.源碼分析
InheritedWidget
先來看下InheritedWidget的源碼:
abstract class?InheritedWidget?extends?ProxyWidget { ??const?InheritedWidget({ Key key,?Widget child }):?super(key: key,?child: child);??@override??InheritedElement?createElement() =InheritedElement(this);??@protected??bool?updateShouldNotify(covariant?InheritedWidget oldWidget);}
它繼承自ProxyWidget:
abstract class?ProxyWidget?extends?Widget { ??const?ProxyWidget({ Key key,?@required?this.child?}) :?super(key: key);??final?Widget?child;}
可以看出Widget內除了實現了createElement方法外沒有其他操作了,它的實現關鍵一定就是InheritedElement了。
InheritedElement 來看下InheritedElement源碼
class?InheritedElement?extends?ProxyElement { ??InheritedElement(InheritedWidget widget) :?super(widget);??@override??InheritedWidget?get?widget?=?super.widget;??// 這個Set記錄了所有依賴的Elementfinal?MapElement,?Object?_dependents?=?HashMapElement,?Object();
//該方法會在Element mount和activate方法中調用,_inheritedWidgets為基類Element中的成員,用于提高Widget查找父節點中的InheritedWidget的效率,它使用HashMap緩存了該節點的父節點中所有相關的InheritedElement,因此查找的時間復雜度為o(1) ??@override??void?_updateInheritance() {final?MapType,?InheritedElement incomingWidgets =?_parent?._inheritedWidgets;if?(incomingWidgets !=?null)??????_inheritedWidgets?=?HashMapType,?InheritedElement.from(incomingWidgets);????else??????_inheritedWidgets?=?HashMapType,?InheritedElement();????_inheritedWidgets[widget.runtimeType] =?this;??}
//該方法在父類ProxyElement中調用,看名字就知道是通知依賴方該進行更新了,這里首先會調用重寫的updateShouldNotify方法是否需要進行更新,然后遍歷_dependents列表并調用didChangeDependencies方法,該方法內會調用mardNeedsBuild,于是在下一幀繪制流程中,對應的Widget就會進行rebuild,界面也就進行了更新 ??@override??void?notifyClients(InheritedWidget oldWidget) {????assert(_debugCheckOwnerBuildTargetExists('notifyClients'));for?(Element dependent?in?_dependents.keys) {??????notifyDependent(oldWidget,?dependent);????}??}
其中_updateInheritance方法在基類Element中的實現如下:
void _updateInheritance() {
_inheritedWidgets = _parent?._inheritedWidgets;
}
總結來說就是Element在mount的過程中,如果不是InheritedElement,就簡單的將緩存指向父節點的緩存,如果是InheritedElement,就創建一個緩存的副本,然后將自身添加到該副本中,這樣做會有兩個值得注意的點:
InheritedElement的父節點們是無法查找到自己的,即InheritedWidget的數據只能由父節點向子節點傳遞,反之不能。
如果某節點的父節點有不止一個同一類型的InheritedWidget,調用inheritFromWidgetOfExactType獲取到的是離自身最近的該類型的InheritedWidget。
看到這里似乎還有一個問題沒有解決,依賴它的Widget是在何時被添加到_dependents這個列表中的呢?
回憶一下從InheritedWidget中取數據的過程,對于InheritedWidget有一個通用的約定就是添加static的of方法,該方法中通過inheritFromWidgetOfExactType找到parent中對應類型的的InheritedWidget的實例并返回,與此同時,也將自己注冊到了依賴列表中,該方法的實現位于Element類,實現如下:
@overrideT?dependOnInheritedWidgetOfExactType
// 這里通過上述mount過程中建立的HashMap緩存找到對應類型的InheritedElement final?InheritedElement ancestor =?_inheritedWidgets?==?null???null?:?_inheritedWidgets[T];if?(ancestor !=?null) {????assert(ancestor?is?InheritedElement);return?dependOnInheritedElement(ancestor,?aspect: aspect);??}??_hadUnsatisfiedDependencies?=?true;??return null;}
@overrideInheritedWidget?dependOnInheritedElement(InheritedElement ancestor,?{ Object aspect }) {??assert(ancestor !=?null);
// 這個列表記錄了當前Element依賴的所有InheritedElement,用于在當前Element deactivate時,將自己從InheritedElement的_dependents列表中移除,避免不必要的更新操作 ??_dependencies???=?HashSetInheritedElement();??_dependencies.add(ancestor);??ancestor.updateDependencies(this,?aspect);return?ancestor.widget;}
3.如何使用InheritedWidget
1)、創建一個類繼承自Inheritedwidget
class?InheritedContext?extends?InheritedWidget{??final?InheritedTestModel?inheritedTestModel;??InheritedContext({????Key key,????@required?this.inheritedTestModel,????@required?Widget child}):?super(key: key,?child: child);static?InheritedContext? of (BuildContext context) {????return?context.dependOnInheritedWidgetOfExactTypeInheritedContext();??}??@override??bool?updateShouldNotify(InheritedContext oldWidget) {????return?inheritedTestModel?!= oldWidget.inheritedTestModel;??}}
2)、InheritedTestModel類為數據容器(這里定義了一個Listint數據源)
class?InheritedTestModel{?final?List?_list;??InheritedTestModel(this._list);??List?getList(){????return?_list;??}}
class?ArrayListData{??static?List? _list ;static?List? getListData (){???? _list? =?new?List();???? _list .add(1);???? _list .add(2);???? _list .add(3);???? _list .add(4);return? _list ;??}}
3)、定義一個Widget?使用?InheritedContext類的數據?InheritedTestModel?
class?ListDemo?extends?StatefulWidget{??@override??State?createState() {????return new?ListDemoState();??}}class?ListDemoState?extends?StateListDemo{List?_list;??InheritedTestModel?_inheritedTestModel;??Timer?_timer;??Duration?oneSec?=?const?Duration(seconds:?1);??@override??void?initState() {????_list?= ArrayListData. getListData ();????_inheritedTestModel?=?new?InheritedTestModel(_list);????_timer?=?Timer.periodic(oneSec,?(timer) {??????_doTimer();????});??}??void?_doTimer() {????for(int i =?0;?i ?_list.length;?i++){??????_list[i] =?_list[i]+?1;????}??setState(() {????_inheritedTestModel?=?new?InheritedTestModel(_list);??});??}Widget?_buildBody() {????return?Container(child:?ListDemo2(),????);??}??@override??Widget?build(BuildContext context) {????return?InheritedContext(inheritedTestModel:?_inheritedTestModel,??????child:?Scaffold(appBar:?AppBar(title:?Text("ListDemo"),????????actions: Widget[????????????IconButton(icon:?Icon(Icons. add ),????????????)????????],),????????body: _buildBody(),??????),????);??}??@override??void?dispose() {????super.dispose();if?(_timer?!=?null) {??????_timer.cancel();????}??}}
4)、在ListDemo中通過Timer更新InheritedTestModel?中的數據,然后再下一個Widget中獲取更新的數據作為展示
class?ListDemo2?extends?StatefulWidget{??@override??State?createState() {????return new?ListDemoState2();??}}class?ListDemoState2?extends?StateListDemo2{InheritedTestModel?_inheritedTestModel;??Widget?_buildListItem(BuildContext context,int index) {????return ?Container(height:?50,????????width:?100,????????alignment: Alignment. center ,????????child:?Text(_inheritedTestModel.getList()[index].toString()),????);??}Widget?_buildBody() {????_inheritedTestModel?= InheritedContext. of (context).inheritedTestModel;return?Container(child:?ListView.builder(itemBuilder:(context,?index)=_buildListItem(context,index),itemCount:?_inheritedTestModel.getList().length,),????);??}??@override??Widget?build(BuildContext context) {????return ?_buildBody();??}}
這樣就可以在父widget中更新數據,子View不需任何操作直接從數據容器InheritedTestModel?中獲取到更新后的新數據
這是一個數據共享的簡單的例子,基本的流程,大致就是A去更新B的數據,A和B有一個共同的父類,實現數據的共享
4.上面說了原理和基本的使用,但是在實際項目當中,我當然不建議這樣來使用,Google?已經為我們封裝好了功能更加強大的插件Provider,其內部原理就是基于InheritedWidget來實現的,我們理解了基本原理,可以更好的在項目中運用Provider
最近一個項目要實現可以無限循環的PageView,主要思路是在初始化pageview的list的時候在開始和結尾多加一個結尾和開頭的widget,當滑動到開頭和結尾的時候手動進行頁面的切換,詳細可以搜索pageview無限輪播。
這種方法有一個要點就是要維護兩個索引,一個是內部list的索引,一個是外部顯示的索引,由于list的容量是比顯示的數量多2的,所以如果要在外部進行一些比如指示器或者計時器功能要進行和頁面同步顯示或者切換頁面操作時,需要將顯示的索引轉換成list的索引。
不過網上說的都是一些比較簡單的實現,看到比較多的就是當滑動到要手動切換的時候進行一個時延,這樣可以避免直接切換頁面造成的卡頓和跳動現象。但是存在一個問題,如果要同時實現一個跟隨頁面切換的指示器,就會出現當頁面切換過去之后指示器才會跟著過去,因為頁面切換的時候執行了時延,而時延之后才會真正改變索引,此時才會setstate,之后指示器才能響應到索引的切換,但是如果在時延之前就切換的話又會出現指示器先行的情況。因此這種方法其實是存在一些問題的。
所以解決這個問題的關鍵在于如何進行頁面切換的判斷。這里可以有兩種思路實現,第一種是實現viewpage的onpagechanged方法,在里面進行邏輯的判斷,然后用controller來進行頁面跳轉,不過這種方法存在當controller跳轉的時候又會回調onpagechanged,所以就會出現多次對索引不必要操作,而且如果有比如計時器等額外的功能的話可能不方便將頁面邏輯分開,而且依舊無法解決指示器延遲問題,同時也很難進行細粒度的操作。
第二種方法我們就要去看pageview的源碼了,從源碼的角度來解決問題才是正確的方法。首先我們點進去pageview的源碼
看到這里其實已經有一些思路了,我們之前難點在于重寫了onpagechanged方法導致問題無法很好的解決,現在我們找到了onpagechanged調用的地方,只要找辦法避免掉就可以實現了。
當然這里我們要說到NotificationListener,以及flutter對應的冒泡事件傳輸機制,這里大家可以去看看這篇 文章 。
我來總結一下,其實就是flutter對于notification這個組件,有一中事件規則叫冒泡傳遞,底層的notification如果在它的 onNotification寫的邏輯中返回是false以及它不是根結點,就會去向上遍歷尋找它的祖先notification組件,知道遇到root節點或者某一個返回true,則事件傳遞結束。
而且在onNotification中可以對多種事件進行監聽和處理,所以我們可以把對viewpage頁面跳轉對索引處理的邏輯寫在這里,而且我們可以分別處理比如滑動開始的start事件和結束的end事件,分別進行細粒度的邏輯的處理,這樣就可以在外部進行操作和別的功能實現了。
因此不僅無限輪播事件可以通過這種方法來解決,如果有其他的操作也可以這樣進行處理,而且因為我們沒有傳入onpagechanged方法,所以不存在多次調用的問題,pageview那里判斷onpagechanged是null方法就不會進去了,會直接我們寫在pageview外面的notification的邏輯。
最后的結構大概這樣
網站標題:flutter源碼分析,flutter源碼下載
網址分享:http://m.kartarina.com/article42/dsesjec.html
成都網站建設公司_創新互聯,為您提供網站設計公司、品牌網站制作、電子商務、Google、網站內鏈、手機網站建設
聲明:本網站發布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創新互聯