iOS核心动画

iOS核心动画

核心动画框架

CoreAnimation框架是基于OpenGL与CoreGraphics图像处理框架的一个跨平台的动画框架。

在CoreAnimation中大部分的动画都是通过Layer层来实现的,通过CALayer,我们可以组织复杂的层级结构。

在CoreAnimation中,大多数的动画效果是添加在图层属性的变化上,例如,改变图层的位置,大小,颜色,圆角半径等。Layer层并不决定视图的展现,它只是存储了视图的几何属性状态。

核心动画类

核心动画中的虚类不能,而应该使用它子类中的实类。

下面介绍几个实类的简单用法

CABasicAnimation——基本动画

基本动画,是CAPropertyAnimation的子类。

🌰一个简单的动画效果

CABasicAnimation*moveAnimation = [CABasicAnimationanimationWithKeyPath:@"position.y"];moveAnimation.duration =0.8;//动画时间//动画起始值和终止值的设置moveAnimation.fromValue = @(self.imageView.center.x);moveAnimation.toValue = @(self.imageView.center.x-30);//一个时间函数,表示它以怎么样的时间运行moveAnimation.timingFunction = [CAMediaTimingFunctionfunctionWithName:kCAMediaTimingFunctionEaseIn];moveAnimation.repeatCount = HUGE_VALF;moveAnimation.repeatDuration =2;moveAnimation.removedOnCompletion =NO;moveAnimation.fillMode = kCAFillModeForwards;//添加动画,后面有可以拿到这个动画的标识[self.imageView.layer addAnimation:moveAnimationforKey:@"可以拿到这个动画的Key值"];

相关属性

keyPath:要改变的属性名称(传字符串)

fromValue:keyPath相应属性的初始值

toValue:keyPath相应属性的结束值

timingFunction:动画随时间运行的关系

动画相关属性

动画过程说明

随着动画的进行,在长度为duration的持续时间内,keyPath相应属性的值fromValue渐渐地变为toValue

keyPath内容是CALayer的可动画Animation属性

如果fillMode=kCAFillModeForwards同时removedOnComletion=NO,那么在动画执行完毕后,图层会保持显示动画执行后的状态。但在实质上,图层的属性值还是动画执行前的初始值,并没有真正被改变。

在CAAnimation中可以实现代理方法

- animationDidStart: //开始时调用'

- animationDidStop:finished: //结束时调用

在addAnimation:forKey:方法中,也可以给这个动画设置一个键,可以在其他地方将其取出来,进行一些操作,比如删除。这也充分体现了kvc的灵活。

用到CALayer的 removeAnimationForKey:方法。

CAKeyframeAnimation——关键帧动画

关键帧动画,也是CAPropertyAnimation的子类。

如果是简单的动画CABasicAnimation就能完成,CAKeyframeAnimation(关键帧动画)弥补了基本动画只能传入一对对应值的不足,关键帧动画支持传入一套数值或一个路径来完成动画。

⚠️与CABasicAnimation的区别是:

CABasicAnimation:只能从一个数值(fromValue)变到另一个数值(toValue)

CAKeyframeAnimation:会使用一个NSArray保存这些数值

🌰一个关键帧动画代码

CAKeyframeAnimation *animaiton = [CAKeyframeAnimationanimationWithKeyPath:@"transform.rotation"];  NSArray *rotationVelues = @[@(M_PI_4), @(-M_PI_4), @(M_PI_4)];  animaiton.values = rotationVelues;  animation.rotationMode = kCAAnimationRotateAuto;//方向animaiton.duration =3.0f;  animation.keyTimes = @[@0.2,@0.8,@1];animation.path = bezierPath.CGPath;animaiton.repeatCount = HUGE_VALF;//#define    HUGE_VALF    1e50f  [self.imageView.layeraddAnimation:animaitonforKey:nil];

属性说明

values:上述的NSArray对象。里面的元素称为“关键帧”(keyframe)。动画对象会在指定的时间(duration)内,依次显示values数组中的每一个关键帧

path:可以设置一个CGPathRef、CGMutablePathRef,让图层按照路径轨迹移动。path只对CALayer的anchorPoint和position起作用。如果设置了path,那么values将被忽略

keyTimes:可以为对应的关键帧指定对应的时间点,其取值范围为0到1.0,keyTimes中的每一个时间值都对应values中的每一帧。如果没有设置keyTimes,各个关键帧的时间是平分的

bezierPath:贝赛尔曲线路径,为动画提供一个动画移动的路线。

UIBezierPath - 贝赛尔曲线

//创建路径UIBezierPath*bezierPath = [[UIBezierPathalloc] init];[bezierPath moveToPoint:CGPointMake(0,450)];[bezierPath addCurveToPoint:CGPointMake(370,500) controlPoint1:CGPointMake(350,200) controlPoint2:CGPointMake(300,600)];//一个曲线    //路径样式CAShapeLayer*shapeLayer = [CAShapeLayerlayer];shapeLayer.path = bezierPath.CGPath;shapeLayer.fillColor = [UIColorclearColor].CGColor;//填充色<默认黑色>shapeLayer.strokeColor = [UIColorblueColor].CGColor;//线色shapeLayer.lineWidth =2;[self.view.layer addSublayer:shapeLayer];

UIBezierPath创建一个路径,画出一条曲线。栗子中 起点(0,450)、终点(370,500) 和 (350,200)、(370,500)来个点来确定线的路径。模拟器更多参考

CAShapeLayer对上面的线进行属性上的设置。最后添加到一个layer上。

++CABasicAnimation可看做是只有2个关键帧的CAKeyframeAnimation++

CAAnimationGroup——动画组

动画组,是CAAnimation的子类,可以保存一组动画对象,将CAAnimationGroup对象加入层后,组中所有动画对象可以同时并发运行

属性说明

animations:用来保存一组动画对象的NSArray

CAAnimationGroup*animationGroup = [CAAnimationGroupanimation];animationGroup.animations = @[animation,basicAnimation];animationGroup.duration =4;animation.repeatCount =9;[_imageLayer addAnimation:animationGroup forKey:@"changeColor"];

++默认情况下,一组动画对象是同时运行的,也可以通过设置动画对象的beginTime属性来更改动画的开始时间++

转场动画——CATransition

CATransition是CAAnimation的子类,用于做转场动画,能够为层提供移出屏幕和移入屏幕的动画效果。iOS比Mac OS X的转场动画效果少一点。

++UINavigationController就是通过CATransition实现了将控制器的视图推入屏幕的动画效果++

CATransition*caTransition = [CATransitionanimation];caTransition.duration =0.5;caTransition.delegate =self;caTransition.timingFunction = [CAMediaTimingFunctionfunctionWithName:@"easeInEaseOut"];//切换时间函数caTransition.type = kCATransitionReveal;//动画切换风格caTransition.subtype = kCATransitionFromLeft;//动画切换方向//    子视图交换位置//[self.parentView exchangeSubviewAtIndex:0 withSubviewAtIndex:1];//动画在父视图[self.parentView.layer addAnimation:caTransition forKey:@"Key"];

动画属性

type:动画过渡类型

subtype:动画过渡方向

startProgress:动画起点(在整体动画的百分比)

endProgress:动画终点(在整体动画的百分比)

//    导航栏切换UIViewController*viewCtr = [[UIViewControlleralloc] init];viewCtr.view.backgroundColor = [UIColorredColor];[self.navigationController pushViewController:viewCtr animated:NO];// 动画设置 NO 效果比较好[self.navigationController.view.layer addAnimation:caTransition forKey:@"animation"];

CALayer

它有一些方法和属性来做动画和变换,和UIView最大的不同是CALayer不处理用户的交互事件。

UIView和CALayer的关系 - 平行关系

每个UIView都有一个CALayer实例的图层属性。

视图的职责就是创建并管理这个图层。

UIView和CALyer有着平行的层级关系,职责分离。

图层能力

UIView的高级API可以实现一些简单的动画效果,但更多的细节和底层还需要通过CALayer直接处理CAAnimation来处理。

UIView能实现但CALayer不能实现

1.处理触摸事件

UIView没有暴露出来的CALayer的功能

1.阴影、圆角、带颜色的边框

2.3D变换

3.非矩形范围

4.透明覆盖

5.多级非线性动画

解释:为什么动画结束后返回原状态?

首先我们需要搞明白一点的是,layer动画运行的过程是怎样的?其实在我们给一个视图添加layer动画时,真正移动并不是我们的视图本身,而是 presentation layer 的一个缓存。动画开始时 presentation layer开始移动,原始layer隐藏,动画结束时,presentation layer从屏幕上移除,原始layer显示。这就解释了为什么我们的视图在动画结束后又回到了原来的状态,因为它根本就没动过。

这个同样也可以解释为什么在动画移动过程中,我们为何不能对其进行任何操作。

所以在我们完成layer动画之后,最好将我们的layer属性设置为我们最终状态的属性,然后将presentation layer 移除掉。

UIView中目前最常用的动画方法应该就是这个方法了

+(void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^ nullable)(BOOL finished))completion ;

稍微复杂点的方法还是使用CALayer调用CAAnimation的API更为方便。

补充

- drawRect:

UIView的方法  没有默认实现

如果UIView检测到- drawRect:方法被调用,它会为视图分配一个寄宿图,这个寄宿图的尺寸等于视图大小乘以 contentsScale的值

- 会造成CPU资源和内存的浪费

- setNeedsDisplay

- drawRect:方法中利用CoreGraphics 去绘制一个寄宿图,然后被缓存起来直到它需要被更新,更新通常使用- setNeedsDisplay这个方法

CALayerDelegate

可以利用这个代理方法实现一些绘图,不必实现-displayLayer:和-drawLayer:inContext:来绘制寄宿图。通常方法是实现 UIView的- drawRect:方法,UIView就会帮你做完剩下的工作,包括在需要重绘的时候调用- display方法

CALayer的子类

核心动画相关类的继承和简介

CAShapeLayer

CAShapeLayer是一个通过矢量图形而不是bitmap(位图)来挥之的图层子类。

你指定诸如颜色和线宽等属性,用CAPath来定义想绘制的图形,最后CAShapeLayer就自动渲染出来了。

优点

1.渲染快速。CAShapeLayer使用了硬件加速,绘制同一个图形会比用Core Graphics快很多。

2.高效使用内存。一个CAShapeLayer不需要像CALayer一样创建一个寄宿图,所以无论有多大,都不会占用大多的内存。

3.不会被图层边界裁剪掉。

4.不会出现像素化。当你给CAShapeLayer做3D变换时,它不像一个有寄宿图普通图层一样变得像素化。

创建一个CGPath

CAShapeLayer可以用来绘制所有能够通过CGPath来表示的形状。这个形状不一定要闭合,图层路径也不一定要不可破的,事实上你可以在一个图层上绘制好几个不同的形状。

你可以控制一些属性比如

lineWith(线宽,用点表示单位)、lineCap(线条结尾的样子)、和lineJoin(线条之间的结合点的样子)。

CAShapeLayer属性时CGPathRef类型,当时我们用 UIBezierPath 帮助类创建了图层路径,这样我们就不用考虑释放CGPath了。

🌰  一个火柴人

- (void)viewDidLoad {    [superviewDidLoad];//create pathUIBezierPath*path = [[UIBezierPathalloc] init];    [path moveToPoint:CGPointMake(175,100)];    [path addArcWithCenter:CGPointMake(150,100) radius:25startAngle:0endAngle:2*M_PI clockwise:YES]; [path moveToPoint:CGPointMake(150,125)];    [path addLineToPoint:CGPointMake(150,175)];    [path addLineToPoint:CGPointMake(125,225)];    [path moveToPoint:CGPointMake(150,175)];    [path addLineToPoint:CGPointMake(175,225)];    [path moveToPoint:CGPointMake(100,150)];    [path addLineToPoint:CGPointMake(200,150)];//create shape layerCAShapeLayer*shapeLayer = [CAShapeLayerlayer];    shapeLayer.strokeColor = [UIColorredColor].CGColor;    shapeLayer.fillColor = [UIColorclearColor].CGColor;    shapeLayer.lineWidth =5;    shapeLayer.lineJoin = kCALineJoinRound;    shapeLayer.lineCap = kCALineCapRound;    shapeLayer.path = path.CGPath;//add it to our view[self.containerView.layer addSublayer:shapeLayer];  }

圆角

我们创建一个圆角矩形其实就是人工绘制单独的直线和弧度,但是事实上UIBezierPath有自动绘制圆角矩形的构造法。

🌰绘制一个有三个圆角一个直角的矩形

//define path parametersCGRect rect = CGRectMake(50, 50, 100, 100);CGSize radii = CGSizeMake(20, 20);UIRectCorner corners = UIRectCornerTopRight | UIRectCornerBottomRight | UIRectCornerBottomLeft; //create pathUIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerR

我们可以通过这个图层路径绘制一个既有直角又有圆角的视图。

CATiledLayer

载入大图片可能会相当地慢,那些对你看上去比较方便的做法(在主线程调用UIImage的-imageNamed:方法或者-imageWithContentsOfFile:方法) 将会阻塞你的界面,至少会引起动画卡顿现象。

CATiledLayer为载入大图造成的性能问题提供了一个解决方案:将大图分解成小片然后将它们单独的载入。

🌰iOS上小图片拼成大图

略。。。在iOS上要先有n多的小图片,然后通过CATiledLayer把图片放对应位置,实现显示大图片。 比如地图的实现。

核心方法-drawLayer:inContext:

使用CALayer加载图片千万注意使用

tileLayer.contentsScale = [UIScreen mainScreen].scale;

保证图片清晰

CAMediaTiming  -- 图层时间

CAMediaTiming协议定义了在一段动画内用来控制逝去时间的属性集合,CALayer和CA Animation都实现了这个协议,所以时间可以背任何基于一个图层或者一段动画的类控制。

—这个协议中有很多关于时间的属性:开始时间,速度,次数。。。

->持续和重复

duration:一个动画的迭代时间    CFTimeInterval 类型<类似双精度浮点型>

repeatCount:动画重复的迭代次数

⚠️这两个值默认值都是0,分别代表0.25s和1次

->相对时间

beginTIme:指定了动画开始之前的延迟。

speed:是一个时间的倍数,默认1。

timeoffset:对于一个持续1s的动画来说,设置timeoffset为0.5意味着动画将从一半的地方开始。

🌰 <1s动画 speed = 2,time offset = 0.5 -> 动画变成:时长0.5s,从0.5s开始 >

fillmode

removeOnCompletion被设置为NO的动画将在动画结束的时候仍然保持之前的状态。

动画开始之前和动画结束之后,被设置的动画属性将会是什么值呢:

保持动画开始之前的那一帧,或者动画结束后的那一帧。

这就是所谓的填充 因为动画开始和结束的值来填充开始之前和结束之后的时间。

fillMode 是一个NSString类型

kCAFillModeForwards

kCAFillModeBackwards

kCAFillModeBoth

kCAFillModeRemoved  默认

⚠️需要把removeOnCompletion设置为NO,另外需要给动画添加一个非空的键,于是可以在不需要动画的时候把它从图层上移除。

CAMediaTimeingFunction    缓冲

+ timingFunctionWithName:

kCAMediaTimingFunctionLinear    快速开始然后匀速。

kCAMediaTimingFunctionEaseIn  慢慢加速后突然停止。

kCAMediaTimingFunctionEaseOut  全速开始后慢慢停止。

kCAMediaTimingFunctionEaseInEaseOut  慢慢开始然后慢慢减速。

kCAMediaTimingFunctionDefault

UIView的动画方法,kCAMediaTimingFunctionEaseInEaseOut是默认的,但当创建CAAnimation的时候,就需要手动设置了。

贝赛尔曲线

一个三次贝赛尔曲线由4个点来定义:

第一个和最后一个:代表曲线的起点和终点。

剩下的两个点:叫做控制点。<贝赛尔曲线的控制点其实是位于曲线之外的点,也就是说曲线并不一定要穿过他们。你可以把它们想象成吸引经过它们曲线的磁铁>

实际上它是一个很奇怪的函数,先加速-减速-加速。

CAMediaTimeingFunction

getControlPointAtIndex:values:可以用来检索曲线的点

使用它我们可以找到标准的缓冲的点,然后用UIBezierPath和CAShapeLayer把它画出来。

曲线的起点和终点始终是{0,0}和{1,1},于是我们只需要检索曲线的第二个和第三个点。

定时器动画

NSTimer

工作原理:iOS上的每一个线程都管理了一个NSRunloop,字面上看就是通过一个循环来完成一些任务列表。

但对主线程,这些任务包涵如下几项:

-处理触摸事件

-发送和接收网络数据包

-执行使用gcd的代码

-处理计时器行为

-屏幕绘制

当你设置一个NSTimer,它会被插入到当前任务列表,然后知道指定时间过去之后才会被执行。但是何时启动定时器并没有一个时间上限,而且它只会在列表中上一个任务完成之后开始执行。

这通常会导致有几毫秒的延迟,但是如果上一个任务过了很久完成就导致延迟很长一段时间。

屏幕重绘的频率是一秒六十次,但适合定时器行为一样,如果列表中上一个执行很长时间,它也会延迟。

这个延迟是一个随机值,于是就不能保证定时器精准的一秒执行六十次。

我们可以通过一些途径来优化

-我们可以用CADisplayLink 让更新频率严格控制在每次屏幕刷新之后。

-基于真实帧的持续时间而不失假设的更新频率来做动画。

-调整动画计时器的runloop模式,这样就不会被别的事情干扰。

隐式动画

Core Animation基于一个假设,说屏幕上的任何东西都是可以(或可能) 做动画。

动画并不需要你在Core Animation中手动打开,相反需要明确的关闭,否者它一只存在。

当你改变CALayer的一个可以做动画的属性,它并不能在屏幕上体现出来。相反,它是从先前的值平滑过渡到新的值。这一切都是默认的行为,你不需要做额外的操作。

🌰 一个View上放一个CALayer,点击按钮改变layer的颜色<颜色是渐变过去的>

这就是隐式动画,之所以叫隐式动画是因为我们并没有指定任何的动画类型。我们仅仅改变了一个属性,然后Core Animation来决定如何并且何时去动画。

CATransaction 管理事务的类

+begin 入栈

+commit 出栈

+setAnimationDuration: 设置当前动画的时间

+AnimationDuration  获取动画时间

Core Animation在每个RUNLOOP周期中自动开始一次新的事物。

RUNROOP:iOS中负责收集用户输入,处理定时器或者网络事件并且重新绘制屏幕的东西。理解:涉及到任何主动或被动的操作改变都会唤醒run  loop。

即使不显式的用[CATransaction begin]开始一次事务,任何在一次run loop循环中属性的改变都会被集中起来,然后做一次0.25秒的动画。

UIView中的layer属性 并不存在 隐形动画

总结

1.UIView关联的图层禁用了隐形动画,对这种图层做动画的唯一方法就是使用IUView的动画函数(而不是依赖CATransaction),或者继承UIView,并覆盖 -actionForLayer:forKey: 方法,或者直接创建一个显性动画。

2.对于独立的图层,我们可以通过实现图层的 -actionForLayer:forKey:委托方法,或者提供一个actions字典来控制隐形动画

显式动画

animationDidStop: finished:

代码demo

属性动画

基础动画

CABasicAnimation*animation = [CABasicAnimationanimation]; animation.keyPath =@"backgroundColor";animation.toValue = (__bridgeid)color.CGColor;animation.delegate =self;//apply animation to layer[self.colorLayer addAnimation:animation forKey:nil];

关键帧路径动画

CAKeyframeAnimation*animation = [CAKeyframeAnimationanimation];animation.keyPath =@"position";animation.duration =4.0;animation.path = bezierPath.CGPath;animation.rotationMode = kCAAnimationRotateAuto;[shipLayer addAnimation:animation forKey:nil];

动画组

CABasicAnimation和CAKeyframeAnimation仅仅是作用于单独的属性。

CAAnimationGroup可以把这些动画组合在一起 

过渡

属性动画只对图层的可动画属性属性起作用

所以如果要改变一个不能动画的属性(比如图片),或者从层级关系中添加或者移除图层,属性动画将不起作用。

过渡:过渡并不像属性动画那样平滑的在两个值之间做动画,而是影响到整个图层的变化

。过渡动画首先展示之前的图层外观,然后通过一个交换过渡到新的外观。

CATransition:CAAnimation子类

CATransition有个type和subtype来识别变换效果,类型 NSString

typekCATransitionFade//默认  淡入淡出效果kCATransitionMoveIn//新图片滑动进入,直接覆盖旧的图片kCATransitionPush//边缘的一侧进来,把旧的图层从另一侧推出去kCATransitionReveal//旧图片滑出去,来显示新图片subtypekCATransitionFromRightkCATransitionFromLeftkCATransitionFromTopkCATransitionFromBottom

在想:控制器之间的过场动画是不是能用这个实现。

仿射变换

UIView可以通过transform属性做变换,transform是一个CGAffineTransform类型

CGAffineTransform  是用矩阵相乘的方法实现仿射变换

CGAffineTransformMakeRotation(CGFloat angle)                旋转

CGAffineTransformMakeScale(CGFloat sx, CGFloat sy)      缩放

CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty)    位移

CALayer 同样又个transform属性,transform的属性是CATransform3D

CALayer对应UIView的transform属性叫做affineTransform

混合变换

当操纵一个变换的时候,初始生成一个什么都不做的变换很重要-也就是创建一个CGAffineTransform类型的空值。<CGAffineTransformIdentity>

CGAffineTransformRotate(CGAffineTransform t, CGFloat angle) CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy) CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty)

最后,如果需要混合两个已经存在的矩阵,就可以使用下面方法,在两个基础上新建一个变换

CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2);

🌰❌

先缩小50%-再旋转30度-最后向右移动200像素

-  (void)viewDidLoad{    [superviewDidLoad];//createa new transform      CGAffineTransform transform = CGAffineTransformIdentity;//scaleby50%transform = CGAffineTransformScale(transform,0.5,0.5);//rotateby30degreestransform = CGAffineTransformRotate(transform, M_PI /180.0*30.0);//translateby200points transform = CGAffineTransformTranslate(transform,200,0);//applytransform to layerself.layerView.layer.affineTransform = transform;  }

结果并不是预期的结果:这意味着变换的顺序会影响最终的结果,也就是说旋转之后的平移和平移之后的的旋转结果可能不同

CG - Core Graphics 框架 :严格意义上说是2D绘图API

CGAffineTransform 仅仅对2D变换有效

3D变换

CALayer的transform属性是CATransform3D类型,就能实现3D空间内移动或者旋转

CATransform3DMakeRotation(CGFloat angle, CGFloat x, CGFloat y, CGFloat z)

CATransform3DMakeScale(CGFloat sx, CGFloat sy, CGFloat sz) CATransform3DMakeTranslation(Gloat tx, CGFloat ty, CGFloat tz)

想象x、y、z向旋转,缩放、位移的效果

🌰

绕Y轴做45度角的旋转

- (void)viewDidLoad{    [superviewDidLoad];//rotate the layer 45 degrees along the Y axisCATransform3Dtransform =CATransform3DMakeRotation(M_PI_4,0,1,0);self.layerView.layer.transform = transform;}

sublayerTransform:CALayer的属性,可以对所有子视图操作

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

推荐阅读更多精彩内容