Flutter入門三部曲(2) - 界面開發(fā)基礎(chǔ)

image.png

上一節(jié)我們熟悉了初始化后的flutter的界面殿漠。這一節(jié)仇祭,我們就來(lái)重點(diǎn)了解一下這部分的內(nèi)容。

StatelessWidgets and StatefulWidgets

  • Flutter中的Widget都必須從Flutter庫(kù)中繼承。
    你將使用的兩個(gè)幾乎總是StatelessWidgetStatefulWidget搔确。顧名思義竟秫,我們只要如果是不需要根據(jù)狀態(tài)變化的組件娃惯,我們可以直接繼承StatelessWidget.如果和狀態(tài)有關(guān)系的組件就必須繼承StatefulWidget
  • Flutter中的Widget都是不可變的狀態(tài)肥败。
    但是實(shí)際上趾浅,總要根據(jù)對(duì)應(yīng)的狀態(tài),視圖發(fā)生變化馒稍,所以就有了state皿哨。用它來(lái)保持我們的狀態(tài)。
    這樣纽谒,一個(gè)Stateful Widget证膨,實(shí)際上是兩個(gè)類:狀態(tài)對(duì)象stateWidget組成的。
    如下代碼
class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;
  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new Center(
        child: new Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new Text(
              'You have pushed the button this many times:',
            ),
            new Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
          ],
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}
  • setState & build
    _MyHomePageState繼承于State.一方面需要管理自己的狀態(tài)_counter鼓黔,一方面需要build來(lái)構(gòu)造組件央勒。
    改變狀態(tài)后,需要通過(guò)setState來(lái)重新構(gòu)建widget澳化,就是會(huì)重新調(diào)用build方法崔步,來(lái)得到狀態(tài)同步。

最常見的Widget

接著先看看一些常用的組件缎谷,這些是隨時(shí)可用的小部件井濒,開箱即用,你會(huì)非常滿意:

  • Text - 用于簡(jiǎn)單地在屏幕上顯示文本的小部件。
  • Image - 用于顯示圖像眼虱。
  • Icon - 用于顯示Flutter的內(nèi)置Material和Cupertino圖標(biāo)喻奥。
  • Container - 在Flutter中,相當(dāng)于div捏悬。允許在其中進(jìn)行添加填充撞蚕,對(duì)齊,背景过牙,力大小以及其他東西的加載甥厦。空的時(shí)候也會(huì)占用0px的空間寇钉,這很方便刀疙。
  • TextInput - 處理用戶反饋。
  • Row, Column- 這些小部件顯示水平或垂直方向的子項(xiàng)列表扫倡。
  • Stack - 堆棧顯示一個(gè)孩子的列表谦秧。這個(gè)功能很像CSS中的'position'屬性。
  • Scaffold - 為應(yīng)用提供基本的布局結(jié)構(gòu)撵溃。它可以輕松實(shí)現(xiàn)底部導(dǎo)航疚鲤,appBars,后退按鈕等缘挑。

更多的可以看目錄集歇。

注意:如果您熟悉基于組件的框架(如React或Vue),則可能不需要閱讀此內(nèi)容语淘。Widget就是組件诲宇。

封裝組件

這樣的話,實(shí)際開發(fā)中惶翻,也是通過(guò)不斷對(duì)組件的封裝姑蓝,來(lái)提高工作效率。
比如簡(jiǎn)單的封裝一個(gè)原型的圖片組件(實(shí)際上维贺,應(yīng)該這個(gè)width和height都可以封裝進(jìn)去的它掂。)

class CircleImage extends StatelessWidget {
  final String renderUrl;

  CircleImage(this.renderUrl);

  @override
  Widget build(BuildContext context) {
    return Container(
      width: 100.0,
      height: 100.0,
      decoration: BoxDecoration(
        shape: BoxShape.circle,
        image: new DecorationImage(
          fit: BoxFit.cover,
          image: NetworkImage(renderUrl ?? ''),
        ),
      ),
    );
  }
}

//直接使用
new CircleImage('https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1533638174553&di=6913961a358faf638b6233e5d3dcc2b2&imgtype=0&src=http%3A%2F%2Fimage.9game.cn%2F2015%2F3%2F5%2F10301938.png')

看看加在上一遍文章下面的效果。(中間皮卡丘)


image.png

Stateful Widget 的生命周期

現(xiàn)在讓我們深入一點(diǎn)溯泣,
先來(lái)思考一下
- 為什么Stateful Widget會(huì)將StateWidget分開呢虐秋?

  • 答案就只有一個(gè):性能。
  • State管理著狀態(tài)垃沦,它是常駐的客给。然而,Widget是不可變的肢簿,當(dāng)配置發(fā)生變化靶剑,它會(huì)立馬發(fā)生重建蜻拨。所以這樣的重建的成本是極低的。
    因?yàn)?code>State在每次重建時(shí)都沒有拋棄桩引,所以可以維護(hù)它并且不必每次重建某些東西時(shí)都要進(jìn)行昂貴的計(jì)算以獲得狀態(tài)屬性缎讼。
  • 此外,這是允許Flutter動(dòng)畫存在的原因坑匠。因?yàn)?code>State沒有丟棄血崭,它可以不斷重建它的Widget以響應(yīng)數(shù)據(jù)變化。

1. createState()

當(dāng)創(chuàng)建一個(gè)StatefulWidget時(shí)厘灼。立即調(diào)用夹纫。通常都是如下,這樣簡(jiǎn)單的操作设凹。

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

2. mounted is true

當(dāng)這個(gè)Widget調(diào)用createState 后, 會(huì)將buildContext 傳入舰讹。 BuildContext 內(nèi)有自己在widget tree上相關(guān)的信息。

所有的widgets 都有 bool this.mounted 這個(gè)屬性. 當(dāng)BuildContext傳入時(shí)闪朱,它將會(huì)被標(biāo)記成 true月匣。如果這個(gè)屬性不是true的話,調(diào)用setState會(huì)報(bào)錯(cuò)监透。

注意:你可以在調(diào)用setState前桶错,檢查一下這個(gè)變量。

if (mounted) {...` to make sure the State exists before calling `setState()

3 . initState()

這個(gè)方法只會(huì)調(diào)用一次胀蛮,在這個(gè)Widget被創(chuàng)建之后。它必須調(diào)用super.initState().
在這里可以做:

  1. 初始化根據(jù)對(duì)應(yīng)BuildContext的狀態(tài)
  2. 初始化根據(jù)在樹上的父節(jié)點(diǎn)的屬性確定的值
  3. 注冊(cè)Streams ChangeNotifiers或者其他會(huì)改變的數(shù)據(jù)的監(jiān)聽糯钙。
@override
initState() {
  super.initState();
  // Add listeners to this class
  cartItemStream.listen((data) {
    _updateWidget(data);
  });
}

4. didChangeDependencies()

它是在initState 方法后粪狼,就會(huì)調(diào)用。
當(dāng)Widget依賴的一些數(shù)據(jù)(比如說(shuō)是InheritedWidget,后面會(huì)介紹)更新時(shí)任岸,它會(huì)立即被調(diào)用再榄。
同時(shí)build方法,會(huì)自動(dòng)調(diào)用享潜。
需要注意的是困鸥,你需要通過(guò)調(diào)用BuildContext.inheritFromWidgetOfExactType,手動(dòng)去注冊(cè)InheritedWidget的監(jiān)聽后剑按,這個(gè)方法才會(huì)起作用疾就。

文檔還建議,當(dāng)InheritedWidget更新時(shí)艺蝴,如果需要進(jìn)行網(wǎng)絡(luò)調(diào)用(或任何其他昂貴的操作)猬腰,它可能會(huì)很有用。

5.build()

這個(gè)方法會(huì)經(jīng)常被調(diào)用猜敢。

6. didUpdateWidget(Widget oldWidget)

如果父組件發(fā)生變化姑荷,而且必須去重建widget時(shí)盒延,而且被相同的runtimeType重建時(shí),這個(gè)方法會(huì)被調(diào)用鼠冕。

因?yàn)镕lutter是復(fù)用state的添寺。所以,你可能需要重新初始化狀態(tài)懈费。
如果你的Widget是需要根據(jù)監(jiān)聽的數(shù)據(jù)畦贸,發(fā)生變化的,那么你就需要從舊的對(duì)象中反注冊(cè)楞捂,然后注冊(cè)新的對(duì)象薄坏。

注意:如果您希望重建與此狀態(tài)關(guān)聯(lián)的Widget,則此方法基本上是'initState'的替代寨闹!

這個(gè)方法胶坠,會(huì)自動(dòng)調(diào)用build,所以不需要去調(diào)用setState

@override
void didUpdateWidget(Widget oldWidget) {
  if (oldWidget.importantProperty != widget.importantProperty) {
    _init();
  }
}

7. setState()

這個(gè)方法會(huì)被framework和開發(fā)者不斷調(diào)用。用來(lái)通知組件刷新繁堡。

這個(gè)方法的不能有異步的回調(diào)沈善。其他,就可以隨便使用椭蹄。

void updateProfile(String name) {
 setState(() => this.name = name);
}

8. deactivate()

(這個(gè)狀態(tài)暫時(shí)不是很理解)
State從樹中刪除時(shí)會(huì)調(diào)用Deactivate 闻牡,但可能會(huì)在當(dāng)前幀更改完成之前重新插入。此方法的存在主要是因?yàn)?code>State對(duì)象可以從樹中的一個(gè)點(diǎn)移動(dòng)到另一個(gè)點(diǎn)绳矩。

這很少使用罩润。

9. dispose()

State刪除對(duì)象時(shí)調(diào)用Dispose ,這是永久性的翼馆。
在此方法取消訂閱并取消所有動(dòng)畫割以,流等

10. mounted is false

state對(duì)象被移除了,如果調(diào)用setState应媚,會(huì)拋出的錯(cuò)誤严沥。

summary with image

來(lái)自網(wǎng)絡(luò)盜圖.png

一些疑問(wèn)

BuildContext

- 1. 每個(gè)widget都有自己的context。這個(gè)context是父組件通過(guò)build方法給他返回的中姜。

首先消玄,先看下面代碼。我們將在四個(gè)地方打印context的hashCode,來(lái)看看有什么不同

//...
 _MyHomePageState() {
   //1. constructor
    print('constructor context hashcode = ${context.hashCode}');
  }

  void _incrementCounter() {
     //2. member method
    print('_incrementCounter context hashcode = ${context.hashCode}');
    setState(() {
      _counter++;
    });
  }

@override
  void initState() {
    super.initState();
    //3. initState
    print('initState context hashcode = ${context.hashCode}');
  }


  @override
  Widget build(BuildContext context) {
    return new Scaffold(
     //...
      floatingActionButton: new FloatingActionButton(
       onPressed: () {
          //4.floattingbutton
          print(
              'FloatingActionButton onPressed context hashcode = ${context.hashCode}');
          _incrementCounter();
        },
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}
image.png

很明顯可以看到丢胚,我們?cè)?code>initState方法時(shí)翩瓜,已經(jīng)分配拿到了父組件的BuildContext.接下來(lái)的直接使用context,也都是同一個(gè)嗜桌。

我們知道可以通過(guò)Scaffold的context來(lái)彈出一個(gè)SnackBar奥溺。這里想通過(guò)點(diǎn)擊彈出這個(gè)。
修改代碼如下:

//...
floatingActionButton: new FloatingActionButton(
        onPressed: () {
          print(
              'FloatingActionButton onPressed context hashcode = ${context.hashCode}');
          Scaffold.of(context).showSnackBar(SnackBar(
                content: Text('I am context from Scaffold'),
              ));
        },
        tooltip: 'Increment',
        child: new Icon(Icons.add),
      ), 

運(yùn)行骨宠,但是運(yùn)行報(bào)錯(cuò)信息如下:


image.png

很明顯浮定。通過(guò)上面的測(cè)試相满,我們知道這里的context,確實(shí)不是Scaffold。那我們要如何在這里拿到Scaffold的context呢桦卒?

2. 通過(guò)builder方法

修改代碼如下立美,通過(guò)Builder方法,得到這個(gè)context.

//...
floatingActionButton: new Builder(
        builder: (context) {
          return new FloatingActionButton(
            onPressed: () {
              print(
                  'FloatingActionButton onPressed context hashcode = ${context.hashCode}');
              Scaffold.of(context).showSnackBar(SnackBar(
                    content: Text('I am context from Scaffold'),
                  ));
              _incrementCounter();
            },
            tooltip: 'Increment',
            child: new Icon(Icons.add),
          );
        },
      )

運(yùn)行結(jié)果


image.png

我們可以看到方灾,我們確實(shí)拿到了Scaffold分配的Context,而且彈出了SnackBar.

后續(xù)過(guò)程中建蹄,一定要注意這個(gè)Context的使用。

注意:這里其實(shí)還有另外一個(gè)方法裕偿,來(lái)得到這個(gè)BuildContext洞慎。就是將FloatingActionButton分離出來(lái),寫成另外一個(gè)組件嘿棘,就能通過(guò)build方法拿到了劲腿。

方法如下:

  • 添加類
class ScaffoldButton extends StatelessWidget {
  ScaffoldButton({this.onPressedButton});

  final VoidCallback onPressedButton;

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      onPressed: () {
        print(
            'FloatingActionButton onPressed context hashcode = ${context.hashCode}');
        Scaffold.of(context).showSnackBar(
            SnackBar(content: Text('I am context from Scaffold')));
        onPressedButton();
      },
      tooltip: 'Increment',
      child: new Icon(Icons.add),
    );
  }
}

再將floatingActionButton修改成這個(gè)類

//...
        floatingActionButton: ScaffoldButton(
          onPressedButton: () {
            _incrementCounter();
          },
        ));

不知所云的構(gòu)造參數(shù) Key

隨意點(diǎn)開一個(gè)Widget,就會(huì)發(fā)現(xiàn),可以傳遞一個(gè)參數(shù)Key.那這個(gè)Key到底是干啥子鸟妙,有什么用呢焦人?

image.png

Flutter是受React啟發(fā)的,所以Virtual Dom的diff算法也參考過(guò)來(lái)了(應(yīng)該是略有修改)重父,在diff的過(guò)程中如果節(jié)點(diǎn)有Key來(lái)比較的話花椭,能夠最大程度重用已有的節(jié)點(diǎn)(特別在列表的場(chǎng)景),除了這一點(diǎn)這個(gè)Key也用在很多其他的地方這個(gè)以后會(huì)總結(jié)一下房午】罅桑總之,這里我們可以知道key能夠提高性能歪沃,所以每個(gè)Widget都會(huì)構(gòu)建方法都會(huì)有一個(gè)key的參數(shù)可選嗦锐,貫穿著整個(gè)框架。

通常情況下沪曙,我們不需要去傳遞這個(gè)Key。因?yàn)?code>framework會(huì)在內(nèi)部自處理它萎羔,來(lái)區(qū)分不同的widgets
下面有幾種情況液走,我們可以使用它

- 使用ObjectKeyValueKey來(lái)對(duì)組件進(jìn)行區(qū)分。

可以看PageStorageKey, 和另外一個(gè)例子贾陷,這個(gè)例子是deletion: https://flutter.io/cookbook/gestures/dismissible/.

簡(jiǎn)單的來(lái)說(shuō)缘眶,當(dāng)我們使用Row或者Column時(shí),想要執(zhí)行一個(gè)remove的動(dòng)畫

new AnimatedList(
  children: [
    new Card(child: new Text("foo")),
    new Card(child: new Text("bar")),
    new Card(child: new Text("42")),
  ]
)

當(dāng)我們移除"bar"后

new AnimatedList(
  children: [
    new Card(child: new Text("foo")),
    new Card(child: new Text("42")),
  ]
)

因?yàn)槲覀儧]有定義Key,所以可能flutter并不知道髓废,我們那個(gè)item發(fā)生了改變巷懈,所以可能發(fā)生在位置1上的動(dòng)畫,可能發(fā)生在其他位置慌洪。
正確的修改如下:

new AnimatedList(
  children: [
    new Card(key: new ObjectKey("foo"), child: new Text("foo")),
    new Card(key: new ObjectKey("bar"), child: new Text("bar")),
    new Card(key: new ObjectKey("42"), child: new Text("42")),
  ]
)

這樣當(dāng)我們移除"bar"的時(shí)候顶燕,flutter就能準(zhǔn)確的區(qū)別到正確的位置上凑保。
Key雖然不是Index,但是對(duì)于每一個(gè)元素來(lái)說(shuō),是獨(dú)一無(wú)二的涌攻。

- 使用GlobalKey
  1. 使用GlobalKey的場(chǎng)景是,從父控件和跨子Widget來(lái)傳遞狀態(tài)時(shí)欧引。
    需要注意的是:不要濫用GlobalKey,如果有更好的方式的恳谎,請(qǐng)使用其他方式來(lái)傳遞狀態(tài)芝此。

這里有一個(gè)例子是 通過(guò)給Scaffold添加GolbalKey。然后通widget.GolbalKey.state來(lái)調(diào)用showSnackBar

class _MyHomePageState extends State<MyHomePage> {
  final globalKey =
      new GlobalKey<ScaffoldState>();

  void _incrementCounter() {
    globalKey.currentState
        .showSnackBar(SnackBar(content: Text('I am context from Scaffold')));
  }

 @override
  Widget build(BuildContext context) {
    return new Scaffold(
        key: globalKey,
      //...
      )
}
}

這樣就可以直接從父控件調(diào)用子Widget的狀態(tài)因痛。

推薦視頻 When to Use Keys - Flutter Widgets 101 Ep. 4
使用LocalKey婚苹,只能在同一層級(jí)上找相同的key,如果包裹了一層的話,就不行了鸵膏。
這個(gè)時(shí)候膊升,可以用globalkey來(lái)完成需求。
valuekey適合于當(dāng)個(gè)變量较性,objectKey應(yīng)用于Object用僧。GlobalKey是全局的

  1. 還有一個(gè)場(chǎng)景是,過(guò)渡動(dòng)畫赞咙,當(dāng)兩個(gè)頁(yè)面都是相同的Widget時(shí)责循,也可以使用GlobalKey。

總結(jié)

這邊文章攀操,我們對(duì)StateFulWidget有了升入的認(rèn)識(shí)院仿。

  • 認(rèn)識(shí)了通用的控件
  • 了解了StatefulWidget的生命周期
  • 對(duì)BuildContext 了解。
  • 對(duì)Key的場(chǎng)景進(jìn)行了了解速和。得到了使用GlobalKey來(lái)跨子組件傳遞狀態(tài)的方式歹垫。

下一遍文章:我們將更加深入的對(duì)Flutter的界面開發(fā)的一些原理

參考文章

Flutter Widgets
Flutter中的Key,LocalKey颠放,GlobalKey... And More
what-are-keys-used-for-in-flutter-framework

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末排惨,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子碰凶,更是在濱河造成了極大的恐慌暮芭,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件欲低,死亡現(xiàn)場(chǎng)離奇詭異辕宏,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)砾莱,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門瑞筐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人腊瑟,你說(shuō)我怎么就攤上這事聚假】榘觯” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵魔策,是天一觀的道長(zhǎng)匈子。 經(jīng)常有香客問(wèn)我,道長(zhǎng)闯袒,這世上最難降的妖魔是什么虎敦? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮政敢,結(jié)果婚禮上其徙,老公的妹妹穿的比我還像新娘。我一直安慰自己喷户,他們只是感情好唾那,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著褪尝,像睡著了一般闹获。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上河哑,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天避诽,我揣著相機(jī)與錄音,去河邊找鬼璃谨。 笑死沙庐,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的佳吞。 我是一名探鬼主播拱雏,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼底扳!你這毒婦竟也來(lái)了铸抑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤衷模,失蹤者是張志新(化名)和其女友劉穎羡滑,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體算芯,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年凳宙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了熙揍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡氏涩,死狀恐怖届囚,靈堂內(nèi)的尸體忽然破棺而出有梆,到底是詐尸還是另有隱情,我是刑警寧澤意系,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布泥耀,位于F島的核電站,受9級(jí)特大地震影響蛔添,放射性物質(zhì)發(fā)生泄漏痰催。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一卷胯、第九天 我趴在偏房一處隱蔽的房頂上張望肉迫。 院中可真熱鬧矾策,春花似錦、人聲如沸缝裁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)捷绑。三九已至,卻和暖如春氢妈,著一層夾襖步出監(jiān)牢的瞬間粹污,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工允懂, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留厕怜,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓蕾总,卻偏偏與公主長(zhǎng)得像粥航,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子生百,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容