Flutter基礎(chǔ)-(2)基本概念及首個(gè)應(yīng)用

上一篇文章 中,我們講解了 Flutter 開(kāi)發(fā)環(huán)境搭建 , 以及運(yùn)行了官方demo簡(jiǎn)單體驗(yàn)了下 Flutter app .

此篇我們將開(kāi)始對(duì)一些 Flutter app 中的一些概念做一些講解 , 一些基本的操作做一些示例 , 主要是參考官網(wǎng)的教程 Write Your First Flutter App

若你對(duì)面向?qū)ο缶幊淌煜?以及對(duì)基本編程概念如變量够庙、循環(huán)、條件了解 , 則適合閱讀本篇教程 . 不必需要擁有 Dart 或移動(dòng)編程經(jīng)驗(yàn).

為了更好的閱讀體驗(yàn) , 請(qǐng)點(diǎn)擊 閱讀原文 :)

  • 步驟 1 : 創(chuàng)建及啟動(dòng) Flutter app
  • 步驟 2 : 使用一個(gè)外部的程序包
  • 步驟 3 : 增加一個(gè) Stateful Widget
  • 步驟 4 : 創(chuàng)建一個(gè)無(wú)限滾動(dòng)的 ListView
  • 步驟 5 : 增加交互
  • 步驟 6 : 跳轉(zhuǎn)到新頁(yè)面
  • 步驟 7 : 通過(guò)主題改變UI
  • 完成!

我們將創(chuàng)建什么

我們將實(shí)現(xiàn)一個(gè)簡(jiǎn)單的移動(dòng)應(yīng)用 , 它會(huì)生成創(chuàng)業(yè)公司的名稱(chēng) . 用戶(hù)可以選擇和反選名稱(chēng) , 保存最好的那些 . 代碼一次生成 10 個(gè)名稱(chēng) . 當(dāng)用戶(hù)滑動(dòng)時(shí) , 新一批的名稱(chēng)就會(huì)生成 . 用戶(hù)可以點(diǎn)擊導(dǎo)航欄右上的按鈕進(jìn)入一個(gè)只展示喜好的名稱(chēng)的列表新頁(yè)面.

image

我們將學(xué)到:

  • Flutter app 的基本結(jié)構(gòu)
  • 使用額外的包去拓展功能
  • 使用熱部署來(lái)快速開(kāi)發(fā)
  • 如何去實(shí)現(xiàn)一個(gè)stateful 小部件
  • 如何創(chuàng)建一個(gè)無(wú)線滑動(dòng),懶加載的列表
  • 如何跳轉(zhuǎn)去下一個(gè)界面
  • 如果通過(guò)主題去修改app外觀

步驟 1 : 創(chuàng)建及啟動(dòng) Flutter app

這里創(chuàng)建一個(gè)簡(jiǎn)單的 flutter app

flutter create flutter_first_app
cd flutter_first_app
flutter run

如有疑問(wèn) , 可參考 前一篇文章 指引

簡(jiǎn)單地 , 我們先將 lib/main.dart中的代碼全部刪除 , 替換為以下代碼 , 其主要就是在屏幕中間展示 'Hello World'

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Welcome to Flutter',
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text('Welcome to Flutter'),
        ),
        body: new Center(
          child: new Text('Hello World'),
        ),
      ),
    );
  }
}

重新運(yùn)行得到結(jié)果

image

發(fā)現(xiàn)

  • 這個(gè)例子創(chuàng)建了一個(gè) Material Design 風(fēng)格的app . Material 是一種在移動(dòng)端及web上標(biāo)準(zhǔn)的視覺(jué)設(shè)計(jì)語(yǔ)言 . Flutter 提供了豐富的 Material 風(fēng)格小部件
  • main 方法使用了一個(gè)大箭頭=>寫(xiě)法 , 它是一行代碼功能或方法的縮寫(xiě)
  • app 繼承 StatefulWidget 使得其自身也是個(gè)widget . 在 Flutter 里 , 大多數(shù)元素都是 widget , 包括對(duì)齊方式(alignment)蕾各、 內(nèi)邊距(padding)度帮、布局(layout) .
  • Material 庫(kù) 中的腳手架小部件 (Scaffold widget) , 提供了一個(gè)默認(rèn)的導(dǎo)航欄、 標(biāo)題、 內(nèi)容屬性在屏幕中維持了部件樹(shù)??.部件子樹(shù)可以是很復(fù)雜的.
  • 一個(gè)小部件的主要工作就是提供 build()方法 , 它是用來(lái)表明如何展示其他低層級(jí)的widget.
  • 這個(gè)示例的部件樹(shù)由 包含一個(gè) Text 子部件 的Center Widget 組成 . 這個(gè) Center Widget 將其子部件樹(shù)排列在屏幕中間 .

步驟 2 : 使用一個(gè)外部的程序包

在這個(gè)步驟里 , 我們將開(kāi)始使用一個(gè)開(kāi)源程序包 english_words , 它包含了較多的常用的英文單詞還有一些工具方法 .

我們可以在 pub.dartlang.org 找到 english_words 及 其他開(kāi)源程序包

1. pubspec 文件負(fù)責(zé)管理 Flutter 應(yīng)用的資源. 在 pubspec.yaml 文件中,添加 english_words(3.1.0或更高版本)到依賴(lài)?yán)?

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^0.1.0
  english_words: ^3.1.0

**2. **當(dāng)我們?cè)贗DEA 視圖中 , 修改yaml文件后 , 可點(diǎn)擊右上方的 Packages get 使之生效.它會(huì)拉取我們才添加的依賴(lài)包, 控制臺(tái)中打印

flutter packages get
Running "flutter packages get" in flutter_first_app...
Process finished with exit code 0

**3. ** 在 lib/main.dart文件中,添加 import 語(yǔ)句 , 導(dǎo)入依賴(lài)相關(guān)類(lèi)

import 'package:english_words/english_words.dart';

**4. ** 用開(kāi)源庫(kù)生成文本代替原來(lái)的 'Hello World'

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final wordPair = new WordPair.random();
    return new MaterialApp(
      title: 'Welcome to Flutter',
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text('Welcome to Flutter'),
        ),
        body: new Center(
          //child: new Text('Hello World'), // Replace the highlighted text...
          child: new Text(wordPair.asPascalCase),  // With this highlighted text.
        ),
      ),
    );
  }
}

**5. **若app正在運(yùn)行, 可以通過(guò)點(diǎn)擊??按鈕進(jìn)行熱部署. 每次點(diǎn)擊或者保存時(shí) , 會(huì)生成新的一個(gè)隨機(jī)單詞. 這是因?yàn)閱卧~是在 build(...) 方法中生成, 它會(huì)在每次 MaterialApp 需要渲染或觸發(fā)平臺(tái)檢視時(shí)執(zhí)行.

步驟 3 : 增加一個(gè) Stateful Widget

Stateless widgets 是不可變的 , 意味著其屬性是不可改變的 - 所有值均為final .

Stateful widgets 維持著生命周期中可變的狀態(tài) . 實(shí)現(xiàn)一個(gè) stateful widget 需要至少兩個(gè)類(lèi): 一個(gè) State 類(lèi) 和 一個(gè)創(chuàng)建State示例的 StatefulWidget . StatefulWidget本身是不可變的 , 但是 State 類(lèi)在widget生成周期中一直存留 .

在這個(gè)步驟里 , 我們將添加一個(gè) stateful widget - RandomWords , 它創(chuàng)建自己的 State 類(lèi) - RandomWordsState . state 類(lèi)將為widget最終維持建議的和喜好的單詞.

**1. ** 添加 stateful RandomWords 到 main.dart

class RandomWords extends StatefulWidget {
  @override
  createState() => new RandomWordsState();
}

**2. ** 添加 RandomWordsState . 大部分app的代碼會(huì)在這個(gè)類(lèi)中 , 將維持著 RandomWords 部件的狀態(tài) . 這個(gè)類(lèi)將會(huì)保存生成的詞對(duì) , 它們隨著用戶(hù)滑動(dòng)頁(yè)面無(wú)線增加 . 然后喜好的詞對(duì) , 用戶(hù)通過(guò)點(diǎn)擊列表的心形按鈕進(jìn)行添加或移除 .

我們一步一步來(lái)創(chuàng)建這個(gè)類(lèi)

class RandomWordsState extends State<RandomWords> {
}

**3. ** 在添加 state 類(lèi)后 , IDE會(huì)提示錯(cuò)誤, 需要我們?nèi)?shí)現(xiàn)未實(shí)現(xiàn)的方法 .

class RandomWordsState extends State<RandomWords> {
  @override
  Widget build(BuildContext context) {
    final wordPair = new WordPair.random();
    return new Text(wordPair.asPascalCase);
  }
}

**4. ** 移除單詞生成代碼 ,

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {

    return new MaterialApp(
      title: 'Welcome to Flutter',
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text('Welcome to Flutter'),
        ),
        body: new Center(
          child: new RandomWords(),
        ),
      ),
    );
  }
}

步驟 4 : 創(chuàng)建一個(gè)無(wú)限滾動(dòng)的 ListView

這個(gè)步驟里, 我們將擴(kuò)充 RandomWordsState 類(lèi) 來(lái)生成和展示單詞對(duì)的列表. 當(dāng)用戶(hù)滑動(dòng)頁(yè)面, ListView widget 展示的列表將會(huì)無(wú)限增加. ListView 的 builder 工廠構(gòu)造器允許我們視需懶加載創(chuàng)建列表視圖

**1. **在 RandomWordsState 類(lèi)中添加成員變量 _suggestions 列表用來(lái)保存推薦的單詞對(duì).在 Dart 語(yǔ)言中 , 以 _下劃線開(kāi)頭的變量/方法為私有訪問(wèn)權(quán)限.

同樣的 , 添加 biggerFont 變量用來(lái)使字體大小更大

class RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];

  final _biggerFont = const TextStyle(fontSize: 18.0);
  ...
}

**2 ~ 3. **添加 _buildSuggestions()方法到 RandomWordsState 類(lèi)中. 此方法負(fù)責(zé)構(gòu)建 ListView 和展示建議的單詞對(duì).

ListView 類(lèi)提供了一個(gè) builder 屬性 , itemBuilder 一個(gè)工廠構(gòu)建者和指定匿名函數(shù)的回調(diào)功能.兩個(gè)參數(shù)被傳遞給函數(shù)- BuildContext 還有行迭代器 i . 迭代器從 0 開(kāi)始且每次方法調(diào)用遞增.

添加 _buildRow 方法

Widget _buildSuggestions() {
    return new ListView.builder(
        // padding 16
        padding: const EdgeInsets.all(16.0),
        // 每一對(duì)單詞對(duì)調(diào)用一次itemBuilder 回調(diào) ,然后放置一個(gè)推薦的單詞對(duì)在行內(nèi)
        // 偶數(shù)行 , 函數(shù)增加個(gè)內(nèi)容行顯示單詞對(duì),
        // 奇數(shù)行 , 函數(shù)添加一條分割線小部件 (Divider Widget)去顯示分割條目

        itemBuilder: (context, i) {
          if (i.isOdd) return new Divider();

          // index 為 i/2 的余整數(shù)
          final index = i ~/ 2;
          if (index >= _suggestions.length) {
            // ...生成10個(gè)詞對(duì),添加到list
            _suggestions.addAll(generateWordPairs().take(10));
          }
          return _buildRow(_suggestions[index]);
        }
    );
  }

**4. **更新 RandomWordsState 的 build 方法 , 使用 _buildSuggestions ,不再直接使用調(diào)用單詞生產(chǎn)庫(kù).

class RandomWordsState extends State<RandomWords> {
  ...
  @override
  Widget build(BuildContext context) {
    return new Scaffold (
      appBar: new AppBar(
        title: new Text('Startup Name Generator'),
      ),
    body: _buildSuggestions(),
    );
  }
  ...
}

**5. **更新 MyApp 的 build 方法 . 從 MyApp 中移除 Scaffold 和 AppBar 實(shí)例. 這些將會(huì)被 RandomWordsState 管理 ,這樣將更容易地在下個(gè)步驟頁(yè)面跳轉(zhuǎn)時(shí)去改變導(dǎo)航欄上的名稱(chēng).

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Startup Name Generator',
      home: new RandomWords(),
    );
  }
}
image

步驟 5 : 增加交互

這個(gè)步驟中 , 我們將增加可點(diǎn)擊的心形圖標(biāo)到每一行 . 當(dāng)用戶(hù)點(diǎn)擊列表?xiàng)l目時(shí) , 觸發(fā)其 "favorite" 狀態(tài) ,狀態(tài)改變會(huì)將對(duì)應(yīng)單詞對(duì)添加到保存的集合或從中移除

**1. ** 添加 _saved 集合到 RandomWordsState 里 . 集合存儲(chǔ)用戶(hù)喜好的單詞對(duì) , 更傾向于用 Set 是因?yàn)?Set 中不允許有重復(fù)的條目

class RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];

  final _saved = new Set<WordPair>();

  final _biggerFont = const TextStyle(fontSize: 18.0);
  ...
}

**2. **在 _buildRow 函數(shù)里 , 添加 alreadySaved 變量來(lái)檢查確保單詞對(duì)還未被添加到喜好的集合中.

Widget _buildRow(WordPair pair) {
  final alreadySaved = _saved.contains(pair);
  ...
}

**3 ~ 4. **還是在_buildRow 里 , 添加心形圖標(biāo) . 重啟應(yīng)用 , 我們可以看到心形已被添加 , 只是暫時(shí)沒(méi)有交互事件

Widget _buildRow(WordPair pair) {
  final alreadySaved = _saved.contains(pair);
  return new ListTile(
    title: new Text(
      pair.asPascalCase,
      style: _biggerFont,
    ),
    trailing: new Icon(
      alreadySaved ? Icons.favorite : Icons.favorite_border,
      color: alreadySaved ? Colors.red : null,
    ),
  );
}

**5. **在_buildRow 中設(shè)置心形可點(diǎn)擊. 如果一個(gè)單詞條目被添加到喜歡的集合時(shí), 再次單擊它就能從喜歡的集合中移除 . 當(dāng)心形被點(diǎn)擊 , 函數(shù)會(huì)調(diào)用 setState()去通知框架狀態(tài)被改變了.

Widget _buildRow(WordPair pair) {
  final alreadySaved = _saved.contains(pair);
  return new ListTile(
    title: new Text(
      pair.asPascalCase,
      style: _biggerFont,
    ),
    trailing: new Icon(
      alreadySaved ? Icons.favorite : Icons.favorite_border,
      color: alreadySaved ? Colors.red : null,
    ),
    onTap: () {
      setState(() {
        if (alreadySaved) {
          _saved.remove(pair);
        } else {
          _saved.add(pair);
        }
      });
    },
  );
}

在 Flutter react式的框架中 , 調(diào)用 setState() 會(huì)為 State 對(duì)象觸發(fā) build() 方法 , 最后更新到UI上.

image

步驟 6 : 跳轉(zhuǎn)到新頁(yè)面

在這個(gè)步驟里 , 我們將添加一個(gè)頁(yè)面 (在Flutter里叫 route ) 展示喜好的推薦詞對(duì) . 我們將學(xué)到如何從主頁(yè)面導(dǎo)航到新頁(yè)面 .

在 Flutter 中 , Navigator 管理著一個(gè)包含app頁(yè)面的棧 . 推送一個(gè)頁(yè)面進(jìn)入 Navigator 的棧中, 則會(huì)更新顯示這個(gè)頁(yè)面 . 從 Navigator棧中推出一個(gè)頁(yè)面 , 則會(huì)顯示上一個(gè)頁(yè)面 .

**1 ~ 3. ** 在 RandomWordsState 的 build 方法中給 AppBar 添加一個(gè)列表圖標(biāo) . 當(dāng)用戶(hù)點(diǎn)擊圖標(biāo) , 一個(gè)包含喜好列表的頁(yè)面會(huì)被推送呈現(xiàn) .

class RandomWordsState extends State<RandomWords> {
  ...
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Startup Name Generator'),
        actions: <Widget>[
          new IconButton(icon: new Icon(Icons.list), onPressed: _pushSaved),
        ],
      ),
      body: _buildSuggestions(),
    );
  }
  ...
}

然后在 RandomWordsState 中添加 _pushSaved方法

class RandomWordsState extends State<RandomWords> {
  ...
  void _pushSaved() {
  }
}

**4 ~ 6. ** 添加 MaterialPageRoute 及它的 builder . 添加代碼生成 ListTile 行 . ListTile 的divideTiles() 方法在每一條條目間增加水平距離 . divided 變量保存著最終的行 , 通過(guò)函數(shù) toList() 轉(zhuǎn)換為列表

builder 屬性返回一個(gè) Scaffold , 包含了新頁(yè)面的導(dǎo)航欄 ,名為 "Saved Suggestion" .新頁(yè)面的內(nèi)容部分由 ListView包含 ListTiles 行組成 , 每一行由一個(gè) divider 分隔 .

void _pushSaved() {
  Navigator.of(context).push(
    new MaterialPageRoute(
      builder: (context) {
        final tiles = _saved.map(
          (pair) {
            return new ListTile(
              title: new Text(
                pair.asPascalCase,
                style: _biggerFont,
              ),
            );
          },
        );
        final divided = ListTile
          .divideTiles(
            context: context,
            tiles: tiles,
          )
          .toList();
      },
    ),
  );
}
image

步驟 7 : 通過(guò)主題改變UI

在這個(gè)最終步驟中, 我們將改變app的主題 .

**1. **我們可以簡(jiǎn)單地通過(guò)配置 ThemeData 類(lèi) 改變app的主題 . 當(dāng)前app是默認(rèn)主題, 我們將改變主色為紫色

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Startup Name Generator',
      home: new RandomWords(),
      theme: new ThemeData(
        primaryColor: Colors.purple
      ),
    );
  }
}

完成!

至此 , 我們第一個(gè) app 已經(jīng)完成 . GitHub 地址

功能相對(duì)來(lái)說(shuō)較簡(jiǎn)單 , 但是大體上讓我們對(duì)開(kāi)發(fā) Flutter app 有了一定了解. 之后我們將延續(xù)閱讀官網(wǎng)的教程 , 開(kāi)始較全面地了解構(gòu)建UI相關(guān)的部分.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市各淀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瞭空,老刑警劉巖揪阿,帶你破解...
    沈念sama閱讀 222,627評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疗我,死亡現(xiàn)場(chǎng)離奇詭異咆畏,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)吴裤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)旧找,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人麦牺,你說(shuō)我怎么就攤上這事钮蛛”掮裕” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,346評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵魏颓,是天一觀的道長(zhǎng)岭辣。 經(jīng)常有香客問(wèn)我,道長(zhǎng)甸饱,這世上最難降的妖魔是什么沦童? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,097評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮叹话,結(jié)果婚禮上偷遗,老公的妹妹穿的比我還像新娘。我一直安慰自己驼壶,他們只是感情好氏豌,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,100評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著热凹,像睡著了一般泵喘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上般妙,一...
    開(kāi)封第一講書(shū)人閱讀 52,696評(píng)論 1 312
  • 那天涣旨,我揣著相機(jī)與錄音,去河邊找鬼股冗。 笑死霹陡,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的止状。 我是一名探鬼主播烹棉,決...
    沈念sama閱讀 41,165評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼怯疤!你這毒婦竟也來(lái)了浆洗?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,108評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤集峦,失蹤者是張志新(化名)和其女友劉穎伏社,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體塔淤,經(jīng)...
    沈念sama閱讀 46,646評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡摘昌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,709評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了高蜂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片聪黎。...
    茶點(diǎn)故事閱讀 40,861評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖备恤,靈堂內(nèi)的尸體忽然破棺而出稿饰,到底是詐尸還是另有隱情锦秒,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布喉镰,位于F島的核電站旅择,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏侣姆。R本人自食惡果不足惜砌左,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,196評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望铺敌。 院中可真熱鬧汇歹,春花似錦、人聲如沸偿凭。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,698評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)弯囊。三九已至痰哨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間匾嘱,已是汗流浹背斤斧。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,804評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留霎烙,地道東北人撬讽。 一個(gè)月前我還...
    沈念sama閱讀 49,287評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像悬垃,于是被迫代替她去往敵國(guó)和親游昼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,860評(píng)論 2 361

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