CoreGraphic框架解析 (二十四) —— 基于Core Graphic的重复图案的绘制(一)

版本记录

版本号 时间
V1.0 2021.03.15 星期一

前言

quartz是一个通用的术语,用于描述在iOSMAC OS X 中整个媒体层用到的多种技术 包括图形、动画、音频、适配。Quart 2D 是一组二维绘图和渲染APICore Graphic会使用到这组APIQuartz Core专指Core Animation用到的动画相关的库、API和类。CoreGraphicsUIKit下的主要绘图系统,频繁的用于绘制自定义视图。Core Graphics是高度集成于UIView和其他UIKit部分的。Core Graphics数据结构和函数可以通过前缀CG来识别。在app中很多时候绘图等操作我们要利用CoreGraphic框架,它能绘制字符串、图形、渐变色等等,是一个很强大的工具。感兴趣的可以看我另外几篇。
1. CoreGraphic框架解析(一)—— 基本概览
2. CoreGraphic框架解析(二)—— 基本使用
3. CoreGraphic框架解析(三)—— 类波浪线的实现
4. CoreGraphic框架解析(四)—— 基本架构补充
5. CoreGraphic框架解析 (五)—— 基于CoreGraphic的一个简单绘制示例 (一)
6. CoreGraphic框架解析 (六)—— 基于CoreGraphic的一个简单绘制示例 (二)
7. CoreGraphic框架解析 (七)—— 基于CoreGraphic的一个简单绘制示例 (三)
8. CoreGraphic框架解析 (八)—— 基于CoreGraphic的一个简单绘制示例 (四)
9. CoreGraphic框架解析 (九)—— 一个简单小游戏 (一)
10. CoreGraphic框架解析 (十)—— 一个简单小游戏 (二)
11. CoreGraphic框架解析 (十一)—— 一个简单小游戏 (三)
12. CoreGraphic框架解析 (十二)—— Shadows 和 Gloss (一)
13. CoreGraphic框架解析 (十三)—— Shadows 和 Gloss (二)
14. CoreGraphic框架解析 (十四)—— Arcs 和 Paths (一)
15. CoreGraphic框架解析 (十五)—— Arcs 和 Paths (二)
16. CoreGraphic框架解析 (十六)—— Lines, Rectangles 和 Gradients (一)
17. CoreGraphic框架解析 (十七)—— Lines, Rectangles 和 Gradients (二)
18. CoreGraphic框架解析 (十八) —— 如何制作Glossy效果的按钮(一)
19. CoreGraphic框架解析 (十九) —— 如何制作Glossy效果的按钮(二)
20. CoreGraphic框架解析 (二十) —— Curves and Layers(一)
21. CoreGraphic框架解析 (二十一) —— Curves and Layers(二)
22. CoreGraphic框架解析 (二十二) —— Gradients 和 Contexts的简单示例(一)
23. CoreGraphic框架解析 (二十三) —— Gradients 和 Contexts的简单示例(二)

开始

首先看下主要内容:

了解如何绘制可重复的图案,以及如何使用Playgrounds原型绘制复杂图像。内容来自翻译

接着看下写作环境:

Swift 5, iOS 14, Xcode 12

接着就是主要内容了。

在本教程中,您将学习如何在视图的背景上绘制重复的图案,如何使用Swift Playgrounds快速创建Core Graphics图纸的原型,如何从绘制中获得最好的结果,以及如何添加简单的动画。

在本教程中,您将使用名为Flo的饮水跟踪应用程序。您可能还记得Core Graphics Tutorial: Getting StartedCore Graphics Tutorial: Gradients and Contexts教程中的该应用程序。如果您错过了它们,这是一个很好的阅读它们的好时机,但是您也可以从这里开始。

您将采用Flo的最终形式。具体来说,您将:

  • 为背景创建重复图案
  • 从头到尾绘制奖牌,奖励每天成功喝八杯水的用户
  • 向按钮添加简单的关键路径(keypath animation)动画

Repeating Pattern for Background

本部分的任务是使用UIKit中的样式方法来创建此背景样式(pattern)

注意:如果需要优化速度,请阅读 Core Graphics Tutorial: Patterns,该教程演示了使用SwiftCore Graphics创建模式的基本方法。 在大多数情况下,例如仅绘制背景一次时,使用UIKit包装器方法应该是可以接受的。

1. Setting up BackgroundView

转到File ▸ New ▸ File…,选择iOS ▸ Source ▸ Cocoa Touch Class模板,然后选择Next。 在Class区域中输入BackgroundView,在Subclass中输入UIView。 确保为该语言选择了Swift。 单击Next,然后单击Create

转到Main.storyboard,选择ViewController的主视图,然后在Identity inspector中将类更改为BackgroundView

BackgroundView.swift中的代码替换为:

import UIKit

@IBDesignable
class BackgroundView: UIView {
  // 1
  @IBInspectable var lightColor: UIColor = .orange
  @IBInspectable var darkColor: UIColor = .yellow
  @IBInspectable var patternSize: CGFloat = 200

  override func draw(_ rect: CGRect) {
    // 2
    guard let context = UIGraphicsGetCurrentContext() else {
      fatalError("\(#function):\(#line) Failed to get current context.")
    }

    // 3
    context.setFillColor(darkColor.cgColor)

    // 4
    context.fill(rect)
  }
}

然后,打开Main.storyboard,注意故事板的背景视图现在为黄色。原因如下:

  • 1) lightColor,darkColorpatternSize具有@IBInspectable属性,因此以后可以在Interface Builder中更轻松地配置它们的值。您将橙色和黄色用作temporary colors,只是为了了解发生了什么。 patternSize控制重复图案的大小。最初设置为大,然后再次设置,因此很容易看到发生了什么。
  • 2) UIGraphicsGetCurrentContext()为您提供视图的上下文,也是draw(_ :)绘制可以绘制的地方。
  • 3) 使用Core Graphics setFillColor()设置上下文的当前填充颜色。请注意,在使用Core Graphics时,需要使用CGColor,它是darkColor的属性。
  • 4) fill()无需设置矩形路径,而是使用当前的填充颜色占据整个上下文。

2. Drawing Triangles

现在,您将使用UIBezierPath绘制这三个橙色三角形。这些数字与以下代码中的点相对应:

仍然在BackgroundView.swift中,将此代码添加到draw(_ :)的末尾:

let drawSize = CGSize(width: patternSize, height: patternSize)
    
// Insert code here

let trianglePath = UIBezierPath()
// 1
trianglePath.move(to: CGPoint(x: drawSize.width / 2, y: 0))
// 2
trianglePath.addLine(to: CGPoint(x: 0, y: drawSize.height / 2))
// 3
trianglePath.addLine(to: CGPoint(x: drawSize.width, y: drawSize.height / 2))

// 4
trianglePath.move(to: CGPoint(x: 0, y: drawSize.height / 2))
// 5
trianglePath.addLine(to: CGPoint(x: drawSize.width / 2, y: drawSize.height))
// 6
trianglePath.addLine(to: CGPoint(x: 0, y: drawSize.height))

// 7
trianglePath.move(to: CGPoint(x: drawSize.width, y: drawSize.height / 2))
// 8
trianglePath.addLine(to: CGPoint(x: drawSize.width / 2, y: drawSize.height))

// 9
trianglePath.addLine(to: CGPoint(x: drawSize.width, y: drawSize.height))

lightColor.setFill()
trianglePath.fill()

注意如何使用一条路径绘制三个三角形。 move(to :)就像在绘图时将笔从纸上抬起并将其移动到新位置一样。

打开Main.storyboard,您将在背景视图的左上方看到橙色和黄色的图像。

到目前为止,您已经直接绘制到视图的绘制上下文中。 要重复此模式,必须在上下文外部创建一个图像,然后将该图像用作上下文中的模式。

BackgroundView.swift中找到以下行。 它靠近draw(_ :)的顶部,但在初始上下文调用之后:

let drawSize = CGSize(width: patternSize, height: patternSize)

将以下代码添加到方便的位置,比如// Insert code here

UIGraphicsBeginImageContextWithOptions(drawSize, true, 0.0)

guard let drawingContext = UIGraphicsGetCurrentContext() else {
  fatalError("\(#function):\(#line) Failed to get current context.")
}

// Set the fill color for the new context
darkColor.setFill()
drawingContext
  .fill(CGRect(x: 0, y: 0, width: drawSize.width, height: drawSize.height))

嘿! 这些橙色三角形从storyboard中消失了。 他们去哪了?

UIGraphicsBeginImageContextWithOptions(_:_:_ :)创建一个新的上下文并将其设置为当前的绘图上下文,因此您现在正在绘制到该新上下文中。 该方法的参数为:

  • 上下文的size
  • 上下文是否不透明。 如果您需要透明度,那么这必须是false
  • 上下文的scale。 如果要在iPhone 11上使用Retina屏幕绘图,则应为2.0;对于iPhone 12,则应使用3.0。 在这里,您使用0.0,以确保自动应用设备的正确比例。

然后,您使用UIGraphicsGetCurrentContext()获得对此新上下文的引用。

接下来,用黄色填充新的上下文。 您可以通过将上下文的不透明度设置为false来使原始背景显示出来,但是绘制不透明的上下文会更快。

3. Getting an Image From a Context

将此代码添加到draw(_ :)的末尾:

guard let image = UIGraphicsGetImageFromCurrentImageContext() else {
  fatalError("""
    \(#function):\(#line) Failed to \
    get an image from current context.
    """)
}
UIGraphicsEndImageContext()

这将从当前上下文中提取UIImage。 当您使用UIGraphicsEndImageContext()结束当前上下文时,绘图上下文将还原为视图的上下文。 因此,draw(_ :)中的任何进一步绘制都会在视图中发生。

要将图像绘制为重复图案,请将此代码添加到draw(_ :)的末尾:

UIColor(patternImage: image).setFill()
context.fill(rect)

这通过使用图像作为颜色而不是纯色来创建新的UIColor

构建并运行该应用程序。 现在,您的应用程序应该具有相当明亮的背景。

4. @IBDesignable Attributes

转到Main.storyboard并选择背景视图。 在Attributes inspector中,将@IBInspectable值更改为以下值:

  • Light Color: RGB(255, 255, 242)
  • Dark Color: RGB(223, 255, 247)
  • Pattern Size: 30

使用绘图背景图案进行更多实验。 看看是否可以将圆点图案作为背景而不是三角形作为背景。

当然,您可以将自己的非矢量图像替换为重复图案。


Drawing Images

在本教程的最后一部分中,您将获得一枚奖章,以奖励用户喝足够的水。 当计数器达到八个眼镜的目标时,将显示此勋章。

那当然不是值得博物馆收藏的艺术品。 您可以自己改善它,甚至可以通过画一个奖杯来将其提高到一个新的水平。

在这种特殊情况下,您仅需在用户喝八杯水时绘制一次图像。 如果用户从未达到目标,则无需制作勋章。

绘制后,也无需使用draw(_ :)setNeedsDisplay()重新绘制。

是时候将画笔放到画布上了。 您无需使用@IBDesignable,而可以使用Swift playground构建奖牌视图,并在完成后将代码复制到Flo项目中。

1. Xcode Playground

转到File ▸ New ▸ File ▸ Playground,选择Blank Playground,然后单击Next。 将playground命名为MedalDrawing,然后单击Create

在新的playground窗口中,将playground代码替换为:

import UIKit

let size = CGSize(width: 120, height: 200)

UIGraphicsBeginImageContextWithOptions(size, false, 0.0)

guard let context = UIGraphicsGetCurrentContext() else {
  fatalError("\(#function):\(#line) Failed to get current context.")
}

// This code must always be at the end of the playground
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

就像您对带图案的图像所做的那样,这将创建一个绘图上下文。

您始终在playground的底部都需要最后两行,以便可以在playground上预览图像。

接下来,单击此代码右侧的灰色结果列中的正方形:

let image = UIGraphicsGetImageFromCurrentImageContext()

这会在代码下放置一个预览图像,该图像将随您对代码所做的每次更改而更新。

2. Drawing Theory

通常最好绘制草图以确定绘制元素所需的顺序。 例如,查看为本教程绘制的以下“杰作”:

如图所示,您将按照以下顺序获得奖牌:

  • 1) The back ribbon (red)
  • 2)The medallion (gold gradient)
  • 3)The clasp (dark gold)
  • 4)The front ribbon (blue)
  • 5)The number 1 (dark gold)

请记住,将playground的最后两行(从上下文中提取图像)保留在最后,并在这些行之前将以下绘制代码添加到playground

首先,设置所需的非标准颜色:

// Gold colors
let darkGoldColor = UIColor(red: 0.6, green: 0.5, blue: 0.15, alpha: 1.0)
let midGoldColor = UIColor(red: 0.86, green: 0.73, blue: 0.3, alpha: 1.0)
let lightGoldColor = UIColor(red: 1.0, green: 0.98, blue: 0.9, alpha: 1.0)

到现在为止,这一切看起来应该都很熟悉。 请注意,颜色在声明时会出现在游乐场的右边缘。

3. Lower Ribbon

为丝带的红色部分添加绘图代码:

// Lower ribbon
let lowerRibbonPath = UIBezierPath()
lowerRibbonPath.move(to: CGPoint(x: 0, y: 0))
lowerRibbonPath.addLine(to: CGPoint(x: 40, y: 0))
lowerRibbonPath.addLine(to: CGPoint(x: 78, y: 70))
lowerRibbonPath.addLine(to: CGPoint(x: 38, y: 70))
lowerRibbonPath.close()
UIColor.red.setFill()
lowerRibbonPath.fill()

这实际上并没有做任何新的事情,只是创建一条路径并填充它。 您应该看到红色路径出现在图像预览部分。

4. Clasp

接下来,添加以下代码以绘制扣环:

// Clasp
let claspPath = UIBezierPath(
  roundedRect: CGRect(x: 36, y: 62, width: 43, height: 20), 
  cornerRadius: 5)
claspPath.lineWidth = 5
darkGoldColor.setStroke()
claspPath.stroke()

在这里,您可以使用UIBezierPath(roundedRect:cornerRadius :)创建所需的曲线。 扣环应绘制在图像预览中。

5. Medallion

现在,该是绘制奖章的时候了。 使用以下代码绘制奖章:

// Medallion
let medallionPath = UIBezierPath(
  ovalIn: CGRect(x: 8, y: 72, width: 100, height: 100))
//context.saveGState()
//medallionPath.addClip()

let colors = [
  darkGoldColor.cgColor, 
  midGoldColor.cgColor, 
  lightGoldColor.cgColor
] as CFArray
guard let gradient = CGGradient(
  colorsSpace: CGColorSpaceCreateDeviceRGB(),
  colors: colors,
  locations: [0, 0.51, 1]) 
else {
  fatalError("""
    Failed to instantiate an instance \
    of \(String(describing: CGGradient.self))
    """)
}
context.drawLinearGradient(
  gradient, 
  start: CGPoint(x: 40, y: 40), 
  end: CGPoint(x: 40, y: 162), 
  options: [])
//context.restoreGState()

注意注释掉的行。 这些是暂时显示渐变绘制的方法:

要将渐变放置在一个角度上(使其从左上角到右下角),请更改渐变的结束x坐标。 将drawLinearGradient(_:start:end:options :)代码更改为:

context.drawLinearGradient(
  gradient, 
  start: CGPoint(x: 40, y: 40), 
  end: CGPoint(x: 100, y: 160), 
  options: [])

现在,取消注释奖章绘制代码中的这三行,以创建剪切路径以将梯度限制在奖章圆内。

就像在Core Graphics Tutorial: Gradients and Contexts中绘制图形时所做的一样,您可以在添加剪切路径之前保存上下文的绘制状态,并在绘制渐变之后将其还原,以使上下文不再被剪切。

要绘制奖章的实线,请使用奖章的圆形路径,但在绘制前先缩放它。 您无需将整个上下文进行转换,只需将transform应用于一条路径即可。

在奖章绘制代码之后添加以下代码:

// Create a transform
// Scale it, and translate it right and down
var transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
transform = transform.translatedBy(x: 15, y: 30)
medallionPath.lineWidth = 2.0

// Apply the transform to the path
medallionPath.apply(transform)
medallionPath.stroke()

这会将路径缩小到其原始大小的80%,然后平移路径以使其在渐变视图中居中。

6. Upper Ribbon

在绘制代码的末尾添加上部功能区图形代码:

// Upper ribbon
let upperRibbonPath = UIBezierPath()
upperRibbonPath.move(to: CGPoint(x: 68, y: 0))
upperRibbonPath.addLine(to: CGPoint(x: 108, y: 0))
upperRibbonPath.addLine(to: CGPoint(x: 78, y: 70))
upperRibbonPath.addLine(to: CGPoint(x: 38, y: 70))
upperRibbonPath.close()

UIColor.blue.setFill()
upperRibbonPath.fill()

这类似于您为下部功能区添加的代码:绘制Bezier路径并填充它。

7. Number One

最后一步是在奖牌上画数字1。 将此代码添加到绘制代码的末尾:

// Number one

// Must be NSString to be able to use draw(in:)
let numberOne = "1" as NSString
let numberOneRect = CGRect(x: 47, y: 100, width: 50, height: 50)
guard let font = UIFont(name: "Academy Engraved LET", size: 60) else {
  fatalError("""
    \(#function):\(#line) Failed to instantiate font \
    with name \"Academy Engraved LET\"
    """)
}
let numberOneAttributes = [
  NSAttributedString.Key.font: font,
  NSAttributedString.Key.foregroundColor: darkGoldColor
]
numberOne.draw(in: numberOneRect, withAttributes: numberOneAttributes)

在这里,您定义了一个具有文本属性的NSString,并使用draw(in:withAttributes :)将其绘制到绘图上下文中。

看起来不错!

您越来越近了,但看上去有点二维。 有一些阴影会很好。


Creating Shadows

要创建阴影,您需要三个元素:color, offset (distance and direction of the shadow) and blur

playground的顶部,在定义了金色之后,但在// Lower ribbon行之前,插入以下阴影代码:

// Add shadow
let shadow = UIColor.black.withAlphaComponent(0.80)
let shadowOffset = CGSize(width: 2.0, height: 2.0)
let shadowBlurRadius: CGFloat = 5

context.setShadow(
  offset: shadowOffset, 
  blur: shadowBlurRadius, 
  color: shadow.cgColor)

那会产生阴影,但是结果可能不是您所想象的。 这是为什么?

当您将对象绘制到上下文中时,此代码为每个对象创建一个阴影。

啊哈! 您的奖牌包括五个物体。 难怪它看起来有点模糊。

幸运的是,它很容易修复。 只需将带有透明层的图形对象分组,您将只为整个组绘制一个阴影。

添加代码以使组在影子代码之后。 从此开始:

context.beginTransparencyLayer(auxiliaryInfo: nil)

当您开始一个小组时,您还需要结束它。 在playground的尽头,但在获取最终图像的位置之前,添加下一个块:

context.endTransparencyLayer()

现在,您已经获得了完整的奖牌图像,并带有干净整洁的阴影:

这样就完成了playground代码,您将有一块奖牌可以显示出来!


Adding the Medal Image to an Image View

现在您已经有了绘制奖牌的代码(顺便说一句,它看起来很棒),您需要将其渲染到主Flo项目中的UIImageView中。

切换回Flo项目并为image view创建一个新文件。

单击File ▸ New ▸ File…,然后选择Cocoa Touch Class模板。 单击Next,然后将类命名为MedalView。 使它成为UIImageView的子类,然后单击Next。 最后,单击Create

转到Main.storyboard并添加UIImageView作为Counter View的子视图。 选择UIImageView,然后在Identity inspector中将类更改为MedalView

Size inspector中,为图像视图提供坐标X=76, Y=147, Width=80, and Height=80

Attributes inspector中,确保将Content Mode设置为Aspect Fit,以便图像自动调整大小以适合视图。

1. MedalView Setup

转到MedalView.swift并添加一种创建奖牌的方法:

func createMedalImage() -> UIImage {
  debugPrint("creating Medal Image")
}

这将创建一个日志,以便您知道何时创建映像。

切换回MedalDrawing playground,并复制除初始import UIKit之外的所有代码。

返回MedalView.swift,然后将Playground代码粘贴到createMedalImage()中。

createMedalImage()的末尾,将以下两行替换为以下内容:

guard let image = UIGraphicsGetImageFromCurrentImageContext() else {
  fatalError("""
    \(#function):\(#line) Failed to get an \
    image from current context.
    """)
}
UIGraphicsEndImageContext()

return image

那应该可以解决编译错误。 请注意,由于UIGraphicsGetImageFromCurrentImageContext()返回了可选的UIImage,因此在返回图像之前先将其展开。

在类的顶部,添加一个属性以保存奖牌图像:

lazy var medalImage = createMedalImage()

延迟声明修饰符意味着运算量大的奖牌图像代码仅在必要时运行。 因此,如果用户从未记录喝八杯酒,则奖牌抽奖代码将永远不会运行。

添加一种方法来显示奖牌:

func showMedal(show: Bool) {
  image = (show == true) ? medalImage : nil
}

转到ViewController.swift并在类顶部添加一个outlet

@IBOutlet weak var medalView: MedalView!

接下来,转到Main.storyboard,然后将新的MedalView连接到此outlet

然后,返回到ViewController.swift并将此方法添加到类中:

func checkTotal() {
  if counterView.counter >= 8 {
    medalView.showMedal(show: true)
  } else {
    medalView.showMedal(show: false)
  }
}

如果您每天喝足够的水,则显示奖牌。

viewDidLoad()pushButtonPressed(_ :)的末尾调用此方法:

checkTotal()

构建并运行该应用程序。 轻击加号按钮几次以获取八杯水。 恭喜你! 您为自己赢得了一枚奖牌。

在调试控制台中,您会看到creating Medal Image日志仅在计数器达到8并显示奖牌时才输出,因为medalImage使用了lazy声明。


Animating

现在,您可以为蛋糕加一些糖衣了。 您将向按钮添加一些简单的动画,使它们在用户点击时旋转。 这应该使您感觉到UIKit图形还可以做些什么,并激发您的好奇心。

首先,将以下方法添加到ViewController.swift中:

func rotateButton(_ button: UIButton) {
  let layer = button.layer  // 1
  let rotationAnimation = 
    CAKeyframeAnimation(keyPath: "transform.rotation")  // 2
  rotationAnimation.keyTimes = [0, 1]  // 3
  rotationAnimation.values = [0, CGFloat.pi]  // 4
  rotationAnimation.duration = 0.25  // 5
  layer.add(rotationAnimation, forKey: "transform.rotation")  // 6
}

这是这样做的:

  • 1) 定义要设置动画的图层。
  • 2) 创建一个CAKeyframeAnimation实例。 该动画将通过更改transform.rotation的值来为图层设置动画。
  • 3) 数组中的每个值都是0.01.0之间的浮点数,它定义何时应用相应的关键帧值。 此属性允许创建和控制真正复杂的动画。 在这种情况下,您只需要起点0和终点1
  • 4) 此属性包含每个keyTimes的值。 对于起点,将旋转角度设置为0,对于终点,将pi设置为180°
  • 5) 在这里,您可以指定动画持续时间(以秒为单位)。
  • 6) 最后,您将此动画添加到图层,这将开始执行动画。

有了这个,您只需要在用户点击按钮时调用此方法即可。 将此代码添加到pushButtonPressed(_ :)的末尾:

rotateButton(button)

构建并运行您的应用程序。 点击加号和减号按钮,享受新奇的动画效果。

如果您仍然对Core Graphics可以提供什么感到好奇,请查看我们的Drawing in iOS with Core Animation and Core Graphics视频的绘画课程。

后记

本篇主要讲述了基于Core Graphic的重复图案的绘制,感兴趣的给个赞或者关注~~~

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

推荐阅读更多精彩内容