3atv精品不卡视频,97人人超碰国产精品最新,中文字幕av一区二区三区人妻少妇,久久久精品波多野结衣,日韩一区二区三区精品

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Flutter技术与实战(4)

發布時間:2024/1/8 编程问答 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Flutter技术与实战(4) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Flutter基礎

文章目錄

    • Flutter基礎
        • Widget,構建Flutter界面的基石。
          • Widget渲染過程
            • Widget
            • Element
            • RenderObject
          • RenderObjectWidget 介紹
          • 案例展示
        • Widget中的State到底是什么
          • UI編程范式
          • StateLessWidget
          • StatefulWidget
          • StatefulWidget 不是萬金油,要慎用
        • 生命周期
          • State生命周期
            • 創建
            • 更新
            • 銷毀
          • App生命周期
            • 生命周期回調
            • 幀繪制回調
        • 經典控件(一):文本、圖片和按鈕
          • 文本控件
          • 圖片
          • 按鈕
        • 經典控件(二):ListView與CustomScrollView
          • ListView
          • CustomScrollView
          • ScrollController與ScrollNotification
            • ScrollController
            • ScrollNotification
          • 問題
        • 經典布局:如何定義子控件在父容器中的排版位置
          • 單子Widget布局:Container、Padding與Center
          • 多子Widget布局:Row、Column與Expanded
          • 層疊Widget布局:Stack與Positioned
        • 組合與自繪,何種方式定義Widget
          • 組裝
          • 自繪
        • 從夜間模式說起,定制不同的App主題
          • 主題定制
            • 全局統一的視覺風格定制
            • 局部獨立的視覺風格定制
            • 分平臺主題定制
        • 依賴管理(一):圖片、配置和字體
          • 資源管理
          • 原生平臺的資源設置
            • 更換App圖標
            • 更換啟動圖
        • 依賴管理(二):第三方組件庫在FLutter如何管理
          • Pub
          • 舉例
          • 問題
        • 用戶交互事件如何響應
          • 指針事件
          • 手勢識別
          • 手勢競技場實現
        • 跨組件傳遞數據
          • InheritedWidget
          • Notification
          • EventBus
        • 路由與導航實現頁面切換
          • 路由管理
            • 基本路由
            • 命名路由
            • 頁面參數
          • 補充

Widget,構建Flutter界面的基石。

  • Widget 是 Flutter 功能的抽象描述,是視圖的配置信息,同樣也是數據的映射,是 Flutter 開發框架中最基本的概念。前端框架中常見的名詞,比如視圖(View)、視圖控制器(View Controller)、活動(Activity)、應用(Application)、布局(Layout)等,在 Flutter 中都是 Widget。
Widget渲染過程
  • 通常情況下,不同的 UI 框架中會以不同的方式去處理這一問題,但無一例外地都會用到視圖樹(View Tree)的概念。而 Flutter 將視圖樹的概念進行了擴展,把視圖數據的組織和渲染抽象為三部分,即 Widget,Element 和 RenderObject。

Widget
  • Widget 是 Flutter 世界里對視圖的一種結構化描述,你可以把它看作是前端中的“控件”或“組件”。Widget 是控件實現的基本邏輯單位,里面存儲的是有關視圖渲染的配置信息,包括布局、渲染屬性、事件響應信息等。
  • Flutter 將 Widget 設計成不可變的,所以當視圖渲染的配置信息發生變化時,Flutter 會選擇重建 Widget 樹的方式進行數據更新,以數據驅動 UI 構建的方式簡單高效。
  • 但,這樣做的缺點是,因為涉及到大量對象的銷毀和重建,所以會對垃圾回收造成壓力。不過,Widget 本身并不涉及實際渲染位圖,所以它只是一份輕量級的數據結構,重建的成本很低。
  • 另外,由于 Widget 的不可變性,可以以較低成本進行渲染節點復用,因此在一個真實的渲染樹中可能存在不同的 Widget 對應同一個渲染節點的情況,這無疑又降低了重建 UI 的成本。
Element
  • Element 是 Widget 的一個實例化對象,它承載了視圖構建的上下文數據,是連接結構化的配置信息到完成最終渲染的橋梁。
  • Flutter 渲染過程,可以分為這么三步:
    • 首先,通過 Widget 樹生成對應的 Element 樹;
    • 然后,創建相應的 RenderObject 并關聯到 Element.renderObject 屬性上;
    • 最后,構建成 RenderObject 樹,以完成最終的渲染。
  • Element 同時持有 Widget 和 RenderObject。而無論是 Widget 還是 Element,其實都不負責最后的渲染,只負責發號施令,真正去干活兒的只有 RenderObject。
  • 增加中間的這層 Element 樹,不直接由 Widget 命令 RenderObject,這樣做會極大地減少渲染帶來的性能損耗。
  • Element 樹存在的意義。因為 Widget 具有不可變性,但 Element 卻是可變的。實際上,Element 樹這一層將 Widget 樹的變化(類似 React 虛擬 DOM diff)做了抽象,可以只將真正需要修改的部分同步到真實的 RenderObject 樹中,最大程度降低對真實渲染視圖的修改,提高渲染效率,而不是銷毀整個渲染視圖樹重建。
RenderObject
  • RenderObject 是主要負責實現視圖渲染的對象。
  • Flutter 通過控件樹(Widget 樹)中的每個控件(Widget)創建不同類型的渲染對象,組成渲染對象樹。
  • 而渲染對象樹在 Flutter 的展示過程分為四個階段,即布局、繪制、合成和渲染。 其中,布局和繪制在 RenderObject 中完成,Flutter 采用深度優先機制遍歷渲染對象樹,確定樹中各個對象的位置和尺寸,并把它們繪制到不同的圖層上。繪制完畢后,合成和渲染的工作則交給 Skia 搞定。
  • Flutter 通過引入 Widget、Element 與 RenderObject 這三個概念,把原本從視圖數據到視圖渲染的復雜構建過程拆分得更簡單、直接,在易于集中治理的同時,保證了較高的渲染效率。
RenderObjectWidget 介紹
  • StatelessWidget 和 StatefulWidget 只是用來組裝控件的容器,并不負責組件最后的布局和繪制。在 Flutter 中,布局和繪制工作實際上是在 Widget 的另一個子類 RenderObjectWidget 內完成的。
//RenderObjectWidget 的源碼 abstract class RenderObjectWidget extends Widget {@overrideRenderObjectElement createElement();@protectedRenderObject createRenderObject(BuildContext context);@protectedvoid updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }... }
  • 實際上,RenderObjectWidget 本身并不負責這些對象的創建與更新。
  • 對于 Element 的創建,Flutter 會在遍歷 Widget 樹時,調用 createElement 去同步 Widget 自身配置,從而生成對應節點的 Element 對象。而對于 RenderObject 的創建與更新,其實是在 RenderObjectElement 類中完成的。
abstract class RenderObjectElement extends Element {RenderObject _renderObject;@overridevoid mount(Element parent, dynamic newSlot) {super.mount(parent, newSlot);_renderObject = widget.createRenderObject(this);attachRenderObject(newSlot);_dirty = false;}@overridevoid update(covariant RenderObjectWidget newWidget) {super.update(newWidget);widget.updateRenderObject(this, renderObject);_dirty = false;}... }
  • 在 Element 創建完畢后,Flutter 會調用 Element 的 mount 方法。在這個方法里,會完成與之關聯的 RenderObject 對象的創建,以及與渲染樹的插入工作,插入到渲染樹后的 Element 就可以顯示到屏幕中了。
  • 如果 Widget 的配置數據發生了改變,那么持有該 Widget 的 Element 節點也會被標記為 dirty。在下一個周期的繪制時,Flutter 就會觸發 Element 樹的更新,并使用最新的 Widget 數據更新自身以及關聯的 RenderObject 對象,接下來便會進入 Layout 和 Paint 的流程。而真正的繪制和布局過程,則完全交由 RenderObject 完成。
abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {...void layout(Constraints constraints, { bool parentUsesSize = false }) {...}void paint(PaintingContext context, Offset offset) { } }
  • 布局和繪制完成后,接下來的事情就交給 Skia 了。在 VSync 信號同步時直接從渲染樹合成 Bitmap,然后提交給 GPU。
案例展示
  • 在 Flutter 遍歷完 Widget 樹,創建了各個子 Widget 對應的 Element 的同時,也創建了與之關聯的、負責實際布局和繪制的 RenderObject。

Widget中的State到底是什么

  • StatefulWidget 應對有交互、需要動態變化視覺效果的場景,而 StatelessWidget 則用于處理靜態的、無狀態的視圖展示。
UI編程范式
  • 要想理解 StatelessWidget 與 StatefulWidget 的使用場景,我們首先需要了解,在 Flutter 中,如何調整一個控件(Widget)的展示樣式,即 UI 編程范式。
  • 對于Android、IOS或原生JavaScript開發者來說,視圖開發是命令式的,需要精確地告訴操作系統或瀏覽器用何種方式去做事情。比如,如果我們想要變更界面的某個文案,則需要找到具體的文本控件并調用它的控件方法命令,才能完成文字變更。
// Android設置某文本控件展示文案為Hello World TextView textView = (TextView) findViewById(R.id.txt); textView.setText("Hello World");// iOS設置某文本控件展示文案為Hello World UILabel *label = (UILabel *)[self.view viewWithTag:1234]; label.text = @"Hello World";// 原生JavaScript設置某文本控件展示文案為Hello World document.querySelector("#demo").innerHTML = "Hello World!";
  • Flutter 的視圖開發是聲明式的,其核心設計思想就是將視圖和數據分離,這與 React 的設計思路完全一致。
  • 總結來說,命令式編程強調精確控制過程細節;而聲明式編程強調通過意圖輸出結果整體。
  • 對應到 Flutter 中,意圖是綁定了組件狀態的 State,結果則是重新渲染后的組件。在 Widget 的生命周期內,應用到 State 中的任何更改都將強制 Widget 重新構建。
  • 當你所要構建的用戶界面不隨任何狀態信息的變化而變化時,需要選擇使用 StatelessWidget,反之則選用 StatefulWidget。前者一般用于靜態內容的展示,而后者則用于存在交互反饋的內容呈現中。
StateLessWidget
  • 在 Flutter 中,Widget 采用由父到子、自頂向下的方式進行構建,父 Widget 控制著子 Widget 的顯示樣式,其樣式配置由父 Widget 在構建時提供。

  • 用這種方式構建出的 Widget,有些(比如 Text、Container、Row、Column 等)在創建時,除了這些配置參數之外不依賴于任何其他信息,換句話說,它們一旦創建成功就不再關心、也不響應任何數據變化進行重繪。在 Flutter 中,這樣的 Widget 被稱為 StatelessWidget(無狀態組件)。

  • 以 Text 的部分源碼為例,說明 StatelessWidget 的構建過程。

class Text extends StatelessWidget { //構造方法及屬性聲明部分const Text(this.data, {Key key,this.textAlign,this.textDirection,//其他參數...}) : assert(data != null),textSpan = null,super(key: key);final String data;final TextAlign textAlign;final TextDirection textDirection;//其他屬性...@overrideWidget build(BuildContext context) {...Widget result = RichText(//初始化配置...));...return result;} }
  • 什么場景下應該使用 StatelessWidget ?父 Widget 是否能通過初始化參數完全控制其 UI 展示效果?如果能,那么我們就可以使用 StatelessWidget 來設計構造函數接口了。
StatefulWidget
  • 與 StatelessWidget 相對應的,有一些 Widget(比如 Image、Checkbox)的展示,除了父 Widget 初始化時傳入的靜態配置之外,還需要處理用戶的交互(比如,用戶點擊按鈕)或其內部數據的變化(比如,網絡數據回包),并體現在 UI 上。
  • 換句話說,這些 Widget 創建完成后,還需要關心和響應數據變化來進行重繪。在 Flutter 中,這一類 Widget 被稱為 StatefulWidget(有狀態組件)。

  • StatefulWidget 是以 State 類代理 Widget 構建的設計方式實現的。接下來,以 Image 的部分源碼為例,說明 StatefulWidget 的構建過程。
class Image extends StatefulWidget {//構造方法及屬性聲明部分const Image({Key key,@required this.image,//其他參數}) : assert(image != null),super(key: key);final ImageProvider image;//其他屬性...@override_ImageState createState() => _ImageState();... }class _ImageState extends State<Image> {ImageInfo _imageInfo;//其他屬性...void _handleImageChanged(ImageInfo imageInfo, bool synchronousCall) {setState(() {_imageInfo = imageInfo;});}...@overrideWidget build(BuildContext context) {final RawImage image = RawImage(image: _imageInfo?.image,//其他初始化配置...);return image;}... }
  • Image 以一種動態的方式運行:監聽變化,更新視圖。與 StatelessWidget 通過父 Widget 完全控制 UI 展示不同,StatefulWidget 的父 Widget 僅定義了它的初始化狀態,而其自身視圖運行的狀態則需要自己處理,并根據處理情況即時更新 UI 展示。
StatefulWidget 不是萬金油,要慎用
  • 對于 UI 框架而言,同樣的展示效果一般可以通過多種控件實現。從定義來看,StatefulWidget 仿佛是萬能的,替代 StatelessWidget 看起來合情合理。于是 StatefulWidget 的濫用,也容易因此變得順理成章,難以避免。但事實是,StatefulWidget 的濫用會直接影響 Flutter 應用的渲染性能。

Widget 是不可變的,更新則意味著銷毀 + 重建(build)。StatelessWidget 是靜態的,一旦創建則無需更新;而對于 StatefulWidget 來說,在 State 類中調用 setState 方法更新數據,會觸發視圖的銷毀和重建,也將間接地觸發其每個子 Widget 的銷毀和重建。

  • 如果我們的根布局是一個 StatefulWidget,在其 State 中每調用一次更新 UI,都將是一整個頁面所有 Widget 的銷毀和重建。
  • 正確評估你的視圖展示需求,避免無謂的 StatefulWidget 使用,是提高 Flutter 應用渲染性能最簡單也是最直接的手段。

需要注意的是,除了我們主動地通過 State 刷新 UI 之外,在一些特殊場景下,Widget 的 build 方法有可能會執行多次。因此,我們不應該在這個方法內部,放置太多有耗時的操作。

反思:build執行多次,通過接口獲取表單數據,不要在build里寫耗時方法,外部處理傳入一個變量即可!

生命周期

  • 從 Widget(的 State)和 App 這兩個維度,介紹它們的生命周期。
State生命周期
  • State 的生命周期,指的是在用戶參與的情況下,其關聯的 Widget 所經歷的,從創建到顯示再到更新最后到停止,直至銷毀等各個過程階段。
  • 這些不同的階段涉及到特定的任務處理,因此為了寫出一個體驗和性能良好的控件,正確理解 State 的生命周期至關重要。
  • State 的生命周期可以分為 3 個階段:創建(插入視圖樹)、更新(在視圖樹中存在)、銷毀(從視圖樹中移除)。接下來,我們一起看看每一個階段的具體流程。
創建
  • State 初始化時會依次執行 :構造方法 -> initState -> didChangeDependencies -> build,隨后完成頁面渲染??匆幌鲁跏蓟^程中每個方法的意義。
    • 構造方法是 State 生命周期的起點,Flutter 會通過調用 StatefulWidget.createState() 來創建一個 State。我們可以通過構造方法,來接收父 Widget 傳遞的初始化 UI 配置數據。這些配置數據,決定了 Widget 最初的呈現效果。
    • initState,會在 State 對象被插入視圖樹的時候調用。這個函數在 State 的生命周期中只會被調用一次,所以我們可以在這里做一些初始化工作,比如為狀態變量設定默認值。
    • didChangeDependencies 則用來專門處理 State 對象依賴關系變化,會在 initState() 調用結束后,被 Flutter 調用。
    • build,作用是構建視圖。經過以上步驟,Framework 認為 State 已經準備好了,于是調用 build。我們需要在這個函數中,根據父 Widget 傳遞過來的初始化配置數據,以及 State 的當前狀態,創建一個 Widget 然后返回。
更新
  • Widget 的狀態更新,主要由 3 個方法觸發:setState、didchangeDependencies 與 didUpdateWidget。這三個方法分別會在什么場景下調用。
    • setState:我們最熟悉的方法之一。當狀態數據發生變化時,我們總是通過調用這個方法告訴 Flutter:“我這兒的數據變啦,請使用更新后的數據重建 UI!”
    • didChangeDependencies:State 對象的依賴關系發生變化后,Flutter 會回調這個方法,隨后觸發組件構建。哪些情況下 State 對象的依賴關系會發生變化呢?典型的場景是,系統語言 Locale 或應用主題改變時,系統會通知 State 執行 didChangeDependencies 回調方法。
    • didUpdateWidget:當 Widget 的配置發生變化時,比如,父 Widget 觸發重建(即父 Widget 的狀態發生變化時),熱重載時,系統會調用這個函數。
  • 一旦這三個方法被調用,Flutter 隨后就會銷毀老 Widget,并調用 build 方法重建 Widget。
銷毀
  • 組件銷毀相對比較簡單。比如組件被移除,或是頁面銷毀的時候,系統會調用 deactivate 和 dispose 這兩個方法,來移除或銷毀組件。

    • 當組件的可見狀態發生變化時,deactivate 函數會被調用,這時 State 會被暫時從視圖樹中移除。值得注意的是,頁面切換時,由于 State 對象在視圖樹中的位置發生了變化,需要先暫時移除后再重新添加,重新觸發組件構建,因此這個函數也會被調用。
    • 當 State 被永久地從視圖樹中移除時,Flutter 會調用 dispose 函數。而一旦到這個階段,組件就要被銷毀了,所以我們可以在這里進行最終的資源釋放、移除監聽、清理環境,等等。

  • 左邊部分展示了當父 Widget 狀態發生變化時,父子雙方共同的生命周期;而中間和右邊部分則描述了頁面切換時,兩個關聯的 Widget 的生命周期函數是如何響應的。State生命周期中的方法調用對比如圖。

App生命周期
  • 視圖的生命周期,定義了視圖的加載到構建的全過程,其回調機制能夠確保我們可以根據視圖的狀態選擇合適的時機做恰當的事情。而 App 的生命周期,則定義了 App 從啟動到退出的全過程。
  • 在原生 Android、iOS 開發中,有時我們需要在對應的 App 生命周期事件中做相應處理,比如 App 從后臺進入前臺、從前臺退到后臺,或是在 UI 繪制完成后做一些處理。在 Flutter 中,我們可以利用 WidgetsBindingObserver 類,來實現同樣的需求。
abstract class WidgetsBindingObserver {//頁面popFuture<bool> didPopRoute() => Future<bool>.value(false);//頁面pushFuture<bool> didPushRoute(String route) => Future<bool>.value(false);//系統窗口相關改變回調,如旋轉void didChangeMetrics() { }//文本縮放系數變化void didChangeTextScaleFactor() { }//系統亮度變化void didChangePlatformBrightness() { }//本地化語言變化void didChangeLocales(List<Locale> locale) { }//App生命周期變化void didChangeAppLifecycleState(AppLifecycleState state) { }//內存警告回調void didHaveMemoryPressure() { }//Accessibility相關特性回調void didChangeAccessibilityFeatures() {} }
  • App 生命周期的回調 didChangeAppLifecycleState,和幀繪制回調 addPostFrameCallback 與 addPersistentFrameCallback。
生命周期回調
  • didChangeAppLifecycleState 回調函數中,有一個參數類型為 AppLifecycleState 的枚舉類,這個枚舉類是 Flutter 對 App 生命周期狀態的封裝。它的常用狀態包括 resumed、inactive、paused 這三個。

    • resumed:可見的,并能響應用戶的輸入。
    • inactive:處在不活動狀態,無法處理用戶響應。
    • paused:不可見并不能響應用戶的輸入,但是在后臺繼續活動中。
    class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver{ ...@override@mustCallSupervoid initState() {super.initState();WidgetsBinding.instance.addObserver(this);//注冊監聽器}@override@mustCallSupervoid dispose(){super.dispose();WidgetsBinding.instance.removeObserver(this);//移除監聽器}@overridevoid didChangeAppLifecycleState(AppLifecycleState state) async {print("$state");if (state == AppLifecycleState.resumed) {//do sth}} }
  • 試著切換一下前、后臺,觀察控制臺輸出的 App 狀態,可以發現:

    • 從后臺切入前臺,控制臺打印的 App 生命周期變化如下: AppLifecycleState.paused->AppLifecycleState.inactive->AppLifecycleState.resumed;
    • 從前臺退回后臺,控制臺打印的 App 生命周期變化則變成了:AppLifecycleState.resumed->AppLifecycleState.inactive->AppLifecycleState.paused。
  • 可以看到,App 前后臺切換過程中打印出的狀態是完全符合預期的。

幀繪制回調
  • 除了需要監聽 App 的生命周期回調做相應的處理之外,有時候我們還需要在組件渲染之后做一些與顯示安全相關的操作。在 Android 開發中,我們可以通過 View.post() 插入消息隊列,來保證在組件渲染后進行相關操作。

  • 在 Flutter 中實現同樣的需求會更簡單:依然使用萬能的 WidgetsBinding 來實現。

  • WidgetsBinding 提供了單次 Frame 繪制回調,以及實時 Frame 繪制回調兩種機制,來分別滿足不同的需求。

    • 單次 Frame 繪制回調,通過 addPostFrameCallback 實現。它會在當前 Frame 繪制完成后進行進行回調,并且只會回調一次,如果要再次監聽則需要再設置一次。
    WidgetsBinding.instance.addPostFrameCallback((_){print("單次Frame繪制回調");//只回調一次});
    • 實時 Frame 繪制回調,則通過 addPersistentFrameCallback 實現。這個函數會在每次繪制 Frame 結束后進行回調,可以用做 FPS 監測。
    WidgetsBinding.instance.addPersistentFrameCallback((_){print("實時Frame繪制回調");//每幀都回調 });

經典控件(一):文本、圖片和按鈕

文本控件
  • 文本是視圖系統中的常見控件,用來顯示一段特定樣式的字符串,就比如 Android 里的 TextView、iOS 中的 UILabel。而在 Flutter 中,文本展示是通過 Text 控件實現的。
  • Text 支持兩種類型的文本展示,一個是默認的展示單一樣式的文本 Text,另一個是支持多種混合樣式的富文本 Text.rich。
  • 單一樣式文本 Text 的初始化,是要傳入需要展示的字符串。而這個字符串的具體展示效果,受構造函數中的其他參數控制。這些參數大致可以分為兩類:
    • 控制整體文本布局的參數,如文本對齊方式 textAlign、文本排版方向 textDirection,文本顯示最大行數 maxLines、文本截斷規則 overflow 等等,這些都是構造函數中的參數;
    • 控制文本展示樣式的參數,如字體名稱 fontFamily、字體大小 fontSize、文本顏色 color、文本陰影 shadows 等等,這些參數被統一封裝到了構造函數中的參數 style 中。
Text('文本是視圖系統中的常見控件,用來顯示一段特定樣式的字符串,就比如Android里的TextView,或是iOS中的UILabel。',textAlign: TextAlign.center,//居中顯示style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20, color: Colors.red),//20號紅色粗體展示 );
  • 混合展示樣式與單一樣式的關鍵區別在于分片,即如何把一段字符串分為幾個片段來管理,給每個片段單獨設置樣式。面對這樣的需求,在 Android 中,我們使用 SpannableString 來實現;在 iOS 中,我們使用 NSAttributedString 來實現;而在 Flutter 中也有類似的概念,即 TextSpan。
  • TextSpan 定義了一個字符串片段該如何控制其展示樣式,而將這些有著獨立展示樣式的字符串組裝在一起,則可以支持混合樣式的富文本展示。
TextStyle blackStyle = TextStyle(fontWeight: FontWeight.normal, fontSize: 20, color: Colors.black); //黑色樣式TextStyle redStyle = TextStyle(fontWeight: FontWeight.bold, fontSize: 20, color: Colors.red); //紅色樣式Text.rich(TextSpan(children: <TextSpan>[TextSpan(text:'文本是視圖系統中常見的控件,它用來顯示一段特定樣式的字符串,類似', style: redStyle), //第1個片段,紅色樣式 TextSpan(text:'Android', style: blackStyle), //第1個片段,黑色樣式 TextSpan(text:'中的', style:redStyle), //第1個片段,紅色樣式 TextSpan(text:'TextView', style: blackStyle) //第1個片段,黑色樣式 ]),textAlign: TextAlign.center, );
圖片
  • 使用 Image,可以讓我們向用戶展示一張圖片。圖片的顯示方式有很多,比如資源圖片、網絡圖片、文件圖片等,圖片格式也各不相同,因此在 Flutter 中也有多種方式,用來加載不同形式、支持不同格式的圖片。
    • 加載本地資源圖片,如 Image.asset(‘images/logo.png’);
    • 加載本地(File 文件)圖片,如 Image.file(new File(’/storage/xxx/xxx/test.jpg’));
    • 加載網絡圖片,如 Image.network(‘http://xxx/xxx/test.gif’) 。
  • 除了可以根據圖片的顯示方式設置不同的圖片源之外,圖片的構造方法還提供了填充模式 fit、拉伸模式 centerSlice、重復模式 repeat 等屬性,可以針對圖片與目標區域的寬高比差異制定排版模式。
  • Flutter 中的 FadeInImage 控件。在加載網絡圖片的時候,為了提升用戶的等待體驗,我們往往會加入占位圖、加載動畫等元素,但是默認的 Image.network 構造方法并不支持這些高級功能,這時候 FadeInImage 控件就派上用場了。
  • FadeInImage 控件提供了圖片占位的功能,并且支持在圖片加載完成時淡入淡出的視覺效果。此外,由于 Image 支持 gif 格式,我們甚至還可以將一些炫酷的加載動畫作為占位圖。
FadeInImage.assetNetwork(placeholder: 'assets/loading.gif', //gif占位image: 'https://xxx/xxx/xxx.jpg',fit: BoxFit.cover, //圖片拉伸模式width: 200,height: 200, )
  • Image 控件需要根據圖片資源異步加載的情況,決定自身的顯示效果,因此是一個 StatefulWidget。圖片加載過程由 ImageProvider 觸發,而 ImageProvider 表示異步獲取圖片數據的操作,可以從資源、文件和網絡等不同的渠道獲取圖片。
  • 首先,ImageProvider 根據 _ImageState 中傳遞的圖片配置生成對應的圖片緩存 key;然后,去 ImageCache 中查找是否有對應的圖片緩存,如果有,則通知 _ImageState 刷新 UI;如果沒有,則啟動 ImageStream 開始異步加載,加載完畢后,更新緩存;最后,通知 _ImageState 刷新 UI。
  • ImageCache 使用 LRU(Least Recently Used,最近最少使用)算法進行緩存更新策略,并且默認最多存儲 1000 張圖片,最大緩存限制為 100MB,當限定的空間已存滿數據時,把最久沒有被訪問到的圖片清除。圖片緩存只會在運行期間生效,也就是只緩存在內存中。如果想要支持緩存到文件系統,可以使用第三方的CachedNetworkImage控件。

  • CachedNetworkImage 的使用方法與 Image 類似,除了支持圖片緩存外,還提供了比 FadeInImage 更為強大的加載過程占位與加載錯誤占位,可以支持比用圖片占位更靈活的自定義控件占位。

CachedNetworkImage(imageUrl: "http://xxx/xxx/jpg",placeholder: (context, url) => CircularProgressIndicator(),//錯誤圖兜底,以備圖片加載出錯errorWidget: (context, url, error) => Icon(Icons.error),)
按鈕
  • 通過按鈕,我們可以響應用戶的交互事件。Flutter 提供了三個基本的按鈕控件,即 FloatingActionButton、FlatButton 和 RaisedButton。
    • FloatingActionButton:一個圓形的按鈕,一般出現在屏幕內容的前面,用來處理界面中最常用、最基礎的用戶動作。
    • RaisedButton:凸起的按鈕,默認帶有灰色背景,被點擊后灰色背景會加深。
    • FlatButton:扁平化的按鈕,默認透明背景,被點擊后會呈現灰色背景。
  • 這三個按鈕控件的使用方法類似,唯一的區別只是默認樣式不同而已。
FloatingActionButton(onPressed: () => print('FloatingActionButton pressed'),child: Text('Btn'),); FlatButton(onPressed: () => print('FlatButton pressed'),child: Text('Btn'),); RaisedButton(onPressed: () => print('RaisedButton pressed'),child: Text('Btn'),);
  • 既然是按鈕,因此除了控制基本樣式之外,還需要響應用戶點擊行為。這就對應著按鈕控件中的兩個最重要的參數了:
    • onPressed 參數用于設置點擊回調,告訴 Flutter 在按鈕被點擊時通知我們。如果 onPressed 參數為空,則按鈕會處于禁用狀態,不響應用戶點擊。
    • child 參數用于設置按鈕的內容,告訴 Flutter 控件應該長成什么樣,也就是控制著按鈕控件的基本樣式。child 可以接收任意的 Widget,比如我們在上面的例子中傳入的 Text,除此之外我們還可以傳入 Image 等控件。
  • 通常情況下,我們還是會進行控件樣式定制。與 Text 控件類似,按鈕控件也提供了豐富的樣式定制功能,比如背景顏色 color、按鈕形狀 shape、主題顏色 colorBrightness,等等。
FlatButton(color: Colors.yellow, //設置背景色為黃色shape:BeveledRectangleBorder(borderRadius: BorderRadius.circular(20.0)), //設置斜角矩形邊框colorBrightness: Brightness.light, //確保文字按鈕為深色onPressed: () => print('FlatButton pressed'), child: Row(children: <Widget>[Icon(Icons.add), Text("Add")],) )

經典控件(二):ListView與CustomScrollView

  • 當元素的排列布局超過屏幕顯示尺寸(即超過一屏)時,我們就需要引入列表控件來展示視圖的完整內容,并根據元素的多少進行自適應滾動展示。
  • 在 Android 中是由 ListView 或 RecyclerView 實現的,在 iOS 中是用 UITableView 實現的;而在 Flutter 中,實現這種需求的則是列表控件 ListView。
ListView
  • 在 Flutter 中,ListView 可以沿一個方向(垂直或水平方向)來排列其所有子 Widget,因此常被用于需要展示一組連續視圖元素的場景,比如通信錄、優惠券、商家列表等。
  • ListView 提供了一個默認構造函數 ListView,我們可以通過設置它的 children 參數,很方便地將所有的子 Widget 包含到 ListView 中。
  • 不過,這種創建方式要求提前將所有子 Widget 一次性創建好,而不是等到它們真正在屏幕上需要顯示時才創建,所以有一個很明顯的缺點,就是性能不好。因此,這種方式僅適用于列表中含有少量元素的場景。
ListView(children: <Widget>[//設置ListTile組件的標題與圖標 ListTile(leading: Icon(Icons.map), title: Text('Map')),ListTile(leading: Icon(Icons.mail), title: Text('Mail')),ListTile(leading: Icon(Icons.message), title: Text('Message')),]);

備注:ListTile 是 Flutter 提供的用于快速構建列表項元素的一個小組件單元,用于 1~3 行(leading、title、subtitle)展示文本、圖標等視圖元素的場景,通常與 ListView 配合使用。上面這段代碼中用到 ListTile,是為了演示 ListView 的能力。

  • 除了默認的垂直方向布局外,ListView 還可以通過設置 scrollDirection 參數支持水平方向布局。如下所示,我定義了一組不同顏色背景的組件,將它們的寬度設置為 140,并包在了水平布局的 ListView 中,讓它們可以橫向滾動。
ListView(scrollDirection: Axis.horizontal,itemExtent: 140, //item延展尺寸(寬度)children: <Widget>[Container(color: Colors.black),Container(color: Colors.red),Container(color: Colors.blue),Container(color: Colors.green),Container(color: Colors.yellow),Container(color: Colors.orange),]);
  • 考慮到創建子 Widget 產生的性能問題,更好的方法是抽象出創建子 Widget 的方法,交由 ListView 統一管理,在真正需要展示該子 Widget 時再去創建。

  • ListView 的另一個構造函數 ListView.builder,則適用于子 Widget 比較多的場景。這個構造函數有兩個關鍵參數:

    • itemBuilder,是列表項的創建方法。當列表滾動到相應位置時,ListView 會調用該方法創建對應的子 Widget。
    • itemCount,表示列表項的數量,如果為空,則表示 ListView 為無限列表。
ListView.builder(itemCount: 100, //元素個數itemExtent: 50.0, //列表項高度itemBuilder: (BuildContext context, int index) => ListTile(title: Text("title $index"), subtitle: Text("body $index")) );
  • itemExtent 并不是一個必填參數。但對于定高的列表項元素,建議提前設置好這個參數的值。
  • 但如果提前設置好 itemExtent,ListView 則可以提前計算好每一個列表項元素的相對位置,以及自身的視圖高度,省去了無謂的計算。
  • 因此,在 ListView 中,指定 itemExtent 比讓子 Widget 自己決定自身高度會更高效。
  • 在 ListView 中,有兩種方式支持分割線:
    • 一種是,在 itemBuilder 中,根據 index 的值動態創建分割線,也就是將分割線視為列表項的一部分;
    • 另一種是,使用 ListView 的另一個構造方法 ListView.separated,單獨設置分割線的樣式。
//使用ListView.separated設置分割線 ListView.separated(itemCount: 100,separatorBuilder: (BuildContext context, int index) => index %2 ==0? Divider(color: Colors.green) : Divider(color: Colors.red),//index為偶數,創建綠色分割線;index為奇數,則創建紅色分割線itemBuilder: (BuildContext context, int index) => ListTile(title: Text("title $index"), subtitle: Text("body $index"))//創建子Widget )
  • ListView常見的構造方法及其適用場景。
CustomScrollView
  • 對于某些特殊交互場景,比如多個效果聯動、嵌套滾動、精細滑動、視圖跟隨手勢操作等,還需要嵌套多個 ListView 來實現。這時,各自視圖的滾動和布局模型就是相互獨立、分離的,就很難保證整個頁面統一一致的滑動效果。
  • Flutter 是如何解決多 ListView 嵌套時,頁面滑動效果不一致的問題的呢?在 Flutter 中有一個專門的控件 CustomScrollView,用來處理多個需要自定義滾動效果的 Widget。在 CustomScrollView 中,這些彼此獨立的、可滾動的 Widget 被統稱為 Sliver。
  • 視差滾動是指讓多層背景以不同的速度移動,在形成立體滾動效果的同時,還能保證良好的視覺體驗。 作為移動應用交互設計的熱點趨勢,越來越多的移動應用使用了這項技術。
CustomScrollView(slivers: <Widget>[SliverAppBar(//SliverAppBar作為頭圖控件title: Text('CustomScrollView Demo'),//標題floating: true,//設置懸浮樣式flexibleSpace: Image.network("https://xx.jpg",fit:BoxFit.cover),//設置懸浮頭圖背景expandedHeight: 300,//頭圖控件高度),SliverList(//SliverList作為列表控件delegate: SliverChildBuilderDelegate((context, index) => ListTile(title: Text('Item #$index')),//列表項創建方法childCount: 100,//列表元素個數),),]);
  • 實現效果類似于可折疊式標題欄。例如QQ好友動態頭部效果!
ScrollController與ScrollNotification
ScrollController
  • 在某些情況下,我們希望獲取視圖的滾動信息,并進行相應的控制。比如,列表是否已經滑到底(頂)了?如何快速回到列表頂部?列表滾動是否已經開始,或者是否已經停下來了?對于前兩個問題,我們可以使用 ScrollController 進行滾動信息的監聽,以及相應的滾動控制;而最后一個問題,則需要接收 ScrollNotification 通知進行滾動事件的獲取。
  • 在 Flutter 中,因為 Widget 并不是渲染到屏幕的最終視覺元素(RenderObject 才是),所以我們無法像原生的 Android 或 iOS 系統那樣,向持有的 Widget 對象獲取或設置最終渲染相關的視覺信息,而必須通過對應的組件控制器才能實現。
  • ListView 的組件控制器則是 ScrollControler,我們可以通過它來獲取視圖的滾動信息,更新視圖的滾動位置。一般而言,獲取視圖的滾動信息往往是為了進行界面的狀態控制,因此 ScrollController 的初始化、監聽及銷毀需要與 StatefulWidget 的狀態保持同步。
/*** 聲明了一個有著 100 個元素的列表項,當滾動視圖到特定位置后,用戶可以點擊按鈕返回列表頂部:*/ class MyAPPState extends State<MyApp> {ScrollController _controller;//ListView控制器bool isToTop = false;//標示目前是否需要啟用"Top"按鈕@overridevoid initState() {_controller = ScrollController();_controller.addListener(() {//為控制器注冊滾動監聽方法if(_controller.offset > 1000) {//如果ListView已經向下滾動了1000,則啟用Top按鈕setState(() {isToTop = true;});} else if(_controller.offset < 300) {//如果ListView向下滾動距離不足300,則禁用Top按鈕setState(() {isToTop = false;});}});super.initState();}Widget build(BuildContext context) {return MaterialApp(...//頂部Top按鈕,根據isToTop變量判斷是否需要注冊滾動到頂部的方法RaisedButton(onPressed: (isToTop ? () {if(isToTop) {_controller.animateTo(.0,duration: Duration(milliseconds: 200),curve: Curves.ease);//做一個滾動到頂部的動畫}}:null),child: Text("Top"),)...ListView.builder(controller: _controller,//初始化傳入控制器itemCount: 100,//列表元素總數itemBuilder: (context, index) => ListTile(title: Text("Index : $index")),//列表項構造方法) ... );@overridevoid dispose() {_controller.dispose(); //銷毀控制器super.dispose();} }
ScrollNotification
  • 在 Flutter 中,ScrollNotification 通知的獲取是通過 NotificationListener 來實現的。與 ScrollController 不同的是,NotificationListener 是一個 Widget,為了監聽滾動類型的事件,我們需要將 NotificationListener 添加為 ListView 的父容器,從而捕獲 ListView 中的通知。而這些通知,需要通過 onNotification 回調函數實現監聽邏輯:
Widget build(BuildContext context) {return MaterialApp(title: 'ScrollController Demo',home: Scaffold(appBar: AppBar(title: Text('ScrollController Demo')),body: NotificationListener<ScrollNotification>(//添加NotificationListener作為父容器onNotification: (scrollNotification) {//注冊通知回調ScrollMetrics metrics = notification.metrics;print(metrics.pixels);// 當前位置print(metrics.atEdge);//是否在頂部或底部print(metrics.axis);//垂直或水平滾動print(metrics.axisDirection);// 滾動方向是down還是upprint(metrics.extentAfter);//視口底部距離列表底部有多大print(metrics.extentBefore);//視口頂部距離列表頂部有多大print(metrics.extentInside);//視口范圍內的列表長度print(metrics.maxScrollExtent);//最大滾動距離,列表長度-視口長度print(metrics.minScrollExtent);//最小滾動距離print(metrics.viewportDimension);//視口長度print(metrics.outOfRange);//是否越過邊界return true;},child: ListView.builder(itemCount: 30,//列表元素個數itemBuilder: (context, index) => ListTile(title: Text("Index : $index")),//列表項創建方法),))); }
  • 相比于 ScrollController 只能和具體的 ListView 關聯后才可以監聽到滾動信息;通過 NotificationListener 則可以監聽其子 Widget 中的任意 ListView,不僅可以得到這些 ListView 的當前滾動位置信息,還可以獲取當前的滾動事件信息 。
問題

在ListView中,如何提前緩存子元素?

答:ListView構造函數中有一個cacheExtent參數,即預渲染區域長度,ListView會在其可視化區域的兩邊留一個cacheExtent長度的區域作為預渲染區域,相當于提前緩存些元素,這樣當滑動時迅速呈現。

經典布局:如何定義子控件在父容器中的排版位置

  • Flutter 提供了 31 種布局 Widget,對布局控件的劃分非常詳細,一些相同(或相似)的視覺效果可以通過多種布局控件實現。
單子Widget布局:Container、Padding與Center
  • 單子 Widget 布局類容器比較簡單,一般用來對其唯一的子 Widget 進行樣式包裝,比如限制大小、添加背景色樣式、內間距、旋轉變換等。
  • 在 Flutter 中,Container 本身可以單獨作為控件存在(比如單獨設置背景色、寬高),也可以作為其他控件的父級存在:Container 可以定義布局過程中子 Widget 如何擺放,以及如何展示。與其他框架不同的是,Flutter 的 Container 僅能包含一個子 Widget。
Container(child: Text('Container(容器)在UI框架中是一個很常見的概念,Flutter也不例外。'),padding: EdgeInsets.all(18.0), // 內邊距margin: EdgeInsets.all(44.0), // 外邊距width: 180.0,height:240,alignment: Alignment.center, // 子Widget居中對齊decoration: BoxDecoration( //Container樣式color: Colors.red, // 背景色borderRadius: BorderRadius.circular(10.0), // 圓角邊框), )
  • 如果我們只需要將子 Widget 設定間距,則可以使用另一個單子容器控件 Padding 進行內容填充。
Padding(padding: EdgeInsets.all(44.0),child: Text('Container(容器)在UI框架中是一個很常見的概念,Flutter也不例外。'), );
  • 在需要設置內容間距時,我們可以通過 EdgeInsets 的不同構造函數,分別制定四個方向的不同補白方式,如均使用同樣數值留白、只設置左留白或對稱方向留白等。
  • Center 會將其子 Widget 居中排列。
Scaffold(body: Center(child: Text("Hello")) // This trailing comma makes auto-formatting nicer for build methods. );
  • 需要注意的是,為了實現居中布局,Center 所占據的空間一定要比其子 Widget 要大才行,這也是顯而易見的:如果 Center 和其子 Widget 一樣大,自然就不需要居中,也沒空間居中了。因此 Center 通常會結合 Container 一起使用。
Container(child: Center(child: Text('Container(容器)在UI框架中是一個很常見的概念,Flutter也不例外。')),padding: EdgeInsets.all(18.0), // 內邊距margin: EdgeInsets.all(44.0), // 外邊距width: 180.0,height:240,decoration: BoxDecoration( //Container樣式color: Colors.red, // 背景色borderRadius: BorderRadius.circular(10.0), // 圓角邊框), );
  • 我們通過 Center 容器實現了 Container 容器中 alignment: Alignment.center 的效果。
多子Widget布局:Row、Column與Expanded
  • 對于擁有多個子 Widget 的布局類容器而言,其布局行為無非就是兩種規則的抽象:水平方向上應該如何布局、垂直方向上應該如何布局。
  • Row 與 Column 的使用方法很簡單,我們只需要將各個子 Widget 按序加入到 children 數組即可。
//Row的用法示范 Row(children: <Widget>[Container(color: Colors.yellow, width: 60, height: 80,),Container(color: Colors.red, width: 100, height: 180,),Container(color: Colors.black, width: 60, height: 80,),Container(color: Colors.green, width: 60, height: 80,),], );//Column的用法示范 Column(children: <Widget>[Container(color: Colors.yellow, width: 60, height: 80,),Container(color: Colors.red, width: 100, height: 180,),Container(color: Colors.black, width: 60, height: 80,),Container(color: Colors.green, width: 60, height: 80,),], );
  • 單純使用 Row 和 Column 控件,在子 Widget 的尺寸較小時,無法將容器填滿,視覺樣式比較難看。對于這樣的場景,我們可以通過 Expanded 控件,來制定分配規則填滿容器的剩余空間。
  • 我們希望 Row 組件(或 Column 組件)中的綠色容器與黃色容器均分剩下的空間,于是就可以設置它們的彈性系數參數 flex 都為 1,這兩個 Expanded 會按照其 flex 的比例(即 1:1)來分割剩余的 Row 橫向(Column 縱向)空間。
Row(children: <Widget>[Expanded(flex: 1, child: Container(color: Colors.yellow, height: 60)), //設置了flex=1,因此寬度由Expanded來分配Container(color: Colors.red, width: 100, height: 180,),Container(color: Colors.black, width: 60, height: 80,),Expanded(flex: 1, child: Container(color: Colors.green,height: 60),)/設置了flex=1,因此寬度由Expanded來分配], );
  • 于 Row 與 Column 而言,Flutter 提供了依據坐標軸的布局對齊行為,即根據布局方向劃分出主軸和縱軸:主軸,表示容器依次擺放子 Widget 的方向;縱軸,則是與主軸垂直的另一個方向。

  • 我們可以根據主軸與縱軸,設置子 Widget 在這兩個方向上的對齊規則 mainAxisAlignment 與 crossAxisAlignment。比如,主軸方向 start 表示靠左對齊、center 表示橫向居中對齊、end 表示靠右對齊、spaceEvenly 表示按固定間距對齊;而縱軸方向 start 則表示靠上對齊、center 表示縱向居中對齊、end 表示靠下對齊。
  • 需要注意的是,對于主軸而言,Flutter 默認是讓父容器決定其長度,即盡可能大,類似 Android 中的 match_parent。
  • Row 的寬度為屏幕寬度,Column 的高度為屏幕高度。主軸長度大于所有子 Widget 的總長度,意味著容器在主軸方向的空間比子 Widget 要大,這也是我們能通過主軸對齊方式設置子 Widget 布局效果的原因。
  • Row 與 Column 自身的大小由父widget的大小、子widget的大小、以及mainSize設置共同決定(mainAxisSize和crossAxisSize)
    • 主軸(縱軸)值為max:主軸(縱軸)大小等于屏幕主軸(縱軸)方向大小或者父widget主軸(縱軸)方向大小。
    • 主軸(縱軸)值為min: 所有子widget組合在一起的主軸(縱軸)大小。
  • 如果想讓容器與子 Widget 在主軸上完全匹配,我們可以通過設置 Row 的 mainAxisSize 參數為 MainAxisSize.min,由所有子 Widget 來決定主軸方向的容器長度,即主軸方向的長度盡可能小,類似 Android 中的 wrap_content。
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, //由于容器與子Widget一樣寬,因此這行設置排列間距的代碼并未起作用mainAxisSize: MainAxisSize.min, //讓容器寬度與所有子Widget的寬度一致children: <Widget>[Container(color: Colors.yellow, width: 60, height: 80,),Container(color: Colors.red, width: 100, height: 180,),Container(color: Colors.black, width: 60, height: 80,),Container(color: Colors.green, width: 60, height: 80,),], )
層疊Widget布局:Stack與Positioned
  • Stack 容器與前端中的絕對定位、Android 中的 Frame 布局非常類似,子 Widget 之間允許疊加,還可以根據父容器上、下、左、右四個角的位置來確定自己的位置。
  • Stack 提供了層疊布局的容器,而 Positioned 則提供了設置子 Widget 位置的能力。
Stack(children: <Widget>[Container(color: Colors.yellow, width: 300, height: 300),//黃色容器Positioned(left: 18.0,top: 18.0,child: Container(color: Colors.green, width: 50, height: 50),//疊加在黃色容器之上的綠色控件),Positioned(left: 18.0,top:70.0,child: Text("Stack提供了層疊布局的容器"),//疊加在黃色容器之上的文本)], )
  • Stack 控件允許其子 Widget 按照創建的先后順序進行層疊擺放,而 Positioned 控件則用來控制這些子 Widget 的擺放位置。需要注意的是,Positioned 控件只能在 Stack 中使用,在其他容器中使用會報錯。

組合與自繪,何種方式定義Widget

  • 在 Flutter 中,自定義 Widget 與其他平臺類似:可以使用基本 Widget 組裝成一個高級別的 Widget,也可以自己在畫板上根據特殊需求來畫界面。
組裝
  • 使用組合的方式自定義 Widget,即通過我們之前介紹的布局方式,擺放項目所需要的基礎 Widget,并在控件內部設置這些基礎 Widget 的樣式,從而組合成一個更高級的控件。
  • 在 Flutter 中,組合的思想始終貫穿在框架設計之中,這也是 Flutter 提供了如此豐富的控件庫的原因之一。比如,在新聞類應用中。
class UpdateItemModel {String appIcon;//App圖標String appName;//App名稱String appSize;//App大小String appDate;//App更新日期String appDescription;//App更新文案String appVersion;//App版本//構造函數語法糖,為屬性賦值UpdateItemModel({this.appIcon, this.appName, this.appSize, this.appDate, this.appDescription, this.appVersion}); } Widget buildTopRow(BuildContext context) {return Row(//Row控件,用來水平擺放子Widgetchildren: <Widget>[Padding(//Paddng控件,用來設置Image控件邊距padding: EdgeInsets.all(10),//上下左右邊距均為10child: ClipRRect(//圓角矩形裁剪控件borderRadius: BorderRadius.circular(8.0),//圓角半徑為8child: Image.asset(model.appIcon, width: 80,height:80)圖片控件//)),Expanded(//Expanded控件,用來拉伸中間區域child: Column(//Column控件,用來垂直擺放子WidgetmainAxisAlignment: MainAxisAlignment.center,//垂直方向居中對齊crossAxisAlignment: CrossAxisAlignment.start,//水平方向居左對齊children: <Widget>[Text(model.appName,maxLines: 1),//App名字Text(model.appDate,maxLines: 1),//App更新日期],),),Padding(//Paddng控件,用來設置Widget間邊距padding: EdgeInsets.fromLTRB(0,0,10,0),//右邊距為10,其余均為0child: FlatButton(//按鈕控件child: Text("OPEN"),onPressed: onPressed,//點擊回調))]); } Widget buildBottomRow(BuildContext context) {return Padding(//Padding控件用來設置整體邊距padding: EdgeInsets.fromLTRB(15,0,15,0),//左邊距和右邊距為15child: Column(//Column控件用來垂直擺放子WidgetcrossAxisAlignment: CrossAxisAlignment.start,//水平方向距左對齊children: <Widget>[Text(model.appDescription),//更新文案Padding(//Padding控件用來設置邊距padding: EdgeInsets.fromLTRB(0,10,0,0),//上邊距為10child: Text("${model.appVersion} ? ${model.appSize} MB"))])); }
  • 將上下兩部分控件通過 Column 包裝起來,這次升級項 UI 定制就完成了。
class UpdatedItem extends StatelessWidget {final UpdatedItemModel model;//數據模型//構造函數語法糖,用來給model賦值UpdatedItem({Key key,this.model, this.onPressed}) : super(key: key);final VoidCallback onPressed;@overrideWidget build(BuildContext context) {return Column(//用Column將上下兩部分合體children: <Widget>[buildTopRow(context),//上半部分buildBottomRow(context)//下半部分]);}Widget buildBottomRow(BuildContext context) {...}Widget buildTopRow(BuildContext context) {...} }
  • 按照從上到下、從左到右去拆解 UI 的布局結構,把復雜的 UI 分解成各個小 UI 元素,在以組裝的方式去自定義 UI 中非常有用,請一定記住這樣的拆解方法。
自繪
  • Flutter 提供了非常豐富的控件和布局方式,使得我們可以通過組合去構建一個新的視圖。但對于一些不規則的視圖,用 SDK 提供的現有 Widget 組合可能無法實現,比如餅圖,k 線圖等,這個時候我們就需要自己用畫筆去繪制了。
  • 在原生 iOS 和 Android 開發中,我們可以繼承 UIView/View,在 drawRect/onDraw 方法里進行繪制操作。其實,在 Flutter 中也有類似的方案,那就是 CustomPaint。
  • CustomPaint 是用以承接自繪控件的容器,并不負責真正的繪制。既然是繪制,那就需要用到畫布與畫筆。
  • 在 Flutter 中,畫布是 Canvas,畫筆則是 Paint,而畫成什么樣子,則由定義了繪制邏輯的 CustomPainter 來控制。將 CustomPainter 設置給容器 CustomPaint 的 painter 屬性,我們就完成了一個自繪控件的封裝。
  • 對于畫筆 Paint,我們可以配置它的各種屬性,比如顏色、樣式、粗細等;而畫布 Canvas,則提供了各種常見的繪制方法,比如畫線 drawLine、畫矩形 drawRect、畫點 DrawPoint、畫路徑 drawPath、畫圓 drawCircle、畫圓弧 drawArc 等。
//用 6 種不同顏色的畫筆依次畫了 6 個 1/6 圓弧,拼成了一張餅圖。 class WheelPainter extends CustomPainter {// 設置畫筆顏色 Paint getColoredPaint(Color color) {//根據顏色返回不同的畫筆Paint paint = Paint();//生成畫筆paint.color = color;//設置畫筆顏色return paint;}@overridevoid paint(Canvas canvas, Size size) {//繪制邏輯double wheelSize = min(size.width,size.height)/2;//餅圖的尺寸double nbElem = 6;//分成6份double radius = (2 * pi) / nbElem;//1/6圓//包裹餅圖這個圓形的矩形框Rect boundingRect = Rect.fromCircle(center: Offset(wheelSize, wheelSize), radius: wheelSize);// 每次畫1/6個圓弧canvas.drawArc(boundingRect, 0, radius, true, getColoredPaint(Colors.orange));canvas.drawArc(boundingRect, radius, radius, true, getColoredPaint(Colors.black38));canvas.drawArc(boundingRect, radius * 2, radius, true, getColoredPaint(Colors.green));canvas.drawArc(boundingRect, radius * 3, radius, true, getColoredPaint(Colors.red));canvas.drawArc(boundingRect, radius * 4, radius, true, getColoredPaint(Colors.blue));canvas.drawArc(boundingRect, radius * 5, radius, true, getColoredPaint(Colors.pink));}// 判斷是否需要重繪,這里我們簡單的做下比較即可@overridebool shouldRepaint(CustomPainter oldDelegate) => oldDelegate != this; } //將餅圖包裝成一個新的控件 class Cake extends StatelessWidget {@overrideWidget build(BuildContext context) {return CustomPaint(size: Size(200, 200),painter: WheelPainter(),);} }
  • 在實現視覺需求上,自繪需要自己親自處理繪制邏輯,而組合則是通過子 Widget 的拼接來實現繪制意圖。因此從渲染邏輯處理上,自繪方案可以進行深度的渲染定制,從而實現少數通過組合很難實現的需求(比如餅圖、k 線圖)。不過,當視覺效果需要調整時,采用自繪的方案可能需要大量修改繪制代碼,而組合方案則相對簡單:只要布局拆分設計合理,可以通過更換子 Widget 類型來輕松搞定。

從夜間模式說起,定制不同的App主題

主題定制
  • 主題,又叫皮膚、配色,一般由顏色、圖片、字號、字體等組成,我們可以把它看做是視覺效果在不同場景下的可視資源,以及相應的配置集合。比如,App 的按鈕,無論在什么場景下都需要背景圖片資源、字體顏色、字號大小等,而所謂的主題切換只是在不同主題之間更新這些資源及配置集合而已。
  • 因此在 App 開發中,我們通常不關心資源和配置的視覺效果好不好看,只要關心資源提供的視覺功能能不能用。比如,對于圖片類資源,我們并不需要關心它渲染出來的實際效果,只需要確定它渲染出來是一張固定寬高尺寸的區域,不影響頁面布局,能把業務流程跑通即可。
  • 視覺效果是易變的,我們將這些變化的部分抽離出來,把提供不同視覺效果的資源和配置按照主題進行歸類,整合到一個統一的中間層去管理,這樣我們就能實現主題的管理和切換了。
  • 在 iOS 中,我們通常會將主題的配置信息預先寫到 plist 文件中,通過一個單例來控制 App 應該使用哪種配置;而 Android 的配置信息則寫入各個 style 屬性值的 xml 中,通過 activity 的 setTheme 進行切換;前端的處理方式也類似,簡單更換 css 就可以實現多套主題 / 配色之間的切換。
  • Flutter 也提供了類似的能力,由 ThemeData 來統一管理主題的配置信息。
  • ThemeData 涵蓋了 Material Design 規范的可自定義部分樣式,比如應用明暗模式 brightness、應用主色調 primaryColor、應用次級色調 accentColor、文本字體 fontFamily、輸入框光標顏色 cursorColor 等。
  • 通過 ThemeData 來自定義應用主題,我們可以實現 App 全局范圍,或是 Widget 局部范圍的樣式切換。
全局統一的視覺風格定制
  • 在 Flutter 中,應用程序類 MaterialApp 的初始化方法,為我們提供了設置主題的能力。我們可以通過參數 theme,選擇改變 App 的主題色、字體等,設置界面在 MaterialApp 下的展示樣式。
MaterialApp(title: 'Flutter Demo',//標題theme: ThemeData(//設置主題brightness: Brightness.dark,//明暗模式為暗色primaryColor: Colors.cyan,//主色調為青色),home: MyHomePage(title: 'Flutter Demo Home Page'), );
  • 雖然我們只修改了主色調和明暗模式兩個參數,但按鈕、文字顏色都隨之調整了。這是因為默認情況下,ThemeData 中很多其他次級視覺屬性,都會受到主色調與明暗模式的影響。再細化一下主題配置。
MaterialApp(title: 'Flutter Demo',//標題theme: ThemeData(//設置主題brightness: Brightness.dark,//設置明暗模式為暗色accentColor: Colors.black,//(按鈕)Widget前景色為黑色primaryColor: Colors.cyan,//主色調為青色iconTheme:IconThemeData(color: Colors.yellow),//設置icon主題色為黃色textTheme: TextTheme(body1: TextStyle(color: Colors.red))//設置文本顏色為紅色),home: MyHomePage(title: 'Flutter Demo Home Page'), );
局部獨立的視覺風格定制
  • 為整個 App 提供統一的視覺呈現效果固然很有必要,但有時我們希望為某個頁面、或是某個區塊設置不同于 App 風格的展現樣式。以主題切換功能為例,我們希望為不同的主題提供不同的展示預覽。
  • 在 Flutter 中,我們可以使用 Theme 來對 App 的主題進行局部覆蓋。Theme 是一個單子 Widget 容器,與 MaterialApp 類似的,我們可以通過設置其 data 屬性,對其子 Widget 進行樣式定制:
    • 如果我們不想繼承任何 App 全局的顏色或字體樣式,可以直接新建一個 ThemeData 實例,依次設置對應的樣式;
    • 而如果我們不想在局部重寫所有的樣式,則可以繼承 App 的主題,使用 copyWith 方法,只更新部分樣式。
// 新建主題 Theme(data: ThemeData(iconTheme: IconThemeData(color: Colors.red)),child: Icon(Icons.favorite) );// 繼承主題 Theme(data: Theme.of(context).copyWith(iconTheme: IconThemeData(color: Colors.green)),child: Icon(Icons.feedback) );
  • 除了定義 Material Design 規范中那些可自定義部分樣式外,主題的另一個重要用途是樣式復用。
  • 比如,如果我們想為一段文字復用 Materia Design 規范中的 title 樣式,或是為某個子 Widget 的背景色復用 App 的主題色,我們就可以通過 Theme.of(context) 方法,取出對應的屬性,應用到這段文字的樣式中。
  • Theme.of(context) 方法將向上查找 Widget 樹,并返回 Widget 樹中最近的主題 Theme。如果 Widget 的父 Widget 們有一個單獨的主題定義,則使用該主題。如果不是,那就使用 App 全局主題。
Container(color: Theme.of(context).primaryColor,//容器背景色復用應用主題色child: Text('Text with a background color',style: Theme.of(context).textTheme.title,//Text組件文本樣式復用應用文本樣式));
分平臺主題定制
  • 有時候,為了滿足不同平臺的用戶需求,我們希望針對特定的平臺設置不同的樣式。比如,在 iOS 平臺上設置淺色主題,在 Android 平臺上設置深色主題。面對這樣的需求,我們可以根據 defaultTargetPlatform 來判斷當前應用所運行的平臺,從而根據系統類型來設置對應的主題。
// iOS淺色主題 final ThemeData kIOSTheme = ThemeData(brightness: Brightness.light,//亮色主題accentColor: Colors.white,//(按鈕)Widget前景色為白色primaryColor: Colors.blue,//主題色為藍色iconTheme:IconThemeData(color: Colors.grey),//icon主題為灰色textTheme: TextTheme(body1: TextStyle(color: Colors.black))//文本主題為黑色 ); // Android深色主題 final ThemeData kAndroidTheme = ThemeData(brightness: Brightness.dark,//深色主題accentColor: Colors.black,//(按鈕)Widget前景色為黑色primaryColor: Colors.cyan,//主題色Wie青色iconTheme:IconThemeData(color: Colors.blue),//icon主題色為藍色textTheme: TextTheme(body1: TextStyle(color: Colors.red))//文本主題色為紅色 ); // 應用初始化 MaterialApp(title: 'Flutter Demo',theme: defaultTargetPlatform == TargetPlatform.iOS ? kIOSTheme : kAndroidTheme,//根據平臺選擇不同主題home: MyHomePage(title: 'Flutter Demo Home Page'), );
  • 當然,除了主題之外,你也可以用 defaultTargetPlatform 這個變量去實現一些其他需要判斷平臺的邏輯,比如在界面上使用更符合 Android 或 iOS 設計風格的組件。

依賴管理(一):圖片、配置和字體

  • 一個應用程序主要由兩部分內容組成:代碼和資源。代碼關注邏輯功能,而如圖片、字符串、字體、配置文件等資源則關注視覺功能。
  • 資源外部化,即把代碼與資源分離,是現代 UI 框架的主流設計理念。因為這樣不僅有利于單獨維護資源,還可以對特定設備提供更準確的兼容性支持,使得我們的應用程序可以自動根據實際運行環境來組織視覺功能,適應不同的屏幕大小和密度等。
資源管理
  • 在移動開發中,常見的資源類型包括 JSON 文件、配置文件、圖標、圖片以及字體文件等。它們都會被打包到 App 安裝包中,而 App 中的代碼可以在運行時訪問這些資源。
  • 在 Android、iOS 平臺中,為了區分不同分辨率的手機設備,圖片和其他原始資源是區別對待的:
    • iOS 使用 Images.xcassets 來管理圖片,其他的資源直接拖進工程項目即可;
    • Android 的資源管理粒度則更為細致,使用以 drawable+ 分辨率命名的文件夾來分別存放不同分辨率的圖片,其他類型的資源也都有各自的存放方式,比如布局文件放在 res/layout 目錄下,資源描述文件放在 res/values 目錄下,原始文件放在 assets 目錄下等。
  • 而在 Flutter 中,資源管理則簡單得多:資源(assets)可以是任意類型的文件,比如 JSON 配置文件或是字體文件等,而不僅僅是圖片。
  • 而關于資源的存放位置,Flutter 并沒有像 Android 那樣預先定義資源的目錄結構,所以我們可以把資源存放在項目中的任意目錄下,只需要使用根目錄下的 pubspec.yaml 文件,對這些資源的所在位置進行顯式聲明就可以了,以幫助 Flutter 識別出這些資源。
assets ├── background.jpg ├── icons │ └── food_icon.jpg ├── loading.gif └── result.json flutter:assets:- assets/background.jpg #挨個指定資源路徑- assets/loading.gif #挨個指定資源路徑- assets/result.json #挨個指定資源路徑- assets/icons/ #子目錄批量指定- assets/ #根目錄也是可以批量指定的
  • 需要注意的是,目錄批量指定并不遞歸,只有在該目錄下的文件才可以被包括,如果下面還有子目錄的話,需要單獨聲明子目錄下的文件。
  • 完成資源的聲明后,我們就可以在代碼中訪問它們了。在 Flutter 中,對不同類型的資源文件處理方式略有差異。
  • 對于圖片類資源的訪問,我們可以使用 Image.asset 構造方法完成圖片資源的加載及顯示。
  • 而對于其他資源文件的加載,我們可以通過 Flutter 應用的主資源 Bundle 對象 rootBundle,來直接訪問。
  • 對于字符串文件資源,我們使用 loadString 方法;而對于二進制文件資源,則通過 load 方法。
rootBundle.loadString('assets/result.json').then((msg)=>print(msg));
  • 與 Android、iOS 開發類似,Flutter 也遵循了基于像素密度的管理方式,如 1.0x、2.0x、3.0x 或其他任意倍數,Flutter 可以根據當前設備分辨率加載最接近設備像素比例的圖片資源。而為了讓 Flutter 更好地識別,我們的資源目錄應該將 1.0x、2.0x 與 3.0x 的圖片資源分開管理。
assets ├── background.jpg //1.0x圖 ├── 2.0x │ └── background.jpg //2.0x圖 └── 3.0x└── background.jpg //3.0x圖
  • 而在 pubspec.yaml 文件聲明這個圖片資源時,僅聲明 1.0x 圖資源即可。
flutter:assets:- assets/background.jpg #1.0x圖資源
  • 1.0x 分辨率的圖片是資源標識符,而 Flutter 則會根據實際屏幕像素比例加載相應分辨率的圖片。這時,如果主資源缺少某個分辨率資源,Flutter 會在剩余的分辨率資源中選擇最接近的分辨率資源去加載。
  • 字體則是另外一類較為常用的資源。手機操作系統一般只有默認的幾種字體,在大部分情況下可以滿足我們的正常需求。但是,在一些特殊的情況下,我們可能需要使用自定義字體來提升視覺體驗。
  • 在 Flutter 中,使用自定義字體同樣需要在 pubspec.yaml 文件中提前聲明。需要注意的是,字體實際上是字符圖形的映射。所以,除了正常字體文件外,如果你的應用需要支持粗體和斜體,同樣也需要有對應的粗體和斜體字體文件。
  • 在將 RobotoCondensed 字體擺放至 assets 目錄下的 fonts 子目錄后,下面的代碼演示了如何將支持斜體與粗體的 RobotoCondensed 字體加到我們的應用中。
fonts:- family: RobotoCondensed #字體名字fonts:- asset: assets/fonts/RobotoCondensed-Regular.ttf #普通字體- asset: assets/fonts/RobotoCondensed-Italic.ttf style: italic #斜體- asset: assets/fonts/RobotoCondensed-Bold.ttf weight: 700 #粗體 Text("This is RobotoCondensed", style: TextStyle(fontFamily: 'RobotoCondensed',//普通字體 )); Text("This is RobotoCondensed", style: TextStyle(fontFamily: 'RobotoCondensed',fontWeight: FontWeight.w700, //粗體 )); Text("This is RobotoCondensed italic", style: TextStyle(fontFamily: 'RobotoCondensed',fontStyle: FontStyle.italic, //斜體 ));
原生平臺的資源設置
  • Flutter 需要原生環境才能運行,但是有些資源我們需要在 Flutter 框架運行之前提前使用,比如要給應用添加圖標,或是希望在等待 Flutter 框架啟動時添加啟動圖,我們就需要在對應的原生工程中完成相應的配置,所以下面介紹的操作步驟都是在原生系統中完成的。
更換App圖標
  • 對于 Android 平臺,啟動圖標位于根目錄 android/app/src/main/res/mipmap 下。我們只需要遵守對應的像素密度標準,保留原始圖標名稱,將圖標更換為目標資源即可。
  • 對于 iOS 平臺,啟動圖位于根目錄 ios/Runner/Assets.xcassets/AppIcon.appiconset 下。同樣地,我們只需要遵守對應的像素密度標準,將其替換為目標資源并保留原始圖標名稱即可。
更換啟動圖
  • 對于 Android 平臺,啟動圖位于根目錄 android/app/src/main/res/drawable 下,是一個名為 launch_background 的 XML 界面描述文件。
<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"><!-- 白色背景 --><item android:drawable="@android:color/white" /><item><!-- 內嵌一張居中展示的圖片 --><bitmapandroid:gravity="center"android:src="@mipmap/bitmap_launcher" /></item> </layer-list>
  • 而對于 iOS 平臺,啟動圖位于根目錄 ios/Runner/Assets.xcassets/LaunchImage.imageset 下。我們保留原始啟動圖名稱,將圖片依次按照對應像素密度標準,更換為目標啟動圖即可。

依賴管理(二):第三方組件庫在FLutter如何管理

  • pubspec.yaml 更為重要的作用是管理 Flutter 工程代碼的依賴,比如第三方庫、Dart 運行環境、Flutter SDK 版本都可以通過它來進行統一管理。所以,pubspec.yaml 與 iOS 中的 Podfile、Android 中的 build.gradle、前端的 package.json 在功能上是類似的。
Pub
  • Dart 提供了包管理工具 Pub,用來管理代碼和資源。從本質上說,包(package)實際上就是一個包含了 pubspec.yaml 文件的目錄,其內部可以包含代碼、資源、腳本、測試和文檔等文件。包中包含了需要被外部依賴的功能抽象,也可以依賴其他包。
  • 與 Android 中的 JCenter/Maven、iOS 中的 CocoaPods、前端中的 npm 庫類似,Dart 提供了官方的包倉庫 Pub。通過 Pub,我們可以很方便地查找到有用的第三方包
  • 。當然,這并不意味著我們可以簡單地拿別人的庫來拼湊成一個應用程序。Dart 提供包管理工具 Pub 的真正目的是,讓你能夠找到真正好用的、經過線上大量驗證的庫,復用他人的成果來縮短開發周期,提升軟件質量。
  • 在 Dart 中,庫和應用都屬于包。pubspec.yaml 是包的配置文件,包含了包的元數據(比如,包的名稱和版本)、運行環境(也就是 Dart SDK 與 Fluter SDK 版本)、外部依賴、內部配置(比如,資源管理)。
  • 下面的例子中,我們聲明了一個 flutter_app_example 的應用配置文件,其版本為 1.0,Dart 運行環境支持 2.1 至 3.0 之間,依賴 flutter 和 cupertino_icon。
name: flutter_app_example #應用名稱 description: A new Flutter application. #應用描述 version: 1.0.0 #Dart運行環境區間 environment:sdk: ">=2.1.0 <3.0.0" #Flutter依賴庫 dependencies:flutter:sdk: fluttercupertino_icons: ">0.1.1"
  • 運行環境和依賴庫 cupertino_icons 冒號后面的部分是版本約束信息,由一組空格分隔的版本描述組成,可以支持指定版本、版本號區間,以及任意版本這三種版本約束方式。比如上面的例子中,cupertino_icons 引用了大于 0.1.1 的版本。
  • 需要注意的是,由于元數據與名稱使用空格分隔,因此版本號中不能出現空格;同時又由于大于符號“>”也是 YAML 語法中的折疊換行符號,因此在指定版本范圍的時候,必須使用引號, 比如">=2.1.0 < 3.0.0"。
  • 對于包,我們通常是指定版本區間,而很少直接指定特定版本,因為包升級變化很頻繁,如果有其他的包直接或間接依賴這個包的其他版本時,就會經常發生沖突。
  • 而對于運行環境,如果是團隊多人協作的工程,建議將 Dart 與 Flutter 的 SDK 環境寫死,統一團隊的開發環境,避免因為跨 SDK 版本出現的 API 差異進而導致工程問題。
environment:sdk: 2.3.0flutter: 1.2.1
  • 基于版本的方式引用第三方包,需要在其 Pub 上進行公開發布,我們可以訪問https://pub.dev/來獲取可用的第三方包。
  • 而對于不對外公開發布,或者目前處于開發調試階段的包,我們需要設置數據源,使用本地路徑或 Git 地址的方式進行包聲明。
dependencies:package1:path: ../package1/ #路徑依賴date_format:git:url: https://github.com/xxx/package2.git #git依賴
  • 在開發應用時,我們可以不寫明具體的版本號,而是以區間的方式聲明包的依賴;但對于一個程序而言,其運行時具體引用哪個版本的依賴包必須要確定下來。因此,除了管理第三方依賴,包管理工具 Pub 的另一個職責是,找出一組同時滿足每個包版本約束的包版本。包版本一旦確定,接下來就是下載對應版本的包了。
  • 對于 dependencies 中的不同數據源,Dart 會使用不同的方式進行管理,最終會將遠端的包全部下載到本地。比如,對于 Git 聲明依賴的方式,Pub 會 clone Git 倉庫;對于版本號的方式,Pub 則會從 pub.dartlang.org 下載包。如果包還有其他的依賴包,比如 package1 包還依賴 package3 包,Pub 也會一并下載。
  • 然后,在完成了所有依賴包的下載后,Pub 會在應用的根目錄下創建.packages 文件,將依賴的包名與系統緩存中的包文件路徑進行映射,方便后續維護。
  • 最后,Pub 會自動創建 pubspec.lock 文件。pubspec.lock 文件的作用類似 iOS 的 Podfile.lock 或前端的 package-lock.json 文件,用于記錄當前狀態下實際安裝的各個直接依賴、間接依賴的包的具體來源和版本號。
  • 除了提供功能和代碼維度的依賴之外,包還可以提供資源的依賴。在依賴包中的 pubspec.yaml 文件已經聲明了同樣資源的情況下,為節省應用程序安裝包大小,我們需要復用依賴包中的資源。
pubspec.yaml └──assets├──2.0x│ └── placeholder.png└──3.0x└── placeholder.png Image.asset('assets/placeholder.png', package: 'package4');AssetImage('assets/placeholder.png', package: 'package4');
舉例
  • 在 Flutter 中,提供了表達日期的數據結構DateTime,這個類擁有極大的表示范圍,可以表達 1970-01-01 UTC 時間后 100,000,000 天內的任意時刻。不過,如果我們想要格式化顯示日期和時間,DateTime 并沒有提供非常方便的方法,我們不得不自己取出年、月、日、時、分、秒,來定制顯示方式。
  • 值得慶幸的是,我們可以通過 date_format 這個第三方包來實現我們的訴求:date_format 提供了若干常用的日期格式化方法,可以很方便地實現格式化日期的功能。
  • 首先,我們在 Pub 上找到 date_format 這個包,確定其使用說明。

  • date_format 包最新的版本是 1.0.6,于是接下來我們把 date_format 添加到 pubspec.yaml 中。
dependencies:date_format: 1.0.6
  • 隨后,IDE(Android Studio)監測到了配置文件的改動,提醒我們進行安裝包依賴更新。于是,我們點擊 Get dependencies,下載 date_format 。
  • 下載完成后,我們就可以在工程中使用 date_format 來進行日期的格式化了。
print(formatDate(DateTime.now(), [mm, '月', dd, '日', hh, ':', n])); //輸出2019年06月30日01:56 print(formatDate(DateTime.now(), [m, '月第', w, '周'])); //輸出6月第5周
  • 現代編程語言大都自帶第依賴管理機制,其核心功能是為工程中所有直接或間接依賴的代碼庫找到合適的版本,但這并不容易。就比如前端的依賴管理器 npm 的早期版本,就曾因為不太合理的算法設計,導致計算依賴耗時過長,依賴文件夾也高速膨脹,一度被開發者們戲稱為“黑洞”。而 Dart 使用的 Pub 依賴管理機制所采用的PubGrub 算法則解決了這些問題,因此被稱為下一代版本依賴解決算法,在 2018 年底被蘋果公司吸納,成為 Swift 所采用的依賴管理器算法。
問題
  • .packages 與 pubspec.lock 是否需要做代碼版本管理呢?為什么?
    答:pubspec.lock需要做版本管理,因為lock文件把版本鎖定,統一工程環境
    .packages不需要版本管理,因為跟本地環境有關,無法做到統一。

用戶交互事件如何響應

  • 手勢操作在 Flutter 中分為兩類:
    • 第一類是原始的指針事件(Pointer Event),即原生開發中常見的觸摸事件,表示屏幕上觸摸(或鼠標、手寫筆)行為觸發的位移行為;
    • 第二類則是手勢識別(Gesture Detector),表示多個原始指針事件的組合操作,如點擊、雙擊、長按等,是指針事件的語義化封裝。
指針事件
  • 指針事件表示用戶交互的原始觸摸數據,如手指接觸屏幕 PointerDownEvent、手指在屏幕上移動 PointerMoveEvent、手指抬起 PointerUpEvent,以及觸摸取消 PointerCancelEvent,這與原生系統的底層觸摸事件抽象是一致的。
  • 在手指接觸屏幕,觸摸事件發起時,Flutter 會確定手指與屏幕發生接觸的位置上究竟有哪些組件,并將觸摸事件交給最內層的組件去響應。與瀏覽器中的事件冒泡機制類似,事件會從這個最內層的組件開始,沿著組件樹向根節點向上冒泡分發。
  • 不過 Flutter 無法像瀏覽器冒泡那樣取消或者停止事件進一步分發,我們只能通過 hitTestBehavior 去調整組件在命中測試期內應該如何表現,比如把觸摸事件交給子組件,或者交給其視圖層級之下的組件去響應。
  • 關于組件層面的原始指針事件的監聽,Flutter 提供了 Listener Widget,可以監聽其子 Widget 的原始指針事件。
Listener(child: Container(color: Colors.red,//背景色紅色width: 300,height: 300,),onPointerDown: (event) => print("down $event"),//手勢按下回調onPointerMove: (event) => print("move $event"),//手勢移動回調onPointerUp: (event) => print("up $event"),//手勢抬起回調 );
手勢識別
  • 通常情況下,響應用戶交互行為的話,我們會使用封裝了手勢語義操作的 Gesture,如點擊 onTap、雙擊 onDoubleTap、長按 onLongPress、拖拽 onPanUpdate、縮放 onScaleUpdate 等。另外,Gesture 可以支持同時分發多個手勢交互行為,意味著我們可以通過 Gesture 同時監聽多個事件。
  • Gesture 是手勢語義的抽象,而如果我們想從組件層監聽手勢,則需要使用 GestureDetector。GestureDetector 是一個處理各種高級用戶觸摸行為的 Widget,與 Listener 一樣,也是一個功能性組件。
//紅色container坐標 double _top = 0.0; double _left = 0.0; Stack(//使用Stack組件去疊加視圖,便于直接控制視圖坐標children: <Widget>[Positioned(top: _top,left: _left,child: GestureDetector(//手勢識別child: Container(color: Colors.red,width: 50,height: 50),//紅色子視圖onTap: ()=>print("Tap"),//點擊回調onDoubleTap: ()=>print("Double Tap"),//雙擊回調onLongPress: ()=>print("Long Press"),//長按回調onPanUpdate: (e) {//拖動回調setState(() {//更新位置_left += e.delta.dx;_top += e.delta.dy;});},),)], );
  • 我們對一個 Widget 同時監聽了多個手勢事件,但最終只會有一個手勢能夠得到本次事件的處理權。對于多個手勢的識別,Flutter 引入了手勢競技場(Arena)的概念,用來識別究竟哪個手勢可以響應用戶事件。手勢競技場會考慮用戶觸摸屏幕的時長、位移以及拖動方向,來確定最終手勢。
手勢競技場實現
  • 實際上,GestureDetector 內部對每一個手勢都建立了一個工廠類(Gesture Factory)。而工廠類的內部會使用手勢識別類(GestureRecognizer),來確定當前處理的手勢。
  • 而所有手勢的工廠類都會被交給 RawGestureDetector 類,以完成監測手勢的大量工作:使用 Listener 監聽原始指針事件,并在狀態改變時把信息同步給所有的手勢識別器,然后這些手勢會在競技場決定最后由誰來響應用戶事件。
  • 有些時候我們可能會在應用中給多個視圖注冊同類型的手勢監聽器,比如微博的信息流列表中的微博,點擊不同區域會有不同的響應:點擊頭像會進入用戶個人主頁,點擊圖片會進入查看大圖頁面,點擊其他部分會進入微博詳情頁等。
  • 像這樣的手勢識別發生在多個存在父子關系的視圖時,手勢競技場會一并檢查父視圖和子視圖的手勢,并且通常最終會確認由子視圖來響應事件。而這也是合乎常理的:從視覺效果上看,子視圖的視圖層級位于父視圖之上,相當于對其進行了遮擋,因此從事件處理上看,子視圖自然是事件響應的第一責任人。
GestureDetector(onTap: () => print('Parent tapped'),//父視圖的點擊回調child: Container(color: Colors.pinkAccent,child: Center(child: GestureDetector(onTap: () => print('Child tapped'),//子視圖的點擊回調child: Container(color: Colors.blueAccent,width: 200.0,height: 200.0,),),),), );
  • 運行這段代碼,然后在藍色區域進行點擊,可以發現:盡管父容器也監聽了點擊事件,但 Flutter 只響應了子容器的點擊事件。
  • 為了讓父容器也能接收到手勢,我們需要同時使用 RawGestureDetector 和 GestureFactory,來改變競技場決定由誰來響應用戶事件的結果。
  • 在此之前,我們還需要自定義一個手勢識別器,讓這個識別器在競技場被 PK 失敗時,能夠再把自己重新添加回來,以便接下來還能繼續去響應用戶事件。
class MultipleTapGestureRecognizer extends TapGestureRecognizer {@overridevoid rejectGesture(int pointer) {acceptGesture(pointer);} }
  • 接下來,我們需要將手勢識別器和其工廠類傳遞給 RawGestureDetector,以便用戶產生手勢交互事件時能夠立刻找到對應的識別方法。事實上,RawGestureDetector 的初始化函數所做的配置工作,就是定義不同手勢識別器和其工廠類的映射關系。
  • 這里,由于我們只需要處理點擊事件,所以只配置一個識別器即可。工廠類的初始化采用 GestureRecognizerFactoryWithHandlers 函數完成,這個函數提供了手勢識別對象創建,以及對應的初始化入口。
RawGestureDetector(//自己構造父Widget的手勢識別映射關系gestures: {//建立多手勢識別器與手勢識別工廠類的映射關系,從而返回可以響應該手勢的recognizerMultipleTapGestureRecognizer: GestureRecognizerFactoryWithHandlers<MultipleTapGestureRecognizer>(() => MultipleTapGestureRecognizer(),(MultipleTapGestureRecognizer instance) {instance.onTap = () => print('parent tapped ');//點擊回調},)},child: Container(color: Colors.pinkAccent,child: Center(child: GestureDetector(//子視圖可以繼續使用GestureDetectoronTap: () => print('Child tapped'),child: Container(...),),),), );
  • 運行一下這段代碼,我們可以看到,當點擊藍色容器時,其父容器也收到了 Tap 事件。

跨組件傳遞數據

  • 通過組合嵌套的方式,利用數據對基礎 Widget 的樣式進行視覺屬性定制,我們已經實現了多種界面布局,在 Flutter 中實現跨組件數據傳遞的標準方式是通過屬性傳值。

  • 對于稍微復雜一點的、尤其視圖層級比較深的 UI 樣式,一個屬性可能需要跨越很多層才能傳遞給子組件,這種傳遞方式就會導致中間很多并不需要這個屬性的組件也需要接收其子 Widget 的數據,不僅繁瑣而且冗余。

  • 所以,對于數據的跨層傳遞,Flutter 還提供了三種方案:InheritedWidget、Notification 和 EventBus。接下來,我將依次為你講解這三種方案。

InheritedWidget
  • InheritedWidget 是 Flutter 中的一個功能型 Widget,適用于在 Widget 樹中共享數據的場景。通過它,我們可以高效地將數據在 Widget 樹中進行跨層傳遞。
  • 之前通過 Theme 去訪問當前界面的樣式風格,從而進行樣式復用的例子,比如 Theme.of(context).primaryColor。
  • Theme 類是通過 InheritedWidget 實現的典型案例。在子 Widget 中通過 Theme.of 方法找到上層 Theme 的 Widget,獲取到其屬性的同時,建立子 Widget 和上層父 Widget 的觀察者關系,當上層父 Widget 屬性修改的時候,子 Widget 也會觸發更新。
  • 以 Flutter 工程模板中的計數器為例,說明 InheritedWidget 的使用方法。
    • 首先,為了使用 InheritedWidget,我們定義了一個繼承自它的新類 CountContainer。
    • 然后,我們將計數器狀態 count 屬性放到 CountContainer 中,并提供了一個 of 方法方便其子 Widget 在 Widget 樹中找到它。最后,我們重寫了 updateShouldNotify 方法,這個方法會在 Flutter 判斷 InheritedWidget 是否需要重建,從而通知下層觀察者組件更新數據時被調用到。在這里,我們直接判斷 count 是否相等即可。
class CountContainer extends InheritedWidget {//方便其子Widget在Widget樹中找到它static CountContainer of(BuildContext context) => context.inheritFromWidgetOfExactType(CountContainer) as CountContainer;final int count;CountContainer({Key key,@required this.count,@required Widget child,}): super(key: key, child: child);// 判斷是否需要更新@overridebool updateShouldNotify(CountContainer oldWidget) => count != oldWidget.count; }
  • 然后,我們使用 CountContainer 作為根節點,并用 0 初始化 count。隨后在其子 Widget Counter 中,我們通過 InheritedCountContainer.of 方法找到它,獲取計數狀態 count 并展示。
class _MyHomePageState extends State<MyHomePage> {@overrideWidget build(BuildContext context) {//將CountContainer作為根節點,并使用0作為初始化countreturn CountContainer(count: 0,child: Counter());} }class Counter extends StatelessWidget {@overrideWidget build(BuildContext context) {//獲取InheritedWidget節點CountContainer state = CountContainer.of(context);return Scaffold(appBar: AppBar(title: Text("InheritedWidget demo")),body: Text('You have pushed the button this many times: ${state.count}',),); }
  • 可以看到 InheritedWidget 的使用方法還是比較簡單的,無論 Counter 在 CountContainer 下層什么位置,都能獲取到其父 Widget 的計數屬性 count,再也不用手動傳遞屬性了。
  • 不過,InheritedWidget 僅提供了數據讀的能力,如果我們想要修改它的數據,則需要把它和 StatefulWidget 中的 State 配套使用。我們需要把 InheritedWidget 中的數據和相關的數據修改方法,全部移到 StatefulWidget 中的 State 上,而 InheritedWidget 只需要保留對它們的引用。
class CountContainer extends InheritedWidget {...final _MyHomePageState model;//直接使用MyHomePage中的State獲取數據final Function() increment;CountContainer({Key key,@required this.model,@required this.increment,@required Widget child,}): super(key: key, child: child);... }
  • 然后,我們將 count 數據和其對應的修改方法放在了 State 中,仍然使用 CountContainer 作為根節點,完成了數據和修改方法的初始化。
  • 在其子 Widget Counter 中,我們還是通過 InheritedCountContainer.of 方法找到它,將計數狀態 count 與 UI 展示同步,將按鈕的點擊事件與數據修改同步。
class _MyHomePageState extends State<MyHomePage> {int count = 0;void _incrementCounter() => setState(() {count++;});//修改計數器@overrideWidget build(BuildContext context) {return CountContainer(model: this,//將自身作為model交給CountContainerincrement: _incrementCounter,//提供修改數據的方法child:Counter());} }class Counter extends StatelessWidget {@overrideWidget build(BuildContext context) {//獲取InheritedWidget節點CountContainer state = CountContainer.of(context);return Scaffold(...body: Text('You have pushed the button this many times: ${state.model.count}', //關聯數據讀方法),floatingActionButton: FloatingActionButton(onPressed: state.increment), //關聯數據修改方法);} }
Notification
  • Notification 是 Flutter 中進行跨層數據共享的另一個重要的機制。如果說 InheritedWidget 的數據流動方式是從父 Widget 到子 Widget 逐層傳遞,那 Notificaiton 則恰恰相反,數據流動方式是從子 Widget 向上傳遞至父 Widget。這樣的數據傳遞機制適用于子 Widget 狀態變更,發送通知上報的場景。
  • 在之前的ListView學習中,介紹了 ScrollNotification 的使用方法:ListView 在滾動時會分發通知,我們可以在上層使用 NotificationListener 監聽 ScrollNotification,根據其狀態做出相應的處理。
  • 自定義通知的監聽與 ScrollNotification 并無不同,而如果想要實現自定義通知,我們首先需要繼承 Notification 類。Notification 類提供了 dispatch 方法,可以讓我們沿著 context 對應的 Element 節點樹向上逐層發送通知。
  • 自定義了一個通知和子 Widget。子 Widget 是一個按鈕,在點擊時會發送通知。
class CustomNotification extends Notification {CustomNotification(this.msg);final String msg; }//抽離出一個子Widget用來發通知 class CustomChild extends StatelessWidget {@overrideWidget build(BuildContext context) {return RaisedButton(//按鈕點擊時分發通知onPressed: () => CustomNotification("Hi").dispatch(context),child: Text("Fire Notification"),);} }
  • 在子 Widget 的父 Widget 中,我們監聽了這個通知,一旦收到通知,就會觸發界面刷新,展示收到的通知信息。
class _MyHomePageState extends State<MyHomePage> {String _msg = "通知:";@overrideWidget build(BuildContext context) {//監聽通知return NotificationListener<CustomNotification>(onNotification: (notification) {setState(() {_msg += notification.msg+" ";});//收到子Widget通知,更新msg},child:Column(mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[Text(_msg),CustomChild()],//將子Widget加入到視圖樹中));} }
EventBus
  • 無論是 InheritedWidget 還是 Notificaiton,它們的使用場景都需要依靠 Widget 樹,也就意味著只能在有父子關系的 Widget 之間進行數據共享。但是,組件間數據傳遞還有一種常見場景:這些組件間不存在父子關系。這時,事件總線 EventBus 就登場了。
  • 事件總線是在 Flutter 中實現跨組件通信的機制。它遵循發布 / 訂閱模式,允許訂閱者訂閱事件,當發布者觸發事件時,訂閱者和發布者之間可以通過事件進行交互。發布者和訂閱者之間無需有父子關系,甚至非 Widget 對象也可以發布 / 訂閱。這些特點與其他平臺的事件總線機制是類似的。
  • EventBus 是一個第三方插件,因此我們需要在 pubspec.yaml 文件中聲明它。
dependencies: event_bus: 1.1.0
  • EventBus 的使用方式靈活,可以支持任意對象的傳遞。所以在這里,我們傳輸數據的載體就選擇了一個有字符串屬性的自定義事件類 CustomEvent。
class CustomEvent {String msg;CustomEvent(this.msg); }
  • 然后定義了一個全局的 eventBus 對象,并在第一個頁面監聽了 CustomEvent 事件,一旦收到事件,就會刷新 UI。需要注意的是,千萬別忘了在 State 被銷毀時清理掉事件注冊,否則你會發現 State 永遠被 EventBus 持有著,無法釋放,從而造成內存泄漏。
//建立公共的event bus EventBus eventBus = new EventBus(); //第一個頁面 class _FirstScreenState extends State<FirstScreen> {String msg = "通知:";StreamSubscription subscription;@overrideinitState() {//監聽CustomEvent事件,刷新UIsubscription = eventBus.on<CustomEvent>().listen((event) {setState(() {msg+= event.msg;});//更新msg});super.initState();}dispose() {subscription.cancel();//State銷毀時,清理注冊super.dispose();}@overrideWidget build(BuildContext context) {return new Scaffold(body:Text(msg),...);} }
  • 最后,我們在第二個頁面以按鈕點擊回調的方式,觸發了 CustomEvent 事件。
class SecondScreen extends StatelessWidget {@overrideWidget build(BuildContext context) {return new Scaffold(...body: RaisedButton(child: Text('Fire Event'),// 觸發CustomEvent事件onPressed: ()=> eventBus.fire(CustomEvent("hello"))),);} }
  • 屬性傳值、InheritedWidget、Notification 與 EventBus 數據傳遞方式對比。

路由與導航實現頁面切換

  • 如果說 UI 框架的視圖元素的基本單位是組件,那應用程序的基本單位就是頁面了。對于擁有多個頁面的應用程序而言,如何從一個頁面平滑地過渡到另一個頁面,我們需要有一個統一的機制來管理頁面之間的跳轉,通常被稱為路由管理或導航管理。
  • 我們首先需要知道目標頁面對象,在完成目標頁面初始化后,用框架提供的方式打開它。比如,在 Android/iOS 中我們通常會初始化一個 Intent 或 ViewController,通過 startActivity 或 pushViewController 來打開一個新的頁面;而在 React 中,我們使用 navigation 來管理所有頁面,只要知道頁面的名稱,就可以立即導航到這個頁面。
  • 其實,Flutter 的路由管理也借鑒了這兩種設計思路。
路由管理
  • 在 Flutter 中,頁面之間的跳轉是通過 Route 和 Navigator 來管理的。
    • Route 是頁面的抽象,主要負責創建對應的界面,接收參數,響應 Navigator 打開和關閉;
    • 而 Navigator 則會維護一個路由棧管理 Route,Route 打開即入棧,Route 關閉即出棧,還可以直接替換棧內的某一個 Route。
  • 而根據是否需要提前注冊頁面標識符,Flutter 中的路由管理可以分為兩種方式。
    • 基本路由。無需提前注冊,在頁面切換時需要自己構造頁面實例。
    • 命名路由。需要提前注冊頁面標識符,在頁面切換時通過標識符直接打開新的路由。
基本路由
  • 在 Flutter 中,基本路由的使用方法和 Android/iOS 打開新頁面的方式非常相似。要導航到一個新的頁面,我們需要創建一個 MaterialPageRoute 的實例,調用 Navigator.push 方法將新頁面壓到堆棧的頂部。
  • 其中,MaterialPageRoute 是一種路由模板,定義了路由創建及切換過渡動畫的相關配置,可以針對不同平臺,實現與平臺頁面切換動畫風格一致的路由切換動畫。
  • 而如果我們想返回上一個頁面,則需要調用 Navigator.pop 方法從堆棧中刪除這個頁面。
class FirstScreen extends StatelessWidget {@overrideWidget build(BuildContext context) {return RaisedButton(//打開頁面onPressed: ()=> Navigator.push(context, MaterialPageRoute(builder: (context) => SecondScreen())););} }class SecondPage extends StatelessWidget {@overrideWidget build(BuildContext context) {return RaisedButton(// 回退頁面onPressed: ()=> Navigator.pop(context));} }
命名路由
  • 基本路由使用方式相對簡單靈活,適用于應用中頁面不多的場景。而在應用中頁面比較多的情況下,再使用基本路由方式,那么每次跳轉到一個新的頁面,我們都要手動創建 MaterialPageRoute 實例,初始化頁面,然后調用 push 方法打開它,還是比較麻煩的。
  • 所以,Flutter 提供了另外一種方式來簡化路由管理,即命名路由。我們給頁面起一個名字,然后就可以直接通過頁面名字打開它了。這種方式簡單直觀,與 React 中的 navigation 使用方式類似。
  • 要想通過名字來指定頁面切換,我們必須先給應用程序 MaterialApp 提供一個頁面名稱映射規則,即路由表 routes,這樣 Flutter 才知道名字與頁面 Widget 的對應關系。
  • 路由表實際上是一個 Map,其中 key 值對應頁面名字,而 value 值則是一個 WidgetBuilder 回調函數,我們需要在這個函數中創建對應的頁面。而一旦在路由表中定義好了頁面名字,我們就可以使用 Navigator.pushNamed 來打開頁面了。
MaterialApp(...//注冊路由routes:{"second_page":(context)=>SecondPage(),}, ); //使用名字打開頁面 Navigator.pushNamed(context,"second_page");
  • 不過由于路由的注冊和使用都采用字符串來標識,這就會帶來一個隱患:如果我們打開了一個不存在的路由會怎么辦?
    • 我們可以約定使用字符串常量去定義、使用路由,但我們無法避免通過接口數據下發的錯誤路由標識符場景。面對這種情況,無論是直接報錯或是不響應錯誤路由,都不是一個用戶體驗良好的解決辦法。
    • 更好的辦法是,對用戶進行友好的錯誤提示,比如跳轉到一個統一的 NotFoundScreen 頁面,也方便我們對這類錯誤進行統一收集、上報。
    • 在注冊路由表時,Flutter 提供了 UnknownRoute 屬性,我們可以對未知的路由標識符進行統一的頁面跳轉處理。
MaterialApp(...//注冊路由routes:{"second_page":(context)=>SecondPage(),},//錯誤路由處理,統一返回UnknownPageonUnknownRoute: (RouteSettings setting) => MaterialPageRoute(builder: (context) => UnknownPage()), );//使用錯誤名字打開頁面 Navigator.pushNamed(context,"unknown_page");
頁面參數
  • 與基本路由能夠精確地控制目標頁面初始化方式不同,命名路由只能通過字符串名字來初始化固定目標頁面。為了解決不同場景下目標頁面的初始化需求,Flutter 提供了路由參數的機制,可以在打開路由時傳遞相關參數,在目標頁面通過 RouteSettings 來獲取頁面參數。
//打開頁面時傳遞字符串參數 Navigator.of(context).pushNamed("second_page", arguments: "Hey");class SecondPage extends StatelessWidget {@overrideWidget build(BuildContext context) {//取出路由參數String msg = ModalRoute.of(context).settings.arguments as String;return Text(msg);} }
  • 除了頁面打開時需要傳遞參數,對于特定的頁面,在其關閉時,也需要傳遞參數告知頁面處理結果。
  • 比如在電商場景下,我們會在用戶把商品加入購物車時,打開登錄頁面讓用戶登錄,而在登錄操作完成之后,關閉登錄頁面返回到當前頁面時,登錄頁面會告訴當前頁面新的用戶身份,當前頁面則會用新的用戶身份刷新頁面。
  • 與 Android 提供的 startActivityForResult 方法可以監聽目標頁面的處理結果類似,Flutter 也提供了返回參數的機制。在 push 目標頁面時,可以設置目標頁面關閉時監聽函數,以獲取返回參數;而目標頁面可以在關閉路由時傳遞相關參數。
class SecondPage extends StatelessWidget {@overrideWidget build(BuildContext context) {return Scaffold(body: Column(children: <Widget>[Text('Message from first screen: $msg'),RaisedButton(child: Text('back'),//頁面關閉時傳遞參數onPressed: ()=> Navigator.pop(context,"Hi"))]));} }class _FirstPageState extends State<FirstPage> {String _msg='';@overrideWidget build(BuildContext context) {return new Scaffold(body: Column(children: <Widget>[RaisedButton(child: Text('命名路由(參數&回調)'),//打開頁面,并監聽頁面關閉時傳遞的參數onPressed: ()=> Navigator.pushNamed(context, "third_page",arguments: "Hey").then((msg)=>setState(()=>_msg=msg)),),Text('Message from Second screen: $_msg'),],),);} }
  • 在中大型應用中,我們通常會使用命名路由來管理頁面間的切換。命名路由的最重要作用,就是建立了字符串標識符與各個頁面之間的映射關系,使得各個頁面之間完全解耦,應用內頁面的切換只需要通過一個字符串標識符就可以搞定,為后期模塊化打好基礎。
補充

問題:Navigator.pushA->B->C->D,請問如何 D頁面 pop 到 B 呢?

答:Navigator.popUntil(context,ModalRoute.withName(‘B’));

總結

以上是生活随笔為你收集整理的Flutter技术与实战(4)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

日日天日日夜日日摸 | 老太婆性杂交欧美肥老太 | 国产av无码专区亚洲awww | 天天综合网天天综合色 | 日本大香伊一区二区三区 | 中文无码伦av中文字幕 | 人妻少妇精品久久 | 久久 国产 尿 小便 嘘嘘 | 国产精品毛片一区二区 | 亚洲精品一区三区三区在线观看 | 午夜精品久久久久久久 | 少妇性俱乐部纵欲狂欢电影 | 黑人巨大精品欧美黑寡妇 | 成人性做爰aaa片免费看不忠 | 狂野欧美激情性xxxx | 成年美女黄网站色大免费视频 | 18黄暴禁片在线观看 | 狠狠综合久久久久综合网 | 国产精品理论片在线观看 | 人妻少妇被猛烈进入中文字幕 | 纯爱无遮挡h肉动漫在线播放 | 大肉大捧一进一出视频出来呀 | 国产农村妇女高潮大叫 | 久久97精品久久久久久久不卡 | 丰满肥臀大屁股熟妇激情视频 | 亚洲一区二区三区四区 | 夜夜夜高潮夜夜爽夜夜爰爰 | 内射巨臀欧美在线视频 | 色综合久久中文娱乐网 | 人妻人人添人妻人人爱 | 丝袜 中出 制服 人妻 美腿 | 国产sm调教视频在线观看 | 久久精品成人欧美大片 | 人妻少妇精品无码专区二区 | 日韩av无码一区二区三区不卡 | 国内精品人妻无码久久久影院 | 内射爽无广熟女亚洲 | 人人妻人人澡人人爽精品欧美 | 成人欧美一区二区三区黑人免费 | 东北女人啪啪对白 | 欧美日韩在线亚洲综合国产人 | 九九在线中文字幕无码 | 中文精品久久久久人妻不卡 | 久久99热只有频精品8 | 一个人看的视频www在线 | 国产女主播喷水视频在线观看 | 99久久精品日本一区二区免费 | 精品久久久无码人妻字幂 | 亚洲自偷自偷在线制服 | 巨爆乳无码视频在线观看 | 精品人人妻人人澡人人爽人人 | 中文字幕无码人妻少妇免费 | 无码午夜成人1000部免费视频 | 国产人妖乱国产精品人妖 | 一本大道久久东京热无码av | 亚洲日韩一区二区三区 | 中文字幕精品av一区二区五区 | 无码吃奶揉捏奶头高潮视频 | 又大又黄又粗又爽的免费视频 | 久久久国产精品无码免费专区 | 丰满少妇人妻久久久久久 | 国产亚洲精品久久久久久久 | 国产精品永久免费视频 | 国产成人无码av片在线观看不卡 | 国产精品福利视频导航 | 天堂а√在线中文在线 | 影音先锋中文字幕无码 | 人妻体内射精一区二区三四 | 国产农村妇女高潮大叫 | 国产亚洲精品久久久久久久久动漫 | 欧美一区二区三区 | 日韩欧美成人免费观看 | 永久免费精品精品永久-夜色 | 欧美人与动性行为视频 | 荫蒂添的好舒服视频囗交 | 在线а√天堂中文官网 | 天堂亚洲2017在线观看 | 中文毛片无遮挡高清免费 | 2020久久香蕉国产线看观看 | 成人片黄网站色大片免费观看 | 啦啦啦www在线观看免费视频 | 午夜福利一区二区三区在线观看 | 国产精品永久免费视频 | 中文字幕av无码一区二区三区电影 | 国精产品一品二品国精品69xx | 好男人www社区 | 88国产精品欧美一区二区三区 | 一本精品99久久精品77 | 国产极品美女高潮无套在线观看 | 男女作爱免费网站 | 亚洲精品久久久久avwww潮水 | 国产成人无码av片在线观看不卡 | 性欧美大战久久久久久久 | 无码人妻av免费一区二区三区 | 国产97色在线 | 免 | 色婷婷香蕉在线一区二区 | 久久99精品国产麻豆 | 少妇无码av无码专区在线观看 | 日韩欧美中文字幕公布 | 人人妻人人澡人人爽欧美一区 | 噜噜噜亚洲色成人网站 | 午夜精品久久久久久久久 | 亚洲午夜久久久影院 | 88国产精品欧美一区二区三区 | 亚洲国产欧美在线成人 | 午夜福利一区二区三区在线观看 | 荫蒂添的好舒服视频囗交 | 十八禁真人啪啪免费网站 | 香港三级日本三级妇三级 | 丰腴饱满的极品熟妇 | 欧美激情内射喷水高潮 | 熟妇人妻无乱码中文字幕 | 亚洲成在人网站无码天堂 | 性生交大片免费看女人按摩摩 | 无码国产色欲xxxxx视频 | 欧洲精品码一区二区三区免费看 | 激情五月综合色婷婷一区二区 | 麻豆国产人妻欲求不满谁演的 | 久久久久亚洲精品中文字幕 | 精品国产麻豆免费人成网站 | 国产sm调教视频在线观看 | 成人三级无码视频在线观看 | 激情五月综合色婷婷一区二区 | 国产亚洲精品久久久久久久久动漫 | 乱人伦人妻中文字幕无码久久网 | 中文无码精品a∨在线观看不卡 | 成人三级无码视频在线观看 | 中文字幕无码人妻少妇免费 | 夜精品a片一区二区三区无码白浆 | 欧洲欧美人成视频在线 | 人妻天天爽夜夜爽一区二区 | 偷窥村妇洗澡毛毛多 | 成人无码视频免费播放 | 久久精品国产一区二区三区 | 国产在线精品一区二区高清不卡 | 欧美人与善在线com | 丝袜 中出 制服 人妻 美腿 | 97夜夜澡人人双人人人喊 | 黑人大群体交免费视频 | 无码av最新清无码专区吞精 | 国产成人精品优优av | 国产精品18久久久久久麻辣 | 亚洲天堂2017无码 | 久久久精品国产sm最大网站 | 亚洲一区av无码专区在线观看 | 成人精品一区二区三区中文字幕 | 精品国精品国产自在久国产87 | 无码人妻av免费一区二区三区 | 日日摸日日碰夜夜爽av | 久久熟妇人妻午夜寂寞影院 | 欧美真人作爱免费视频 | 无码帝国www无码专区色综合 | 大肉大捧一进一出视频出来呀 | 国产无av码在线观看 | 亚洲一区av无码专区在线观看 | 无码国产乱人伦偷精品视频 | 老司机亚洲精品影院无码 | 麻豆md0077饥渴少妇 | 国模大胆一区二区三区 | 天海翼激烈高潮到腰振不止 | 亚洲中文字幕无码中文字在线 | 最近的中文字幕在线看视频 | 天海翼激烈高潮到腰振不止 | 亚洲色偷偷男人的天堂 | 亚洲精品国产精品乱码视色 | 成人无码精品1区2区3区免费看 | 国产午夜精品一区二区三区嫩草 | 中文字幕无码乱人伦 | 十八禁视频网站在线观看 | 国产高清av在线播放 | 一个人看的www免费视频在线观看 | 福利一区二区三区视频在线观看 | 精品成在人线av无码免费看 | 久久亚洲a片com人成 | 国产精品va在线观看无码 | 欧美丰满少妇xxxx性 | 国产亚洲精品久久久闺蜜 | 久久久久国色av免费观看性色 | 国产香蕉尹人综合在线观看 | 久久久精品人妻久久影视 | 国产人妻精品午夜福利免费 | 国产一区二区三区精品视频 | 一二三四社区在线中文视频 | 亚洲 a v无 码免 费 成 人 a v | 亚洲色欲久久久综合网东京热 | 久久精品中文字幕一区 | 成人无码视频在线观看网站 | 婷婷色婷婷开心五月四房播播 | 精品成在人线av无码免费看 | 老司机亚洲精品影院无码 | 国产精品毛片一区二区 | 国产精品二区一区二区aⅴ污介绍 | 久久久久99精品成人片 | 黑森林福利视频导航 | 中文字幕乱码人妻二区三区 | 最新国产麻豆aⅴ精品无码 | 少妇人妻偷人精品无码视频 | 97久久精品无码一区二区 | 久久国产自偷自偷免费一区调 | 双乳奶水饱满少妇呻吟 | 国产凸凹视频一区二区 | 国内揄拍国内精品人妻 | 色狠狠av一区二区三区 | 骚片av蜜桃精品一区 | 无码人妻丰满熟妇区毛片18 | 久久午夜无码鲁丝片秋霞 | 国产人妻大战黑人第1集 | v一区无码内射国产 | 欧美猛少妇色xxxxx | 少妇的肉体aa片免费 | 激情内射亚州一区二区三区爱妻 | 亚洲国产精品无码一区二区三区 | 久久99精品久久久久久动态图 | 色狠狠av一区二区三区 | 亚洲国产精品无码一区二区三区 | √8天堂资源地址中文在线 | 人妻人人添人妻人人爱 | 国内精品久久毛片一区二区 | 亚洲人成网站免费播放 | 久久人人97超碰a片精品 | 亚洲精品欧美二区三区中文字幕 | 国产成人一区二区三区在线观看 | 亚洲欧美精品伊人久久 | 国产极品视觉盛宴 | 性史性农村dvd毛片 | 亚洲日本在线电影 | 麻豆精产国品 | 国产精品亚洲lv粉色 | 久9re热视频这里只有精品 | 亚洲天堂2017无码 | 伊人久久大香线蕉亚洲 | 国产精品毛片一区二区 | 欧美肥老太牲交大战 | 两性色午夜视频免费播放 | 国产精品久久久久9999小说 | 色一情一乱一伦一视频免费看 | 激情内射日本一区二区三区 | 久久国产精品偷任你爽任你 | 国产偷国产偷精品高清尤物 | 婷婷六月久久综合丁香 | 国产两女互慰高潮视频在线观看 | 澳门永久av免费网站 | 牲欲强的熟妇农村老妇女 | 国产成人精品一区二区在线小狼 | 亚洲精品国产第一综合99久久 | 3d动漫精品啪啪一区二区中 | 国产成人精品久久亚洲高清不卡 | 亚洲精品无码国产 | 人妻体内射精一区二区三四 | 色一情一乱一伦一区二区三欧美 | 丰满少妇高潮惨叫视频 | 女高中生第一次破苞av | 久久久久久九九精品久 | 精品成在人线av无码免费看 | 精品无码一区二区三区爱欲 | 我要看www免费看插插视频 | 久久久婷婷五月亚洲97号色 | 欧美人与善在线com | 亚洲s色大片在线观看 | 日本熟妇乱子伦xxxx | 国产精品a成v人在线播放 | 无码精品人妻一区二区三区av | 亚洲精品一区二区三区大桥未久 | 中国女人内谢69xxxx | 亚洲精品成a人在线观看 | 性欧美熟妇videofreesex | 美女张开腿让人桶 | 日韩精品a片一区二区三区妖精 | 无码人妻黑人中文字幕 | 国产深夜福利视频在线 | 久久午夜夜伦鲁鲁片无码免费 | 无码av岛国片在线播放 | 97夜夜澡人人双人人人喊 | 久久久久成人精品免费播放动漫 | 美女黄网站人色视频免费国产 | 婷婷丁香六月激情综合啪 | 无码帝国www无码专区色综合 | 无码人妻黑人中文字幕 | 少妇性l交大片 | 国产麻豆精品精东影业av网站 | 无套内射视频囯产 | 日日碰狠狠躁久久躁蜜桃 | 日韩精品无码一区二区中文字幕 | 日本大乳高潮视频在线观看 | 日本精品人妻无码77777 天堂一区人妻无码 | 日日碰狠狠丁香久燥 | 99久久精品国产一区二区蜜芽 | 夫妻免费无码v看片 | 精品一二三区久久aaa片 | 国产亚洲精品久久久久久大师 | 纯爱无遮挡h肉动漫在线播放 | 欧美自拍另类欧美综合图片区 | 奇米影视888欧美在线观看 | 亚洲国产av美女网站 | 国产成人午夜福利在线播放 | 欧美一区二区三区 | 少妇邻居内射在线 | a在线亚洲男人的天堂 | 成人一在线视频日韩国产 | 疯狂三人交性欧美 | 精品日本一区二区三区在线观看 | 日韩精品久久久肉伦网站 | 国产人妻大战黑人第1集 | 乱人伦人妻中文字幕无码久久网 | 成人性做爰aaa片免费看不忠 | 日韩人妻少妇一区二区三区 | 国产熟妇高潮叫床视频播放 | 无码乱肉视频免费大全合集 | 久久综合久久自在自线精品自 | 色综合久久中文娱乐网 | а√天堂www在线天堂小说 | 成人精品一区二区三区中文字幕 | 日韩精品乱码av一区二区 | 国产免费久久精品国产传媒 | 国产人成高清在线视频99最全资源 | 国产做国产爱免费视频 | 色妞www精品免费视频 | 亚洲熟妇自偷自拍另类 | 国产一区二区三区日韩精品 | 欧美自拍另类欧美综合图片区 | 国产亚av手机在线观看 | 久久亚洲精品中文字幕无男同 | 波多野42部无码喷潮在线 | 国产亚洲欧美日韩亚洲中文色 | 俺去俺来也在线www色官网 | 永久免费观看国产裸体美女 | 国产亚洲精品久久久ai换 | 欧美丰满熟妇xxxx | 丰满妇女强制高潮18xxxx | 欧美一区二区三区视频在线观看 | 波多野结衣一区二区三区av免费 | 无码人妻出轨黑人中文字幕 | 国产免费久久精品国产传媒 | 老熟妇仑乱视频一区二区 | 无码福利日韩神码福利片 | 国产成人无码午夜视频在线观看 | 九九综合va免费看 | 又大又硬又黄的免费视频 | 中文字幕av伊人av无码av | 久久综合九色综合97网 | 蜜桃臀无码内射一区二区三区 | 亚洲一区二区三区偷拍女厕 | 国产av无码专区亚洲a∨毛片 | 无人区乱码一区二区三区 | 日本一本二本三区免费 | 亚洲性无码av中文字幕 | 蜜桃av抽搐高潮一区二区 | 中文字幕无码av激情不卡 | 国产乱人无码伦av在线a | 成人一在线视频日韩国产 | 午夜无码区在线观看 | 国产极品美女高潮无套在线观看 | v一区无码内射国产 | 最近中文2019字幕第二页 | 国产激情艳情在线看视频 | 精品国产福利一区二区 | 性色欲网站人妻丰满中文久久不卡 | 亚洲爆乳精品无码一区二区三区 | 久久综合九色综合欧美狠狠 | 麻豆果冻传媒2021精品传媒一区下载 | 乌克兰少妇xxxx做受 | 欧美猛少妇色xxxxx | 日韩欧美中文字幕公布 | 精品一二三区久久aaa片 | 久久久精品欧美一区二区免费 | 大乳丰满人妻中文字幕日本 | 鲁鲁鲁爽爽爽在线视频观看 | 国产精品久久久久久无码 | 欧美性猛交内射兽交老熟妇 | 国产精品办公室沙发 | 精品一区二区不卡无码av | 男女爱爱好爽视频免费看 | 无码播放一区二区三区 | 草草网站影院白丝内射 | 久久精品成人欧美大片 | 亚洲日韩av一区二区三区四区 | 色综合久久久久综合一本到桃花网 | 无码播放一区二区三区 | 成在人线av无码免观看麻豆 | 日本在线高清不卡免费播放 | 在线观看国产午夜福利片 | 国产精品无码一区二区桃花视频 | 无码人妻av免费一区二区三区 | 天天爽夜夜爽夜夜爽 | 永久免费精品精品永久-夜色 | 色一情一乱一伦 | 俺去俺来也在线www色官网 | 日日麻批免费40分钟无码 | 伊在人天堂亚洲香蕉精品区 | 亚洲阿v天堂在线 | 成熟人妻av无码专区 | 久久久www成人免费毛片 | 真人与拘做受免费视频 | 久久婷婷五月综合色国产香蕉 | 国産精品久久久久久久 | 国产精品资源一区二区 | 亚洲高清偷拍一区二区三区 | 国产在线一区二区三区四区五区 | 日韩成人一区二区三区在线观看 | 欧美午夜特黄aaaaaa片 | 国产午夜无码视频在线观看 | 国产情侣作爱视频免费观看 | 131美女爱做视频 | 超碰97人人射妻 | 欧美成人高清在线播放 | 国产成人无码区免费内射一片色欲 | 色窝窝无码一区二区三区色欲 | 日韩无码专区 | 久久伊人色av天堂九九小黄鸭 | a在线观看免费网站大全 | 成在人线av无码免观看麻豆 | 帮老师解开蕾丝奶罩吸乳网站 | 任你躁在线精品免费 | 精品欧洲av无码一区二区三区 | 人人妻人人藻人人爽欧美一区 | 国产精品久免费的黄网站 | 欧美人妻一区二区三区 | 中文字幕无码日韩欧毛 | 久久久精品欧美一区二区免费 | 国产猛烈高潮尖叫视频免费 | 国产精品va在线观看无码 | 强辱丰满人妻hd中文字幕 | 国产无套内射久久久国产 | 十八禁真人啪啪免费网站 | 国产人妻精品一区二区三区不卡 | 思思久久99热只有频精品66 | 捆绑白丝粉色jk震动捧喷白浆 | 又大又硬又黄的免费视频 | 久精品国产欧美亚洲色aⅴ大片 | 亚欧洲精品在线视频免费观看 | 女人和拘做爰正片视频 | 久久综合九色综合97网 | 日本va欧美va欧美va精品 | 久久久中文久久久无码 | 99久久亚洲精品无码毛片 | 伊人久久婷婷五月综合97色 | 欧美日韩一区二区三区自拍 | 纯爱无遮挡h肉动漫在线播放 | 亚洲成a人片在线观看无码 | 国产农村妇女高潮大叫 | 97色伦图片97综合影院 | 丰满岳乱妇在线观看中字无码 | 中文精品无码中文字幕无码专区 | 精品久久久无码中文字幕 | 成人欧美一区二区三区黑人免费 | 秋霞成人午夜鲁丝一区二区三区 | 中文字幕无码热在线视频 | 乱码av麻豆丝袜熟女系列 | 国产内射爽爽大片视频社区在线 | 亚洲春色在线视频 | 内射老妇bbwx0c0ck | 亚洲精品一区二区三区大桥未久 | a片免费视频在线观看 | 久久亚洲国产成人精品性色 | 青青草原综合久久大伊人精品 | 丰满妇女强制高潮18xxxx | 蜜臀aⅴ国产精品久久久国产老师 | 中文字幕人成乱码熟女app | 日本丰满护士爆乳xxxx | 丰满肥臀大屁股熟妇激情视频 | 亚洲爆乳大丰满无码专区 | 波多野结衣av一区二区全免费观看 | 国产免费久久精品国产传媒 | 久久综合色之久久综合 | 欧美 丝袜 自拍 制服 另类 | 两性色午夜免费视频 | 欧美激情一区二区三区成人 | 国产精品美女久久久久av爽李琼 | 久久人人爽人人爽人人片ⅴ | 18禁黄网站男男禁片免费观看 | 国产特级毛片aaaaaa高潮流水 | 水蜜桃av无码 | 亚洲综合无码久久精品综合 | 国产精品二区一区二区aⅴ污介绍 | 在线观看免费人成视频 | 人妻中文无码久热丝袜 | 久久精品国产精品国产精品污 | 国产成人无码专区 | 伊人久久大香线蕉av一区二区 | 人妻人人添人妻人人爱 | 在线精品国产一区二区三区 | 国产在热线精品视频 | 久久久中文字幕日本无吗 | 300部国产真实乱 | 亚洲日韩av片在线观看 | 久久久亚洲欧洲日产国码αv | 国产国产精品人在线视 | 日韩欧美成人免费观看 | 亚洲va欧美va天堂v国产综合 | 国产内射老熟女aaaa | 精品无码国产一区二区三区av | 对白脏话肉麻粗话av | 亚洲中文字幕无码中字 | 国产亚洲欧美在线专区 | 免费观看激色视频网站 | 高清不卡一区二区三区 | 99久久久无码国产精品免费 | 国产精品igao视频网 | 波多野结衣一区二区三区av免费 | 亚洲成av人影院在线观看 | 波多野42部无码喷潮在线 | 日本免费一区二区三区最新 | 久久久精品成人免费观看 | 亚洲精品成a人在线观看 | 亚洲日韩av片在线观看 | 丝袜美腿亚洲一区二区 | 精品无人区无码乱码毛片国产 | 国产在线精品一区二区高清不卡 | 亚洲日韩精品欧美一区二区 | 久久午夜夜伦鲁鲁片无码免费 | 国产在线精品一区二区三区直播 | 国产亚av手机在线观看 | 久久99国产综合精品 | 免费中文字幕日韩欧美 | 99精品国产综合久久久久五月天 | 午夜精品久久久久久久久 | 国产内射老熟女aaaa | 四虎国产精品一区二区 | 无码任你躁久久久久久久 | 国产高潮视频在线观看 | 无码纯肉视频在线观看 | 激情五月综合色婷婷一区二区 | 亚洲国产欧美在线成人 | 国产精品美女久久久久av爽李琼 | 久久综合给合久久狠狠狠97色 | 亚洲欧美国产精品久久 | 中文无码成人免费视频在线观看 | 波多野结衣 黑人 | 一区二区三区高清视频一 | 亚洲 a v无 码免 费 成 人 a v | 51国偷自产一区二区三区 | 影音先锋中文字幕无码 | 国产精品久久久久9999小说 | 国产熟妇高潮叫床视频播放 | 丝袜足控一区二区三区 | 亚洲精品一区二区三区婷婷月 | 51国偷自产一区二区三区 | 熟妇人妻无码xxx视频 | 娇妻被黑人粗大高潮白浆 | 精品人人妻人人澡人人爽人人 | 蜜桃无码一区二区三区 | 免费观看又污又黄的网站 | 欧美丰满少妇xxxx性 | 久久久久久a亚洲欧洲av冫 | 毛片内射-百度 | 欧美日韩视频无码一区二区三 | 少妇无码av无码专区在线观看 | 人妻少妇精品无码专区动漫 | 亚洲一区二区三区含羞草 | 小泽玛莉亚一区二区视频在线 | 99久久婷婷国产综合精品青草免费 | 亚洲爆乳大丰满无码专区 | 国产情侣作爱视频免费观看 | 成人精品视频一区二区 | 天堂在线观看www | 亚洲综合精品香蕉久久网 | 无码成人精品区在线观看 | 77777熟女视频在线观看 а天堂中文在线官网 | 婷婷综合久久中文字幕蜜桃三电影 | 国产69精品久久久久app下载 | 国产亚洲视频中文字幕97精品 | 漂亮人妻洗澡被公强 日日躁 | 久久国产精品_国产精品 | 中文字幕av伊人av无码av | 中文精品无码中文字幕无码专区 | 精品一区二区不卡无码av | 色婷婷综合激情综在线播放 | 亚洲男人av香蕉爽爽爽爽 | 久精品国产欧美亚洲色aⅴ大片 | 中文字幕无码免费久久9一区9 | aⅴ在线视频男人的天堂 | 午夜精品一区二区三区的区别 | 久久久无码中文字幕久... | 久久精品国产大片免费观看 | 欧美猛少妇色xxxxx | 成人精品天堂一区二区三区 | 亚洲中文字幕va福利 | 野外少妇愉情中文字幕 | 成人欧美一区二区三区黑人 | 欧美激情综合亚洲一二区 | 2020久久香蕉国产线看观看 | 成年女人永久免费看片 | 免费看男女做好爽好硬视频 | 性欧美牲交xxxxx视频 | 99久久精品国产一区二区蜜芽 | 久激情内射婷内射蜜桃人妖 | 精品午夜福利在线观看 | 牲欲强的熟妇农村老妇女视频 | 欧洲vodafone精品性 | 日韩精品无码免费一区二区三区 | 在线a亚洲视频播放在线观看 | www一区二区www免费 | 久久久久99精品成人片 | 色一情一乱一伦 | 亚洲国产成人a精品不卡在线 | 性欧美疯狂xxxxbbbb | 日日天日日夜日日摸 | 国产xxx69麻豆国语对白 | 中文字幕精品av一区二区五区 | 久久精品国产日本波多野结衣 | 麻豆国产丝袜白领秘书在线观看 | 亚洲精品中文字幕久久久久 | 一本久道高清无码视频 | 日韩 欧美 动漫 国产 制服 | 久久久久久国产精品无码下载 | 宝宝好涨水快流出来免费视频 | 欧洲熟妇色 欧美 | 亚洲人成网站在线播放942 | 国产 浪潮av性色四虎 | 欧美老人巨大xxxx做受 | 国产亚洲欧美日韩亚洲中文色 | 九一九色国产 | 亚洲色大成网站www国产 | 丝袜足控一区二区三区 | 成人欧美一区二区三区 | 精品国产一区av天美传媒 | 久久国产精品萌白酱免费 | 国产精品igao视频网 | 久久国语露脸国产精品电影 | 亚洲日韩精品欧美一区二区 | 国产精品高潮呻吟av久久 | 久久久久成人片免费观看蜜芽 | 狠狠色丁香久久婷婷综合五月 | 亚洲色在线无码国产精品不卡 | 天堂亚洲免费视频 | 波多野结衣高清一区二区三区 | 国产亚洲美女精品久久久2020 | 国产精品手机免费 | 亚洲国产精品无码一区二区三区 | 东京无码熟妇人妻av在线网址 | 美女黄网站人色视频免费国产 | av无码不卡在线观看免费 | 欧洲极品少妇 | 国产麻豆精品一区二区三区v视界 | 日韩精品无码一本二本三本色 | 亚洲日韩av片在线观看 | 久久99国产综合精品 | 国产艳妇av在线观看果冻传媒 | 欧美精品无码一区二区三区 | 国产精品二区一区二区aⅴ污介绍 | 两性色午夜视频免费播放 | 久久伊人色av天堂九九小黄鸭 | 男女下面进入的视频免费午夜 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 性色av无码免费一区二区三区 | 性色av无码免费一区二区三区 | 精品久久久中文字幕人妻 | 亚洲日韩av片在线观看 | 国产熟妇高潮叫床视频播放 | 国产亚洲精品久久久久久久 | 欧美激情内射喷水高潮 | 亚洲精品综合一区二区三区在线 | 性欧美大战久久久久久久 | 亚洲一区二区三区播放 | 高清国产亚洲精品自在久久 | 人人妻人人澡人人爽人人精品 | 国产精品香蕉在线观看 | 国产精品久久国产三级国 | 少妇太爽了在线观看 | 欧美亚洲国产一区二区三区 | 中文字幕久久久久人妻 | 少妇的肉体aa片免费 | 久久成人a毛片免费观看网站 | 欧美黑人乱大交 | 欧美成人家庭影院 | 夜夜影院未满十八勿进 | 国精品人妻无码一区二区三区蜜柚 | 图片小说视频一区二区 | 成人无码精品一区二区三区 | 亚洲啪av永久无码精品放毛片 | 国内综合精品午夜久久资源 | 中文字幕乱码亚洲无线三区 | 人妻天天爽夜夜爽一区二区 | 扒开双腿疯狂进出爽爽爽视频 | 永久免费观看美女裸体的网站 | 福利一区二区三区视频在线观看 | 亚洲人成网站色7799 | 人妻体内射精一区二区三四 | 久久综合网欧美色妞网 | 久久国产精品萌白酱免费 | 国产免费观看黄av片 | 国产九九九九九九九a片 | 亚洲精品久久久久久一区二区 | 国产熟女一区二区三区四区五区 | 免费中文字幕日韩欧美 | 国产内射爽爽大片视频社区在线 | 综合网日日天干夜夜久久 | 久久国产自偷自偷免费一区调 | 图片小说视频一区二区 | 伊人久久大香线蕉亚洲 | а√天堂www在线天堂小说 | 国产尤物精品视频 | 国产午夜亚洲精品不卡 | 99精品无人区乱码1区2区3区 | 亚洲国产一区二区三区在线观看 | 色狠狠av一区二区三区 | 国产肉丝袜在线观看 | 久久精品国产一区二区三区肥胖 | 97久久超碰中文字幕 | 国内少妇偷人精品视频免费 | 国产三级精品三级男人的天堂 | 亚洲日本va午夜在线电影 | 四虎永久在线精品免费网址 | 亚洲精品欧美二区三区中文字幕 | 亚洲自偷自拍另类第1页 | 精品国产福利一区二区 | 中文字幕无码av波多野吉衣 | 久久久久久九九精品久 | 免费观看的无遮挡av | 欧美自拍另类欧美综合图片区 | 国语自产偷拍精品视频偷 | 欧美喷潮久久久xxxxx | 国产精品无码永久免费888 | 国产成人综合在线女婷五月99播放 | 高清不卡一区二区三区 | 日韩成人一区二区三区在线观看 | 国产精品久久久久7777 | 午夜成人1000部免费视频 | 亚洲の无码国产の无码步美 | 爆乳一区二区三区无码 | 亚洲日韩乱码中文无码蜜桃臀网站 | 内射老妇bbwx0c0ck | 国产性猛交╳xxx乱大交 国产精品久久久久久无码 欧洲欧美人成视频在线 | 国产精品毛片一区二区 | 国内少妇偷人精品视频 | 欧美日韩亚洲国产精品 | 又紧又大又爽精品一区二区 | 日本护士xxxxhd少妇 | 天堂一区人妻无码 | 精品乱子伦一区二区三区 | 又大又紧又粉嫩18p少妇 | 久久久久se色偷偷亚洲精品av | 国产97在线 | 亚洲 | 欧美性猛交xxxx富婆 | 亚洲色欲色欲天天天www | 日本爽爽爽爽爽爽在线观看免 | 国产精品丝袜黑色高跟鞋 | 久久久精品人妻久久影视 | 乌克兰少妇xxxx做受 | 久9re热视频这里只有精品 | 国产精品久久国产三级国 | 四虎永久在线精品免费网址 | 国产精品对白交换视频 | 国产无遮挡又黄又爽免费视频 | 精品午夜福利在线观看 | 97久久精品无码一区二区 | 久久精品丝袜高跟鞋 | 九月婷婷人人澡人人添人人爽 | 午夜精品一区二区三区的区别 | 一本无码人妻在中文字幕免费 | 久久99精品国产.久久久久 | 久久久久se色偷偷亚洲精品av | 精品成人av一区二区三区 | 无码午夜成人1000部免费视频 | 国产精品内射视频免费 | 国产两女互慰高潮视频在线观看 | 国产精品欧美成人 | 人妻熟女一区 | 日韩少妇内射免费播放 | 欧美激情内射喷水高潮 | 少妇人妻av毛片在线看 | 高清不卡一区二区三区 | 日本精品久久久久中文字幕 | 无码成人精品区在线观看 | 欧美精品在线观看 | 少妇被黑人到高潮喷出白浆 | 亲嘴扒胸摸屁股激烈网站 | 国产成人精品一区二区在线小狼 | 久久国产精品_国产精品 | 一本色道久久综合亚洲精品不卡 | 欧美喷潮久久久xxxxx | 波多野结衣高清一区二区三区 | 国产真人无遮挡作爱免费视频 | 国产激情精品一区二区三区 | 亚洲精品一区二区三区大桥未久 | 亚洲综合另类小说色区 | 麻豆国产97在线 | 欧洲 | 特黄特色大片免费播放器图片 | 成在人线av无码免观看麻豆 | 欧美激情综合亚洲一二区 | 激情内射日本一区二区三区 | 强奷人妻日本中文字幕 | 日韩无码专区 | 狠狠噜狠狠狠狠丁香五月 | 少妇性俱乐部纵欲狂欢电影 | 天天av天天av天天透 | 少妇人妻av毛片在线看 | 日本一卡二卡不卡视频查询 | 亚洲熟熟妇xxxx | 午夜肉伦伦影院 | 中文字幕人妻无码一夲道 | 久久精品无码一区二区三区 | 中文字幕乱码中文乱码51精品 | 在线 国产 欧美 亚洲 天堂 | 精品国产国产综合精品 | 久久久成人毛片无码 | 丰满少妇人妻久久久久久 | 亚洲精品一区二区三区婷婷月 | 亚洲自偷精品视频自拍 | 欧美freesex黑人又粗又大 | 日韩精品成人一区二区三区 | 成人三级无码视频在线观看 | 性欧美videos高清精品 | 青青青手机频在线观看 | 亚洲熟悉妇女xxx妇女av | 亚洲色大成网站www国产 | 欧洲熟妇色 欧美 | 国产乡下妇女做爰 | 欧美黑人乱大交 | 四虎影视成人永久免费观看视频 | 国产精品a成v人在线播放 | 无码人妻精品一区二区三区下载 | 无遮挡国产高潮视频免费观看 | 婷婷丁香五月天综合东京热 | 国产精品怡红院永久免费 | 久久久久99精品成人片 | 人人妻人人澡人人爽精品欧美 | 黑森林福利视频导航 | 久久亚洲日韩精品一区二区三区 | 天堂а√在线中文在线 | 欧美乱妇无乱码大黄a片 | 国产成人精品视频ⅴa片软件竹菊 | 一本久道高清无码视频 | 成人欧美一区二区三区黑人免费 | 激情人妻另类人妻伦 | 久久久国产精品无码免费专区 | 免费无码肉片在线观看 | 成人一区二区免费视频 | 久久综合给久久狠狠97色 | 99久久人妻精品免费一区 | 成人试看120秒体验区 | 人人妻在人人 | 性啪啪chinese东北女人 | 成人片黄网站色大片免费观看 | 无码av最新清无码专区吞精 | 久久午夜无码鲁丝片午夜精品 | 欧美人与禽猛交狂配 | 98国产精品综合一区二区三区 | 久久精品女人天堂av免费观看 | 精品久久久久香蕉网 | 亚洲中文字幕无码中文字在线 | 欧美35页视频在线观看 | 中文字幕无码免费久久99 | 久久99久久99精品中文字幕 | 牲欲强的熟妇农村老妇女视频 | 亚洲精品成a人在线观看 | 人妻天天爽夜夜爽一区二区 | 扒开双腿疯狂进出爽爽爽视频 | 亚洲精品国产精品乱码不卡 | 国产亚av手机在线观看 | 国产成人精品久久亚洲高清不卡 | 国产猛烈高潮尖叫视频免费 | 少妇性荡欲午夜性开放视频剧场 | 人人妻人人澡人人爽欧美一区九九 | 精品无码成人片一区二区98 | 国产精品久久久久久亚洲影视内衣 | 国产午夜福利100集发布 | 亚洲国产av美女网站 | 伊人色综合久久天天小片 | 国产在线一区二区三区四区五区 | av无码电影一区二区三区 | 欧美35页视频在线观看 | 真人与拘做受免费视频 | 特级做a爰片毛片免费69 | 亚洲色欲久久久综合网东京热 | 图片区 小说区 区 亚洲五月 | 欧美日韩色另类综合 | 亚洲日本va午夜在线电影 | 99久久精品日本一区二区免费 | 一本大道久久东京热无码av | 成人影院yy111111在线观看 | 国内老熟妇对白xxxxhd | 女人高潮内射99精品 | 久久久久久av无码免费看大片 | 人妻无码久久精品人妻 | 图片区 小说区 区 亚洲五月 | 免费看男女做好爽好硬视频 | 亚洲乱码日产精品bd | 久久成人a毛片免费观看网站 | 国产精品丝袜黑色高跟鞋 | 天堂а√在线地址中文在线 | 内射巨臀欧美在线视频 | 国内少妇偷人精品视频 | 精品无人区无码乱码毛片国产 | 人妻有码中文字幕在线 | 国内精品久久毛片一区二区 | 精品国偷自产在线 | 美女毛片一区二区三区四区 | 色综合久久久久综合一本到桃花网 | aa片在线观看视频在线播放 | 午夜精品久久久久久久 | 日韩人妻无码中文字幕视频 | 国产精品无码mv在线观看 | 亚洲国产欧美日韩精品一区二区三区 | 中文字幕乱码人妻无码久久 | 国产福利视频一区二区 | 欧美激情综合亚洲一二区 | 国产精品香蕉在线观看 | 国产免费无码一区二区视频 | 色婷婷久久一区二区三区麻豆 | 中文字幕无码免费久久9一区9 | 自拍偷自拍亚洲精品10p | 国语自产偷拍精品视频偷 | 俺去俺来也www色官网 | 美女扒开屁股让男人桶 | 黑人巨大精品欧美黑寡妇 | 色五月五月丁香亚洲综合网 | 国产xxx69麻豆国语对白 | 国产成人综合美国十次 | 少妇太爽了在线观看 | 精品国产麻豆免费人成网站 | 乱人伦人妻中文字幕无码久久网 | 激情人妻另类人妻伦 | 国产精品第一区揄拍无码 | 精品无码一区二区三区爱欲 | 性色欲情网站iwww九文堂 | 欧美黑人性暴力猛交喷水 | 亚洲精品无码人妻无码 | 激情人妻另类人妻伦 | 日本一区二区三区免费高清 | 久久国产精品萌白酱免费 | 亚洲狠狠色丁香婷婷综合 | 中文字幕无码视频专区 | 欧美黑人巨大xxxxx | 高中生自慰www网站 | 中文字幕乱妇无码av在线 | 免费观看的无遮挡av | 乌克兰少妇xxxx做受 | 大色综合色综合网站 | 亚洲熟妇色xxxxx欧美老妇 | 无码帝国www无码专区色综合 | 伊人久久大香线焦av综合影院 | 我要看www免费看插插视频 | 成人欧美一区二区三区黑人免费 | 中文字幕乱码人妻无码久久 | 欧美熟妇另类久久久久久多毛 | 精品偷拍一区二区三区在线看 | 内射老妇bbwx0c0ck | 久久伊人色av天堂九九小黄鸭 | 51国偷自产一区二区三区 | 亚洲一区二区三区国产精华液 | 亚洲熟妇色xxxxx亚洲 | 色婷婷久久一区二区三区麻豆 | 国产午夜福利亚洲第一 | 高清不卡一区二区三区 | 久久久久久av无码免费看大片 | 在线精品亚洲一区二区 | 成人无码视频在线观看网站 | 97se亚洲精品一区 | 国产精品美女久久久久av爽李琼 | 99久久99久久免费精品蜜桃 | 亚洲成在人网站无码天堂 | 玩弄少妇高潮ⅹxxxyw | 一本久久a久久精品亚洲 | 欧美 亚洲 国产 另类 | 国产欧美熟妇另类久久久 | 性做久久久久久久免费看 | 色诱久久久久综合网ywww | 亚洲精品综合一区二区三区在线 | 无人区乱码一区二区三区 | 内射白嫩少妇超碰 | 国产无遮挡又黄又爽又色 | 少妇无码一区二区二三区 | 欧美高清在线精品一区 | 中文字幕乱码人妻二区三区 | 国产超碰人人爽人人做人人添 | 亚洲精品美女久久久久久久 | 亚洲乱码中文字幕在线 | 亚洲日本在线电影 | 亚洲国产精品久久人人爱 | 黑人巨大精品欧美黑寡妇 | 欧美日韩精品 | 国产特级毛片aaaaaaa高清 | 国产99久久精品一区二区 | 亚洲成av人影院在线观看 | 国产两女互慰高潮视频在线观看 | 国产成人精品视频ⅴa片软件竹菊 | 秋霞特色aa大片 | 欧美日韩久久久精品a片 | 曰韩少妇内射免费播放 | 天堂无码人妻精品一区二区三区 | 精品国产一区二区三区四区在线看 | 国产亚洲视频中文字幕97精品 | 久久精品国产亚洲精品 | 国产激情无码一区二区app | 国产真实乱对白精彩久久 | 99久久精品午夜一区二区 | 亚洲国产成人a精品不卡在线 | 波多野结衣av一区二区全免费观看 | 日日橹狠狠爱欧美视频 | 网友自拍区视频精品 | 国产乱人无码伦av在线a | 一本久久a久久精品vr综合 | 国产激情一区二区三区 | 人妻无码αv中文字幕久久琪琪布 | 99久久精品国产一区二区蜜芽 | 国产偷抇久久精品a片69 | 国精品人妻无码一区二区三区蜜柚 | 国产艳妇av在线观看果冻传媒 | 中文字幕精品av一区二区五区 | 国产午夜亚洲精品不卡 | 国产精品无码一区二区桃花视频 | 久久国产精品萌白酱免费 | 女人和拘做爰正片视频 | 国产网红无码精品视频 | 老太婆性杂交欧美肥老太 | 性色av无码免费一区二区三区 | 国内精品人妻无码久久久影院蜜桃 | 99久久久无码国产精品免费 | 99精品视频在线观看免费 | aⅴ亚洲 日韩 色 图网站 播放 | 免费男性肉肉影院 | 久久久久亚洲精品中文字幕 | 日本护士xxxxhd少妇 | 自拍偷自拍亚洲精品被多人伦好爽 | 人人爽人人澡人人人妻 | 国产午夜精品一区二区三区嫩草 | 国产农村妇女高潮大叫 | 国产成人无码午夜视频在线观看 | 夜精品a片一区二区三区无码白浆 | 国产精品对白交换视频 | 最近中文2019字幕第二页 | 成人精品天堂一区二区三区 | 亚洲色成人中文字幕网站 | 超碰97人人射妻 | 国内少妇偷人精品视频 | 亚洲精品国偷拍自产在线观看蜜桃 | 久久久精品国产sm最大网站 | 亚洲性无码av中文字幕 | 久久精品中文字幕一区 | 青春草在线视频免费观看 | 一本色道婷婷久久欧美 | www成人国产高清内射 | 曰韩无码二三区中文字幕 | 久久久久成人精品免费播放动漫 | 天天躁日日躁狠狠躁免费麻豆 | 日韩精品成人一区二区三区 | 在线播放无码字幕亚洲 | 无码精品人妻一区二区三区av | 国产精品无码永久免费888 | 亚洲人亚洲人成电影网站色 | 亚洲七七久久桃花影院 | 国产欧美精品一区二区三区 | 欧美国产日产一区二区 | 婷婷丁香五月天综合东京热 | 人人妻人人澡人人爽欧美一区九九 | 久久www免费人成人片 | 男女超爽视频免费播放 | 国产精品二区一区二区aⅴ污介绍 | 国产精品第一国产精品 | 曰本女人与公拘交酡免费视频 | 99国产欧美久久久精品 | 亚洲中文字幕av在天堂 | 色狠狠av一区二区三区 | 黑人巨大精品欧美一区二区 | 中文亚洲成a人片在线观看 | a在线亚洲男人的天堂 | 国产综合色产在线精品 | 帮老师解开蕾丝奶罩吸乳网站 | 亚洲色欲久久久综合网东京热 | 大色综合色综合网站 | 给我免费的视频在线观看 | 免费观看黄网站 | 久久精品人人做人人综合试看 | 性做久久久久久久免费看 | 永久黄网站色视频免费直播 | 精品无码成人片一区二区98 | 国产欧美精品一区二区三区 | 免费观看的无遮挡av | 国色天香社区在线视频 | 一本久道久久综合婷婷五月 | 中文久久乱码一区二区 | 在线亚洲高清揄拍自拍一品区 | 无码一区二区三区在线观看 | 亚洲一区二区三区在线观看网站 | 久久久久久久人妻无码中文字幕爆 | 51国偷自产一区二区三区 | 日本丰满护士爆乳xxxx | www一区二区www免费 | 国产人妻精品一区二区三区不卡 | 成年美女黄网站色大免费全看 | 熟妇女人妻丰满少妇中文字幕 | 亚洲啪av永久无码精品放毛片 | www一区二区www免费 | 97夜夜澡人人爽人人喊中国片 | 中文字幕人妻无码一夲道 | 欧洲美熟女乱又伦 | 久久精品成人欧美大片 | 亚洲综合伊人久久大杳蕉 | 精品久久久无码中文字幕 | 亚洲一区二区三区在线观看网站 | 久久久久人妻一区精品色欧美 | 精品国产精品久久一区免费式 | 清纯唯美经典一区二区 | 又湿又紧又大又爽a视频国产 | 色诱久久久久综合网ywww | 久久国产自偷自偷免费一区调 | 99久久亚洲精品无码毛片 | 美女毛片一区二区三区四区 | 蜜桃av蜜臀av色欲av麻 999久久久国产精品消防器材 | 国产成人无码av在线影院 | 成年美女黄网站色大免费视频 | 爆乳一区二区三区无码 | 又大又黄又粗又爽的免费视频 | 国产精品毛多多水多 | 久久成人a毛片免费观看网站 | 亚洲人成影院在线观看 | 久久久久99精品国产片 | 日日碰狠狠躁久久躁蜜桃 | 国产69精品久久久久app下载 | 97夜夜澡人人爽人人喊中国片 | www一区二区www免费 | 国产亚洲精品久久久久久国模美 | 精品无码成人片一区二区98 | 亚洲国产精品久久人人爱 | 精品一二三区久久aaa片 | 乱人伦人妻中文字幕无码久久网 | 亚洲午夜福利在线观看 | 亚洲成av人影院在线观看 | 国产凸凹视频一区二区 | 黑森林福利视频导航 | 亚洲а∨天堂久久精品2021 | 曰本女人与公拘交酡免费视频 | 国产乱人无码伦av在线a | 国产亚洲美女精品久久久2020 | 成人免费视频一区二区 | 亚洲国产成人a精品不卡在线 | 久久久久成人精品免费播放动漫 | 国产成人精品久久亚洲高清不卡 | 东京热男人av天堂 | 国产 浪潮av性色四虎 | 亚洲精品久久久久久久久久久 | 亚洲啪av永久无码精品放毛片 | 国产农村妇女aaaaa视频 撕开奶罩揉吮奶头视频 | 欧美三级不卡在线观看 | 午夜精品一区二区三区在线观看 | 中文无码伦av中文字幕 | 国产猛烈高潮尖叫视频免费 | 亚洲精品一区二区三区在线观看 | 无码国内精品人妻少妇 | 国产亚洲精品久久久ai换 | а√天堂www在线天堂小说 | 秋霞成人午夜鲁丝一区二区三区 | 曰本女人与公拘交酡免费视频 | 国内精品久久久久久中文字幕 | 国产疯狂伦交大片 | 精品水蜜桃久久久久久久 | 国产97人人超碰caoprom | 无人区乱码一区二区三区 | 日本www一道久久久免费榴莲 | 强奷人妻日本中文字幕 | 色诱久久久久综合网ywww | 成人无码视频在线观看网站 | 日本一卡2卡3卡四卡精品网站 | av在线亚洲欧洲日产一区二区 | 亚洲国产欧美在线成人 | 国产精品高潮呻吟av久久 | 国产午夜无码视频在线观看 | 中文精品无码中文字幕无码专区 | 久久99精品久久久久久 | 国产亚洲精品精品国产亚洲综合 | 一区二区三区乱码在线 | 欧洲 | 欧美阿v高清资源不卡在线播放 | 亚洲无人区一区二区三区 | 国产成人午夜福利在线播放 | 中文亚洲成a人片在线观看 | 熟妇人妻激情偷爽文 | 一本一道久久综合久久 | 久久亚洲精品成人无码 | 亚洲精品鲁一鲁一区二区三区 | 成人aaa片一区国产精品 | 又大又硬又黄的免费视频 | 无码国模国产在线观看 | 久久国产精品萌白酱免费 | 国产精品va在线播放 | 日韩精品一区二区av在线 | 国产人妻久久精品二区三区老狼 | 国产亚洲日韩欧美另类第八页 | 欧美黑人乱大交 | 国产美女极度色诱视频www | 欧美精品国产综合久久 | 国产精品人妻一区二区三区四 | 国产激情艳情在线看视频 | 桃花色综合影院 | 又黄又爽又色的视频 | 精品国产精品久久一区免费式 | 97夜夜澡人人爽人人喊中国片 | 亚洲国产精品无码一区二区三区 | 国精品人妻无码一区二区三区蜜柚 | 亚洲自偷自拍另类第1页 | 精品久久久无码人妻字幂 | 日韩无码专区 | 日日麻批免费40分钟无码 | 丝袜足控一区二区三区 | 99久久久无码国产aaa精品 | 国产免费久久久久久无码 | 欧美丰满少妇xxxx性 | 亚洲精品国产品国语在线观看 | 男女爱爱好爽视频免费看 | 精品国产av色一区二区深夜久久 | 鲁鲁鲁爽爽爽在线视频观看 | 人人爽人人爽人人片av亚洲 | 亚洲日韩乱码中文无码蜜桃臀网站 | 极品嫩模高潮叫床 | 色婷婷欧美在线播放内射 | 欧洲vodafone精品性 | 樱花草在线播放免费中文 | 国产性猛交╳xxx乱大交 国产精品久久久久久无码 欧洲欧美人成视频在线 | 国产小呦泬泬99精品 | 久久综合给合久久狠狠狠97色 | 国语精品一区二区三区 | 中文字幕乱妇无码av在线 | 人妻少妇精品久久 | 亚洲国产精品久久人人爱 | 久久国产精品二国产精品 | 激情五月综合色婷婷一区二区 | 中文字幕精品av一区二区五区 | 亚洲小说春色综合另类 | 国产精品无码一区二区桃花视频 | 亚洲中文字幕av在天堂 | 色一情一乱一伦一视频免费看 | 国产午夜亚洲精品不卡 | 在线观看国产午夜福利片 | 国产无遮挡又黄又爽免费视频 | 精品一区二区三区波多野结衣 | 亚洲熟妇色xxxxx欧美老妇y | 亚洲色偷偷偷综合网 | 久久久久久国产精品无码下载 | 成熟妇人a片免费看网站 | 夜夜影院未满十八勿进 | 蜜桃av抽搐高潮一区二区 | 麻豆成人精品国产免费 | 无码国产乱人伦偷精品视频 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 国产亚洲精品久久久久久大师 | 亚洲人成人无码网www国产 | 中文字幕乱码亚洲无线三区 | 亚洲天堂2017无码 | 蜜臀av无码人妻精品 | 小鲜肉自慰网站xnxx | 小鲜肉自慰网站xnxx | 国产乱人无码伦av在线a | 丰满少妇女裸体bbw | 国产精品久久久久7777 | 中文无码成人免费视频在线观看 | 成熟人妻av无码专区 | 国内老熟妇对白xxxxhd | 午夜成人1000部免费视频 | 精品无码国产一区二区三区av | 亚洲区欧美区综合区自拍区 | 国产人妻久久精品二区三区老狼 | 1000部夫妻午夜免费 | 亚洲欧美日韩成人高清在线一区 | 免费中文字幕日韩欧美 | 日本一卡二卡不卡视频查询 | 又色又爽又黄的美女裸体网站 | 亚拍精品一区二区三区探花 | 国产无套内射久久久国产 | 漂亮人妻洗澡被公强 日日躁 | 东京热无码av男人的天堂 | 国产一区二区不卡老阿姨 | 久久精品丝袜高跟鞋 | 亚洲最大成人网站 | 欧美三级a做爰在线观看 | 亚洲精品一区二区三区婷婷月 | 久久国产精品精品国产色婷婷 | 高潮毛片无遮挡高清免费视频 | 精品一二三区久久aaa片 | 国产精品久久久久9999小说 | 久久国产精品精品国产色婷婷 | 国产精品国产三级国产专播 | 成人精品一区二区三区中文字幕 | 少妇性l交大片 | 又大又硬又黄的免费视频 | 99久久精品日本一区二区免费 | 久久久中文字幕日本无吗 | 国产真实乱对白精彩久久 | 色窝窝无码一区二区三区色欲 | 奇米影视7777久久精品 | 给我免费的视频在线观看 | 日本爽爽爽爽爽爽在线观看免 | 成人免费视频一区二区 | 精品人妻av区 | 中文字幕乱妇无码av在线 | 国产在线精品一区二区高清不卡 | 狠狠亚洲超碰狼人久久 | 亚洲熟妇色xxxxx欧美老妇y | 国产精品人人妻人人爽 | 强奷人妻日本中文字幕 | 18精品久久久无码午夜福利 | 国产精品久久久久久亚洲影视内衣 | 青青青手机频在线观看 | 日韩av激情在线观看 | 人人澡人人妻人人爽人人蜜桃 | 在线播放免费人成毛片乱码 | 中文字幕中文有码在线 | 国产熟女一区二区三区四区五区 | 无码福利日韩神码福利片 | 国产麻豆精品一区二区三区v视界 | 欧洲精品码一区二区三区免费看 | 久久精品女人天堂av免费观看 | 国精品人妻无码一区二区三区蜜柚 | 国产真实夫妇视频 | 亚洲精品成人福利网站 | 爱做久久久久久 | 午夜免费福利小电影 | 国色天香社区在线视频 | 香蕉久久久久久av成人 | √天堂中文官网8在线 | 日本爽爽爽爽爽爽在线观看免 | 婷婷丁香六月激情综合啪 | 成人免费无码大片a毛片 | 亚洲一区av无码专区在线观看 | 久久久久久av无码免费看大片 | 东北女人啪啪对白 | 乌克兰少妇性做爰 | 欧美性生交xxxxx久久久 | 久久久久久久久蜜桃 | 久久人人爽人人人人片 | 55夜色66夜色国产精品视频 | 欧美国产亚洲日韩在线二区 | 亚洲七七久久桃花影院 | 帮老师解开蕾丝奶罩吸乳网站 | 久久成人a毛片免费观看网站 | 免费男性肉肉影院 | 极品尤物被啪到呻吟喷水 | 亚洲国精产品一二二线 | 九九热爱视频精品 | 国产人妻久久精品二区三区老狼 | 婷婷综合久久中文字幕蜜桃三电影 | 熟妇人妻激情偷爽文 | 性欧美大战久久久久久久 | 水蜜桃亚洲一二三四在线 | 欧美变态另类xxxx | 四虎永久在线精品免费网址 | 欧美日韩色另类综合 | 亚洲日韩一区二区三区 | 国产特级毛片aaaaaa高潮流水 | 樱花草在线社区www | v一区无码内射国产 | 男人的天堂2018无码 | 午夜成人1000部免费视频 | 久久久久久久久蜜桃 | 久久精品一区二区三区四区 | 男女作爱免费网站 | 精品国产一区二区三区四区在线看 | 国产网红无码精品视频 | 国产乱人无码伦av在线a | 丰满少妇女裸体bbw | a片免费视频在线观看 | 人妻体内射精一区二区三四 | 精品亚洲成av人在线观看 | 色综合久久中文娱乐网 | 亚洲欧洲中文日韩av乱码 | 美女黄网站人色视频免费国产 | 大乳丰满人妻中文字幕日本 | 女高中生第一次破苞av | 亚洲精品一区二区三区在线观看 | 亚洲精品成a人在线观看 | 亚洲 欧美 激情 小说 另类 | 日欧一片内射va在线影院 | 国产无遮挡吃胸膜奶免费看 | 人妻天天爽夜夜爽一区二区 | 初尝人妻少妇中文字幕 | 国产亚洲日韩欧美另类第八页 | 丁香花在线影院观看在线播放 | 国产精品毛多多水多 | 久久99国产综合精品 | 精品久久综合1区2区3区激情 | 亚洲理论电影在线观看 | 日本精品高清一区二区 | 国产乱人伦app精品久久 国产在线无码精品电影网 国产国产精品人在线视 | 国产综合久久久久鬼色 | 在线亚洲高清揄拍自拍一品区 | 久久伊人色av天堂九九小黄鸭 | 色婷婷av一区二区三区之红樱桃 | 1000部夫妻午夜免费 | 国产精品va在线播放 | 国语自产偷拍精品视频偷 | 国产真实乱对白精彩久久 | 三上悠亚人妻中文字幕在线 | 67194成是人免费无码 | 国内精品人妻无码久久久影院蜜桃 | 老子影院午夜伦不卡 | 亚洲国产成人a精品不卡在线 | 又色又爽又黄的美女裸体网站 | 国产亚洲欧美日韩亚洲中文色 | 狠狠色噜噜狠狠狠狠7777米奇 | 亚洲 a v无 码免 费 成 人 a v | 亚洲乱码国产乱码精品精 | 久青草影院在线观看国产 | 亚洲性无码av中文字幕 | 免费男性肉肉影院 | 久久天天躁狠狠躁夜夜免费观看 | 日本成熟视频免费视频 | 国产99久久精品一区二区 | 扒开双腿疯狂进出爽爽爽视频 | 亚洲热妇无码av在线播放 | 无码吃奶揉捏奶头高潮视频 | 蜜桃视频韩日免费播放 | 大乳丰满人妻中文字幕日本 | 亚洲精品中文字幕乱码 | 国产乱人偷精品人妻a片 | 久久人人爽人人爽人人片av高清 | 好男人社区资源 | 激情综合激情五月俺也去 | 国产另类ts人妖一区二区 | 亚洲精品成人福利网站 | 成人亚洲精品久久久久 | 国产女主播喷水视频在线观看 | 国产福利视频一区二区 | 狠狠躁日日躁夜夜躁2020 | 天堂亚洲2017在线观看 | 5858s亚洲色大成网站www | 三级4级全黄60分钟 | 欧美黑人乱大交 | 国产激情无码一区二区 | 久久精品人妻少妇一区二区三区 | 日本一卡2卡3卡4卡无卡免费网站 国产一区二区三区影院 | 久久精品视频在线看15 | 亚洲国产高清在线观看视频 | 久久综合网欧美色妞网 | 无码福利日韩神码福利片 | 图片区 小说区 区 亚洲五月 | 内射爽无广熟女亚洲 | 东京无码熟妇人妻av在线网址 | 亚洲男女内射在线播放 | 亚洲区小说区激情区图片区 | 无码一区二区三区在线 | 日本高清一区免费中文视频 | 亚洲国产欧美日韩精品一区二区三区 | 国产精品久久久久9999小说 | 亚洲精品中文字幕 | 免费人成在线视频无码 | 国产av一区二区精品久久凹凸 | 亚洲一区二区三区无码久久 | 少妇人妻偷人精品无码视频 | 午夜时刻免费入口 | 亚洲一区二区三区在线观看网站 | 中文字幕无码av激情不卡 | √天堂资源地址中文在线 | 四虎影视成人永久免费观看视频 | 4hu四虎永久在线观看 | 久久久久久a亚洲欧洲av冫 | 中国女人内谢69xxxxxa片 | 搡女人真爽免费视频大全 | 欧美 丝袜 自拍 制服 另类 | 人人澡人人妻人人爽人人蜜桃 | 永久免费观看国产裸体美女 | 国产精品亚洲一区二区三区喷水 | 国产美女极度色诱视频www | 一本精品99久久精品77 | 丰腴饱满的极品熟妇 | 双乳奶水饱满少妇呻吟 | 亚洲 日韩 欧美 成人 在线观看 | 撕开奶罩揉吮奶头视频 | 精品 日韩 国产 欧美 视频 | 女人被爽到呻吟gif动态图视看 | 国产va免费精品观看 | 国产精品va在线播放 | 97久久精品无码一区二区 | 我要看www免费看插插视频 | 大肉大捧一进一出视频出来呀 | 玩弄中年熟妇正在播放 | 国产偷国产偷精品高清尤物 | 老司机亚洲精品影院无码 | 精品成在人线av无码免费看 | 午夜肉伦伦影院 | 偷窥村妇洗澡毛毛多 | 国产在线无码精品电影网 | 国产成人精品优优av | 装睡被陌生人摸出水好爽 | 日本一区二区更新不卡 | 少妇无码吹潮 | 成人欧美一区二区三区黑人 | 久久天天躁狠狠躁夜夜免费观看 | 无码帝国www无码专区色综合 | 久久无码中文字幕免费影院蜜桃 | 久久久av男人的天堂 | 亚洲阿v天堂在线 | 18禁黄网站男男禁片免费观看 | 又色又爽又黄的美女裸体网站 | 欧美成人免费全部网站 | 欧美一区二区三区视频在线观看 | 99久久精品午夜一区二区 | 亚洲欧洲中文日韩av乱码 | 国产精品办公室沙发 | 好男人社区资源 | 亚洲熟妇色xxxxx欧美老妇 | 丰满人妻一区二区三区免费视频 | 国产成人一区二区三区在线观看 | 黑人粗大猛烈进出高潮视频 | 亚洲一区二区三区 | 国内精品人妻无码久久久影院 | 国产人妻久久精品二区三区老狼 | 国产亚av手机在线观看 | 国产成人无码av片在线观看不卡 | 大胆欧美熟妇xx | 国产精品久久久久7777 | 久久精品中文字幕大胸 | 亚洲熟妇自偷自拍另类 | 亚洲自偷精品视频自拍 | 亚洲第一无码av无码专区 | 国内少妇偷人精品视频 | 国产成人一区二区三区在线观看 | 51国偷自产一区二区三区 | 一本久道久久综合婷婷五月 | a片免费视频在线观看 | 日本一卡二卡不卡视频查询 | 狠狠综合久久久久综合网 | 亚洲区小说区激情区图片区 | 亚洲国产精品无码久久久久高潮 | 日韩欧美成人免费观看 | 国产成人精品久久亚洲高清不卡 | 大屁股大乳丰满人妻 | 性欧美牲交在线视频 | 国产成人久久精品流白浆 | 成人欧美一区二区三区黑人 | 久久综合久久自在自线精品自 | 人人妻人人澡人人爽欧美一区 | 一区二区三区乱码在线 | 欧洲 | 欧美性生交活xxxxxdddd | 国产偷自视频区视频 | 国产高清不卡无码视频 | 国产人妻精品一区二区三区不卡 | 亚洲精品成人福利网站 | 亚洲精品国产精品乱码不卡 | 一本色道久久综合亚洲精品不卡 | 波多野结衣av在线观看 | 无码人妻丰满熟妇区五十路百度 | 亚洲中文字幕无码中文字在线 | 亚洲小说春色综合另类 | 俺去俺来也www色官网 | 精品久久综合1区2区3区激情 | 高清国产亚洲精品自在久久 | 丰腴饱满的极品熟妇 | 国产精品久久久久久亚洲影视内衣 | 中文字幕乱码人妻二区三区 | 亚洲一区二区三区播放 | 中文字幕无码乱人伦 | 亚洲色偷偷男人的天堂 | 麻豆国产人妻欲求不满 | 国产疯狂伦交大片 | √天堂资源地址中文在线 | 国产精品久久久av久久久 | 日本成熟视频免费视频 | 欧美兽交xxxx×视频 | 小鲜肉自慰网站xnxx | 99久久精品无码一区二区毛片 | 三级4级全黄60分钟 | 小泽玛莉亚一区二区视频在线 | 国产内射爽爽大片视频社区在线 | 亚洲精品国产精品乱码视色 | 国产麻豆精品精东影业av网站 | 最近中文2019字幕第二页 | 久久无码中文字幕免费影院蜜桃 | 人人妻人人藻人人爽欧美一区 | 精品国产一区二区三区四区 | 青青草原综合久久大伊人精品 | 黑人巨大精品欧美一区二区 | 人妻体内射精一区二区三四 | 亚洲精品久久久久avwww潮水 | 国产精品理论片在线观看 | 男女性色大片免费网站 | 人人澡人人妻人人爽人人蜜桃 | 一本大道久久东京热无码av | 日韩精品a片一区二区三区妖精 | 欧美兽交xxxx×视频 | 亚洲自偷自偷在线制服 | 欧洲美熟女乱又伦 | 中文字幕无码日韩欧毛 | 99精品久久毛片a片 | 国产极品美女高潮无套在线观看 | 国产av人人夜夜澡人人爽麻豆 | 久久www免费人成人片 | 亚洲精品一区二区三区大桥未久 | 日韩人妻无码一区二区三区久久99 | 麻豆蜜桃av蜜臀av色欲av | 台湾无码一区二区 | 97久久国产亚洲精品超碰热 | 性欧美videos高清精品 | 精品欧洲av无码一区二区三区 | 最近中文2019字幕第二页 | 蜜臀av在线播放 久久综合激激的五月天 | 小鲜肉自慰网站xnxx | 国产精品久久久久久亚洲毛片 | 亚洲精品国产品国语在线观看 | 小sao货水好多真紧h无码视频 | 久久午夜无码鲁丝片秋霞 | 国产综合色产在线精品 | 日本在线高清不卡免费播放 | 中文字幕无线码 | √天堂资源地址中文在线 | 欧美丰满熟妇xxxx性ppx人交 | 国产猛烈高潮尖叫视频免费 | 又湿又紧又大又爽a视频国产 | 婷婷色婷婷开心五月四房播播 | 伊人久久大香线蕉av一区二区 | 精品久久久无码中文字幕 | 亚洲大尺度无码无码专区 | 国产精品沙发午睡系列 | 国产激情无码一区二区app | 亚洲熟妇色xxxxx欧美老妇y | 国产精品99久久精品爆乳 | 亚洲欧洲日本无在线码 | 亚洲国产午夜精品理论片 | 亚洲成av人影院在线观看 | 亚洲七七久久桃花影院 | www成人国产高清内射 | 国产成人精品三级麻豆 | 欧美丰满熟妇xxxx | 国产网红无码精品视频 | 国产成人精品视频ⅴa片软件竹菊 | 国产精品亚洲а∨无码播放麻豆 | 欧美精品免费观看二区 | 国产熟妇另类久久久久 | 日本精品高清一区二区 | 亚洲中文字幕在线观看 | 亚洲精品午夜国产va久久成人 | 丝袜美腿亚洲一区二区 | 久久午夜无码鲁丝片午夜精品 | 亚洲精品国偷拍自产在线观看蜜桃 | 波多野结衣高清一区二区三区 | 亚洲中文字幕在线观看 | 免费无码一区二区三区蜜桃大 | 亚洲va欧美va天堂v国产综合 | 精品国产精品久久一区免费式 | 丰满人妻翻云覆雨呻吟视频 | 国产午夜亚洲精品不卡下载 | 亚洲精品久久久久久一区二区 | 午夜成人1000部免费视频 | 国产亚洲欧美在线专区 | 国产精品久久久午夜夜伦鲁鲁 | 亚洲中文字幕无码一久久区 | 亚洲成av人影院在线观看 | 丝袜美腿亚洲一区二区 | 妺妺窝人体色www婷婷 | 亚洲中文字幕无码中字 | 国产在线精品一区二区高清不卡 | 欧美老熟妇乱xxxxx | 色五月五月丁香亚洲综合网 | √8天堂资源地址中文在线 | 日韩av无码一区二区三区不卡 | 99麻豆久久久国产精品免费 | 日韩av无码一区二区三区 | 无码人妻精品一区二区三区不卡 | 鲁一鲁av2019在线 | 婷婷五月综合激情中文字幕 | 永久免费精品精品永久-夜色 | 国产成人无码午夜视频在线观看 | 欧美第一黄网免费网站 | 无套内谢老熟女 | 久久久中文字幕日本无吗 | 亚洲日韩乱码中文无码蜜桃臀网站 | 免费国产成人高清在线观看网站 | 色五月丁香五月综合五月 | 樱花草在线播放免费中文 | 日本欧美一区二区三区乱码 | 国产人成高清在线视频99最全资源 | 国产精华av午夜在线观看 | 丰满妇女强制高潮18xxxx | 人妻少妇被猛烈进入中文字幕 | 精品欧美一区二区三区久久久 | 欧美野外疯狂做受xxxx高潮 | 精品夜夜澡人妻无码av蜜桃 | 精品久久久久久人妻无码中文字幕 | 亚洲啪av永久无码精品放毛片 | 在线 国产 欧美 亚洲 天堂 | 国产精品人人爽人人做我的可爱 | 激情内射日本一区二区三区 | 性色av无码免费一区二区三区 | 玩弄少妇高潮ⅹxxxyw | 精品人妻人人做人人爽夜夜爽 | 亚洲第一网站男人都懂 | 国产人妻精品午夜福利免费 | 麻豆精产国品 |