Flutter入门09 -- Widget之滚动组件类

  • 常见的滚动组件有:ListView、GridView、SingleChildScrollView、CustomScrollView、NestedScrollView、Scrollable、Scrollbar、NotificationListener、ScrollConfiguration、RefreshIndicator、PageView

ListView

  • ListView创建的方式通常有三种,分别为ListView()ListView.builder()ListView.separated()
ListView创建方式之ListView()
  • 构造方法如下:
ListView({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    this.itemExtent,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double cacheExtent,
    List<Widget> children = const <Widget>[],
    int semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
})
  • scrollDirection:滚动方向,属于Axis类型;
  • itemExtent:item单元格的高度;
  • children:单元格数组,这里的单元格指的是ListTile;
  • shrinkWrap:其值为true时,会将item子组件的高度相加,然后赋值给ListView的高度,容易引发ListView高度溢出异常的问题;
  • 案例代码如下:
class SFHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView(
      scrollDirection: Axis.vertical,
      itemExtent: 100,//设置Item的高度
      children: List.generate(100, (index) {
        return ListTile(
          leading: Icon(Icons.people),
          trailing: Icon(Icons.delete),
          title: Text("联系人${index+1}"),
          subtitle: Text("联系人电话号码:19991604555"),
        );
      }),
    );
  }
}
  • 效果图如下:
image.png
  • 通过ListView()创建,会一次性创建100个Item,这样性能比较差,其适用于Item个数确定,且数量较少的情况下才会采用;
ListView创建方式之ListView.builder()
  • 不会一次性创建所有Item,而是需要展示的Item才会去创建,性能较好,构造方法如下:
ListView.builder({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    this.itemExtent,
    @required IndexedWidgetBuilder itemBuilder,
    int itemCount,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double cacheExtent,
    int semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
})
  • scrollDirection:滚动方向,属于Axis类型;
  • itemExtent:单元格高度;
  • itemCount:单元格数量;
  • itemBuilder:获取item单元格,其类型为Widget Function(BuildContext context, int index),调用函数创建item;
  • 案例代码如下:
class SFHomeBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return ListView.builder(
      itemExtent: 80,
      itemCount: 100,
      itemBuilder: (BuildContext context,int index) {
        return Container(
            color: Colors.red,
            child: Text("Hello World!!! ${index}",style: TextStyle(fontSize: 20)),
            padding: EdgeInsets.all(10),
        );
      }
    );
  }
}
  • 效果图如下:
Snip20220214_22.png
ListView创建方式之ListView.separated()
  • 创建带有分割线的ListView,构造函数如下:
ListView.separated({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    @required IndexedWidgetBuilder itemBuilder,
    @required IndexedWidgetBuilder separatorBuilder,
    @required int itemCount,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double cacheExtent,
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
})
  • scrollDirection:滚动方向,属于Axis类型;
  • itemExtent:单元格高度;
  • itemCount:单元格数量;
  • itemBuilder:获取item单元格组件,其类型为Widget Function(BuildContext context, int index),调用函数创建item;
  • separatorBuilder:获取分割线组件,类型同上;
  • 不能设置单元格item的高度
  • 案例代码如下:
class SFHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ListView.separated(
        itemBuilder: (BuildContext ctx, int index){
          return Text("Hello World!!! ${index}",style: TextStyle(fontSize: 20),);
        },
        //分割线
        separatorBuilder: (BuildContext ctx,int index){
          return Divider(color: Colors.red,indent: 20,endIndent: 20,thickness: 5);
        },
        itemCount: 100
    );
  }
}
  • 效果图如下:
image.png

GridView

  • GridView的创建方式有:GridView()GridView.builder()
创建方式之GridView()
  • 构造方法如下所示:
GridView({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    @required this.gridDelegate,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double cacheExtent,
    List<Widget> children = const <Widget>[],
    int semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
}) 
  • children:子组件数组,item单元格数组;
  • scrollDirection:表示滚动方向,默认为垂直方向,属于Axis类型;
  • gridDelegate:GridView的代理,属于SliverGridDelegate类型,其本质是控制GridView如何排列内部子元素的一个委托;
    • SliverGridDelegate是一个抽象类,其有两个实现子类,如下所示:
    • SliverGridDelegateWithFixedCrossAxisCount:用于固定列数的场景;
    • SliverGridDelegateWithMaxCrossAxisExtent:用于子元素有最大宽度限制的场景;
  • controller:GridView的滚动监听者;
  • primary:当内容不足以滚动时,是否支持滚动;
  • 使用gridDelegateSliverGridDelegateWithFixedCrossAxisCount来创建GridView,其中SliverGridDelegateWithFixedCrossAxisCount的构造方法如下:
const SliverGridDelegateWithFixedCrossAxisCount({
    @required this.crossAxisCount,
    this.mainAxisSpacing = 0.0,
    this.crossAxisSpacing = 0.0,
    this.childAspectRatio = 1.0,
})
  • 交叉轴方向上item数量固定,其宽度根据屏幕的宽度与item的数量进行计算;
  • crossAxisCount:交叉轴方向(水平方向)item的个数;
  • childAspectRatio:item的宽高比;
  • crossAxisSpacing:交叉轴方向上 item之间的间距;
  • mainAxisSpacing:主轴方向上 item之间的间距;
  • 案例代码如下:
class SFHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GridView(
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 3,
        childAspectRatio: 1.5,
        crossAxisSpacing: 8,
        mainAxisSpacing: 8
      ),
      children:
        List.generate(100, (index) {
           return Container(
             color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256), Random().nextInt(256)),
           );
        }),
    );
  }
}
  • 效果图如下所示:
image.png
  • 使用gridDelegateSliverGridDelegateWithMaxCrossAxisExtent来创建GridView,其中SliverGridDelegateWithMaxCrossAxisExtent的构造方法如下:
const SliverGridDelegateWithMaxCrossAxisExtent({
    @required this.maxCrossAxisExtent,
    this.mainAxisSpacing = 0.0,
    this.crossAxisSpacing = 0.0,
    this.childAspectRatio = 1.0,
})
  • 交叉轴方向上的设置item的最大宽度,item的个数不固定;
  • maxCrossAxisExtent:交叉轴方向上item的最大宽度;
  • childAspectRatio:item的宽高比;
  • crossAxisSpacing:交叉轴方向上 item之间的间距;
  • mainAxisSpacing:主轴方向上 item之间的间距;
  • 案例代码如下:
class SFHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GridView(
      gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
        maxCrossAxisExtent: 220,
        crossAxisSpacing: 8,
        mainAxisSpacing: 8,
        childAspectRatio: 1.5
      ),
      children: List.generate(100, (index) {
        return Container(
          color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256),Random().nextInt(256)),
        );
      })
    );
  }
}
  • 效果图如下:
image.png
创建方式之GridView.builder()
  • 构造方法如下:
  GridView.builder({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    @required this.gridDelegate,
    @required IndexedWidgetBuilder itemBuilder,
    int itemCount,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double cacheExtent,
    int semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
})
  • 属性大部分相同;
  • gridDelegate:GridView的代理,属于SliverGridDelegate类型,其本质是控制GridView如何排列内部子元素的一个委托;
  • itemBuilder:创建item的构造函数;
  • itemCount:item的数量;
  • 案例代码如下:
class SFHomeBody extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return GridView.builder(
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 3,
          mainAxisSpacing: 10,
          crossAxisSpacing: 10,
          childAspectRatio: 1.5
        ),
        itemBuilder: (BuildContext ctx,int index){
          return Container(color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256),Random().nextInt(256)));
        },
      itemCount: 100,
    );
  }
}
  • 效果图如下:
image.png

CustomScrollView

  • CustomScrollView:自定义滚动组件,需要传入Slivers即Sliver数组,我们知道ListViewGridView都是继承自BoxScrollView,而BoxScrollView是一个抽象类,从源码来看ListViewGridView在创建的过程中都需要执行buildSlivers方法,其内部调用buildChildLayout方法,这是一个抽象方法,分别由ListViewGridView来实现,最终提供一个Sliver数组,其中ListView提供的Sliver为SliverFixedExtentListGridView提供的Sliver为SliverGrid
  • CustomScrollView的构造函数如下:
const CustomScrollView({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    Key center,
    double anchor = 0.0,
    double cacheExtent,
    this.slivers = const <Widget>[],
    int semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
})
  • slivers:Sliver的数组,最重要的一个参数;
  • 常见的Sliver组件如下:
    • SliverSafeArea
    • SliverAppBar
    • SliverList
    • SliverGrid
    • SliverPadding
    • SliverFixedExtentList
    • SliverPrototypeExtentList
    • SliverToBoxAdapter
    • SliverPersistentHeader
单个Sliver -- SliverSafeArea
  • SliverSafeArea的构造函数如下:
const SliverSafeArea({
    Key key,
    this.left = true,
    this.top = true,
    this.right = true,
    this.bottom = true,
    this.minimum = EdgeInsets.zero,
    @required this.sliver,
}) 
  • 案例代码如下:
import 'dart:math';
import 'package:flutter/material.dart';

void main() => runApp(SFMyApp());

class SFMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: SFHomePage());
  }
}

class SFHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text("基础widget")),
        body: SFHomeContent());
  }
}

class SFHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return SliverDemo1();
  }
}

class SliverDemo1 extends StatelessWidget {
  const SliverDemo1({
    Key key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      slivers: [
        SliverSafeArea(
          sliver: SliverPadding(
            padding: EdgeInsets.all(8),
            sliver: SliverGrid(
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 2,
                crossAxisSpacing: 8,
                mainAxisSpacing: 8,
                childAspectRatio: 1.5
              ),
              delegate: SliverChildBuilderDelegate(
                  (BuildContext ctx,int index){
                    return Container(
                      color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256),Random().nextInt(256)),
                    );
                  },
                childCount: 100
              ),
            ),
          ),
        )
      ],
    );
  }
}
  • 自定义CustomScrollView,需传入Slivers数组,这里传入的Sliver为SliverSafeArea
  • SliverPadding:属于Sliver组件,可设置内边距;
  • SliverGrid:属于Sliver组件,是实现网格视图的核心组件,其构造函数如下:
const SliverGrid({
    Key key,
    @required SliverChildDelegate delegate,
    @required this.gridDelegate,
 })
  • gridDelegate:GridView的代理,属于SliverGridDelegate类型,其本质是控制GridView如何排列内部子元素的一个委托;
  • delegate:是提供item组件,类型为SliverChildDelegate
  • SliverChildDelegate是抽象类,其作用是用来创建滚动组件的item,其有两个子类分别为SliverChildListDelegateSliverChildBuilderDelegate
    • SliverChildListDelegate:性能较差,item一次性创建所有;
    • SliverChildBuilderDelegate:性能较好,创建需要展示的item,其构造函数如下:
const SliverChildBuilderDelegate(
    this.builder, {
    this.findChildIndexCallback,
    this.childCount,
    this.addAutomaticKeepAlives = true,
    this.addRepaintBoundaries = true,
    this.addSemanticIndexes = true,
    this.semanticIndexCallback = _kDefaultSemanticIndexCallback,
    this.semanticIndexOffset = 0,
}) 
  • builder:是一个item的构造函数;

  • childCount:item的数量;

  • SafeAreaSliverSafeArea的区别:

    • SafeArea:安全区域,让目标组件在安全区域内显示;
    • SliverSafeArea:给Sliver设置安全区域,且在滚动时可以在非安全区域内滚动,而SafeArea不可以;
  • 效果图如下:

image.png
多个Sliver (SliverAppBar + SliverGrid + SliverList)
import 'dart:math';
import 'package:flutter/material.dart';

void main() => runApp(SFMyApp());

class SFMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: SFHomePage());
  }
}

class SFHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        // appBar: AppBar(title: Text("基础widget")),
        body: SFHomeContent());
  }
}

class SFHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CustomScrollView(
      slivers: [
        SliverAppBar(
          pinned: true,
          expandedHeight: 200,
          flexibleSpace: FlexibleSpaceBar(
            title: Text("Hello World!!",style: TextStyle(fontSize: 25),),
          ),
        ),
        SliverGrid(
          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 2,
              crossAxisSpacing: 8,
              mainAxisSpacing: 8,
              childAspectRatio: 2.5
          ),
            delegate: SliverChildBuilderDelegate(
              (BuildContext ctx,int index){
                  return Container(
                    color: Color.fromARGB(255, Random().nextInt(256), Random().nextInt(256),Random().nextInt(256)),
                  );
               },
             childCount: 10
            ),
        ),
        SliverList(
          delegate: SliverChildBuilderDelegate(
              (BuildContext ctx,int index){
                return ListTile(
                  leading: Icon(Icons.people),
                  title: Text("联系人$index"),
                );
              },
            childCount: 20
          ),
        )
      ],
    );
  }
}
  • slivers数组中传入了SliverAppBarSliverGridSliverList三种类型的Sliver,效果如下:
    image.png

滚动组件的监听

  • 滚动组件的监听通常有两种方式,分别为controllerNotificationListener
controller监听
  • 可以设置默认值offset;
  • 监听滚动,也可以监听滚动的位置;
  • 案例代码:
import 'package:flutter/material.dart';

void main() => runApp(SFMyApp());

class SFMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: SFHomePage());
  }
}

class SFHomePage extends StatefulWidget {
  @override
  _SFHomePageState createState() => _SFHomePageState();
}

class _SFHomePageState extends State<SFHomePage> {
  ScrollController controller = ScrollController(initialScrollOffset: 300);
  bool isShowFloatButton = false;

  @override
  void initState() {
    super.initState();
    controller.addListener(() {
      print("监听到滚动: ${controller.offset}");
      setState(() {
        isShowFloatButton = controller.offset >= 1000;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("基础widget")),
      body: SFHomeContent(controller),
      floatingActionButton: isShowFloatButton ? FloatingActionButton(
        child: Icon(Icons.arrow_upward),
        onPressed: (){
          controller.animateTo(0, duration: Duration(seconds: 1), curve: Curves.easeIn);
        },
      ) : null,
    );
  }
}

class SFHomeContent extends StatelessWidget {
  final ScrollController controller;
  SFHomeContent(this.controller);

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      controller: controller,
      itemBuilder: (BuildContext ctx, int index) {
        return ListTile(
          leading: Icon(Icons.people),
          title: Text("联系人$index"),
        );
      },
      itemCount: 100,
    );
  }
}
  • 右下角悬浮按钮,当前滚动偏移量>=1000时显示;
NotificationListener监听
  • 案例代码:
import 'package:flutter/material.dart';

void main() => runApp(SFMyApp());

class SFMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: SFHomePage());
  }
}

class SFHomePage extends StatefulWidget {
  @override
  _SFHomePageState createState() => _SFHomePageState();
}

class _SFHomePageState extends State<SFHomePage> {
  bool isShowFloatButton = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("基础widget")),
      body: SFHomeContent(),
      floatingActionButton: isShowFloatButton
          ? FloatingActionButton(
              child: Icon(Icons.arrow_upward),
              onPressed: () {

              },
            )
          : null,
    );
  }
}

class SFHomeContent extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return NotificationListener(
      onNotification: (ScrollNotification notification){
        if(notification is ScrollStartNotification){
          print("开始滚动");
        }else if (notification is ScrollUpdateNotification){
          print("正在滚动 -- 总区域:${notification.metrics.maxScrollExtent} 当前位置: ${notification.metrics.pixels}");
        }else if (notification is ScrollEndNotification){
          print("结束滚动");

        }

        return true;
      },
      child: ListView.builder(
        itemBuilder: (BuildContext ctx, int index) {
          return ListTile(
            leading: Icon(Icons.people),
            title: Text("联系人$index"),
          );
        },
        itemCount: 100,
      ),
    );
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352

推荐阅读更多精彩内容