iOS开发-核心动画(CAAnimation)相关

此文章单方面对 贝塞尔曲线画图 核心动画 图层相关 方面做下整理,方便查看!

基本概念

Core Animation(核心动画)是一组功能强大,在开发中可以用他来实现很多复杂和绚丽的动画效果,核心动画作用在CALayer(Core animation layer)上

结构

盗图 -_-.png

代码和效果

/**
 *  fillMode 视图在非Active时的行为
 *
 *  kCAFillModeForwards 动画开始之后layer迅速移到动画开始的位置
 *  kCAFillModeBackwards 动画被添加的那一刻(动画开始之前)layer迅速移到开始位置,并且在 removedOnCompletion 为 NO 的情况下,动画结束会移到layer本身的位置
 *  kCAFillModeBoth 动画添加的那一刻(动画开始之前)layer迅速移到开始位置,并且在 removedOnCompletion 为 NO 的情况下,动画结束layer会停留在动画结束的位置
 *  kCAFillModeRemoved  动画开始之后layer迅速移到动画开始的位置,并且在 removedOnCompletion 为 NO 的情况下,动画结束回忆道layer本身位置
 */
    
/**
 *  timingFunction  动画节奏 [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]
 *
 *  kCAMediaTimingFunctionLinear            匀速
 *  kCAMediaTimingFunctionEaseIn            慢进
 *  kCAMediaTimingFunctionEaseOut           慢出
 *  kCAMediaTimingFunctionEaseInEaseOut     慢进慢出
 *  kCAMediaTimingFunctionDefault           默认值(慢进慢出)
 */
    
/**
 *  removedOnCompletion 动画执行完毕后是否从图层上移除 默认为 YES (动画结束layer移到本身位置)
 */
    
/** 
 *  这样个要配合使用 repeatDuration = repeatCount * duration(动画一遍持续时间,主要控制速度)
 *  repeatCount     动画重复执行次数
 *  repeatDuration  动画重复执行时间
 */
CABasicAnimation
1.位置相关动画 (position.y 和 position.x transform.translation.x 和 transform.translation.y)

position fromValue:默认自身位置为起始位置 toValue:移动后的位置
transform.translation fromValue:默认自身位置为起始位置(默认0) toValue:相对于layer原始位置距离

  • position.y 和 position.x
/// y轴方向移动  fromValue:默认自身位置为起始位置 toValue:移动后的位置
- (void)animation_CABasicAnimation_position_y
{
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position.y"];
    //animation.fromValue = @(self.img.center.y); 默认初始位置
    animation.toValue = @(self.img.center.y + 100);
    animation.duration = 3;
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    [self.img.layer addAnimation:animation forKey:@"position.y"];
}
position.x/y.gif
  • transform.translation.x 和 transform.translation.y
- (void)animation_CABasicAnimation_translation_x
{
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
    //animation.fromValue = @0; 默认初始位置
    animation.toValue = @100;
    animation.duration = 1;
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    [self.img.layer addAnimation:animation forKey:@"transform.translation.x"];
}
transform.translation.x/y.gif
2.旋转相关动画
  • transform.rotation.x | transform.rotation.y | transform.rotation.z
transform.rotation.x.gif
transform.rotation.y.gif
transform.rotation.z.gif
3.缩放相关动画
  • transform.scale.x | transform.scale.y | transform.scale.z
- (void)animation_CABasicAnimation_transform_scale
{
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
    animation.toValue = @8.0;
    animation.duration = 3;
    animation.removedOnCompletion = NO;
    animation.fillMode = kCAFillModeForwards;
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    [self.img.layer addAnimation:animation forKey:@"transform.scale"];
}
transform.scale.gif
4.自身大小相关
  • bounds.origin.x | bounds.origin.y
    • 动画view图层上图层和view上的其他控件 不动画view本身
    • 方向x 右为负 左为正
    • 方向y 上为负 下为正
//bounds.origin.x
//animation1.fromValue = @0; 默认自身位置为开始位置 为0
animation1.toValue = @(-self.img.bounds.size.width * 0.5);

//bounds.origin.y
//animation2.fromValue = @0; 默认自身位置为开始位置 为0
animation2.toValue = @(-self.img.bounds.size.height * 0.5);
bounds.origin.x/y.gif
  • bounds.size.width | bounds.size.height
    • 动画view本身 不动画view上的其他控件
//bounds.size.width
//animation1.fromValue = @(self.img.bounds.size.width); 默认起始值为自身宽度
animation1.toValue = @(self.img.bounds.size.width * 0.5);

//bounds.size.height
//animation2.fromValue = @(self.img.bounds.size.height); 默认起始值为自身高度
animation2.toValue = @(self.img.bounds.size.height * 0.5);
bounds.size.width:height.gif
5.边角动画
  • cornerRadius | borderWidth | borderColor
//cornerRadius
//animation1.fromValue = @(self.img.layer.cornerRadius); 默认自身圆角为起始值
animation1.toValue = @50;
animation1.repeatCount = 3;
animation1.repeatDuration = animation.duration * animation.repeatCount;

//borderWidth
//animation.fromValue = @(self.img.layer.borderWidth); 默认自身边框宽度为起始值
animation2.toValue = @20;
animation2.repeatCount = 3;
animation2.repeatDuration = animation.duration * animation.repeatCount;

//borderColor
//animation3.toValue = (__bridge id _Nullable)([self.img.layer borderColor]); //默认自身边框颜色为起始值
animation3.toValue = (__bridge id _Nullable)([[UIColor cyanColor] CGColor]);
animation3.repeatCount = 3;
animation3.repeatDuration = animation.duration * animation.repeatCount;
cornerRadius/borderWidth/borderColor.gif
6.自身的一些属性
  • opacity 不透明
animation.fromValue = @1;
animation.toValue = @0;
opacity.gif
  • backgroundColor
animation.toValue = (__bridge id _Nullable)([[UIColor yellowColor] CGColor]);
backgroundColor.gif
  • contents (layer.contents)
animation.fromValue = (__bridge id _Nullable)([[UIImage imageNamed:@"春雨医生"] CGImage]);
animation.toValue = (__bridge id _Nullable)([[UIImage imageNamed:@"丁香医生"] CGImage]);
contents.gif
7.阴影相关动画
  • shadowOffset 阴影位置偏移
//self.img.layer.shadowOpacity = 0.5;
//self.img.layer.shadowOffset = CGSizeMake(0, 0);
animation.fromValue = [NSValue valueWithCGSize:CGSizeMake(0, 0)];
animation.toValue = [NSValue valueWithCGSize:CGSizeMake(20, 20)];
animation.duration = 3;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
shadowOffset.gif
  • shadowColor 阴影颜色
//self.img.layer.shadowOpacity = 0.5;
//self.img.layer.shadowOffset = CGSizeMake(20, 20);
animation.toValue = (__bridge id _Nullable)([UIColor redColor].CGColor);
shadowColor.gif
  • shadowOpacity 阴影不透明值 01(透明完全不透明)
//self.img.layer.shadowOpacity = 0;
//self.img.layer.shadowOffset = CGSizeMake(20, 20);
animation.fromValue = @0;
animation.toValue = @1;
shadowOpacity.gif
  • shadowRadius 暂且叫做阴影模糊度吧
//self.img.layer.shadowOpacity = 0.5;
//self.img.layer.shadowOffset = CGSizeMake(20, 20);
animation.fromValue = @3;
animation.toValue = @10;
shadowRadius.gif
CAKeyframeAnimation

可以看做是一个有更多位置设定的CABaseAnimation,可以设定keyPath起点、中间关键点(可以是多个)、终点的值,每一帧所对应的时间,动画会沿着设定点进行移动

CAKeyframeAnimation的一些独有属性

  • values: 关键帧数组对象,里面每一个元素即为一个关键帧
  • path: 动画路径对象,可以指定一个路径,在执行动画时路径会沿着路径移动,注:Path在动画中只会影响视图的Position
  • keyTimes: 设置关键帧对应的时间数组,范围:0.0-1.0之间的浮点型
    • 数组中的每一个连续值都必须大于或等于前面的值,因为里面存储的是动画持续时间内的每一帧的时间点,时间点是从0%-100%,时间不可能回退
    • 为了得到最好的结果,数组中的元素个数应该与values中的元素个数或路径属性中的控制点的数量相匹配
  • timingFunctions: 设置关键帧对应速度效果的数组
  • calculationMode: 这个属性用来设定 关键帧中间的值是怎么被计算的
    NSString * const kCAAnimationLinear
    NSString * const kCAAnimationDiscrete 只展示关键帧的状态,没有中间过程,没有动画
    NSString * const kCAAnimationPaced
    NSString * const kCAAnimationCubic
    NSString * const kCAAnimationCubicPaced
- (void)animation_CAKeyframeAnimation_Rect
{
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    animation.duration = 4.0;
    animation.repeatCount = 2;
    animation.repeatDuration = animation.duration * animation.repeatCount;
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    NSValue *value1 = [NSValue valueWithCGPoint:CGPointMake(self.img.center.x, self.img.center.y)];
    NSValue *value2 = [NSValue valueWithCGPoint:CGPointMake(self.img.center.x + 150, self.img.center.y)];
    NSValue *value3 = [NSValue valueWithCGPoint:CGPointMake(self.img.center.x + 150, self.img.center.y + 150)];
    NSValue *value4 = [NSValue valueWithCGPoint:CGPointMake(self.img.center.x, self.img.center.y + 150)];
    NSValue *value5 = [NSValue valueWithCGPoint:CGPointMake(self.img.center.x, self.img.center.y)];
    animation.values = @[value1, value2, value3, value4, value5];
    animation.keyTimes = @[@0, @0.4, @0.5, @0.9, @1.0];
    
    /*  利用贝塞尔画的一个矩形,跟上面效果一样,只不过不能设置关键帧动画时间
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(self.img.center.x, self.img.center.y, 150, 150)];
    animation.path = path.CGPath;
     */
    
    [self.img.layer addAnimation:animation forKey:@"position"];
}
values_rect.gif
path_rect.gif

利用贝塞尔画个圆路径动画

- (void)animation_CAKeyframeAnimation_Circle
{
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    animation.duration = 3;
    animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    animation.repeatCount = 2;
    animation.repeatDuration = animation.repeatCount * animation.duration;
    
    UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.img.center.x + 75, self.img.center.y) radius:75 startAngle:M_PI endAngle:3*M_PI clockwise:YES];
  
    animation.path = path.CGPath;
    [self.img.layer addAnimation:animation forKey:@"position"];
}
path_circle.gif
CATransition

转场动画,在开发中巧用会有意想不到的效果,还方便

/**
 *  CATransition (type) 过渡动画的类型
 *
 *  kCATransitionFade   渐变
 *  kCATransitionMoveIn 覆盖
 *  kCATransitionPush   推出
 *  kCATransitionReveal 揭开(可以说是抽开)
 *
 *  私有动画类型的值有:"cube"、"suckEffect"、"oglFlip"、 "rippleEffect"、"pageCurl"、"pageUnCurl"等等
 */
    
/**
 *  CATransition (subtype) 过渡动画的方向
 *
 *  kCATransitionFromRight  从右边
 *  kCATransitionFromLeft   从左边
 *  kCATransitionFromTop    从顶部
 *  kCATransitionFromBottom 从底部
 */

下面来组示例,看下效果,生成四个颜色的图片,来个切换动画

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    static int i = 0;
    i = i >= self.imgsArr.count-1 ? 0:i+1;
    self.img.image = [self.imgsArr objectAtIndex:i];
    CATransition *animation = [CATransition animation];
    animation.duration = 0.5;
    animation.type = @"cube";
    animation.subtype = kCATransitionFromRight;
    [self.img.layer addAnimation:animation forKey:@"transition"];
}
- (NSArray *)imgsArr
{
    if (!_imgsArr) {
        _imgsArr = [NSArray arrayWithObjects:[self returnImage1WithCGSize:self.img.bounds.size andColor:[UIColor redColor]],
                                             [self returnImage1WithCGSize:self.img.bounds.size andColor:[UIColor greenColor]],
                                             [self returnImage1WithCGSize:self.img.bounds.size andColor:[UIColor cyanColor]],
                                             [self returnImage1WithCGSize:self.img.bounds.size andColor:[UIColor magentaColor]], nil];
    }
    return _imgsArr;
}
//根据size和传进来的color生成一张颜色图片
- (UIImage *)returnImage1WithCGSize:(CGSize)size andColor:(UIColor *)color
{
    UIGraphicsBeginImageContextWithOptions(size, NO, 0);
    
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, size.width, size.height)];
    [color setFill];
    [path fill];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return image;
}

我平时开发中的图片轮播一般都是用这个动画做的,加两个手势就行,缺点就是不能翻页中间不能停留,没有scroll那么全的功能,但是代码简单方便


kCATransitionPush_模仿轮播图.gif

kCATransitionFade.gif

kCATransitionMoveIn.gif

kCATransitionReveal.gif

私有-cube.gif

示例: 我最近做项目遇到这样一个问题,登录界面跳到主界面的时候我一般喜欢用根视图去跳,因为我认为登录注册界面可能八百年才用到一次,用导航或者模态来转场不是太好,但是用根视图跳转显得太突兀,动画不太好看,这时转场动画便派上用场

//登录
SideslipViewController *sideslip = [[SideslipViewController alloc] init];
[UIApplication sharedApplication].keyWindow.rootViewController = sideslip;
    
CATransition *transition = [[CATransition alloc] init];
transition.duration = 0.3;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
transition.type = kCATransitionReveal;
transition.subtype = kCATransitionFromTop;
[[UIApplication sharedApplication].keyWindow.layer addAnimation:transition forKey:@"LoginIn"];
//登出
LoginViewController *login = [[LoginViewController alloc] init];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:login];
nav.navigationBar.hidden = YES;
[UIApplication sharedApplication].keyWindow.rootViewController = nav;
    
CATransition *transition = [[CATransition alloc] init];
transition.duration = 0.3;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromBottom;
[[UIApplication sharedApplication].keyWindow.layer addAnimation:transition forKey:@"LoginOut"];
LoginInOrOut_Animation.gif
CASpringAnimation

iOS9之后出来的新的弹簧动画,继承CABasicAnimation

/** 一些重要的属性
 *  CASpringAnimation的重要属性:
 *
 *  mass:质量(影响弹簧的惯性,质量越大,弹簧惯性越大,运动的幅度越大)
 *  stiffness:弹性系数(弹性系数越大,弹簧的运动越快)
 *  damping:阻尼系数(阻尼系数越大,弹簧的停止越快)
 *  initialVelocity:初始速率(弹簧动画的初始速度大小,弹簧运动的初始方向与初始速率的正负一致,若初始速率为0,表示忽略该属性)
 *  settlingDuration:结算时间(根据动画参数估算弹簧开始运动到停止的时间,动画设置的时间最好根据此时间来设置)
 */
CASpringAnimation *animation = [CASpringAnimation animationWithKeyPath:@"position"];
animation.mass = 10.0;
animation.stiffness = 500;
animation.damping = 10;
animation.initialVelocity = 5.0f;
animation.duration = animation.settlingDuration; //时间需要注意下,用系统计算出来动画所需时间
animation.toValue = [NSValue valueWithCGPoint:CGPointMake(self.img.center.x, self.img.center.y + 200)];
[self.img.layer addAnimation:animation forKey:@"position"];

CASpringAnimation_position.gif

以上零散单个Demo地址: https://github.com/SupermanChao/Animation

CAAnimationGroup

使用Group可以将多个动画合并一起加入到图层中,Group中所有动画一起执行,可以展示很多动画种类
一个简单的动画组例子,选取颜色
Demo地址:https://github.com/SupermanChao/AnimationGroup

Animation-Group.gif

CATransaction

最后讲一下事务(CATransaction),在核心动画里面存在事务(CATransaction)这样一个概念,它负责协调多个动画原子更新显示操作,简单来说事务是核心动画里面的一个基本的单元,动画的产生必然伴随着layer的Animatable属性的变化,而layer属性的变化必须属于某一个事务

事务分为隐式和显式:

  • 隐式:没有明显调用事务的方法,由系统自动生成事务,比如直接设置一个layer的position属性,则会在当前线程自动生成一个事务,并在下一个runLoop中自动commit事务
  • 显式:明显调用事务的方法([CATransaction begin]和[CATransaction commit])
事务的可设置属性(会覆盖隐式动画的设置):
//动画持续时间
+ (CFTimeInterval)animationDuration;
//动画时间曲线
+ (nullable CAMediaTimingFunction *)animationTimingFunction;
//是否关闭动画
+ (BOOL)disableActions;
//动画执行完毕回调
+ (nullable void (^)(void))completionBlock;

下面来个小例子,圆环进度动画,先看效果后上代码
代码地址:https://github.com/SupermanChao/CATransaction-Demo

LCProgressAnimation.gif

//圆环贝塞尔曲线
- (UIBezierPath *)path
{
    if (!_path) {
        CGFloat radius = (MIN(self.frame.size.width, self.frame.size.height) - self.lineWidth) * 0.5;
        CGPoint center = CGPointMake(self.frame.size.width * 0.5, self.frame.size.height * 0.5);
        _path = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:-M_PI_2 endAngle:M_PI + M_PI_2 clockwise:YES];
    }
    return _path;
}

//轨道
- (CAShapeLayer *)outLayer
{
    if (!_outLayer) {
        _outLayer = [CAShapeLayer layer];
        _outLayer.lineWidth = self.lineWidth;
        _outLayer.fillColor = [UIColor clearColor].CGColor;
        _outLayer.strokeColor = self.pathwayColor.CGColor;
        _outLayer.path = self.path.CGPath;
    }
    return _outLayer;
}

//进度
- (CAShapeLayer *)progressLayer
{
    if (!_progressLayer) {
        _progressLayer = [CAShapeLayer layer];
        _progressLayer.lineWidth = self.lineWidth;
        _progressLayer.fillColor = [UIColor clearColor].CGColor;
        _progressLayer.lineCap = kCALineCapRound;
        _progressLayer.strokeColor = self.scheduleColor.CGColor;
        _progressLayer.path = self.path.CGPath;
        _progressLayer.strokeStart = 0;
        _progressLayer.strokeEnd = 0.001;
    }
    return _progressLayer;
}

//定时器协调运作
- (void)onTimer
{
    switch (self.accuracy) {
        case LCAnimationAccuracyLow:
            self.progress += 2;
            break;
        case LCAnimationAccuracyHeight:
            self.progress += 0.5;
            break;
        case LCAnimationAccuracyVeryHeight:
            self.progress += 0.1;
            break;
        default:
            self.progress += 1;
            break;
    }
    
    [CATransaction begin];
    [CATransaction setAnimationTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]];
    [CATransaction setAnimationDuration:self.totalTime / self.count];
    self.progressLayer.strokeEnd = self.progress / 100.0;
    [CATransaction commit];
    
    if (self.progress >= 99.99) {
        [self lc_stopAnimation];
        if ([self.delegate respondsToSelector:@selector(lc_animationFinishAction)]) {
            [self.delegate lc_animationFinishAction];
        }
    };
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容