SwiftUI框架详细解析 (八) —— 基于SwiftUI的动画的实现(一)

版本记录

版本号 时间
V1.0 2019.12.11 星期三

前言

今天翻阅苹果的API文档,发现多了一个框架SwiftUI,这里我们就一起来看一下这个框架。感兴趣的看下面几篇文章。
1. SwiftUI框架详细解析 (一) —— 基本概览(一)
2. SwiftUI框架详细解析 (二) —— 基于SwiftUI的闪屏页的创建(一)
3. SwiftUI框架详细解析 (三) —— 基于SwiftUI的闪屏页的创建(二)
4. SwiftUI框架详细解析 (四) —— 使用SwiftUI进行苹果登录(一)
5. SwiftUI框架详细解析 (五) —— 使用SwiftUI进行苹果登录(二)
6. SwiftUI框架详细解析 (六) —— 基于SwiftUI的导航的实现(一)
7. SwiftUI框架详细解析 (七) —— 基于SwiftUI的导航的实现(二)

开始

首先看下主要内容

主要内容:在本教程中,您将学习如何使用SwiftUI添加精美的动画。 您将从基本(basic)动画过渡到复杂的自定义spring动画。

接着看下写作环境

Swift 5, iOS 13, Xcode 11

SwiftUI带来了大量的新功能,并且为Apple的所有平台编写UI代码提供了更简便的方法。作为额外的好处,它还具有一种新的状态转换动画方法。

如果您习惯使用UIView动画,则可能会发现它们更易于编写和理解。如果您要使用SwiftUI,那么恭喜您!这将使您轻松使用它。

在本教程中,您将学习SwiftUI动画的基础知识,包括:

  • animation修改器。
  • withAnimation,该函数可让您对状态更改进行动画处理。
  • 自定义动画。

最好使用Xcode 11.2.1或更高版本,该版本包含SwiftUI代码中已知动画bug的修复程序。如果您运行的是macOS Catalina,则时间会更短一些,因为Xcode会在Canvas窗格中并排显示代码和实时预览。

但是,如果您仍然有Mojave,那也可以;您可以构建并运行以查看代码更改如何影响应用程序。实际上,由于预览窗口可能有问题,因此该技术在Catalina上仍然很方便。

打开已有项目。

应用MySolarSystem是对太阳系中行星的简单清单(假设您正在地球上阅读)。 完成后,点按一下即可显示每个行星,并列出该行星最大的卫星。 从那里轻敲月球将为用户提供有关行星的其他信息。

ContentView代表应用程序的主屏幕。 它是一个由Planets数组构成表格的列表。 列表中的每个元素都是一个带有多个控件的HStack:一个用于行星图片的Image,一个用于行星名称的Text,一个Spacer和一个按钮Button(如果该行星上有卫星)。 此按钮的目的是显示有关卫星的信息。

在本教程的后面,您将把可选的MoonList放入VStack。 点击月亮按钮将显示或隐藏它。 这将是您要添加并设置动画的第一件事。

1. Preview Window

如果您在macOS Catalina上运行,则可以在编辑器窗格中访问Canvas。 如果看不到它,请选择Editor ▸ Canvas以启用它。 更改代码后,将显示视图和实时更新。

如果单击Live Preview按钮(又称播放图标),它将使预览具有交互性。 添加动画和交互性后,您就可以单击按钮并观察状态变化。

注意:在撰写本文时,预览窗口并非100%可靠。 如果预览停止更新,请检查Canvas顶部是否显示以下消息:Automatic preview updating paused。 如果看到,只需单击Resume即可重建预览。 另外,如果动画似乎无法正常运行,请通过在模拟器或设备上运行该应用进行仔细检查。 实时应用是任何UI行为的真实来源。


Basic Animations

要添加动画,您需要做的第一件事就是制作动画。 因此,首先,您将创建一个状态更改,以触发对UI的更新。

打开ContentView.swift并将以下内容添加到makePlanetRow(planet :)中的VStack的末尾:

if self.toggleMoons(planet.name) {
  MoonList(planet: planet)
}

这会检查toggleMoons(_ :),以确定是否应该切换该星球的行。 如果打开了卫星,则MoonStack视图将出现在VStack中。 此方法与state属性showMoon绑定。

接下来,通过设置showMoon属性完成操作。 在按钮的操作回调中,添加以下代码:

self.showMoon = self.toggleMoons(planet.name) ? nil : planet.name

如果用户已经按下按钮,此代码将清除showMoon;如果用户选择其他行,则将其设置为新的行星。

生成并运行该应用程序。 轻触带有“moon disclosure”图标的行,将显示卡通地图以及该行星最大的一些卫星的列表。 该按钮的大小也加倍,以引起人们的注意。 这样,用户便知道在何处点击以关闭该行。

这使月亮列表视图内外闪烁,这几乎不是很棒的用户体验。 幸运的是,添加一些动画很容易。

首先为按钮尺寸创建平滑的动画。 在月球图像Image上的scaleEffect修改器之后,添加以下修改器:

.animation(.default)

这会将默认动画添加到缩放效果。 现在,如果您展开或折叠一行,则按钮将平滑地增大或缩小。 如果您发现月亮视图的瞬时外观和消失有一些问题,请不要担心。 您将在下面的Animating State Changes部分中对此进行修复。

1. Animation Timing

animation(_ :)修饰符采用Animation参数。

您可以将多个选项用于动画。 基本的是简单的时序曲线,它们描述了动画速度在持续时间内如何变化。 这些都修改了变化的属性,在起始值和结束值之间平滑地插值。

您有以下选择:

  • .linear:随着时间的推移,它将属性从开始值到结束值均匀过渡。 这是重复动画的良好时序曲线,但看起来不像eased function那样自然。
  • .easeIn:缓动动画从慢速开始,并随着时间的推移加快速度。 这对于从静止点开始并在屏幕外结束的动画很有用。
  • .easeOut:缓动的动画开始快而结束慢。 这对于使物体达到稳态或最终位置具有动画效果是很有用的。
  • .easeInOut:缓入和缓出曲线结合了easyIneaseOut。 这对于从一个稳定点开始并在另一个平衡点处结束的动画很有用。 这是大多数应用程序的最佳选择。 这就是为什么这是.default使用的时序曲线的原因。
  • .timingCurve:这使您可以指定自定义时序曲线。 这很少需要,并且不在本教程的讨论范围之内。

大多数情况下,.default足以满足您的需求。 如果您还需要其他一些功能,则基本的计时功能之一将为您提供一些额外的改进。 通过将.default替换为其中的一种来进行尝试,以找出最适合您的选择。

上图显示了每个时序曲线随时间的速度。 但是动画将如何实际显示? 将每个曲线应用到月亮按钮的scaleEffect时,这是随着时间变化的外观。

Linear
EaseIn
EaseOut
EaseInOut

2. Tinkering With Timing

如果您看不到细微的差异,可以通过花费更多时间来放慢动画的播放速度。 将animation修改器替换为:

.animation(.easeIn(duration: 5))

指定更长的持续时间将使时序曲线更明显。

在模拟器中而不是SwiftUI预览窗口中构建和运行的一个优点是,您可以启用Debug ▸ Slow Animations标志。 这将大大降低任何动画的速度,因此您可以更清楚地看到细微的差异。 这样,您无需添加额外的duration参数。

除了时序曲线和持续时间外,您还可以使用其他一些控制杆来控制动画的时序。 首先,有速度。

ContentView的顶部创建一个新属性:

let moonAnimation = Animation.easeInOut.speed(0.1)

该常量仅存储动画,因此您以后可以在代码中更轻松地使用它。 它还为您提供了一个改变周围事物的场所。

接下来,替换Image修改器:

.animation(moonAnimation)

现在,月亮的大小将非常缓慢地调整。 尝试将速度更改为2,动画将变得非常活泼。

除了速度之外,您还可以添加延迟delay。 将moonAnimation更改为:

let moonAnimation = Animation.easeInOut.delay(1)

现在,动画具有一秒钟的延迟。 在月亮列表中点击该行闪烁,然后稍后,该按钮将更改大小。 一次为多个属性或对象设置动画时,Delay最为有用,这将在后面介绍。

最后,您可以使用修饰符来重复动画。 将动画更改为:

let moonAnimation = Animation.easeInOut.repeatForever(autoreverses: true)

现在,该按钮将永远跳动。 您还可以使用repeatCount(autoreverses :)重复动画有限的次数。

完成实验后,将动画设置回:

let moonAnimation = Animation.default

3. Simultaneous Animations

.animation是一个修饰符,可像其他修饰符一样叠加到SwiftUI视图上。 如果视图具有多个变化的属性,则单个Animation可以应用于所有这些属性。

通过将其放置在ImagescaleEffect线之间,添加以下旋转效果:

.rotationEffect(.degrees(self.toggleMoons(planet.name) ? -50 : 0))

这会使moon按钮略微旋转,因此当出现“月亮”视图时,新月形会横向排列。 由于将动画添加到最后,这些动画将一起动画。

当然,您可以为每个属性指定单独的动画。 例如,在rotationEffect修改器之后添加以下修改器:

.animation(.easeOut(duration: 1))

这为旋转提供了一秒钟的动画,因此与缩放效果相比,您会注意到旋转结束的时间稍晚一些。 接下来,将moonAnimation更改为:

let moonAnimation = Animation.default.delay(1)

这会将大小动画延迟一秒,因此缩放在旋转完成后开始。 试试看。

最后,可以通过为动画指定nil来选择不为特定属性更改设置动画。 将rotationEffect动画更改为:

.animation(nil)

并将moonAnimation更改为:

let moonAnimation = Animation.default

现在,仅动画大小。


Animating State Changes

您花了一段时间完善了“月亮”按钮的动画效果,但是会出现在所有圆圈中的大视野呢?

好了,您可以使用简单的withAnimation块为任何block转换设置动画。 将按钮操作块的内容替换为:

withAnimation {
  self.showMoon = self.toggleMoons(planet.name) ? nil : planet.name
}

withAnimation明确告诉SwiftUI要设置动画。 在这种情况下,它将对showMoon和具有依赖于该属性的任何视图的切换进行动画处理。

如果您现在观看动画,将会看到月亮视图使用默认的出现动画逐渐消失,并且列表行滑出以腾出空间。

您还可以向显式动画块提供特定的动画。 用以下内容替换withAnimation

withAnimation(.easeIn(duration: 2))

现在,它使用持续时间为两秒的缓动动画,而不是default

如果您尝试一下,放慢的动画可能看起来不正确。 有更好的方法可以对进出层次结构的视图进行动画处理。

在继续之前,将withAnimation更改为:

withAnimation(.easeInOut)

1. Transitions

Transitions介绍了如何插入或删除视图。 要查看其工作原理,请将此修饰符添加到if self.toggleMoons(planet.name)块中的MoonList中:

.transition(.slide)

现在,视图将滑入而不是淡入。从前缘滑入(Slide)动画,然后从尾随滑出。

Transitions属于AnyTransition类型。 SwiftUI带有一些预制的过渡:

  • .slide:您已经在使用中看到了它-将视图从侧面滑入。
  • .opacity:此过渡使视图淡入和淡出。
  • .scale:通过放大或缩小视图进行动画处理。
  • .move:类似于slide,但是可以指定边缘。
  • .offset:沿任意方向移动视图。

继续尝试这些过渡,以了解它们的工作方式。

2. Combining Transitions

您还可以结合过渡来组成自己的自定义效果。 在ContentView.swift的顶部,添加以下扩展名:

extension AnyTransition {
  static var customTransition: AnyTransition {
    let transition = AnyTransition.move(edge: .top)
      .combined(with: .scale(scale: 0.2, anchor: .topTrailing))
      .combined(with: .opacity)
    return transition
  }
}

它结合了三个过渡:从顶部边缘移动,从20%锚定到顶部的scale以及不透明的淡入淡出效果。

要使用它,请将MoonList实例的transition行更改为:

.transition(.customTransition)

构建并运行您的项目,然后尝试打开月球列表。 组合的效果就像视图从“月亮”按钮突然进出一样。

3. Asynchronous Transitions

如果需要,还可以使入口过渡与出口过渡不同。

ContentView.swift中,将customTransition的定义替换为:

static var customTransition: AnyTransition {
  let insertion = AnyTransition.move(edge: .top)
    .combined(with: .scale(scale: 0.2, anchor: .topTrailing))
    .combined(with: .opacity)
  let removal = AnyTransition.move(edge: .top)
  return .asymmetric(insertion: insertion, removal: removal)
}

这样可以保持插入状态,但是现在,月亮视图通过移动到顶部退出屏幕。


Springs

现在,卫星列表仅显示卫星以同心圆彼此堆叠。 如果它们间隔开,甚至可以动画进去,看起来会更好。

MoonView.swift中,将以下修饰符添加到body链的末尾:

.onAppear {
  withAnimation {
    self.angle = self.targetAngle
  }
}

这将在代表月亮的橙色球上设置一个随机角度。 然后,月亮将从直线上的零度动画到新位置。

该动画效果还不错,因此您需要再添加一点点爵士乐。 Spring动画使您可以添加一些弹跳和跳动,以使视图生动活泼。

SolarSystem.swift中,将此方法添加到SolarSystem中:

func animation(index: Double) -> Animation {
  return Animation.spring(dampingFraction: 0.5)
}

此辅助方法创建一个Spring动画。

然后,在makeSystem(_ :)的最后一个ForEach中,将以下修饰符附加到self.moon行:

.animation(self.animation(index: Double(index)))

这会将动画添加到每个月球圆圈。 现在不用担心index; 您将在下一部分中使用它。

下次加载视图时,随着卫星到达其最终位置,将会有一些反弹。

1. Using SwiftUI’s Pre-Built Spring Options

SwiftUI中的所有其他内容一样,spring有一些内置选项:

  • spring():默认的弹簧行为。 这是一个很好的起点。
  • spring(response:dampingFraction:blendDuration):带有更多选项以微调其行为的弹簧动画。
  • interpolatingSpring(mass:stiffness:damping:initialVelocity :):一种非常可定制的基于物理建模的弹簧。

Spring动画使用弹簧的真实世界作为基础。 想象一下,如果可以的话,请在弹簧线圈的末端安装一个重块。 如果该块的质量较大,则释放弹簧会导致其下落时产生较大的位移,从而使其弹跳得越来越远。

弹簧越硬,其作用就相反-弹簧越硬,它将移动得越远。 增大阻尼就像增大摩擦:它将减少行程并缩短弹跳时间。 初始速度设置动画的速度:更大的速度将使视图移动更多,并使其弹跳时间更长。

spring(response:dampingFraction:) curves for various parameters

很难将物理原理映射到动画的实际工作方式。这就是为什么每次都使用一个参数以获得漂亮的动画非常有用。

这也是为什么有spring(response:dampingFraction:blendDuration)的原因,它是编写spring动画的一种更容易理解的方式。在幕后,它仍然使用物理模型,但杠杆较少。

阻尼(dampingFraction)阻尼控制视图反弹的时间。零阻尼是一个无阻尼的系统,这意味着它将永远反弹。大于1的阻尼阻尼值根本不会弹起。如果您使用弹簧,通常会选择一个介于0到1之间的值。较大的值会降低速度。

响应(response)是完成一次振荡的时间量。这将控制动画的持续时间。这两个值协同工作以调整视图的距离和速度,并且经常会反弹视图。如果更改response或组合多个弹簧,blendDuration将影响动画。这是一种高级机动。

Response curves for various interpolating spring parameters

2. Refining the Animation

现在您对弹簧的工作原理有了更好的了解,请花点时间整理动画。 为一堆相对独立的视图制作动画时,一件好事是给它们一些稍有不同的时间。 这有助于使动画更生动。

SolarSystem.swift中,将animation(index :)更改为以下内容:

func animation(index: Double) -> Animation {
  return Animation
    .spring(response: 0.55, dampingFraction: 0.45, blendDuration: 0)
    .speed(2)
    .delay(0.075 * index)
}

这给每个“月球”增加了滚动延迟,因此它们彼此之间在75毫秒后开始下降,从而产生了一些奇特的效果。


Animatable

这看起来不错,但如果卫星沿轨道移动而不是从顶部弹起,那将更加现实。 您可以通过使用自定义修饰符GeometryEffect来实现。 几何效果描述了一种修改视图位置,形状或大小的动画。

将以下结构体添加到SolarSystem.swift

struct OrbitEffect: GeometryEffect {
  let initialAngle = CGFloat.random(in: 0 ..< 2 * .pi)

  var percent: CGFloat = 0
  let radius: CGFloat

  var animatableData: CGFloat {
    get { return percent }
    set { percent = newValue }
  }

  func effectValue(size: CGSize) -> ProjectionTransform {
    let angle = 2 * .pi * percent + initialAngle
    let pt = CGPoint(
      x: cos(angle) * radius,
      y: sin(angle) * radius)
    return ProjectionTransform(CGAffineTransform(translationX: pt.x, y: pt.y))
  }
}

OrbitEffect将其视图绕一圈移动。它通过GeometryEffecteffectValue(size :)方法实现。此方法返回一个ProjectionTransform,它是一个坐标变换。在这种情况下,将使用平移CGAffineTransform创建平移,该平移对应于圆上的一个点。

GeometryEffect的神奇之处在于它符合Animatable。这意味着它需要一个名为animatableData的属性。在这种情况下,它包装了一个名为percentCGFloat属性。动画数据可以是符合VectorArithmetic的任何值,这意味着可以在起始值和结束值之间进行插值。为此,您将选择0%100%之间的动画。

通过计算0到之间的相对值,可以将百分比应用于effectValue。还有一个随机的initialAngle。这样,每个“月球”将从不同的位置开始;否则,将它们全部紧紧地锁定似乎很奇怪。

要应用此效果,您需要稍微更改SolarSystem视图。首先,向其添加一个新的state属性:

@State private var animationFlag = false

动画是根据状态变化而发生的。 由于存在视图时轨道动画将保持不变,因此此简单的布尔值将充当动画的触发器。

接下来,添加此辅助方法:

func makeOrbitEffect(diameter: CGFloat) -> some GeometryEffect {
  return OrbitEffect(
    percent: self.animationFlag ? 1.0 : 0.0,
    radius: diameter / 2.0)
}

这将创建一个OrbitEffect,将其百分比从0开始,并在切换标志后将其更改为1

要调用此方法,请在单个月亮ForEach中,将.animation修饰符替换为以下内容:

.modifier(self.makeOrbitEffect(
  diameter: planetSize + radiusIncrement * CGFloat(index)
))
.animation(Animation
  .linear(duration: Double.random(in: 10 ... 100))
  .repeatForever(autoreverses: false)
)

通过使用辅助方法,这会将OrbitEffect应用于每个月圆。 它还应用了随机时间重复的线性动画。 这样,每个月球将以各自独立的速度无限期绕轨道运行。 这不是行星的逼真的视图,但看起来很漂亮。

接下来,替换moon(planetSize:moonSize:radiusIncrement:index :)的实现,如下所示:

func moon(planetSize: CGFloat,     
          moonSize: CGFloat,     
          radiusIncrement: CGFloat,    
          index: CGFloat) -> some View {
  return Circle()
    .fill(Color.orange)
    .frame(width: moonSize, height: moonSize)
}

以前的辅助方法使用MoonView,该方法用于将自身放置在正确的半径处。 现在,OrbitEffect处理此放置,因此更改辅助函数可以消除冲突。

最后,在ZStackmakeSystem(_ :)末尾,应用以下修饰符:

.onAppear {
  self.animationFlag.toggle()
}

这将触发该标志,并在显示视图时开始播放动画。

现在,再次构建并运行。 现在,月球将以不同的速度绕地球旋转。

1. An Alternate Look at Animatable Data

在上一步中,您通过将百分比从0更改为1来使效果动画起来。这是对任何事物进行动画处理的良好通用策略。 例如,您可以对进度条执行相同操作,并使用GeometryEffect在整个屏幕上扩展该条的宽度。

或者,您可以直接为角度设置动画并保存一个步骤。

OrbitEffect中,将percentage重命名为angle,现在将其从0设置为。 确保您也用animatableData定义的angle n替换了percentage

接下来,将effectValue(size :)中的计算更改为:

func effectValue(size: CGSize) -> ProjectionTransform {
  let pt = CGPoint(
    x: cos(angle + initialAngle) * radius,
    y: sin(angle + initialAngle) * radius)
  let translation = CGAffineTransform(translationX: pt.x, y: pt.y)
  return ProjectionTransform(translation)
}

这将直接使用angle属性,而不是基于百分比来计算角度。

然后,将makeOrbitEffect(diameter :)更改为:

func makeOrbitEffect(diameter: CGFloat) -> some GeometryEffect {
  return OrbitEffect(
    angle: self.animationFlag ? 2 * .pi : 0,
    radius: diameter / 2.0)
}

在此,当切换animationFlag时,更改将从0更改为,而不是0。

现在就构建并运行,但是您不会注意到动画上的差异。

当起点和终点是任意的时,例如将用户设置的两个位置之间的视图或其他视图设置为动画时,使用值与百分比的动画效果最佳。


Other Ways to Animate

迄今为止,当视图由于状态变化而变化时,您所看到的动画类型将隐式或显式地发生。 您还可以通过显式控制视图的属性来为视图设置动画。 例如,您可以循环更改视图的offset,以将其从一个位置移动到另一个位置。

在月球列表中点击一个月球名称,将显示该行星的详细信息屏幕,其中包含可滚动的月球缩略图列表。 现在,这个列表很无聊。

您可以通过使用rotation3DEffect为其提供类似于CoverFlow的界面来为列表增添趣味。

MoonFlow.swift中,将以下内容添加到body中间的VStack的末尾:

.rotation3DEffect(
  .degrees(Double(moonGeometry
    .frame(in: .global).midX - geometry.size.width / 2) / 3),
  axis: (x: 0, y: 1, z: 0)
)

这将沿y轴应用旋转效果,具体取决于单个VStack距整个视图中心的距离。 当用户滚动时,该堆栈的几何形状将不断变化,从而导致旋转效果更新。 这样可以平滑地改变旋转效果,从而制作出动画。

通过结合使用GeometryReader和诸如projectionEffecttransformEffectscaleEffectrotationEffect之类的效果,您可以在视图更改屏幕上位置时更改其位置和形状。

后记

本篇主要讲述了基于SwiftUI的动画的实现,感兴趣的给个赞或者关注~~~

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

推荐阅读更多精彩内容