iOS文档补完计划--UIScrollView

目录主要分为以下几个样式:
常用、会用、了解

目录

  • UIScrollViewDelegate
  • 滚动&&拖动
    • scrollViewDidScroll:
    • scrollViewWillEndDragging:withVelocity:targetContentOffset:
    • scrollViewDidEndDragging:willDecelerate:
    • scrollViewShouldScrollToTop:
    • scrollViewDidScrollToTop:
    • scrollViewWillBeginDecelerating:
    • scrollViewDidEndDecelerating:
  • 缩放管理
    • viewForZoomingInScrollView:
    • scrollViewWillBeginZooming:withView:
    • scrollViewDidEndZooming:withView:atScale:
    • scrollViewDidZoom:
  • UIScrollView
  • 滚动动画
    • scrollViewDidEndScrollingAnimation:
  • adjustedContentInset更改
    • scrollViewDidChangeAdjustedContentInset:
  • UIScrollView
  • 内容大小&&偏移量
    • contentSize
    • contentOffset
    • setContentOffset:animated:
  • 内容嵌入
    • adjustedContentInset(iOS11+)
    • contentInset(iOS11-)
    • contentInsetAdjustmentBehavior
    • adjustedContentInsetDidChange
  • 配置UIScrollView
    • scrollEnabled
    • directionalLockEnabled
    • pagingEnabled
    • scrollsToTop
    • bounces
    • alwaysBounceVertical
    • alwaysBounceHorizontal
  • 获得滚动状态
    • tracking
    • dragging
    • decelerating
    • decelerationRate
  • 滚动条 && 刷新控件
    • indicatorStyle
    • scrollIndicatorInsets
    • showsHorizontalScrollIndicator
    • showsVerticalScrollIndicator
    • flashScrollIndicators
    • refreshControl
  • 滚动到指定区域
    • scrollRectToVisible:animated:
  • 触摸管理
    • touchesShouldBegin:withEvent:inContentView:
    • touchesShouldCancelInContentView:
    • canCancelContentTouches
    • delaysContentTouches
  • 缩放和移动
    • panGestureRecognizer
    • pinchGestureRecognizer
    • zoomToRect:animated:
    • setZoomScale:animated:
    • maximumZoomScale
    • minimumZoomScale
    • zoomBouncing
    • zooming
    • bouncesZoom
  • 键盘管理
    • keyboardDismissMode
  • 索引
    • indexDisplayMode
  • 参考资料

UIScrollViewDelegate

通过协议方法响应滚动、缩放、滚动内容的减速和滚动动画等操作。


滚动&&拖动

  • scrollViewDidScroll:

视图滚动(contentOffset改变)时触发

- (void)scrollViewDidScroll:(UIScrollView *)scrollView;

这个方法在任何方式触发 contentOffset 变化的时候都会被调用(包括用户拖动,减速过程,直接通过代码设置等),可以用于监控 contentOffset 的变化,并根据当前的 contentOffset 对其他 view 做出随动调整。

  • scrollViewWillEndDragging:withVelocity:targetContentOffset:

用户手指离开屏幕(滚动完成)时触发

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView 
                     withVelocity:(CGPoint)velocity 
              targetContentOffset:(inout CGPoint *)targetContentOffset;

velocity表示惯性速度、targetContentOffset表示将会在哪里完全静止。
velocity不为CGPointZero时,UIScrollview 会以 velocity 为初速度,减速直到 targetContentOffset

需要注意的是:

这里的 targetContentOffset 是个指针,没错,你可以改变减速运动的目的地

  • scrollViewDidEndDragging:willDecelerate:

用户手指离开屏幕(滚动完成)时触发

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView 
                  willDecelerate:(BOOL)decelerate;

deceleratebool值、表示是否有继续滚动的减速过程。
这个值、取决于WillEndDragging方法中velocity是否为CGPointZero

需要注意的是:
  1. didEndDragging 之后,如果有减速过程,UIScrollviewdragging属性 并不会立即置为 NO,而是要等到减速结束之后。
  2. 通过代码的方式(- setContentOffset:animated:)并不会触发。
  • scrollViewShouldScrollToTop:

当触摸状态栏时、是否允许UIScrollView滚动到顶部。默认返回YES

- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView;

这个效果依赖scrollsToTop属性、当然他默认也是YES

  • scrollViewDidScrollToTop:

UIScrollView滚动到顶部之后的回调

- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView;
  • scrollViewWillBeginDecelerating:

UIScrollView开始自动减速时触发

- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView;
  • scrollViewDidEndDecelerating:

UIScrollView减速结束时触发

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
这里有一种特殊情况需要注意:

当一次减速动画尚未结束的时候再次拖动 UIScrollViewdidEndDecelerating 先不会被调用,并且这时 UIScrollViewdraggingdecelerating 属性都是 YES

  1. 新的拖动如果有加速度
    那么 willBeginDecelerating 会再一次被调用,然后才是 didEndDecelerating
  2. 新的拖动如果没有加速度
    虽然 willBeginDecelerating 不会被调用,但前一次留下的 didEndDecelerating 会被调用。

缩放管理

  • viewForZoomingInScrollView:

在缩放手势发生时、返回需要变化的View。不允许缩放可以返回nil

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView;

你还需要其他属性进行配合才能实现缩放功能
Demo的话可以看一个帖子《iOS开发UI篇—UIScrollView控件实现图片缩放功能》

此外有几点需要注意:

  1. 返回的View并不必须为UIScrollView的子视图
  2. 系统会根据手势修改该Viewtransform进而完成缩放。
  • scrollViewWillBeginZooming:withView:

缩放开始时触发

- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView 
                          withView:(UIView *)view;

此时View的状态为缩放之前

  • scrollViewDidEndZooming:withView:atScale:

缩放完成时触发

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView 
                       withView:(UIView *)view 
                        atScale:(CGFloat)scale;

此时View的状态为缩放之后

  • scrollViewDidZoom:

发生缩放时触发

- (void)scrollViewDidZoom:(UIScrollView *)scrollView;

实时触发、和scrollViewDidScroll一样


滚动动画

  • scrollViewDidEndScrollingAnimation:

滚动结束时触发

- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView;

在通过代码的方式(- setContentOffset:animated:)令UIScrollView滚动结束时触发。


adjustedContentInset更改

  • scrollViewDidChangeAdjustedContentInset:

adjustedContentInset属性改变时触发

- (void)scrollViewDidChangeAdjustedContentInset:(UIScrollView *)scrollView;

UIScrollView

一个允许对其包含视图进行滚动、缩放操作的视图

继承关系

UIScrollView是UITableViewUITextView的父类。

响应原理

当用户手指触摸UIScrollView及其子类时,其属性 isTracking 设置为YES,同时内部会开启一个NSTimer, 在timer的一个极短的时间周期内,如果手指发生了较大距离的移动,UIScrollView接收这个事件开始滚动到相应的位置, isTracking 设置为NO。 如果手指没有发生较大距离的移动,而touch位置正好位于子视图上,并且其子视图接受touch事件,那么就触发子视图的touch事件。

我们可以通过以下三个方法来影响它处理滚动手势的方式:
touchesShouldBegin:withEvent:inContentView:pagingEnabledtouchesShouldCancelInContentView:

滚动时会触发layoutSubviews

详见《iOS文档补完计划--UIView》

UIScrollView还可以处理内容的缩放和平移:
  • 当手势正在进行时,滚动视图不会向子视图发送任何跟踪调用。
  • UIScrollView类可以有一个必须采用UIScrollViewDelegate协议的委托。要使缩放和平移工作,委托必须实现viewForZoomingInScrollView:scrollViewDidEndZooming:withView:atScale:
  • 最大变焦比例尺和最小变焦比例尺必须不同。
状态恢复

如果你实现了restorationIdentifier、那么它会试图在应用程序启动之间保存与滚动相关的信息(zoomScale、contentInset和contentOffset)。


内容大小&&偏移量

  • contentSize

可以滚动的内容大小

@property(nonatomic) CGSize contentSize;
  • contentOffset

偏移量

@property(nonatomic) CGPoint contentOffset;
  • -setContentOffset:animated:

允许通过动画的方式设置contentOffset

- (void)setContentOffset:(CGPoint)contentOffset 
                animated:(BOOL)animated;

内容嵌入

主要控制内容展示的范围、frame(0.0)的位置。
十分重要、因为iOS11之后安全区域以及adjustedContentInset的引入。

  • adjustedContentInset

允许内容到边缘的距离(决定iOS11以上)

@property(nonatomic, readonly) UIEdgeInsets adjustedContentInset;

在iOS11中替代了contentInset起到最终决定作用、其值和contentInset以及contentInsetAdjustmentBehavior有关

以MJRefresh内的代码为例

- (UIEdgeInsets)mj_inset
{
#ifdef __IPHONE_11_0
    if (respondsToAdjustedContentInset_) {
        return self.adjustedContentInset;
    }
#endif
    return self.contentInset;
}

contentInsetAdjustmentBehavior的类型决定是否在调整中包含安全区域、具体的数值可以参考《本文》

  • contentInset

允许内容到边缘的距离(决定iOS11以下)

@property(nonatomic) UIEdgeInsets contentInset;

使用此属性可扩展内容和内容视图边缘之间的空间。

  • contentInsetAdjustmentBehavior

决定调整后的adjustedContentInset具体值、默认UIScrollViewContentInsetAdjustmentAutomatic

@property(nonatomic) UIScrollViewContentInsetAdjustmentBehavior contentInsetAdjustmentBehavior;
UIScrollViewContentInsetAdjustmentAutomatic:如果scrollview在一个automaticallyAdjustsScrollViewInsets = YES的controller上,并且这个Controller包含在一个navigation controller中,这种情况下会设置在top & bottom上 adjustedContentInset = safeAreaInset + contentInset不管是否滚动。其他情况下与UIScrollViewContentInsetAdjustmentScrollableAxes相同

UIScrollViewContentInsetAdjustmentScrollableAxes: 在可滚动方向上adjustedContentInset = safeAreaInset + contentInset,在不可滚动方向上adjustedContentInset = contentInset;依赖于scrollEnabled和alwaysBounceHorizontal / vertical = YES,scrollEnabled默认为yes,所以大多数情况下,计算方式还是adjustedContentInset = safeAreaInset + contentInset

UIScrollViewContentInsetAdjustmentNever: adjustedContentInset = contentInset

UIScrollViewContentInsetAdjustmentAlways: adjustedContentInset = safeAreaInset + contentInset

通常、如果我们想要屏蔽系统的安全区域(比如我们想满屏显示、甚至被状态栏遮挡)

if (@available(iOS 11.0, *)) {
        self.tableView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
    }
  • adjustedContentInsetDidChange

adjustedContentInset值改变时触发的放啊

- (void)adjustedContentInsetDidChange;

需要注意UIScrollViewDelegatescrollViewDidChangeAdjustedContentInset也能捕获这个动作、当然、他是给外界用的。


配置UIScrollView

能否滚动、滚动方向、分页、回归顶部、弹簧效果等

  • scrollEnabled

是否能够滚动

@property(nonatomic, getter=isScrollEnabled) BOOL scrollEnabled;
  • directionalLockEnabled

滚动时是否锁定另一坐标轴上的滚动

@property(nonatomic, getter=isDirectionalLockEnabled) BOOL directionalLockEnabled;

如果为YES、那么在同一次操作中你将只能在一个坐标轴上滚动。

但是官方文档上指出、如果对角(45°)滑动这个属性将失效。
有兴趣可以参阅《Stackoverflow》中的讨论。

  • pagingEnabled

其否启用分页滑动

@property(nonatomic, getter=isPagingEnabled) BOOL pagingEnabled;

每页的宽/高度为scrollview的bounds

  • scrollsToTop

是否允许(当触摸状态栏时)滚动到顶部、默认YES

@property(nonatomic) BOOL scrollsToTop;

对于一个scrollsToTop为YES的UIScrollView、你可以通过scrollViewShouldScrollToTop代理的返回值控制具体是否可以回归顶部。

需要注意的是如果有多个UIScrollView该值为YES、那么他们都将失效。

  • bounces

弹簧效果、默认YES

@property(nonatomic) BOOL bounces;

在滚动超出内容后、会反弹回来。

  • alwaysBounceVertical

垂直方向否有弹簧效果

@property(nonatomic) BOOL alwaysBounceVertical;
  • alwaysBounceHorizontal

水平方向否有弹簧效果

@property(nonatomic) BOOL alwaysBounceHorizontal;
这里不同的控件的默认值会有些差异
  1. UIScrollViewUICollectionView
    默认情况下垂直alwaysBounceVertical和水平alwaysBounceHorizontal都是NO;只有当内容视图的尺寸超过了自己的bounds的尺寸的时候,相应方向上反弹属性才会自动设置为YES;
  2. UITableView
    默认情况下垂直alwaysBounceVertical是YES,水平alwaysBounceHorizontal是NO;

获得滚动状态

开始触碰、拖拽、减速、停止等一些列属性

  • tracking

用户是否已经触摸了内容

@property(nonatomic, readonly, getter=isTracking) BOOL tracking;

如果用户仅仅是触摸、但并未拖动、也是YES。

比如我们在手指离开时为YES、在惯性滚动之后为NO。

2018-10-11 11:23:34.580538+0800 DocumentDemo[19889:1060480] scrollViewDidEndDragging - 1
2018-10-11 11:23:36.916507+0800 DocumentDemo[19889:1060480] scrollViewDidEndDecelerating - 0

  • dragging

获取用户是否开始拖动视图

@property(nonatomic, readonly, getter=isDragging) BOOL dragging;
  1. 惯性时 >> YES
  2. 回弹时 >> NO
  3. 代码设置滚动 >> NO
  • decelerating

获取视图是否开始减速(用户停止拖动但视图仍在滚动)

@property(nonatomic, readonly, getter=isDecelerating) BOOL decelerating;
  • decelerationRate

惯性速度的衰减速率

@property(nonatomic) UIScrollViewDecelerationRate decelerationRate;

UIScrollViewDecelerationRate本身是一个浮点型、范围是0-1。系统为我们提供了两个参考值(当然你也可以自己设置):

UIScrollViewDecelerationRateNormal:0.998(默认值)
UIScrollViewDecelerationRateFast:0.99

滚动条 && 刷新控件

  • indicatorStyle

设置滑动条样式

@property(nonatomic) UIScrollViewIndicatorStyle indicatorStyle;

具体值为一个枚举

typedef NS_ENUM(NSInteger, UIScrollViewIndicatorStyle) {

    UIScrollViewIndicatorStyleDefault,     //默认

    UIScrollViewIndicatorStyleBlack,       //黑色风格

    UIScrollViewIndicatorStyleWhite        //白色风格
};
  • scrollIndicatorInsets

滑动条的位置(长短)。默认UIEdgeInsetsZero

@property(nonatomic) UIEdgeInsets scrollIndicatorInsets;

需要注意的是UIEdgeInsets结构体中有4个值。正好对应右侧、下方两个滑动条。

支付宝这种滑动条从屏幕中央开始的效果就是用了这个属性(我猜)


  • showsHorizontalScrollIndicator
  • showsVerticalScrollIndicator

水平/垂直的滑动条是否会被显示

@property(nonatomic)  BOOL showsHorizontalScrollIndicator; 
@property(nonatomic)  BOOL  showsVerticalScrollIndicator;  

在滑动发生时、是否会显示出来。并不是一直显示

  • flashScrollIndicators

当你的滑动条短暂的显示一下

- (void)flashScrollIndicators;

你想让用户知道目前的位置时、可以用一下、而不是偷偷滚动一点点距离。

  • refreshControl

刷新控件(iOS10+)

@property(nonatomic, strong) UIRefreshControl *refreshControl;

你可以用UIRefreshControl自己实现刷新的控件、这个我没看


滚动到指定区域

  • - scrollRectToVisible:animated:

UIScrollView坐标系中的指定Rect展示出来

- (void)scrollRectToVisible:(CGRect)rect 
                   animated:(BOOL)animated;
  1. 宽高不能为0
  2. 如果已经可以展示则不再移动

触摸管理

能否响应某处子视图、事件传递相关

  • - touchesShouldBegin:withEvent:inContentView:

自定义手指触碰到显示内容时的默认行为

- (BOOL)touchesShouldBegin:(NSSet<UITouch *> *)touches 
                 withEvent:(UIEvent *)event 
             inContentView:(UIView *)view;

当点击到了一个子View会触发这个方法。
如果返回YES(默认)、UIScrollView会把当前事件分发到子view去处理(调用对应touchesBegan方法)、如果NO就不分发此事件。

需要注意的是:

只有系统识别到点击、而不是拖动动作才会下发。具体识别方法可以看本文中《UIScrollView >> 响应原理》处的解释。

  • - touchesShouldCancelInContentView:

是否取消与子视图相关的触摸、并恢复拖动

- (BOOL)touchesShouldCancelInContentView:(UIView *)view;

默认情况下有两种情况

  1. 如果视图不是UIControl对象
    返回值为YES
  2. 如果是UIControl对象
    返回值为NO

也就是说默认情况下、我们长按一个UIButton只有、就不能继续滚动了。如果返回NO、那么一旦我们继续滚动、UIButton的事件则会被取消。

  • canCancelContentTouches

当长按后继续滚动时、是否给子视图传递取消动作的消息

@property(nonatomic) BOOL canCancelContentTouches;

默认设置为YES。
对于子View、长按后令UIScrollView继续滚动、会收到touchesCancelled消息。如果这个值为NO、则不会收到该消息。

  • delaysContentTouches

设置视图是否延迟处理触摸事件。默认YES

@property(nonatomic) BOOL delaysContentTouches;

不等待识别是否为拖动手势、直接将消息先下发给内容控件(touchesBegan方法)。


缩放和移动

拖动、捏合的手势获取。缩放相关

  • panGestureRecognizer

返回ScrollView的拖动手势识别器

@property(nonatomic, readonly) UIPanGestureRecognizer *panGestureRecognizer;

主要用于处理手势冲突、比如右滑返回等等需要获取具体手势的时候。

  • pinchGestureRecognizer

返回ScrollView的捏合手势识别器

@property(nonatomic, readonly) UIPinchGestureRecognizer *pinchGestureRecognize
  • - zoomToRect:animated:

缩放到特定区域、使其可见

- (void)zoomToRect:(CGRect)rect 
          animated:(BOOL)animated;

这个rect必须处在viewForZoomingInScrollView:方法返回的View之中、也就是允许对该View进行缩放。
大于当前rect则缩小、否则放大。

该方法的主要用于双击缩放图片的效果:《iOS 图片缩放》
  • - setZoomScale:animated:

设置当前的缩放比例

- (void)setZoomScale:(CGFloat)scale 
            animated:(BOOL)animated;

新的scale必须介于minimumZoomScalemaximumZoomScale之间。

该方法的主要用于双击缩放图片的效果:《iOS 图片缩放》
  • maximumZoomScale
  • minimumZoomScale

允许缩放的最大/最小值

@property(nonatomic) CGFloat maximumZoomScale;
@property(nonatomic) CGFloat minimumZoomScale;

默认值都为1、如果想实现缩放效果必须设置。

  • zoomBouncing

当前的缩放比例是否超出设置的峰值

@property(nonatomic, readonly, getter=isZoomBouncing) BOOL zoomBouncing;

bounce也就是意味着将会有一个回弹效果出现

  • zooming

是否正在进行缩放

@property(nonatomic, readonly, getter=isZooming) BOOL zooming;
  • bouncesZoom

当缩放超过阈值、是否有会回弹动画发生。默认YES

@property(nonatomic) BOOL bouncesZoom;

键盘管理

  • keyboardDismissMode

当开始滚动时、键盘消失的方式

@property(nonatomic) UIScrollViewKeyboardDismissMode keyboardDismissMode;

具体为一个枚举值、包含三个样式

typedef NS_ENUM(NSInteger, UIScrollViewKeyboardDismissMode) {

    UIScrollViewKeyboardDismissModeNone,

    UIScrollViewKeyboardDismissModeOnDrag,      //手指滑动视图键盘就会消失

    UIScrollViewKeyboardDismissModeInteractive, //表示键盘可以随着手指下滑而移出屏幕
};

这个键盘的控件、并不必须是scrollView的子视图。


索引

  • indexDisplayMode

滚动时索引的显示方式

@property(nonatomic) UIScrollViewIndexDisplayMode indexDisplayMode;

UIScrollViewIndexDisplayMode类型的枚举、有两种方式

typedef NS_ENUM(NSInteger, UIScrollViewIndexDisplayMode) {
    UIScrollViewIndexDisplayModeAutomatic,    // 索引将根据需要自动显示或隐藏
    UIScrollViewIndexDisplayModeAlwaysHidden, // 索引将永远不会显示
} API_AVAILABLE(tvos(10.2));

TVOS专用


最后

本文主要是自己的学习与总结。如果文内存在纰漏、万望留言斧正。如果愿意补充以及不吝赐教小弟会更加感激。


参考资料

官方文档-UIScrollView
UIScrollView 实践经验
scrollViewDidEndScrollingAnimation和scrollViewDidEndDecelerating的区别
iOS 图片缩放

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

推荐阅读更多精彩内容