Swift无限循环的图片轮播

前言

我们常见的一些广告位、图片轮播都是可以无限轮播的,以前参考文章 iOS开发系列--无限循环的图片浏览器,自己也在项目中实际应用了,现在用Swift重新实现一遍,发现代码原来可以更加精简,并且轮播手机相册的所有图片时遇到了一些问题,所以记录一下,帮助需要用到的同学快速实现这个小功能。

功能实现

  1. 先看一下效果图,这里分为本地图片、网络图片、相册三个部分来实现,首先看一下本地效果。

其实本地的实现是最简单的,相册的实现更加复杂一些,因为相册图片牵扯到 PHCachingImageManager 的异步回调获取图片,需要单独去处理。网络图片如果使用 Kingfisher 来处理,那么就和本地图片一样了,但是如果使用系统的方法,即使用

        let url = URL(string: urlString)
        let imageData = try! Data(contentsOf: url)
        let image = UIImage(data: imageData)

来获取图片,会有一些问题。下面会有单独说明。

这里使用的 Xcode8Swift3

本地图片轮播

  1. 原理分析
    这里一共使用了3个 UIImageView ,然后滑动结束时重置 ScrollView 的偏移值。

  2. 代码实现-首页
    首先是项目简单地文件目录。

    简单文件结构

    然后是 Main.storyboard 文件。这里能用故事版画的控件我都是拒绝手写的。
    Storyboard

    这里有一点需要注意,获取相册信息时,需要在 info.plist 文件中添加字段,并且是必须添加,否则直接报错。
    访问系统相册

    首页的代码并没有多少要说明的,就是一些基本的准备工作,全部代码如下(部分说明都在注释中:

import UIKit
import Photos
class ViewController: UIViewController {

//  本地图片
fileprivate var localImages: [UIImage]! {
    var newImages = [UIImage]()
    for index in 1 ... 4 {
           newImages.append(UIImage(named: "\(index).jpg")!)
        }
    return newImages
}   

// 网络图片链接
fileprivate var netImageUrls = ["http://photocdn.sohu.com/20141225/Img407278780.jpg",
"http://imgsrc.baidu.com/forum/w=580/sign=72d55a713b6d55fbc5c6762e5d234f40/85950d338744ebf84e0e5290dff9d72a6159a713.jpg",
"http://p5.image.hiapk.com/uploads/allimg/150210/7730-150210155949-50.jpg",
"http://file26.mafengwo.net/M00/80/AB/wKgB4lL5tX6AZGB6ABBgnBkJakw73.jpeg"]

// 相册图片
fileprivate var allAssets = PHAsset

override func viewDidLoad() {
    super.viewDidLoad()
    
    getAlbumAssets { (assets) in
        self.allAssets = assets
    }
}

/// 获取系统相册数据
///
/// - parameter callback: 回调结果
fileprivate func getAlbumAssets(callback: @escaping ([PHAsset]) -> Void) {
    
    PHPhotoLibrary.requestAuthorization { (status) in
        guard status == PHAuthorizationStatus.authorized else {
            print("获取相册权限后再进行操作")
            return
        }
        PHAssetCollection.fetchAssetCollections(with: .smartAlbum, subtype: .smartAlbumUserLibrary, options: nil).enumerateObjects({ (collection, index, flag) in
            let result = PHAsset.fetchAssets(in: collection, options: nil)
            result.enumerateObjects({ (asset, index, flag) in
                self.allAssets.append(asset)
            })
            callback(self.allAssets)
        })       
    }
}

fileprivate let localImageSegue = "localImageSegue"
fileprivate let netUrlImageSegue = "netUrlImageSegue"
fileprivate let assetImageSegue = "assetImageSegue"
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    guard let previewVC = segue.destination as? PreviewViewController else {
        return
    }
    
    switch segue.identifier! {
    case localImageSegue:
        previewVC.getDate(resourceArray: localImages, .localImage)
    case netUrlImageSegue:
        previewVC.getDate(resourceArray: netImageUrls, .netImageUrl)
    case assetImageSegue:
        previewVC.getDate(resourceArray: allAssets, .asset)

    default:
        break
    }
}

}



#####轮播主体代码  - 本地图片
   
 - 我把主要需要注意的点和注释都写在代码里了,参考时只需要注意一些 `storyboard` 的连接控件即可。因为我在Demo中需要处理 `UIImage` 、`String` 、 `PHAsset` 三种类型,所以在接收方法里用了 `[Any]` ,导致处理的时候要去分别判断。只是单个数据类型的话并不需要这么麻烦。

 下面这些是实现的基本代码,后面关于网络图片和相册图片都是在这个页面里添加对应的方法。      
   
 

import UIKit
import Photos

enum DataType {
case localImage
case netImageUrl
case asset
}

class PreviewViewController: UIViewController, UIScrollViewDelegate {

//  MARK: - Properties
@IBOutlet weak var imageScrollView: UIScrollView!

fileprivate let mScreenWidth = UIScreen.main.bounds.width
fileprivate let mScreenHeight = UIScreen.main.bounds.height

fileprivate var leftImageView: UIImageView!
fileprivate var centerImageView: UIImageView!
fileprivate var rightImageView: UIImageView!

fileprivate var currentIndex = 0

fileprivate var resource: [Any]!
fileprivate var dataType: DataType!


//  MARK: - 开放接口
internal func getDate(resourceArray: [Any], _ type: DataType) {
    dataType = type
    resource = resourceArray
}


//  MARK: - LifeCycle

override func viewDidLoad() {
    super.viewDidLoad()
    //  初始化控件
    setupScrollView()
    
    //  展示
    prepareToShowPhotos()
}
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    navigationController?.isNavigationBarHidden = true
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    navigationController?.isNavigationBarHidden = false
    
}
//  隐藏StatusBar
override var prefersStatusBarHidden: Bool {
    return true
}

//  MARK: - Private Methods

/// 初始化ScrollView
fileprivate func setupScrollView() {
    leftImageView = prepareShowImageView(imageViewPosition: .leftImageView)
    centerImageView = prepareShowImageView(imageViewPosition: .centerImageView)
    rightImageView = prepareShowImageView(imageViewPosition: .rightImageView)
    
    //  FIXME: 测试

// leftImageView.backgroundColor = UIColor.red
// centerImageView.backgroundColor = UIColor.green
// rightImageView.backgroundColor = UIColor.orange

    imageScrollView.contentSize = CGSize(width: mScreenWidth * 3,
                                    height: 0)
    imageScrollView.showsHorizontalScrollIndicator = false
    imageScrollView.contentOffset = CGPoint(x: mScreenWidth, y: 0)
    imageScrollView.isPagingEnabled = true
}

//  位置
fileprivate enum ShowImageViewPosition {
    case leftImageView
    case centerImageView
    case rightImageView
}

/// 统一设置展示控件
///
/// - parameter imageViewPosition: 左、中、右位置
///
/// - returns: 设置完成UIImageView
fileprivate func prepareShowImageView(imageViewPosition: ShowImageViewPosition) -> UIImageView {
    let imageView = UIImageView()
    imageView.clipsToBounds = true
    imageView.contentMode = .scaleAspectFit
    imageView.frame = view.bounds
    imageView.backgroundColor = UIColor.black
    switch imageViewPosition {
    case .leftImageView:
        imageView.frame.origin.x = 0
    case .centerImageView:
        imageView.frame.origin.x = mScreenWidth
    case .rightImageView:
        imageView.frame.origin.x = 2 * mScreenWidth
    }
    imageScrollView.addSubview(imageView)
    
    return imageView
}


/// 展示图片
fileprivate func prepareToShowPhotos() {
    //  如果仅仅有一张
    if resource.count == 1 {
        imageScrollView.contentSize = view.bounds.size
    }
    
    switch dataType! {
    case .localImage:
        setLocalImages()
    case .netImageUrl:
        setNetImages()
    case .asset:
        setPHAssetImages()
    }
}


/// 展示本地图片
fileprivate func setLocalImages() {
    leftImageView.image = resource[(currentIndex + resource.count - 1) % resource.count] as? UIImage
    centerImageView.image = resource[currentIndex % resource.count] as? UIImage
    rightImageView.image = resource[(currentIndex + resource.count + 1) % resource.count] as? UIImage
}

/// 展示网络图片
fileprivate func setNetImages() {
    
}

/// 展示相册图片
fileprivate func setPHAssetImages() {
    
}



//  MARK: - UIScrollView Delegate

///  开始拖拽ScrollView时
fileprivate var originalOffsetX: CGFloat = 0
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
    originalOffsetX = scrollView.contentOffset.x
}

///  ScrollView 惯性结束
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    //  当滑动幅度不够时,保留ScrollView的原生特性
    if originalOffsetX == scrollView.contentOffset.x {
        return
    }
    
    if currentIndex == 0 {
        currentIndex = resource.count
    }
    //  滑动时,更改显示
    if originalOffsetX < scrollView.contentOffset.x {
        currentIndex += 1
    } else {
        currentIndex -= 1
    }
    
    //  更改偏移,重新展示
    imageScrollView.setContentOffset(CGPoint(x: mScreenWidth, y: 0), animated: false)
    
    switch dataType! {
    case .localImage:
        setLocalImages()
    case .netImageUrl:
        setNetImages()
    case .asset:
        setPHAssetImages()
    }
}

//  MARK: - Outlet Actions 
@IBAction func backAction(_ sender: UIButton) {
    navigationController!.popViewController(animated: true)
}

}

核心代码到这里就结束了。上面的代码直接复制到自己新建的demo  中就是可以直接运行的。下面则是对于网络图片和相册图片的扩展,因为都牵扯到异步处理,所以都是对上面代码的补充。

##### 网络图片的轮播

1. 首先说不使用第三方框架,仅仅使用下面主要代码获取网络图片时,

let url = URL(string: urlString)
let imageData = try! Data(contentsOf: url)
let image = UIImage(data: imageData)

如果放在主线程去做,会造成很严重的卡顿,如果开启线程,然后单独下载图片,会因为异步的问题导致保存图片的数组内部重复保存。如果开启线程依赖,也会有问题。总之,尝试失败,好在不是轮播重点,暂时pass。有好的思路的时候再来补充。

2. 利用 `cocoapods` 导入第三方图片框架 `Kingfisher` ,至于 `cocoapods` 的使用,网上有很多非常完善的教程。那么问题处理就简单很多了,完全像本地图片一样了。

  效果图:
![轮播网络图片.gif](http://upload-images.jianshu.io/upload_images/1910830-65dd5f6ae5850b95.gif?imageMogr2/auto-orient/strip)

  首先,定义默认图片属性

fileprivate var placeHodlerImage = UIImage(named: "defaultLoad.png")


  然后,完善方法 `setNetImages()` :
///  展示网络图片
fileprivate func setNetImages() {      
    
    let leftUrl = URL(string: (self.resource[(self.currentIndex + self.resource.count - 1) % self.resource.count] as? String)!)
    leftImageView.kf.setImage(with: leftUrl, placeholder:placeHodlerImage, options: nil, progressBlock: nil, completionHandler: nil)
    
    let centerUrl = URL(string: (self.resource[self.currentIndex % self.resource.count] as? String)!)
    centerImageView.kf.setImage(with: centerUrl, placeholder:placeHodlerImage, options: nil, progressBlock: nil, completionHandler: nil)
    
    let rightUrl = URL(string: (self.resource[(self.currentIndex + self.resource.count + 1) % self.resource.count] as? String)!)
    rightImageView.kf.setImage(with: rightUrl, placeholder:placeHodlerImage, options: nil, progressBlock: nil, completionHandler: nil)

}


##### 本地相册图片的轮播
1. 带有瑕疵的方案,之所以要说这一点,因为以前我就一直使用的这种方式,遇到了一些坑,直到最后解决。

  **首先**,添加相册元数据 `PHAsset` 转化为 `UIImage` 的方法:
/// 将Asset转化为UIImage
///
/// - parameter singleAsset: 元数据
///
/// - returns: image
fileprivate func transformAssetToImage(singleAsset: PHAsset,_ callback: @escaping (UIImage) -> Void) {
    PHCachingImageManager.default().requestImage(for: singleAsset, targetSize: view.bounds.size, contentMode: .aspectFit, options: nil) { (requestImage, nil) in
        callback(requestImage!)
    }
}
  **然后**,完善方法 `setPHAssetImages()` :
/// 展示相册图片
fileprivate func setPHAssetImages() {
    //  左
    let leftAsset = resource[(currentIndex + resource.count - 1) % resource.count] as! PHAsset
    transformAssetToImage(singleAsset: leftAsset) { (requestImage) in
        DispatchQueue.main.async {
            self.leftImageView.image = requestImage
        }
    }
    
    //  中
    let centerAsset = resource[currentIndex % resource.count] as! PHAsset
    transformAssetToImage(singleAsset: centerAsset) { (requestImage) in
        DispatchQueue.main.async {
            self.centerImageView.image = requestImage
        }
    }
    
    //  右
    let rightAsset = resource[(currentIndex + 1) % resource.count] as! PHAsset
    transformAssetToImage(singleAsset: rightAsset) { (requestImage) in
        DispatchQueue.main.async {
            self.rightImageView.image = requestImage
        }
    }
}

  最后,看效果:

  ![图片会抖动.gif](http://upload-images.jianshu.io/upload_images/1910830-65be999ad4540379.gif?imageMogr2/auto-orient/strip)
  可以很明显的看到图片的抖动现象,这个问题其实现在我也不确定是什么原因造成的,只是结合自己后来的尝试找到了解决方法,算是以结果倒推原因。

2. 改善版。为了避免重复的使用方法 `PHCachingImageManager` 来解析图片,建立一个临时图片数组,存放解析到的图片,然后从从图片数组中读取图片。

  **首先**,创建一个临时图片数组:

/// 将相册图片缓存到内存中
fileprivate var cacheImages: [UIImage]!

  **然后**,在接收方法中初始化:
//  MARK: - 开放接口
internal func getDate(resourceArray: [Any], _ type: DataType) {
    dataType = type
    resource = resourceArray
    
    cacheImages = Array(repeating:placeHodlerImage!, count: resource.count)
}
  **再然后**,完善方法 `setPHAssetImages()` 。定义一个 标识数组,确保相册的每一项 `PHAsset` 都已经被转化为 `UIImage` 。只有第一次解析图片时,才会直接将图片放置到左中右三个 `UIImageView` 上,然后开始滑动 `ScrollView` 时,再次解析图片, `ScrollView` 惯性结束时, 则是从缓存中读取。
  设置标识数组:
/// asset 是否完全转化为 Image 的标识
fileprivate var transformAssetToImageFlags = [Int]()
  完善 `setPHAssetImages()` 方法。这里添加了一个 `isFirst` 布尔参数,为了判断是否是第一次进来解析图片。

/// 展示相册图片
fileprivate func setPHAssetImages(isFirst: Bool) {

    let index = currentIndex % resource.count
    if transformAssetToImageFlags.count == 0 {
        //  第一次执行
        transformAssetToImageFlags.append(index)
    }
    
    //  判断是否已经有相同图片添加到缓存数组
    var isAdd = false
    transformAssetToImageFlags.forEach { (flag) in
        if flag == index {
            isAdd = true
        }
    }
    if !isAdd {
        transformAssetToImageFlags.append(index)
    }
    
    //  左
    let leftAsset = resource[(currentIndex + resource.count - 1) % resource.count] as! PHAsset
    transformAssetToImage(singleAsset: leftAsset) { (requestImage) in
        if isFirst {
           self.leftImageView.image = requestImage
        }
        self.cacheImages[(self.currentIndex + self.resource.count - 1) % self.resource.count] = requestImage
    }
    
    //  中
    let centerAsset = resource[currentIndex % resource.count] as! PHAsset
    transformAssetToImage(singleAsset: centerAsset) { (requestImage) in
        if isFirst {
            self.centerImageView.image = requestImage
        }
        self.cacheImages[self.currentIndex % self.resource.count] = requestImage
    }
    
    //  右
    let rightAsset = resource[(currentIndex + 1) % resource.count] as! PHAsset
    transformAssetToImage(singleAsset: rightAsset) { (requestImage) in
        if isFirst {
            self.rightImageView.image = requestImage
        }
        self.cacheImages[(self.currentIndex + 1) % self.resource.count] = requestImage
    }
}

  修改第一次获取图片的方法:
/// 展示图片
fileprivate func prepareToShowPhotos() {
    //  如果仅仅有一张
    if resource.count == 1 {
        imageScrollView.contentSize = view.bounds.size
    }
    
    switch dataType! {
    case .localImage:
        setLocalImages()
    case .netImageUrl:
        setNetImages()
    case .asset:
        setPHAssetImages(isFirst: true)
    }
}
  修改 `ScrollView` 拖动时的方法:
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
    originalOffsetX = scrollView.contentOffset.x
    
    switch dataType! {
    case .localImage:
        break
    case .netImageUrl:
        break
    case .asset:
        //  判断是否所有图片都已经转化
        if transformAssetToImageFlags.count == resource.count {
            break
        }
        setPHAssetImages(isFirst: false)
    }
}
  添加读取图片的方法:
/// 从缓存的图片数组中读取数据
fileprivate func showImageFromCacheImages() {
    leftImageView.image = cacheImages[(self.currentIndex - 1) % self.resource.count]
    centerImageView.image = cacheImages[self.currentIndex % self.resource.count]
    rightImageView.image = cacheImages[(self.currentIndex + 1) % self.resource.count]
}
修改 `ScrollView` 惯性结束的方法:
///  ScrollView 惯性结束
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    //  当滑动幅度不够时,保留ScrollView的原生特性
    if originalOffsetX == scrollView.contentOffset.x {
        return
    }
    
    if currentIndex == 0 {
        currentIndex = resource.count
    }
    //  滑动时,更改显示
    if originalOffsetX < scrollView.contentOffset.x {
        currentIndex += 1
    } else {
        currentIndex -= 1
    }
    
    //  更改偏移,重新展示
    imageScrollView.setContentOffset(CGPoint(x: mScreenWidth, y: 0), animated: false)
    
    switch dataType! {
    case .localImage:
        setLocalImages()
    case .netImageUrl:
        setNetImages()
    case .asset:
        showImageFromCacheImages()
    }
}

  展示效果:

  ![还是有问题的](http://upload-images.jianshu.io/upload_images/1910830-2f8506e7564cdc85.gif?imageMogr2/auto-orient/strip)
    其实也就是从这里我推测最开始抖动的原因是解析图片是异步回调的,但是修改 `ScrollView` 时是重置了 `offset` ,在重置刚结束时,获取到新的图片,就发现第一次展示的图片会出现一个占位图。如果同时转化5张图片,并存放到数组,就没有任何问题了。这里只是我根据猜测找到的一种解决方法,甚至猜测的原因都可能是错的,但是确实解决了问题,并且从复用或者逻辑的角度来看,也并没有什么问题,算是作为一个轮播的点写出来共享一下。

  完善方法 `setPHAssetImages(_: )` :
/// 展示相册图片
fileprivate func setPHAssetImages(isFirst: Bool) {
    
    let index = currentIndex % resource.count
    if transformAssetToImageFlags.count == 0 {
        //  第一次执行
        transformAssetToImageFlags.append(index)
    }
    
    //  判断是否已经有相同图片添加到缓存数组
    var isAdd = false
    transformAssetToImageFlags.forEach { (flag) in
        if flag == index {
            isAdd = true
        }
    }
    if !isAdd {
        transformAssetToImageFlags.append(index)
    }
    
    //  左-1
    let leftAssetleft = resource[(currentIndex + resource.count - 2) % resource.count] as! PHAsset
    transformAssetToImage(singleAsset: leftAssetleft) { (requestImage) in
        self.cacheImages[(self.currentIndex + self.resource.count - 2) % self.resource.count] = requestImage
    }
    
    //  左
    let leftAsset = resource[(currentIndex + resource.count - 1) % resource.count] as! PHAsset
    transformAssetToImage(singleAsset: leftAsset) { (requestImage) in
        if isFirst {
           self.leftImageView.image = requestImage
        }
        self.cacheImages[(self.currentIndex + self.resource.count - 1) % self.resource.count] = requestImage
    }
    
    //  中
    let centerAsset = resource[currentIndex % resource.count] as! PHAsset
    transformAssetToImage(singleAsset: centerAsset) { (requestImage) in
        if isFirst {
            self.centerImageView.image = requestImage
        }
        self.cacheImages[self.currentIndex % self.resource.count] = requestImage
    }
    
    //  右
    let rightAsset = resource[(currentIndex + 1) % resource.count] as! PHAsset
    transformAssetToImage(singleAsset: rightAsset) { (requestImage) in
        if isFirst {
            self.rightImageView.image = requestImage
        }
        self.cacheImages[(self.currentIndex + 1) % self.resource.count] = requestImage
    }
    
    //  右+1
    let rightAssetRight = resource[(currentIndex + 2) % resource.count] as! PHAsset
    transformAssetToImage(singleAsset: rightAssetRight) { (requestImage) in
        self.cacheImages[(self.currentIndex + 2) % self.resource.count] = requestImage
    }
}  
  运行效果:

  ![完善后](http://upload-images.jianshu.io/upload_images/1910830-1ec2ecca7812e090.gif?imageMogr2/auto-orient/strip)

 
##### 再说一点
  其实还是存在一点问题的。上面处理 `ScrollView` 的偏移值重置操作是在惯性结束后的方法里 `scrollViewDidEndDecelerating(_: )`,但是如果滑动很快,即惯性还没结束就连续滑动,就会出来拉不动的情况,因为偏移值没有修改的话,始终只有三个 `UIImageView`。如果取消惯性,即令  `scrollview.bounces = false` ,就会出现偶尔划不动的情况。这里我在 `ScrollView` 开始滑动时,令 `originalOffsetX = scrollView.bounds.width` ,虽然缓解了问题,但是还是会出现偶尔快速滑动两次,但是惯性结束方法执行一次的情况,因为 `scrollViewWillBeginDragging(_: )` 和 `scrollViewDidEndDecelerating(_: )` 并不是一对一的关系。
上面都是在快速滑动的时候出现的问题,如果开启 `timer` 实现自动轮播并不会出现上面的问题。
在网上找了一些类似无限轮播的例子,好像并没有注意这方面的问题。还有利用 `UICollectionView` 来实现轮播功能,不知道会不会出现这个问题,暂时并没有尝试。如果以后有好的思路解决这个问题,再来更新。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,123评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,031评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,723评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,357评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,412评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,760评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,904评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,672评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,118评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,456评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,599评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,264评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,857评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,731评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,956评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,286评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,465评论 2 348

推荐阅读更多精彩内容