Day08 - Flutter -滾動Widget

概述

  • ListView
  • GridView
  • sliver
  • 滾動的監(jiān)聽
一、ListView

移動端數(shù)據(jù)量比較大時,我們都是通過列表來進(jìn)行展示的,比如商品數(shù)據(jù)、聊天列表、通信錄、朋友圈等。
Android中,我們可以使用ListViewRecyclerView來實現(xiàn),在iOS中,我們可以通過UITableView來實現(xiàn)。
Flutter中,我們也有對應(yīng)的列表Widget,就是ListView

  • 1.1、ListView 基本創(chuàng)建
    ListView可以沿一個方向(垂直或水平方向,默認(rèn)是垂直方向)來排列其所有子Widget。
    一種最簡單的使用方式是直接將所有需要排列的子Widget放在ListView的children屬性中即可。
    我們來看一下直接使用ListView的代碼演練:

    • 1>、為了讓文字之間有一些間距,我使用了Padding Widget


      ListView的基本創(chuàng)建
      class MyHomeBody extends StatelessWidget {
       @override
       Widget build(BuildContext context) {
          return ListView(
             children: <Widget>[
                Padding(
                    padding: const EdgeInsets.all(20.0),
                    child: Text("人的一切痛苦,本質(zhì)上都是對自己無能的憤怒。", style: TextStyle(fontSize: 22.0, backgroundColor: Colors.brown),),
                ),
                Padding(
                    padding: const EdgeInsets.all(20.0),
                    child: Text("人活在世界上,不可以有偏差;而且多少要費點勁兒,才能把自己保持到理性的軌道上。", style: TextStyle(fontSize: 22.0, backgroundColor: Colors.brown),),
                ),
                Padding(
                    padding: const EdgeInsets.all(20.0),
                    child: Text("我活在世上,無非想要明白些道理,遇見些有趣的事。", style: TextStyle(fontSize: 22.0, backgroundColor: Colors.brown),),
                ),
              ],
            );
        }
      }
      

      提示:我們可以通過 List.generate創(chuàng)建子 Widget

      • List.generate(100, (index):第一個參數(shù)是加載多少個Widget, 第二個是第幾個

        class MyHomeBody extends StatelessWidget {
          @override
          Widget build(BuildContext context) {
        
             return ListView(
                children: List.generate(100, (index) {
                   return Text("Hello World $index");
                })
             );
          }
        }
        
    • 2>、ListTile的使用
      在開發(fā)中,我們經(jīng)常見到一種列表,有一個圖標(biāo)或圖片(Icon),有一個標(biāo)題(Title),有一個子標(biāo)題(Subtitle),還有尾部一個圖標(biāo)(Icon)。
      這個時候,我們可以使用ListTile來實現(xiàn):


      class MyHomeBody extends StatelessWidget {
         @override
         Widget build(BuildContext context) {
      
            return ListView(
                children: <Widget>[
                     ListTile(
                        leading: Icon(Icons.people, size: 20,),
                        title: Text("聯(lián)系人"),
                        subtitle: Text("聯(lián)系人信息"),
                        trailing: Icon(Icons.arrow_right),
                     ),
                     ListTile(
                        leading: Icon(Icons.people, size: 20,),
                        title: Text("郵箱"),
                        subtitle: Text("郵箱地址信息"),
                        trailing: Icon(Icons.arrow_right),
                     ),
                ],
            );
         }
      }
      
    • 3>、垂直方向滾動,默認(rèn)是垂直方向
      我們可以通過設(shè)置 scrollDirection 參數(shù)來控制視圖的滾動方向
      我們通過下面的代碼實現(xiàn)一個水平滾動的內(nèi)容:
      這里需要注意,我們需要給Container設(shè)置width,否則它是沒有寬度的,就不能正常顯示。或者我們也可以給ListView設(shè)置一個 itemExtent該屬性會設(shè)置滾動方向上每個item所占據(jù)的寬度

      class MyHomeBody extends StatelessWidget {
      
        @override
        Widget build(BuildContext context) {
            return ListView(
                scrollDirection: Axis.horizontal,
                itemExtent: 200,
                children: <Widget>[
                    Container(color: Colors.red, width: 200),
                    Container(color: Colors.green, width: 200),
                    Container(color: Colors.blue, width: 200),
                    Container(color: Colors.purple, width: 200),
                    Container(color: Colors.orange, width: 200),
                ],
            );
        }
      }
      
  • 1.2、ListView.build 創(chuàng)建
    通過構(gòu)造函數(shù)中的children傳入所有的子Widget有一個問題:默認(rèn)會創(chuàng)建出所有的子Widget。
    但是對于用戶來說,一次性構(gòu)建出所有的Widget并不會有什么差異,但是對于我們的程序來說會產(chǎn)生性能問題,而且會增加首屏的渲染時間。
    我們可以ListView.build來構(gòu)建子Widget,提供性能。

    class MyHomeBody extends StatelessWidget {
    
         @override
         Widget build(BuildContext context) {
             return ListView.builder(
                // 創(chuàng)建多少個 row
                itemCount: 50,
                // 滾動方向的 row 寬度
                itemExtent: 100,
                // 生成 Widget
                itemBuilder: (BuildContext ctx, int index) {
                   return ListTile(title: Text("標(biāo)題$index"), subtitle: Text("詳情內(nèi)容$index"));
                }
             );
         }
    }
    
  • 1.3、ListView.separated 創(chuàng)建(帶分割線)
    ListView.separated 可以生成列表項之間的分割器,它除了比ListView.builder多了一個separatorBuilder參數(shù),該參數(shù)是一個分割器生成器。
    下面我們看一個例子:奇數(shù)行添加一條藍(lán)色下劃線,偶數(shù)行添加一條紅色下劃線:

    ListView.separated 創(chuàng)建(帶分割線)

    class MyHomeBody extends StatelessWidget {
        @override
        Widget build(BuildContext context) {
            return ListView.separated(
                itemBuilder: (BuildContext context, int index) {
                   return ListTile(
                      leading: Icon(Icons.people),
                      trailing: Icon(Icons.arrow_right),
                      title: Text("聯(lián)系人${index+1}"),
                      subtitle: Text("聯(lián)系人電話${index+1}"),
                   );
                },
                itemCount: 10,
                separatorBuilder: (BuildContext context, int index) {
                   return Divider(
                     // 每個Widget 之間的距離
                     height: 30,
                     // 距離左邊的距離
                     indent: 16,
                     // 距離右邊的距離
                     endIndent: 16,
                     // 每條分割線的高度
                     thickness: 10,
                     color: index % 2 == 0 ? Colors.red : Colors.green,
                   );
                 },
             );
        }
    }
    
二、GridView 組件

GridView用于展示多列的展示,在開發(fā)中也非常常見,比如直播App中的主播列表、電商中的商品列表等等。
在Flutter中我們可以使用GridView來實現(xiàn),使用方式和ListView也比較相似。

  • 2.1、GridView構(gòu)造函數(shù)
    使用GridView的方式就是使用構(gòu)造函數(shù)來創(chuàng)建,和ListView對比有一個特殊的參數(shù):gridDelegate
    gridDelegate用于控制交叉軸的item數(shù)量或者寬度,需要傳入的類型是SliverGridDelegate,但是它是一個抽象類,所以我們需要傳入它的子類:

    • SliverGridDelegateWithFixedCrossAxisCount

      SliverGridDelegateWithFixedCrossAxisCount({
         @requireddouble crossAxisCount, // 交叉軸的item個數(shù)
         double mainAxisSpacing = 0.0,   // 主軸的間距
         double crossAxisSpacing = 0.0,  // 交叉軸的間距
         double childAspectRatio = 1.0,  // 子Widget的寬高比
      })
      

      如下代碼

      class MyHomeBody extends StatelessWidget {
         @override
         Widget build(BuildContext context) {
             return GridView(
                 gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                    crossAxisCount: 3,
                    crossAxisSpacing: 20,
                    mainAxisSpacing: 20,
                    // 寬 / 高
                    childAspectRatio: 2
                 ),
                 children: List.generate(100, (index) {
                      return Container(
                          color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256)),
                      );
                 }
             ),
          );
        }
      }
      
    • SliverGridDelegateWithMaxCrossAxisExtent

      SliverGridDelegateWithMaxCrossAxisExtent({
         double maxCrossAxisExtent, // 交叉軸的item寬度
         double mainAxisSpacing = 0.0, // 主軸的間距
         double crossAxisSpacing = 0.0, // 交叉軸的間距
         double childAspectRatio = 1.0, // 子Widget的寬高比
      })
      

      如下代碼

      class MyHomeBody extends StatelessWidget {
         @override
         Widget build(BuildContext context) {
              return GridView(
                  gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent (
                      maxCrossAxisExtent: 100,
                      mainAxisSpacing: 20,
                      crossAxisSpacing: 20,
                      childAspectRatio: 2
                  ),
                  children: List.generate(100, (index) {
                      return Container(
                         color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256)),
                      );
                  }
              ),
           );
         }
      }
      

    提示:前面兩種方式也可以不設(shè)置delegate,可以分別使用:GridView.count構(gòu)造函數(shù)和GridView.extent構(gòu)造函數(shù)實現(xiàn)相同的效果

  • 2.2. GridView.build
    和ListView一樣,使用構(gòu)造函數(shù)會一次性創(chuàng)建所有的子Widget,會帶來性能問題,所以我們可以使用GridView.build來交給GridView自己管理需要創(chuàng)建的子Widget。
    我們直接使用之前的數(shù)據(jù)來進(jìn)行代碼演練:


    GridView.build
    class MyHomeBody extends StatelessWidget {
       @override
       Widget build(BuildContext context) {
          return Padding(
             padding: const EdgeInsets.all(5.0),
             child: GridView.builder(
                  shrinkWrap: true,
                  physics: ClampingScrollPhysics(),
                  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount (
                      crossAxisCount: 2,
                      mainAxisSpacing: 10,
                      crossAxisSpacing: 10,
                      childAspectRatio: 1.2
                  ),
                  itemCount: 10,
                  itemBuilder: (BuildContext context, int index) {
                      return Container(
                           child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: <Widget>[
                                  Image.network('http://image.xcar.com.cn/attachments/a/day_200323/2020032314_59939b0716c40f9be872JrmcP75B4KfO.jpg-app'),
                                  SizedBox(height: 5),
                                  Text('王三', style: TextStyle(fontSize: 6),), 
                              ],
                           ),
                      );
                  }
              ),
         );
      }
    }
    
三、Sliver
  • 3.1、Sliver 的簡單介紹
    我們考慮一個這樣的布局:一個滑動的視圖中包括一個標(biāo)題視圖(HeaderView),一個列表視圖(ListView),一個網(wǎng)格視圖(GridView)。
    我們怎么可以讓它們做到統(tǒng)一的滑動效果呢?使用前面的滾動是很難做到的。
    Flutter中有一個可以完成這樣滾動效果的Widget:CustomScrollView,可以統(tǒng)一管理多個滾動視圖。
    在CustomScrollView中,每一個獨立的,可滾動的Widget被稱之為Sliver。
    補(bǔ)充:Sliver可以翻譯成裂片、薄片,你可以將每一個獨立的滾動視圖當(dāng)做一個小裂片。

  • 3.2、Slivers 的基本使用
    因為我們需要把很多的Sliver放在一個CustomScrollView中,所以CustomScrollView有一個slivers屬性,里面讓我們放對應(yīng)的一些Sliver:不可以放棄他的

    • SliverList:類似于我們之前使用過的ListView;

    • SliverFixedExtentList:類似于SliverList只是可以設(shè)置滾動的高度;

    • SliverGrid:類似于我們之前使用過的GridView;

    • SliverPadding:設(shè)置Sliver的內(nèi)邊距,因為可能要單獨給Sliver設(shè)置內(nèi)邊距;

    • SliverAppBar:添加一個AppBar,通常用來作為CustomScrollView的HeaderView;

    • SliverSafeArea:設(shè)置內(nèi)容顯示在安全區(qū)域(比如不讓齊劉海擋住我們的內(nèi)容),也就是可以滾動過安全區(qū)域

      class MyHomeBody1 extends StatelessWidget {
         @override
         Widget build(BuildContext context) {
             return CustomScrollView(
                  slivers: <Widget>[
                      SliverGrid(
                           gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                               crossAxisCount: 2,
                               crossAxisSpacing: 16,
                               childAspectRatio: 2,
                               mainAxisSpacing: 16
                           ),
                           delegate: SliverChildBuilderDelegate(
                               (BuildContext context, int index) {
                                   return Container(
                                      color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256)),
                                   );
                               },
                               childCount: 10
                           )
                      ),
                  ],
            );
         }
      }
      
  • 3.3、Slivers的組合使用:SliverAppBar、SliverGrid、SliverList 的設(shè)置


    多個slivers的使用:SliverAppBar、SliverGrid、SliverList 的設(shè)置
    class MyHomeBody extends StatelessWidget {
       @override
       Widget build(BuildContext context) {
            return CustomScrollView(
                slivers: <Widget>[
                    SliverAppBar(
                       // true: bar不動
                       // false: bar動
                       pinned: true,
                       // bar 的高度
                      expandedHeight: 200,
                      flexibleSpace: FlexibleSpaceBar(
                          title: Text("Hello World!"),
                          background: Image.asset("assets/images/iron.png", fit: BoxFit.cover,),
                      ),
                   ),
                   SliverSafeArea(
                      sliver: SliverPadding(
                         padding: EdgeInsets.all(16),
                         sliver: SliverGrid(
                              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                              crossAxisCount: 2,
                              crossAxisSpacing: 16,
                              childAspectRatio: 2,
                              mainAxisSpacing: 16
                         ),
                         delegate: SliverChildBuilderDelegate(
                             (BuildContext context, int index) {
                               return Container(
                                  color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256)),
                               );
                            },
                            childCount: 6
                         )
                      ),
                    ),
                  ),
                  SliverList(
                     delegate: SliverChildBuilderDelegate(
                         (BuildContext context, int index) {
                            return ListTile(
                               leading: Icon(Icons.people),
                               title: Text("聯(lián)系人"),
                            );
                         },
                         childCount: 20
                     ),
                  )
             ],
         );
      }
    }
    
四、滾動的監(jiān)聽

對于滾動的視圖,我們經(jīng)常需要監(jiān)聽它的一些滾動事件,在監(jiān)聽到的時候去做對應(yīng)的一些事情。
比如視圖滾動到底部時,我們可能希望做上拉加載更多;
比如滾動到一定位置時顯示一個回到頂部的按鈕,點擊回到頂部的按鈕,回到頂部;
比如監(jiān)聽滾動什么時候開始,什么時候結(jié)束;
Flutter 中監(jiān)聽滾動相關(guān)的內(nèi)容由兩部分組成ScrollControllerScrollNotification

  • 4.1、ScrollController 監(jiān)聽,可以預(yù)先設(shè)置offset,也可以監(jiān)聽滾動的位置,缺點是:無法檢測股東開始和結(jié)束
    在Flutter中,Widget并不是最終渲染到屏幕上的元素(真正渲染的是RenderObject),因此通常這種監(jiān)聽事件以及相關(guān)的信息并不能直接從Widget中獲取,而是必須通過對應(yīng)的Widget的Controller來實現(xiàn)。
    ListView、GridView的組件控制器是ScrollController,我們可以通過它來獲取視圖的滾動信息,并且可以調(diào)用里面的方法來更新視圖的滾動位置。
    另外,通常情況下,我們會根據(jù)滾動的位置來改變一些Widget的狀態(tài)信息,所以ScrollController通常會和StatefulWidget一起來使用,并且會在其中控制它的初始化、監(jiān)聽、銷毀等事件。
    我們來做一個案例,當(dāng)滾動到500位置的時候,顯示一個回到頂部的按鈕:

    • jumpTo(double offset)、animateTo(double offset,...):這兩個方法用于跳轉(zhuǎn)到指定的位置,它們不同之處在于,后者在跳轉(zhuǎn)時會執(zhí)行一個動畫,而前者不會。

    • ScrollController間接繼承自Listenable,我們可以根據(jù)ScrollController來監(jiān)聽滾動事件。



      代碼如下

      class HomePage extends StatefulWidget {
         @override
         _HomePageState createState() => _HomePageState();
      }
      
      class _HomePageState extends State<HomePage> {
         // 設(shè)置變量 _controller 并設(shè)置偏移量
         ScrollController _controller = ScrollController(initialScrollOffset: 200);
         /* 默認(rèn)設(shè)置為 false */
         bool _isFloatingActionButton = false;
         @override
         void initState() {
             // TODO: implement initState
             super.initState();
      
             _controller.addListener(() {
                print("監(jiān)聽到滾動");
                setState(() {
                    _isFloatingActionButton = _controller.offset > 500 ? true : false;
                });
            });
         }
      
         @override
         Widget build(BuildContext context) {
            return Scaffold(
               appBar: AppBar(
                   title: Text("列表滾動測試"),
               ),
               body: ListView.builder(
                   controller: _controller,
                   itemCount: 20,
                   itemBuilder: (BuildContext context, int index) {
                     return ListTile(
                        leading: Icon(Icons.people),
                        title: Text("測試 $index"),
                     );
                   }
               ),
               floatingActionButton: _isFloatingActionButton ? FloatingActionButton(
                  child: Icon(Icons.arrow_upward),
                  onPressed: () {
                    // 返回到頂部
                    _controller.animateTo(0, duration: Duration(milliseconds: 200), curve: Curves.easeIn);
                  },
               ) : null,
           );
      
         }
      }
      
  • 4.2、ScrollNotification
    如果我們希望監(jiān)聽什么時候開始滾動,什么時候結(jié)束滾動,這個時候我們可以通過NotificationListener。

    • NotificationListener是一個Widget,模板參數(shù)T是想監(jiān)聽的通知類型,如果省略,則所有類型通知都會被監(jiān)聽,如果指定特定類型,則只有該類型的通知會被監(jiān)聽。
    • NotificationListener需要一個onNotification回調(diào)函數(shù),用于實現(xiàn)監(jiān)聽處理邏輯。

    該回調(diào)可以返回一個布爾值,代表是 false 阻止該事件繼續(xù)向上冒泡,如果為true時,則冒泡終止,事件停止向上傳播,如果不返回或者返回值為false 時,則冒泡繼續(xù)。
    案例: 列表滾動, 并且在中間顯示滾動進(jìn)度

    class HomePage extends StatefulWidget {
       @override
        _HomePageState createState() => _HomePageState();
    }
    
    class _HomePageState extends State<HomePage> {
        int _progress = 0;
    
        @override
        Widget build(BuildContext context) {
             return Scaffold(
                  appBar: AppBar(
                     title: Text("列表滾動測試"),
                  ),
                  body: NotificationListener(
                     onNotification: (ScrollNotification notification ) {
                          if (notification is ScrollStartNotification) {
                             print("----開始滾動----");
                          } else if (notification is ScrollUpdateNotification) {
                               // 當(dāng)前滾動的位置和總長度
                               final currentPixel = notification.metrics.pixels;
                               final totalPixel = notification.metrics.maxScrollExtent;
                               double progress = currentPixel / totalPixel;
                               setState(() {
                                  _progress = (progress * 100).toInt();
                               });
                               print("正在滾動:${notification.metrics.pixels} - ${notification.metrics.maxScrollExtent}");
                          } else if (notification is ScrollEndNotification) {
                               print("----結(jié)束滾動----");
                          }
                          return true;
                     },
                     child: Stack(
                          alignment: Alignment(0.9, 0.9),
                          children: <Widget>[
                              ListView.builder(
                                   itemCount: 100,
                                   itemExtent: 60,
                                   itemBuilder: (BuildContext context, int index) {
                                        return ListTile(title: Text("item$index"));
                                   }
                              ),
                              CircleAvatar(
                                   radius: 30,
                                   child: Text("$_progress%"),
                                   backgroundColor: Colors.black54,
                              )
                          ],
                     ),
               ),
           );
        }
     }
    
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,415評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,104評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,884評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,647評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,130評論 1 323
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,366評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,887評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,737評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,174評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,586評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,827評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,608評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,914評論 2 372