CoreGraphic框架解析 (十六)—— Lines, Rectangles 和 Gradients (一)

版本记录

版本号 时间
V1.0 2019.02.12 星期二

前言

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 (二)

开始

首先看下写作环境

Swift 4.2, iOS 12, Xcode 10

在本教程中,您将学习如何使用Core Graphics绘制线条,矩形和渐变 - 从美化表格视图开始!

这篇将揭开Core Graphics的神秘面纱。您将通过实际练习逐步学习API,首先使用Core Graphics美化table views

Core Graphics是iOS上非常酷的API。作为开发人员,您可以使用它来自定义您的UI,并使用一些非常简洁的效果,通常甚至无需让设计师参与其中。任何与2D绘图相关的东西 - 比如绘制形状,填充它们并赋予它们渐变 - 都是使用Core Graphics的一个很好的选择。

Core Graphics的历史可以追溯到OS X的早期阶段,是目前仍在使用的最古老的API之一。也许这就是为什么,对于许多iOS开发人员来说,Core Graphics起初可能有些令人生畏:它是一个庞大的API,并且有很多障碍可以发现。但是,自从Swift 3以来,C风格的API已经更新,看起来和感觉就像您熟悉和喜爱的现代Swift API!

在本教程中,您将构建一个Star Wars Top Trumps应用程序,该应用程序由包含Starships列表的主视图组成:

以及一个每个Starship的详细视图

在创建此应用程序时,您将学习如何开始使用Core Graphics,如何填充和描边矩形以及如何绘制线条和渐变以制作自定义表格视图单元格和背景。

是时候与Core Graphics享受一些乐趣了!

打开入门项目并快速浏览一下。该应用程序基于Xcode提供的Master-Detail App模板。主视图控制器包含Star Ships列表,详细视图控制器显示每艘船的详细信息。

打开MasterViewController.swift。在该类的顶部,注意一个starships变量,它包含Starship类型的数组和StarshipDataProvider类型的dataProvider变量。

通过Command-单击StarshipDataProvider并选择Jump to Definition跳转到StarshipDataProvider.swift。这是一个简单的类,它读取bundle文件Starships.json,并将内容转换为Starship数组。

你可以在Starship.swift找到Starship的定义。它只是一个简单的结构体,具有Starships常见属性的属性。

接下来,打开DetailViewController.swift。在类定义为枚举之前定义在文件的顶部,FieldsToDisplay,它定义要在枚举中显示的Starship属性的人类可读标题。在这个文件中,tableView(_:cellForRowAt :)只是一个很大的switch语句,用于将每个Starship属性的数据格式化为正确的格式。

构建并运行应用程序。

登陆页面是MasterViewController,显示星球大战宇宙中的星舰列表。 点击以选择X-wing,应用程序将导航到该船的详细视图,其中显示了X翼的图像,然后是各种属性,例如它的成本和飞行速度。

这是一个功能齐全,如果非常无聊的应用程序。 是时候添加一些bling了!


Analyzing the Table View Style

在本教程中,您将为两个不同的表视图添加不同的样式。 仔细看看这些变化是什么样的。

在主视图控制器中,每个单元格:

  • 有从深蓝色到黑色的渐变色。
  • 以黄色勾勒出轮廓,从cell bounds 有一定的inset。

并在详细视图控制器中:

  • table本身有从深蓝色到黑色的渐变色。
  • 每个cell都有一个黄色分离线,将其与相邻cell分开。

要绘制这两种设计,您只需要知道如何使用Core Graphics绘制矩形,渐变和线条,这正是您将要学习的内容。


Hello, Core Graphics!

虽然本教程涵盖了在iOS上使用Core Graphics,但重要的是要知道Core Graphics可用于所有主要的Apple平台,包括通过AppKitMacOS,iOS和通过UIKittvOS以及通过WatchKitApple Watch

您可以考虑使用Core Graphics,如在物理画布上绘画;绘图操作的顺序很重要。例如,如果您绘制重叠的形状,那么您添加的最后一个将位于顶部并与下面的重叠。

Apple以这样的方式构建Core Graphics,使您作为开发人员在单独的时刻提供有关绘制内容的说明而不是在何处绘制。

CGContext类表示的核心图形上下文定义了where。您可以告诉上下文要执行的绘制操作。 CGContexts用于绘制到位图图像,绘制为PDF文件,最常见的是直接绘制到UIView中。

在这个绘画类比中,核心图形上下文代表画家绘制的画布。

核心图形上下文是状态机(State Machines)。也就是说,当您设置填充颜色时,可以为整个画布设置填充颜色,并且在您更改之前,您绘制的任何形状都将具有相同的填充颜色。

每个UIView都有自己的核心图形上下文。要使用Core Graphics绘制UIView的内容,必须在视图的draw(_ :)中编写绘图代码。这是因为iOS在调用draw(_ :)之前设置了正确的CGContext以便绘制到视图中。

现在您了解了如何在UIKit中使用Core Graphics的基础知识,现在是时候更新您的应用了!


Drawing Rectangles

首先,通过从“文件”菜单中选择New ▸ File…来创建新的视图文件。选择Cocoa Touch Class,按Next,然后将类名设置为StarshipsListCellBackground。使其成为UIView的子类,然后创建类文件。将以下代码添加到新类:

override func draw(_ rect: CGRect) {
  // 1
  guard let context = UIGraphicsGetCurrentContext() else {
    return
  }
  // 2  
  context.setFillColor(UIColor.red.cgColor)
  // 3
  context.fill(bounds)
}

下面逐行分析:

  • 1) 首先,使用UIGraphicsGetCurrentContext()获取此UIView实例的当前CGContext。请记住,iOS会在调用draw(_ :)之前自动为您设置。如果由于任何原因无法获取上下文,则可以从方法中提前返回。
  • 2) 然后,在上下文本身上设置填充颜色。
  • 3) 最后,您告诉它填充视图的边界。

如您所见,Core Graphics API不包含直接绘制填充颜色的形状的方法。相反,有点像添加油漆到特定的画笔,你将颜色设置为CGContext的状态,然后,你告诉上下文分别用该颜色绘制什么。

您可能还注意到,当您在上下文中调用setFillColor(_ :)时,您没有提供标准的UIColor。相反,您必须使用CGColor,这是Core Graphics内部用于表示颜色的基本数据类型。只需访问任何UIColor的cgColor属性,将UIColor转换为CGColor非常容易。

1. Showing Your New Cell

要查看您的新视图,请打开MasterViewController.swift。在tableView(_:cellForRowAt :)中,在方法的第一行中出现单元格后立即添加以下代码:

if !(cell.backgroundView is StarshipsListCellBackground) {
  cell.backgroundView = StarshipsListCellBackground()
}
    
if !(cell.selectedBackgroundView is StarshipsListCellBackground) {
  cell.selectedBackgroundView = StarshipsListCellBackground()
}

此代码将单元格的背景视图设置为新视图的背景视图。 构建并运行应用程序,您将在每个单元格中看到可爱的,如果花哨的红色背景。

惊人! 您现在可以使用Core Graphics进行绘制。 不管你信不信,你已经学会了一系列非常重要的技巧:如何绘制上下文,如何更改填充颜色以及如何用颜色填充矩形。 你可以用它制作一些非常漂亮的用户界面。

但是你要更进一步,学习一种最有用的技术来制作优秀的用户界面:渐变!


Creating New Colors

您将在此项目中反复使用相同的颜色,因此为UIColor创建一个扩展,以使这些颜色易于访问。 转到File ▸ New ▸ File…并创建一个名为UIColorExtensions.swift的新Swift文件。 用以下内容替换文件的内容:

import UIKit

extension UIColor {
  public static let starwarsYellow = 
    UIColor(red: 250/255, green: 202/255, blue: 56/255, alpha: 1.0)
  public static let starwarsSpaceBlue = 
    UIColor(red: 5/255, green: 10/255, blue: 85/255, alpha: 1.0)
  public static let starwarsStarshipGrey = 
    UIColor(red: 159/255, green: 150/255, blue: 135/255, alpha: 1.0)
} 

此代码定义了三种新颜色,您可以在UIColor上以静态属性的形式访问这些颜色。


Drawing Gradients

接下来,由于您要在此项目中绘制大量渐变,因此请添加辅助方法来绘制渐变。 这将通过将渐变代码保存在一个位置来简化项目,并避免重复自己。

选择File ▸ New ▸ File…并创建一个名为CGContextExtensions.swift的新Swift文件。 用以下内容替换文件的内容:

import UIKit

extension CGContext {
  func drawLinearGradient(
    in rect: CGRect, 
    startingWith startColor: CGColor, 
    finishingWith endColor: CGColor
  ) {
    // 1
    let colorSpace = CGColorSpaceCreateDeviceRGB()

    // 2
    let locations = [0.0, 1.0] as [CGFloat]    

    // 3
    let colors = [startColor, endColor] as CFArray

    // 4
    guard let gradient = CGGradient(
      colorsSpace: colorSpace, 
      colors: colors, 
      locations: locations
    ) else {
      return
    }
  }
}

这个方法做了很多:

  • 1) 首先,设置正确的色彩空间。您可以使用色彩空间做很多事情,但是您几乎总是希望使用CGColorSpaceCreateDeviceRGB来使用与设备相关的标准RGB色彩空间。
  • 2) 接下来,设置一个数组,跟踪渐变范围内每种颜色的位置。值0表示渐变的开始,1表示渐变的结束。

注意:如果需要,可以在渐变中使用三种或更多颜色,并且可以设置每种颜色在渐变中的位置,就像这样的数组。这对某些效果很有用。

  • 3) 之后,使用传递给方法的颜色创建一个数组。注意在这里使用CFArray而不是Array,因为您正在使用较低级别的C API
  • 4) 然后,通过初始化CGGradient对象,传入颜色空间,颜色数组和先前创建的位置来创建渐变。如果由于某种原因,可选的初始化程序失败,则提前返回。

你现在有一个渐变引用,但它实际上还没有绘制任何东西 - 它只是一个指向你稍后实际绘制时使用的信息的指针。现在几乎是绘制渐变的时候了,但在你做之前,还需要更多的理论。

1. The Graphics State Stack

请记住,Core Graphics上下文是状态机。 在上下文中设置状态时必须要小心,特别是在传递上下文的函数中,或者在本例中是上下文本身的方法,因为在修改上下文之前无法知道上下文的状态。 请考虑UIView中的以下代码:

override func draw(_ rect: CGRect) {
  // ... get context
     
  context.setFillColor(UIColor.red.cgColor)
  drawBlueCircle(in: context)
  context.fill(someRect)    
}
  
// ... many lines later
  
func drawBlueCircle(in context: CGContext) {
  context.setFillColor(UIColor.blue.cgColor)
  context.addEllipse(in: bounds)
  context.drawPath(using: .fill)
}

看一下这段代码,您可能会认为它会在视图中绘制一个红色矩形和一个蓝色圆圈,但你错了! 相反,这段代码绘制了一个蓝色矩形和一个蓝色圆圈 - 但为什么呢?

因为drawBlueCircle(in :)在上下文中设置了蓝色填充颜色,并且因为上下文是状态机,所以它会覆盖先前的红色填充颜色集。

这就是saveGState()及其伙伴方法restoreGState()的用武之地!

每个CGContext都维护一个图形状态的堆栈,其中包含当前绘图环境的大部分(尽管不是全部)方面。 saveGState()将当前状态的副本推送到图形状态堆栈,然后您可以使用restoreGState()将上下文恢复到该状态,并在该过程中从堆栈中删除状态。

在上面的示例中,您应该像这样修改drawBlueLines(in :)

func drawBlueCircle(in context: CGContext) {
  context.saveGState()
  context.setFillColor(UIColor.blue.cgColor)
  context.addEllipse(in: bounds)
  context.drawPath(using: .fill)
  context.restoreGState()
}

您可以打开RedBluePlayground.playground来自行测试。

2. Completing the Gradient

掌握有关图形状态堆栈的知识,是时候完成绘制背景渐变了。 将以下内容添加到drawLinearGradient(in:startingWith:finishingWith:)的末尾:

// 5
let startPoint = CGPoint(x: rect.midX, y: rect.minY)
let endPoint = CGPoint(x: rect.midX, y: rect.maxY)
    
// 6
saveGState()

// 7
addRect(rect)
clip()
drawLinearGradient(
  gradient, 
  start: startPoint, 
  end: endPoint, 
  options: CGGradientDrawingOptions()
)

restoreGState()
  • 5) 首先计算渐变的起点和终点。您可以将其设置为从矩形的顶部中间到底部中间的线。有用的是,CGRect包含一些实例变量,如midXmaxY,这使得这非常简单。
  • 6) 接下来,由于您即将修改上下文的状态,因此您可以保存其图形状态并通过还原它来结束该方法。
  • 7) 最后,在提供的矩形中绘制渐变。 drawLinearGradient(_:start:end:options :)是实际绘制渐变的方法,但除非另有说明,否则它将使用渐变填充整个上下文,即整个视图。在这里,您只想填充提供的矩形中的渐变。要做到这一点,你需要了解clipping

剪切(clipping)Core Graphics中一个非常棒的功能,可以将绘图限制为任意形状。您所要做的就是将形状添加到上下文中,然后,不要像通常那样填充它,而是在上下文中调用clip(),然后将所有未来的绘制限制到该区域。

因此,在这种情况下,您将在最终调用drawLinearGradient(_:start:end:options :)绘制渐变之前,在上下文和剪辑上设置提供的矩形。

是时候给这个方法一个旋转!打开StarshipsListCellBackground.swift,在获取当前的UIGraphicsContext之后,用以下内容替换代码:

let backgroundRect = bounds
context.drawLinearGradient(
  in: backgroundRect, 
  startingWith: UIColor.starwarsSpaceBlue.cgColor, 
  finishingWith: UIColor.black.cgColor
)

构建并运行

您现在已成功将渐变背景添加到自定义单元格。 干得好,但是,可以公平地说,现在成品并不是很好看。 是时候用一些标准的UIKit主题来修复它了。


Fixing the Theme

打开Main.storyboard并选择Master scene中的表格视图。 在“属性”检查器中,将Separator设置为None

然后,在Master Navigation Controller场景中选择Navigation Bar并将导航栏样式设置为黑色并取消选择半透明(Translucent)。 在Detail Navigation Controller场景中重复Navigation Bar

接下来,打开MasterViewController.swift。 在viewDidLoad()的末尾,添加以下内容:

tableView.backgroundColor = .starwarsSpaceBlue

然后在tableView(_:cellForRowAt :)中,在返回单元格之前,设置文本的颜色:

cell.textLabel!.textColor = .starwarsStarshipGrey

最后,打开AppDelegate.swift并在application(_:didFinishLaunchingWithOptions:)中返回之前添加以下内容:

// Theming
UINavigationBar.appearance().tintColor = .starwarsYellow
UINavigationBar.appearance().barTintColor = .starwarsSpaceBlue
UINavigationBar.appearance().titleTextAttributes = 
  [.foregroundColor: UIColor.starwarsStarshipGrey]

构建并运行

那更好!您的主表视图开始看起来非常太空。


Stroking Paths

Core Graphics中进行描边意味着沿着路径绘制一条线,而不是像之前那样填充它。

Core Graphics描绘路径时,它会在路径的精确边缘的中间绘制描边线。这可能会导致一些常见问题。

1. Outside the Bounds

首先,如果您正在绘制一个矩形的边缘,例如,边框,默认情况下,Core Graphics将不会绘制一半的描边路径。

为什么?因为为UIView设置的上下文仅扩展到视图的边界。想象一下,在视图边缘周围有一个点边框。因为Core Graphics在路径的中间向下划线,所以该线将在视图边界之外半个点,在视图边界内半个点。

一个常见的解决方案是将行程的路径插入每个方向的线宽度的一半,使其位于视图内。

下图显示了一个黄色矩形,在灰色背景上有一个点宽的红色描边,以一个点间隔条纹。在左图中,描边路径遵循视图的边界并已被裁剪。您可以看到这一点,因为红线是灰色方块宽度的一半。在右图中,描边路径已插入半个点的间隔,现在具有正确的线宽。

2. Anti-Aliasing

其次,您需要了解可能影响边框外观的抗锯齿效果。消除锯齿,如果您不熟悉它(即使您可能在计算机游戏设置屏幕上听说过它!),也是一种渲染引擎用于避免在显示图形时出现“锯齿状”边缘和线条的技术不要完美映射到设备上的物理像素。

以前一段视图周围的一点边框为例。如果边框遵循视图的边界,则Core Graphics将尝试在矩形的任一侧绘制半个点宽的线。

在非视网膜显示器上,一个点等于设备上的一个像素。不可能只照亮一半像素,因此Core Graphics将使用消除锯齿来绘制两个像素,但是在较浅的阴影中只能呈现单个像素的外观。

在以下几组屏幕截图中,左图像是非视网膜显示器,中间图像是视网膜显示器,其比例为2,第三图像是视网膜显示器,其比例为3。

对于第一个图,请注意2x图像如何不显示任何抗锯齿,因为黄色矩形的两边的半点落在像素边界上。然而,在1x和3x图像中发生抗锯齿。

在下一组屏幕截图中,描边矩形已插入半个点间隔,使得笔划线与点精确对齐,从而与像素边界对齐。 注意没有锯齿伪像。


Adding a Border

回到你的应用程序! cell开始看起来很好,但你会增加另一种触感,让它们脱颖而出。 这一次,你将在cell边缘绘制一个明亮的黄色框。

您已经知道如何轻松填充矩形。 好吧,在他们周围描边也同样容易。

打开StarshipsListCellBackground.swift并将以下内容添加到draw(_ :)的底部:

let strokeRect = backgroundRect.insetBy(dx: 4.5, dy: 4.5)
context.setStrokeColor(UIColor.starwarsYellow.cgColor)
context.setLineWidth(1)
context.stroke(strokeRect)

在这里,您可以创建一个用于描边的矩形,它在x和y方向上从背景矩形中插入4.5个点。 然后将描边颜色设置为黄色,将线宽设置为一个点,最后描边矩形。 构建并运行您的项目。

现在你的星舰列表真的看起来像是来自遥远的星系!


Building a Card Layout

虽然你的主视图控制器看起来很花哨,但细节视图控制器仍然需要一些修饰!

对于此视图,您将首先使用自定义UITableView子类在表视图背景上绘制渐变。

创建一个名为StarshipTableView.swift的新Swift文件。 用以下内容替换生成的代码:

import UIKit

class StarshipTableView: UITableView {
  override func draw(_ rect: CGRect) {
    guard let context = UIGraphicsGetCurrentContext() else {
      return
    }

    let backgroundRect = bounds
    context.drawLinearGradient(
      in: backgroundRect, 
      startingWith: UIColor.starwarsSpaceBlue.cgColor, 
      finishingWith: UIColor.black.cgColor
    )
  }
}

现在这应该开始变得熟悉了。 在新表视图子类的draw(_ :)方法中,您将获得当前的CGContext,然后在视图的边界绘制一个渐变,从顶部的蓝色开始,在底部朝向黑色。 简单!

打开Main.storyboard并单击Detail场景中的TableView。 在Identity检查器中,将类设置为新的StarshipTableView

构建并运行应用程序,然后点击X-wing那一行 。

您的详细视图现在具有从上到下运行的漂亮的全屏渐变,但表格视图中的单元格遮挡了效果的最佳部分。 是时候解决这个问题并为细节cell添加更多的天赋。

回到Main.storyboard,在Detail Scene中选择FieldCell。 在“属性”检查器中,将背景设置为Clear Color。 接下来,打开DetailViewController.swift,在tableView(_:cellForRowAt :)的最底部,在返回单元格之前,添加以下内容:

cell.textLabel!.textColor = .starwarsStarshipGrey
cell.detailTextLabel!.textColor = .starwarsYellow

这只是将单元格的字段名称和值设置为适合星球大战主题的更合适的颜色。

然后,在tableView(_:cellForRowAt :)之后添加以下方法来设置表视图头的样式:

override func tableView(
  _ tableView: UITableView, 
  willDisplayHeaderView view: UIView, 
  forSection section: Int
) {
    view.tintColor = .starwarsYellow
    if let header = view as? UITableViewHeaderFooterView {
      header.textLabel?.textColor = .starwarsSpaceBlue
    }
  }

在这里,您将表格视图的标题视图的色调颜色设置为主题黄色,为其提供黄色背景,并将其文本颜色设置为主题蓝色。


Drawing Lines

作为bling的最后一点,您将在详细视图中为每个单元格添加一个拆分器。 创建一个新的Swift文件,这次称为YellowSplitterTableViewCell.swift。 用以下内容替换生成的代码:

import UIKit

class YellowSplitterTableViewCell: UITableViewCell {
  override func draw(_ rect: CGRect) {
    guard let context = UIGraphicsGetCurrentContext() else {
      return
    }
    
    let y = bounds.maxY - 0.5
    let minX = bounds.minX
    let maxX = bounds.maxX

    context.setStrokeColor(UIColor.starwarsYellow.cgColor)
    context.setLineWidth(1.0)
    context.move(to: CGPoint(x: minX, y: y))
    context.addLine(to: CGPoint(x: maxX, y: y))
    context.strokePath()
  }
}

YellowSplitterTableVIewCell中,您使用Core Graphics来划分单元格边界底部的一条线。 注意所使用的y值是如何比视图的边界小半个点,以确保分割器完全在单元内绘制。

现在,您需要实际绘制显示拆分器的线。

要在A和B之间绘制一条线,首先移动到A点,这不会导致Core Graphics绘制任何东西。 然后,您将一条线添加到B点,该线将点A到点B的线添加到上下文中。 然后,您可以调用strokePath()来描边该行。

最后,再次打开Main.storyboard并使用Identity检查器将Detail场景中的FieldCell类设置为新创建的YellowSplitterTableViewCell。 构建并运行您的应用程序。 然后,打开X-wing细节视图。 漂亮!

后记

本篇主要讲述了Lines, Rectangles 和 Gradients,感兴趣的给个赞或者关注~~~

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

推荐阅读更多精彩内容