动画示例(十) —— 一种弹性动画的实现 (一)

版本记录

版本号 时间
V1.0 2018.08.26

前言

如果你细看了我前面写的有关动画的部分,就知道前面介绍了CoreAnimation、序列帧以及LOTAnimation等很多动画方式,接下来几篇我们就以动画示例为线索,进行动画的讲解。相关代码已经上传至GitHub - 刀客传奇。感兴趣的可以看我写的前面几篇。
1. 动画示例(一) —— 一种外扩的简单动画
2. 动画示例(二) —— 一种抖动的简单动画
3. 动画示例(三) —— 仿头条一种LOTAnimation动画
4. 动画示例(四) —— QuartzCore之CAEmitterLayer下雪❄️动画
5. 动画示例(五) —— QuartzCore之CAEmitterLayer烟花动画
6. 动画示例(六) —— QuartzCore之CAEmitterLayer、CAReplicatorLayer和CAGradientLayer简单动画
7. 动画示例(七) —— 基于CAShapeLayer图像加载过程的简单动画(一)
8. 动画示例(八) —— UIViewController间转场动画的实现 (一)
9. 动画示例(九) —— 一种复杂加载动画的实现 (一)

开始

首先我们看一个效果

每个好一点的iOS应用程序都有自定义元素,自定义UI,自定义动画等。自定义,自定义,自定义!

如果您希望自己的应用能够脱颖而出,那么您必须花时间添加一些独特的功能,这些功能将为您的应用提供WOW因素。

在本教程中,您将构建一个自定义text field,在其被轻敲时可以生成一个可爱的小弹性反弹动画。

在此过程中,您将使用许多有趣的API:

  • 1)CAShapeLayer
  • 2)CADisplayLink
  • 3)UIView spring animations
  • 4)IBInspectable

下面我们就开始了。

新建立一个工程,该项目基于Single View Application iOS\Application\Single View Application。 它目前在容器视图中有两个text field和一个按钮。

你的目标是在获得焦点时给予弹性弹跳。 你说,你是如何实现这一目标的?

技术很简单:您将使用四个控制点视图和一个CAShapeLayer,然后使用UIView spring animations为控制点设置动画。 当它们正在动画时,你会在它们的位置周围重新绘制形状。

如果这听起来有点复杂,请不要担心! 它比你想象的容易。


Create a Base Elastic View - 创建一个弹性视图

首先,您将创建基本弹性视图,你将它作为子视图嵌入到UITextfield中,你将为这个视图设置动画,让你的控件弹性反弹。

右键单击项目导航器中的ElasticUI组,然后选择New File ...,然后选择iOS / Source / Cocoa Touch Class模板。 点击Next

调用类ElasticView,在子类的字段中输入UIView,并确保语言为Swift。 单击“下一步”,然后单击Create以选择存储与此新类关联的文件的默认位置。

首先,您需要创建四个控制点视图和一个CAShapeLayer。 添加以下代码,最终得到以下类定义:

import UIKit

class ElasticView: UIView {

  private let topControlPointView = UIView()
  private let leftControlPointView = UIView()
  private let bottomControlPointView = UIView()
  private let rightControlPointView = UIView()
  
  private let elasticShape = CAShapeLayer()
    
  override init(frame: CGRect) {
    super.init(frame: frame)
    setupComponents()
  }
  
  required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    setupComponents()
  }

  private func setupComponents() {
    
  }
}

视图和图层可以立即创建。setUpComponents()是从所有初始化路径调用的设置方法。 你现在要实现它。

setupComponents()中添加以下内容:

elasticShape.fillColor = backgroundColor?.CGColor
elasticShape.path = UIBezierPath(rect: self.bounds).CGPath
layer.addSublayer(elasticShape)

在这里,您要配置形状图层,将其填充颜色设置为与ElasticView的背景颜色相同,并将路径填充为与视图边界相同的大小。 最后,将其添加到图层层次结构中。

接下来,在`setupComponents()``的末尾添加以下代码:

for controlPoint in [topControlPointView, leftControlPointView,
  bottomControlPointView, rightControlPointView] {
  addSubview(controlPoint)
  controlPoint.frame = CGRect(x: 0.0, y: 0.0, width: 5.0, height: 5.0)
  controlPoint.backgroundColor = UIColor.blueColor()
}

这会将所有四个控制点添加到您的视图中。 为了帮助调试,这也将控制点的背景更改为蓝色,以便在模拟器中很容易看到。 您将在本教程结束时删除它。

您需要将控制点定位在顶部中心,底部中心,左中心和右侧中心。 这使得当您做远离视图的动画时,可以使用它们的定位在CAShapeLayer中绘制新路径。

你需要经常这样做,所以创建一个新功能来做到这一点。 将以下内容添加到ElasticView.swift

private func positionControlPoints(){
  topControlPointView.center = CGPoint(x: bounds.midX, y: 0.0)
  leftControlPointView.center = CGPoint(x: 0.0, y: bounds.midY)
  bottomControlPointView.center = CGPoint(x:bounds.midX, y: bounds.maxY)
  rightControlPointView.center = CGPoint(x: bounds.maxX, y: bounds.midY)
}

该函数将每个控制点移动到视图边缘上的正确位置。

现在从setupComponents()的末尾调用new函数:

positionControlPoints()

在深入了解动画之前,您将添加一个视图,因此您可以看到ElasticView的工作原理。 为此,您将为sb添加新视图。

打开Main.storyboard,将新的UIView拖到视图控制器的视图中,并将其Custom Class设置为ElasticView。 不要担心它的位置,只要它在屏幕上,你就能看到发生了什么。

Build并运行你的项目

看那个! 四个小蓝方块 - 这些是您在setupComponents中添加的控制点视图。

现在,您将使用它们在CAShapeLayer上创建路径以获得弹性外观。


Drawing Shapes with UIBezierPath - 使用UIBezierPath绘制形状

在深入研究下一系列步骤之前,请考虑如何在2D中绘制内容 - 您依赖于绘制线,特别是直线和曲线。 在绘制任何内容之前,如果要绘制直线,则需要指定起始位置和结束位置;如果要绘制更复杂的内容,则需要指定多个位置。

这些点是CGPoints,您可以在当前坐标系中指定x和y。

当您想要绘制基于矢量的形状(如正方形,多边形和复杂的曲线形状)时,它会变得更复杂一些。

为了模拟弹性效果,您将绘制一个看起来像矩形的二次Bézier曲线(quadratic Bézier),但它将为矩形的每一边都有控制点,这样就可以创建一条曲线来创建弹性效果。

Bézier曲线以PierreBézier命名,他是法国工程师,曾在CAD / CAM系统中表示曲线。 看一下二次Bézier曲线的样子:

蓝色圆圈是您的控制点,它们是您之前创建的四个视图,红色圆点是矩形的角。

注意:Apple有一个针对UIBezierPath的深入的类参考文档。 如果您想深入了解如何创建路径,那么值得一试。

现在是时候将理论付诸实践了! 将以下方法添加到ElasticView.swift

 
private func bezierPathForControlPoints()->CGPathRef {
  // 1
  let path = UIBezierPath()

  // 2
  let top = topControlPointView.layer.presentationLayer().position
  let left = leftControlPointView.layer.presentationLayer().position
  let bottom = bottomControlPointView.layer.presentationLayer().position
  let right = rightControlPointView.layer.presentationLayer().position

  let width = frame.size.width
  let height = frame.size.height

  // 3
  path.moveToPoint(CGPointMake(0, 0))
  path.addQuadCurveToPoint(CGPointMake(width, 0), controlPoint: top)
  path.addQuadCurveToPoint(CGPointMake(width, height), controlPoint:right)
  path.addQuadCurveToPoint(CGPointMake(0, height), controlPoint:bottom)
  path.addQuadCurveToPoint(CGPointMake(0, 0), controlPoint: left)

  // 4
  return path.CGPath
}

这个方法做了很多事,这里进行详细拆解:

  • 1)创建一个UIBezierPath来保持你的形状。
  • 2)将控制点位置提取为四个常量。 您使用presentationLayer的原因是在动画期间获取视图的“实时”位置。
  • 3)通过使用控制点从矩形的角到角添加曲线来创建路径
  • 4)将路径作为CGPathRef返回,因为这是形状层所期望的。

在为控制点设置动画时,需要调用此方法,因为它可以让您不断重新绘制新形状。 你是怎样做的?

CADisplayLink对象是一个计时器,允许您的应用程序将活动与显示器的刷新率同步。 您可以添加目标和在屏幕内容更新时调用的操作。

这是重新绘制路径和更新形状图层的绝佳机会。

首先,每次需要更新时添加一个方法来调用:

func updateLoop() {
  elasticShape.path = bezierPathForControlPoints()
}

然后,通过将以下变量添加到ElasticView.swift来创建显示链接:

private lazy var displayLink : CADisplayLink = {
  let displayLink = CADisplayLink(target: self, selector: Selector("updateLoop"))
  displayLink.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes)
  return displayLink
}()

这是一个懒加载变量,意味着在访问它之前不会创建它。 每次屏幕更新时,它都会调用updateLoop()函数。

您将需要方法来启动和停止链接,因此添加以下内容:

private func startUpdateLoop() {
  displayLink.paused = false
}
  
private func stopUpdateLoop() {
  displayLink.paused = true
}

每当控制点移动时,你已准备好绘制一条新路径。 现在,你必须移动他们!


UIView Spring Animations - UIView弹性动画

Apple非常擅长在每个iOS版本中添加新功能,spring animations的添加可以轻松提升应用程序的WOW因素。

它允许您使用自定义阻尼和速度(amping and velocity)为元素设置动画,使其更加特殊和有弹性!

将以下方法添加到ElasticView.swift以使这些控制点移动:

func animateControlPoints() {  
  //1
  let overshootAmount : CGFloat = 10.0
  // 2
  UIView.animateWithDuration(0.25, delay: 0.0, usingSpringWithDamping: 0.9, initialSpringVelocity: 1.5,
    options: nil, animations: {
    // 3
    self.topControlPointView.center.y -= overshootAmount
    self.leftControlPointView.center.x -= overshootAmount
    self.bottomControlPointView.center.y += overshootAmount
    self.rightControlPointView.center.x += overshootAmount
  },
  completion: { _ in
    // 4
    UIView.animateWithDuration(0.45, delay: 0.0, usingSpringWithDamping: 0.15, initialSpringVelocity: 5.5,
      options: nil, animations: {
        // 5
        self.positionControlPoints()
      }, 
      completion: { _ in
        // 6
        self.stopUpdateLoop()
      })
  })
}

以下是逐步细分:

  • 1)overshootAmount是控制点移动的量。
  • 2)在spring animation中包含即将到来的UI更改,持续时间为五分之一秒。如果您不熟悉spring animation但擅长物理,请查看UIView类参考,了解阻尼和速度变量的详细说明。对于我们其余的只要知道这些变量控制着动画如何反弹。使用数字来寻找感觉正确的配置是很正常的。
  • 3)向上,向左,向下或向右移动控制点 - 这将是动画。
  • 4)创建另一个弹簧动画以反弹所有内容。
  • 5)重置控制点位置 - 这也将是动画。
  • 6)一旦停止移动,停止display link

到目前为止,您尚未调用animateControlPoints。自定义控件的主要目的是在点击它时进行动画处理,因此调用上述方法的最佳位置是在touchBegan中。

添加以下内容:

override func touchesBegan(touches: Set, withEvent event: UIEvent) {
  startUpdateLoop()
  animateControlPoints()
}

Build并运行,然后点击View。


Refactoring and Polishing

现在你已经看到了很酷的动画,但你还有一些工作要做,以使你的ElasticView更抽象。

清除的第一个障碍是overshootAmount。 目前,它的硬编码值为10,但以编程方式和Interface Builder更改其值非常棒。

Xcode 6.0的一个新功能是@IBInspectable,这是一种通过界面构建器设置自定义属性的好方法。

您将通过添加overshootAmount作为@IBInspectable属性来利用这个非常棒的新功能,这样您创建的每个ElasticView都可以具有不同的值。

将以下变量添加到ElasticView

@IBInspectable var overshootAmount : CGFloat = 10

通过替换此行来引用animateControlPoints()中的属性:

let overshootAmount : CGFloat = 10.0

以及下面这行

let overshootAmount = self.overshootAmount

转到Main.storyboard,单击ElasticView并选择Attributes Inspector选项卡。

您会注意到一个新选项卡,其中显示了您的视图名称以及一个名为Overshoot Ainput field

对于使用@IBInspectable声明的每个变量,您将在Interface Builder中看到一个新的输入字段,您可以在其中编辑其值。

要查看此操作,请复制ElasticView,以便最终得到两个视图,并将新视图放在当前视图上方,就像这样。

在新视图中将原始视图中的Overshoot Amount值更改为20和40。

Build并运行。 点按两个视图即可查看差异。 如您所见,动画略有不同,并且取决于您在界面构建器中输入的数量。

尝试将值更改为-40而不是40,并查看会发生什么。 您可以看到控制点向内动画,但背景似乎没有变化。

你一定可以解决这个问题吧!

我会给你一个线索:你需要在setupComponents方法中改变一些东西。 自己尝试一下,但是如果你遇到困难,请看看下面的解决方案。

// You have to change the background color of your view after the elasticShape is created, otherwise the view and layer have the same color
backgroundColor = UIColor.clearColor()
clipsToBounds = false

干得好,你终于完成了你的ElasticView

现在您已经拥有了ElasticView,您可以将其嵌入到不同的控件中,例如text fields或按钮。


Making an Elastic UITextfield - 做一个有弹性的UITextfield

现在您已经构建了弹性视图的核心功能,接下来的任务是将其嵌入到自定义text fields中。

右键单击项目导航器中的ElasticUI组,然后选择New File…。 选择iOS / Source / Cocoa Touch Class模板,然后单击Next。

调用类ElasticTextField,在子类字段中输入UITextfield并确保语言为Swift。 单击Next,然后单击Create

打开ElasticTextField.swift并用以下内容替换其内容:

import UIKit

class ElasticTextField: UITextField {
  
  // 1
  var elasticView : ElasticView!
  
  // 2
  @IBInspectable var overshootAmount: CGFloat = 10 {
    didSet {
      elasticView.overshootAmount = overshootAmount
    }
  }
  
  // 3
  override init(frame: CGRect) {
    super.init(frame: frame)
    setupView()
  }
  
  required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    setupView()
  }

  // 4
  func setupView() {
    // A
    clipsToBounds = false
    borderStyle = .None

    // B
    elasticView = ElasticView(frame: bounds)
    elasticView.backgroundColor = backgroundColor
    addSubview(elasticView)

    // C
    backgroundColor = UIColor.clearColor()
    
    // D
    elasticView.userInteractionEnabled = false
  }
  
  // 5
  override func touchesBegan(touches: Set, withEvent event: UIEvent) {
    elasticView.touchesBegan(touches, withEvent: event)
  }
}

那里有很多事情发生!这是一步一步细分:

  • 1)这是保存ElasticView的A属性。
  • 2)一个名为overshootAmountIBInspectable变量,因此您可以通过Interface Builder更改控件的弹性。它会覆盖didSet并只设置弹性视图的overshootAmount
  • 3)该类的标准初始值设定项,因此调用设置方法。
  • 4)这是您设置text field的位置。让我们进一步细分:
    • A)将clipsToBounds更改为false。这使弹性视图超出其父级边界,并将UITextField的边框样式更改为.None以展平控件。
    • B)创建并添加ElasticView作为控件的子视图。
    • C)更改控件的backgroundColor以使其clear;你这样做是因为你希望ElasticView决定颜色。
    • D)最后,这会将ElasticViewuserInteractionEnabled设置为false。否则,它会从您的控制中窃取。
    • E)覆盖touchesBegan并将其转发到您的ElasticView,以便它可以设置动画。

转到Main.storyboard,选择UITextfield的两个实例,并在Identity Inspector中将它们的类从UITextField更改为ElasticTextField

此外,请确保删除为测试目的而添加的两个ElasticView实例。

Build并运行,点按您的textfield,注意它实际上是如何工作的:

原因是当您在代码中创建ElasticView时,它会获得清晰的背景颜色,并将其传递给形状图层。

要更正此问题,只要在视图上设置新的背景颜色,就需要一种方式将颜色传递到shape layer


Forwarding the Background Color - 传递背景色

因为要使用elasticShape作为视图的主要背景,所以必须覆盖ElasticView中的backgroundColor

将以下代码添加到ElasticView.swift

override var backgroundColor: UIColor? {
  willSet {
    if let newValue = newValue {
      elasticShape.fillColor = newValue.CGColor
      super.backgroundColor = UIColor.clearColor()
    }
  }
}

在设置值之前,将调用willSet。 检查是否已传递值,然后将elasticShapefillColor设置为用户选择的颜色。 然后,您调用super并将其背景颜色设置为clear

Build并运行,你应该有一个可爱的弹性控制。


Final Tweaks - 最后的调整

请注意UITextField的占位符文本与左边缘的接近程度。 它有点舒服,你不觉得吗? 你想自己解决这个问题吗?

看下面的解决方案。

// Add some padding to the text and editing bounds
override func textRectForBounds(bounds: CGRect) -> CGRect {
  return CGRectInset(bounds, 10, 5)
}

override func editingRectForBounds(bounds: CGRect) -> CGRect {
  return CGRectInset(bounds, 10, 5)
}

Removing Debug Information - 删除调试信息

打开ElasticView.swift并从setupComponents中删除以下内容。

controlPoint.backgroundColor = UIColor.blueColor()

到目前为止就全部完成了。


源码

1. ElasticView.swift
import UIKit

class ElasticView: UIView {
    
    private let topControlPointView = UIView()
    private let leftControlPointView = UIView()
    private let bottomControlPointView = UIView()
    private let rightControlPointView = UIView()
    
    private let elasticShape = CAShapeLayer()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupComponents()
    }
    
    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupComponents()
    }
    
    private func setupComponents() {
        elasticShape.fillColor = backgroundColor?.CGColor
        elasticShape.path = UIBezierPath(rect: self.bounds).CGPath
        layer.addSublayer(elasticShape)
        
        for controlPoint in [topControlPointView, leftControlPointView,
            bottomControlPointView, rightControlPointView] {
                addSubview(controlPoint)
                controlPoint.frame = CGRect(x: 0.0, y: 0.0, width: 5.0, height: 5.0)
        }
        
        positionControlPoints()
        
        // You have to change the background color of your view after the elasticShape is created, otherwise the view and layer have the same color
        backgroundColor = UIColor.clearColor()
        clipsToBounds = false
    }
    
    private func positionControlPoints(){
        topControlPointView.center = CGPoint(x: bounds.midX, y: 0.0)
        leftControlPointView.center = CGPoint(x: 0.0, y: bounds.midY)
        bottomControlPointView.center = CGPoint(x:bounds.midX, y: bounds.maxY)
        rightControlPointView.center = CGPoint(x: bounds.maxX, y: bounds.midY)
    }
    
    private func bezierPathForControlPoints()->CGPathRef {
        // 1
        let path = UIBezierPath()
        
        // 2
        let top = topControlPointView.layer.presentationLayer().position
        let left = leftControlPointView.layer.presentationLayer().position
        let bottom = bottomControlPointView.layer.presentationLayer().position
        let right = rightControlPointView.layer.presentationLayer().position
        
        let width = frame.size.width
        let height = frame.size.height
        
        // 3
        path.moveToPoint(CGPointMake(0, 0))
        path.addQuadCurveToPoint(CGPointMake(width, 0), controlPoint: top)
        path.addQuadCurveToPoint(CGPointMake(width, height), controlPoint:right)
        path.addQuadCurveToPoint(CGPointMake(0, height), controlPoint:bottom)
        path.addQuadCurveToPoint(CGPointMake(0, 0), controlPoint: left)
        
        // 4
        return path.CGPath
    }
    
    private lazy var displayLink : CADisplayLink = {
        let displayLink = CADisplayLink(target: self, selector: Selector("updateLoop"))
        displayLink.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes)
        return displayLink
        }()
    
    func updateLoop() {
        elasticShape.path = bezierPathForControlPoints()
    }
    
    private func startUpdateLoop() {
        displayLink.paused = false
    }
    
    private func stopUpdateLoop() {
        displayLink.paused = true
    }
    
    @IBInspectable var overshootAmount : CGFloat = 10
    
    func animateControlPoints() {
        //1
        let overshootAmount = self.overshootAmount
        // 2
        UIView.animateWithDuration(0.25, delay: 0.0, usingSpringWithDamping: 0.9, initialSpringVelocity: 1.5,
            options: nil, animations: {
                // 3
                self.topControlPointView.center.y -= overshootAmount
                self.leftControlPointView.center.x -= overshootAmount
                self.bottomControlPointView.center.y += overshootAmount
                self.rightControlPointView.center.x += overshootAmount
            },
            completion: { _ in
                // 4
                UIView.animateWithDuration(0.45, delay: 0.0, usingSpringWithDamping: 0.15, initialSpringVelocity: 5.5,
                    options: nil, animations: {
                        // 5
                        self.positionControlPoints()
                    }, 
                    completion: { _ in
                        // 6
                        self.stopUpdateLoop()
                })
        })
    }
    
    override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
        startUpdateLoop()
        animateControlPoints()
    }
    
    override var backgroundColor: UIColor? {
        willSet {
            if let newValue = newValue {
                elasticShape.fillColor = newValue.CGColor
                super.backgroundColor = UIColor.clearColor()
            }
        }
    }
}
2. ElasticTextField.swift
import UIKit

class ElasticTextField: UITextField {
    
    // 1
    var elasticView : ElasticView!
    
    // 2
    @IBInspectable var overshootAmount: CGFloat = 10 {
        didSet {
            elasticView.overshootAmount = overshootAmount
        }
    }
    
    // 3
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupView()
    }
    
    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupView()
    }
    
    // 4
    func setupView() {
        // A
        clipsToBounds = false
        borderStyle = .None
        
        // B
        elasticView = ElasticView(frame: bounds)
        elasticView.backgroundColor = backgroundColor
        addSubview(elasticView)
        
        // C
        backgroundColor = UIColor.clearColor()
        
        // D
        elasticView.userInteractionEnabled = false
    }
    
    // 5
    override func touchesBegan(touches: Set<NSObject>, withEvent event: UIEvent) {
        elasticView.touchesBegan(touches, withEvent: event)
    }
    
    // Add some padding to the text and editing bounds
    override func textRectForBounds(bounds: CGRect) -> CGRect {
        return CGRectInset(bounds, 10, 5)
    }
    
    override func editingRectForBounds(bounds: CGRect) -> CGRect {
        return CGRectInset(bounds, 10, 5)
    }
}

后记

本篇主要讲述了一种弹性动画的实现,感兴趣的给个赞或者关注~~~

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

推荐阅读更多精彩内容