CoreAnimation之CALayer基础

Core Animation是一个复合引擎,它的职责就是尽可能快地组合屏幕上不同的可视内容,这个内容是被分解成独立的图层,存储在一个叫做图层树的体系之中。

1. UIView与CALayer的关系:

  • CALayer在概念上和UIView类似,同样也是一些被层级关系树管理的矩形块,同样也可以包含一些内容(像图片,文本或者背景色),管理子图层的位置。并且它们有一些方法和属性用来做动画和变换,但是CALayer不能响应事件。

  • UIView是基于CALayer的一种封装,每一个UIView都有一个CALayer实例的图层属性,也就是所谓的backing layer,视图的职责就是创建并管理这个图层,以确保当子视图在层级关系中添加或者被移除的时候,他们关联的图层也同样对应在层级关系树当中有相同的操作。UIView的尺寸样式以及内容都由CALayer提供。

  • 大多数情况下我们都使用UIView而不使用CALayer的原因是,首先是CALayer不能响应事件,其次是CALayer不方便自动布局,最重要的是苹果已经基于CALayer封装好了UIView,我们在使用UIView高级接口的同时也能使用CALayer的底层功能。

  • 既然这里提到了层级树,顺便提一下事件响应链

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    UIResponder * next = [self nextResponder];
    NSMutableString * prefix = @"-".mutableCopy;
    while (next != nil) {
        NSLog(@"%@%@", prefix, [next class]);
        [prefix appendString: @"--"];
        next = [next nextResponder];
    }
}

2. CALayer内容相关的属性

  • contents

可以给CALayer的contents赋值

self.layerView.layer.contents = (__bridge id)image.CGImage;
  • contentGravity

可以类似设置UIView的contentModel一样,设置CALayer的contentGravity

self.layerView.layer.contentsGravity = kCAGravityResizeAspect;

contentGravity可以赋值的常量值如下

CA_EXTERN NSString * const kCAGravityCenter
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAGravityTop
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAGravityBottom
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAGravityLeft
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAGravityRight
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAGravityTopLeft
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAGravityTopRight
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAGravityBottomLeft
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAGravityBottomRight
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAGravityResize
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAGravityResizeAspect
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
CA_EXTERN NSString * const kCAGravityResizeAspectFill
    CA_AVAILABLE_STARTING (10.5, 2.0, 9.0, 2.0);
  • contentsScale

CALayer的contentsScale属性可以用以适配retina屏幕,该属性值默认为1.0,将会以每个点1个像素绘制图片,如果设置为2.0,则会以每个点2个像素绘制图片,这就是我们熟知的Retina屏幕。

UIImage *image = [UIImage imageNamed:@"doll"];
UIView *view = [[UIView alloc] initWithFrame:self.view.bounds];
view.layer.contents = (__bridge id _Nullable)(image.CGImage);
view.layer.contentsGravity = kCAGravityCenter;
[self.view addSubview:view];

运行结果:


6CC1E4E8-6490-4586-BFA6-B146EC440776.png

可以看到显示出来的图片有像素颗粒感,在上面代码的基础上再加上一句

view.layer.contentsScale = [UIScreen mainScreen].scale;

运行结果:


B01FE8E0-7E06-4BA3-A95D-99D1F5BFC1CD.png
  • maskToBounds

类似UIView的clipsToBounds,CALayer有一个属性叫maskToBounds

self.layerView.layer.masksToBounds = YES;
  • contentsRect

CALayer的contentsRect属性可以用于图片拼合
举个栗子,有如下图片:


doll@2x.png

我们可以利用contentsRect属性来分别单独显示三个娃娃:

#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *leftTopImage;
@property (weak, nonatomic) IBOutlet UIView *rightTopImage;
@property (weak, nonatomic) IBOutlet UIView *leftBottomImage;
@end
@implementation ViewController
-(void)viewDidLoad {
        [super viewDidLoad];

        UIImage *image = [UIImage imageNamed:@"doll"];

        self.leftTopImage.layer.contents = (__bridge id _Nullable)(image.CGImage);
        self.leftTopImage.layer.contentsRect = CGRectMake(0.0f, 0.0f, 0.5f, 0.5f);

        self.rightTopImage.layer.contents = (__bridge id _Nullable)(image.CGImage);
        self.rightTopImage.layer.contentsRect = CGRectMake(0.5f, 0.0f, 0.5f, 0.5f);

        self.leftBottomImage.layer.contents = (__bridge id _Nullable)(image.CGImage);
        self.leftBottomImage.layer.contentsRect = CGRectMake(0.0f, 0.5f, 0.5f, 0.5f);
}
@end

运行结果:


0EE9992A-027F-43C4-84DB-F2127EC2BCEE.png

拼合不仅给app提供了一个整洁的载入方式,还有效地提高了载入性能(单张大图比多张小图载入地更快),但是如果有手动安排的话,他们还是有一些不方便的,如果你需要在一个已经创建好的品和图上做一些尺寸上的修改或者其他变动,无疑是比较麻烦的。

Mac上有一些商业软件可以为你自动拼合图片,这些工具自动生成一个包含拼合后的坐标的XML或者plist文件,拼合图片的使用大大简化。这个文件可以和图片一同载入,并给每个拼合的图层设置contentsRect,这样开发者就不用手动写代码来摆放位置了。

  • contentsCenter

CALayer的另一个属性contentsCenter,有点类似于UIImage里的-resizableImageWithCapInsets: 方法,楼主浅薄,实在不懂如何描述,这里有篇文章讲得很清楚:iOS: 关于CALayer的contentsCenter属性


3. CALayer位置坐标相关的属性

对应于UIView的frame、bounds、center,CALayer有frame、bounds、position,访问UIView的这三个属性,返回的其实就是CALayer对应的这三个值。但是CALayer另外还有一个比较难于理解的属性anchorPoint。

  • anchorPoint

anchorPoint决定了CALayer的那个点处于position的位置,其作用效果如下图:


3.3.jpeg
  • 坐标转换

CALayer也有用于在不同坐标系中转换坐标的函数:

-(CGPoint)convertPoint:(CGPoint)p fromLayer:(nullable CALayer *)l;
-(CGPoint)convertPoint:(CGPoint)p toLayer:(nullable CALayer *)l;
-(CGRect)convertRect:(CGRect)r fromLayer:(nullable CALayer *)l;
-(CGRect)convertRect:(CGRect)r toLayer:(nullable CALayer *)l;
  • zPosition

CALayer的zPosition属性可以控制图层的显示顺序,Interface Build布局如下:
Paste_Image.png

修改zPosition:

#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *blueView;
@property (weak, nonatomic) IBOutlet UIView *orangeView;
@end
@implementation ViewController
-(void)viewDidLoad {
    [super viewDidLoad];
    self.blueView.layer.zPosition = 1.0f;
}
@end```
运行结果:
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1396900-5790571a630184af.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
zPosition属性虽然可以改变图层的显示顺序,但是不能改变图层在图层树中的顺序


- #####CALayer事件处理
CALayer并不关心任何响应链事件,所以不能直接处理触摸事件或者手势。但是它有一系列的方法帮你处理事件:-containsPoint:和-hitTest:。
-containsPoint:接受一个在本图层坐标系下的CGPoint,如果这个点在图层frame范围内就返回YES。举个栗子:

import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *whiteView;
@property (weak, nonatomic) IBOutlet UIView *orangeView;
@end
@implementation ViewController
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

UITouch *touch = touches.anyObject;
CGPoint originPoint = [touch locationInView:self.view];

CGPoint thePoint = [self.whiteView.layer convertPoint:originPoint fromLayer:self.view.layer];
BOOL containsResult = [self.whiteView.layer containsPoint:thePoint];

if (containsResult) {
    thePoint = [self.orangeView.layer convertPoint:thePoint fromLayer:self.whiteView.layer];
    containsResult = [self.orangeView.layer containsPoint:thePoint];
    UIAlertController *alert;
    if (containsResult) {
        alert = [UIAlertController alertControllerWithTitle:nil message:@"inside orange layer" preferredStyle:UIAlertControllerStyleAlert];
    }else{
        alert = [UIAlertController alertControllerWithTitle:nil message:@"inside white layer" preferredStyle:UIAlertControllerStyleAlert];
    }
    [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]];
    [self presentViewController:alert animated:YES completion:nil];
}

}
@end

运行结果:
![Paste.gif](http://upload-images.jianshu.io/upload_images/1396900-5dbead9b42769221.gif?imageMogr2/auto-orient/strip)
-hitTest:方法同样接受一个CGPoint类型参数,而不是BOOL类型,它返回图层本身,或者包含这个坐标点的叶子节点图层,如果这个点在最外面图层的范围之外,则返回nil。
下面这段代码实现了上图一样的效果:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

UITouch *touch = touches.anyObject;
CGPoint originPoint = [touch locationInView:self.view];
CALayer *layer = [self.whiteView.layer hitTest:originPoint];

UIAlertController *alert;
if (layer == self.orangeView.layer) {
    alert = [UIAlertController alertControllerWithTitle:nil message:@"inside orange layer" preferredStyle:UIAlertControllerStyleAlert];
}else{
    alert = [UIAlertController alertControllerWithTitle:nil message:@"inside white layer" preferredStyle:UIAlertControllerStyleAlert];
}
[alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]];
[self presentViewController:alert animated:YES completion:nil];

}

---
#4. CALayer视觉相关的属性
- #####conrnerRadius
CALayer有一个叫做conrnerRadius的属性控制着图层角的曲率。它是一个浮点数,默认为0(为0的时候就是直角),但是你可以把它设置成任意值。默认情况下,这个曲率值只影响背景颜色而不影响背景图片或是子图层。不过,如果把masksToBounds设置成YES的话,图层里面的所有东西都会被截取。
Interface Build布局如下:
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1396900-70164d489a904bbb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
添加如下代码:

import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *topWhiteView;
@property (weak, nonatomic) IBOutlet UIView *bottomWhiteView;
@end
@implementation ViewController
-(void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.topWhiteView.layer.cornerRadius = 20.0f;
self.bottomWhiteView.layer.cornerRadius = 20.0f;
self.bottomWhiteView.layer.masksToBounds = YES;
}
@end

运行效果:
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1396900-5fe67d997034d76d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


- #####borderWidth和borderColor
CALayer另外两个非常有用属性就是borderWidth和borderColor。二者共同定义了图层边的绘制样式。这条线(也被称作stroke)沿着图层的bounds绘制,同时也包含图层的角。在上面代码的基础上再加上几句:
self.topWhiteView.layer.borderWidth = 5.0f;
self.bottomWhiteView.layer.borderWidth = 5.0f;
self.topWhiteView.layer.borderColor = [UIColor yellowColor].CGColor;
self.bottomWhiteView.layer.borderColor = [UIColor yellowColor].CGColor;
运行效果:
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1396900-1f0d94448d3d8080.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

- #####阴影效果
CALayer还有一系列属性用来绘制阴影:

/** Shadow properties. */
/
The color of the shadow. Defaults to opaque black. Colors created

  • from patterns are currently NOT supported. Animatable. /
    @property(nullable) CGColorRef shadowColor;
    /
    The opacity of the shadow. Defaults to 0. Specifying a value outside the
  • [0,1] range will give undefined results. Animatable. /
    @property float shadowOpacity;
    /
    The shadow offset. Defaults to (0, -3). Animatable. /
    @property CGSize shadowOffset;
    /
    The blur radius used to create the shadow. Defaults to 3. Animatable. /
    @property CGFloat shadowRadius;
    /
    When non-null this path defines the outline used to construct the
  • layer's shadow instead of using the layer's composited alpha
  • channel. The path is rendered using the non-zero winding rule.
  • Specifying the path explicitly using this property will usually
  • improve rendering performance, as will sharing the same path
  • reference across multiple layers. Upon assignment the path is copied.
  • Defaults to null. Animatable. */
    @property(nullable) CGPathRef shadowPath;
在前面代码的基础上再加上几句:
self.topWhiteView.layer.shadowColor = [UIColor blackColor].CGColor;
self.bottomWhiteView.layer.shadowColor = [UIColor blackColor].CGColor;
self.topWhiteView.layer.shadowOpacity = 0.5f;
self.bottomWhiteView.layer.shadowOpacity = 0.5f;
运行效果:
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1396900-01d2b9a2d8292937.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
发现两个问题:
1、topWhiteView有阴影,但是阴影在视图的上方
2、而bottomWhiteView干脆根本没有阴影
第一个问题的原因在于这个属性:

/* The shadow offset. Defaults to (0, -3). Animatable. */
@property CGSize shadowOffset;

CALayer的阴影偏移量默认为(0, -3),所以阴影出现在了视图的上方
第二个问题的原因在于这句代码:

self.bottomWhiteView.layer.masksToBounds = YES;

如果你开启了masksToBounds属性,所有从图层中突出来的内容都会被才剪掉,一个简单的解决办法就是在bottomWhiteView的下面再插入一个view来充当阴影视图。
修改之前的代码,最终代码如下:

import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *topWhiteView;
@property (weak, nonatomic) IBOutlet UIView *bottomWhiteView;
@property (weak, nonatomic) IBOutlet UIView *bottomBackView;
@end
@implementation ViewController
-(void)viewDidLoad {
[super viewDidLoad];

// Do any additional setup after loading the view, typically from a nib.
self.bottomWhiteView.layer.masksToBounds = YES;
[self configLayer:self.topWhiteView.layer];
[self configLayer:self.bottomWhiteView.layer];
[self configLayer:self.bottomBackView.layer];

}
-(void)configLayer:(CALayer *)aLayer{
aLayer.cornerRadius = 20.0f; //圆角
aLayer.borderWidth = 5.0f; //边框线宽
aLayer.borderColor = [UIColor yellowColor].CGColor; //边框颜色
aLayer.shadowColor = [UIColor blackColor].CGColor; //阴影颜色
aLayer.shadowOpacity = 0.5f; //阴影透明度
aLayer.shadowOffset = CGSizeMake(5.0f, 5.0f); //阴影偏移量
}
@end

运行效果:
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1396900-36c508fe929347cb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

- #####shadowRadius
shadowRadius属性可以控制CALayer阴影的模糊度,在viewDidLoad方法的最后加上一句:
self.topWhiteView.layer.shadowRadius = 10.0f;
运行结果:
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1396900-4f17b30a21551e4b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

- #####shadowPath
shadowPath可以为CALayer指定特定的阴影路径。
举个栗子:

import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIView *orangeView;
@property (weak, nonatomic) IBOutlet UIView *cyanView;
@end
@implementation ViewController
-(void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.

[self configLayer:self.orangeView.layer];
[self configLayer:self.cyanView.layer];

//create a square shadow
CGMutablePathRef squarePath = CGPathCreateMutable();
CGPathAddRect(squarePath, NULL, self.cyanView.bounds);
self.cyanView.layer.shadowPath = squarePath; CGPathRelease(squarePath);

//create a circular shadow
CGMutablePathRef circlePath = CGPathCreateMutable();
CGPathAddEllipseInRect(circlePath, NULL, self.orangeView.bounds);
self.orangeView.layer.shadowPath = circlePath; CGPathRelease(circlePath);

}
-(void)configLayer:(CALayer *)aLayer{
aLayer.shadowOpacity = 0.5f;
aLayer.cornerRadius = aLayer.bounds.size.width / 2.0f;
aLayer.shadowOffset = CGSizeMake(0.0f, 0.0f);
aLayer.shadowRadius = 5.0f;
}
@end

运行效果:

![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1396900-2f314b7d2e5536aa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

- #####mask
mask图层定义了父图层的部分可见区域,mask图层的Color属性是无关紧要的,真正重要的是图层的轮廓。mask属性就像是一个饼干切割机,mask图层实心的部分会被保留下来,其他的则会被抛弃。
举个栗子:
Interface Build布局如下:
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1396900-6e6dfbfbd4c3c5b5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
使用下面这个小雪人来充当图层蒙版:
![3.png](http://upload-images.jianshu.io/upload_images/1396900-abaada4d571f838d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
代码如下:

import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation ViewController
-(void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.

//create mask layer
CALayer *maskLayer = [CALayer layer];
maskLayer.frame = self.imageView.bounds;
UIImage *maskImage = [UIImage imageNamed:@"3"];
maskLayer.contents = (__bridge id)maskImage.CGImage;
maskLayer.contentsGravity = kCAGravityCenter;
maskLayer.contentsScale = [UIScreen mainScreen].scale;

//apply mask to image layer
self.imageView.layer.mask = maskLayer;

}
@end

运行效果:
![Paste_Image.png](http://upload-images.jianshu.io/upload_images/1396900-7b49885e8a7f1014.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
一个利用图层蒙版实现的有趣动画:[iOS-maskLayer实现的炫酷动画](http://www.jianshu.com/p/a52ba15edc35)

[Responder Chain](http://blog.csdn.net/chun799/article/details/8223612)、[Core Animation](https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreAnimation_guide/Introduction/Introduction.html)、[UIView与CALayer的关系](http://www.cocoachina.com/ios/20150828/13244.html)、[[iOS: 关于CALayer的contentsCenter属性](https://www.mgenware.com/blog/?p=489)](https://www.mgenware.com/blog/?p=489)、[iOS-maskLayer实现的炫酷动画](http://www.jianshu.com/p/a52ba15edc35)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容