换一种思路实现 - 九宫格群头像

前言

九宫格头像在网上一搜一大把,相关的优质博文也很多。在这里我将从另一个角度来分析和实现她。很多朋友对微信的群头像很感兴趣,那种九宫格头像乍一看还蛮高级的。但真要我们说出个具体实现方案,相信也没几个人能说的清楚。

我们先给几个方案:

  • sever 端生成好九宫格头像,client 端直接通过生成好的图片 url 来显示
  • 在一个 ImageView 上(View 上也可),放九个 Imageview,然后让他们分别加载图片。
  • 将九张图片拼接生成一张图片,保存在本地然后使用她

看看具体效果


九宫格头像.jpeg

思路

根据上面的方案我们来详细讲解一下。
第一种,很明显和我们关系不大,只需要选择一款第三方图片加载库就能轻松搞定。

第二种,也是现在在网上大量文献使用的方法,相对好理解。在这里我也简单介绍一下。直接上代码,代码是我从网上找的(根据需要做了部分修改)。因为时间有点久,原地址在哪也不记得了。望原文作者见谅。

class NineGridImageView {
  
  private var cellImageViewSideLength: CGFloat?
  
  private var margin: CGFloat?
  
  var delegate: NineGridImageViewDelegate?
  
  // 生成九宫格图片到传入的 Imageview 中。
  func generateNineGridImageViewTo(_ canvasView: UIImageView, _ urls: [String?]) {
      var imageviews: [UIImageView] = []
      // 根据传入的urls的个数,生成对应的 Imageview,并添加到 Imageview数组备用
      for url in urls {
        let imageview = UIImageView(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
        // 这里是一个代理,用来在外部实现Imageview的加载
        delegate?.onDisplayImage(imageview, url)
        
        imageviews.append(imageview)
      }
      // 将加载的Imageview添加到原始Imageview上
      stitchingOn(canvasView, withImageViews: &imageviews)
     
  }
 
  // 根据 Imageview 的个数来设置对应的 Imageview 在原始 Imageview 的位置和大小,并将子 Imageview添加到原始Imageview里 
  private func stitchingOn(_ canvasView: UIImageView, withImageViews imageviews: inout [UIImageView], marginValue: CGFloat? = nil) {
    // 根据子Imageview的个数来确定子Imageview直接的间距 
    if marginValue == nil {
      margin = canvasView.frame.size.width / 18.0
    } else if imageviews.count == 4 {// 解决4张图遮挡头像的问题
      margin = canvasView.frame.size.width / 15.0
    } else {
      margin = marginValue
    }
    
    imageViewSideLengthWith(canvasView.frame, imageviews.count)
    
    if imageviews.count == 1 {
      let imageView1 = imageviews[0]
      let row_1_origin = (canvasView.frame.size.width - cellImageViewSideLength!) / 2
      imageView1.frame = CGRect(x: row_1_origin, y: row_1_origin, width: cellImageViewSideLength!, height: cellImageViewSideLength!)
      
    } else if imageviews.count == 2 {
      let row_1_origin_y = (canvasView.frame.size.width - cellImageViewSideLength!) / 2
      imageviews = matrixFor(&imageviews, row_1_origin_y)
      
    } else if imageviews.count == 3 {
      let row_1_origin_y = (canvasView.frame.size.height - cellImageViewSideLength! * 2) / 3
      
      let imageview1 = imageviews[0]
      imageview1.frame = CGRect(x: (canvasView.frame.size.width - cellImageViewSideLength!)/2, y: row_1_origin_y, width: cellImageViewSideLength!, height: cellImageViewSideLength!)
      
      imageviews = matrixFor(&imageviews, row_1_origin_y + cellImageViewSideLength! + margin!)
      
    } else if imageviews.count == 4 {
      let row_1_origin_y = (canvasView.frame.size.height - cellImageViewSideLength! * 2) / 3
      imageviews = matrixFor(&imageviews, row_1_origin_y)
      
    } else if imageviews.count == 5 {
      let row_1_origin_y = (canvasView.frame.size.height - cellImageViewSideLength! * 2 - margin!) / 2
      
      let imageview1 = imageviews[0]
      imageview1.frame = CGRect(x: (canvasView.frame.size.width - 2 * cellImageViewSideLength! - margin!) / 2, y: row_1_origin_y, width: cellImageViewSideLength!, height: cellImageViewSideLength!)
      
      let imageview2 = imageviews[1]
      imageview2.frame = CGRect(x: imageview1.frame.origin.x + imageview1.frame.size.width + margin!, y: row_1_origin_y, width: cellImageViewSideLength!, height: cellImageViewSideLength!)
      
      imageviews = matrixFor(&imageviews, row_1_origin_y + cellImageViewSideLength! + margin!)
      
    } else if imageviews.count == 6 {
      let row_1_origin_y = (canvasView.frame.size.height - cellImageViewSideLength! * 2 - margin!) / 2
      imageviews = matrixFor(&imageviews, row_1_origin_y)
      
    } else if imageviews.count == 7 {
      let row_1_origin_y = (canvasView.frame.size.height - cellImageViewSideLength! * 3) / 4
      
      let imageview1 = imageviews[0]
      imageview1.frame = CGRect(x: (canvasView.frame.size.width - cellImageViewSideLength!) / 2, y: row_1_origin_y, width: cellImageViewSideLength!, height: cellImageViewSideLength!)
      
      imageviews = matrixFor(&imageviews, row_1_origin_y + cellImageViewSideLength! + margin!)
      
    } else if imageviews.count == 8 {
      let row_1_origin_y = (canvasView.frame.size.height - cellImageViewSideLength! * 3) / 4
      
      let imageview1 = imageviews[0]
      imageview1.frame = CGRect(x: (canvasView.frame.size.width - 2 * cellImageViewSideLength! - margin!) / 2, y: row_1_origin_y, width: cellImageViewSideLength!, height: cellImageViewSideLength!)
      
      let imageview2 = imageviews[1]
      imageview2.frame = CGRect(x: imageview1.frame.origin.x + imageview1.frame.size.width + margin!, y: row_1_origin_y, width: cellImageViewSideLength!, height: cellImageViewSideLength!)
      
      imageviews = matrixFor(&imageviews, row_1_origin_y + cellImageViewSideLength! + margin!)
      
    } else if imageviews.count == 9 {
      let row_1_origin_y = (canvasView.frame.size.height - cellImageViewSideLength! * 3) / 4
      imageviews = matrixFor(&imageviews, row_1_origin_y)
      
    }
    
    for imageview in imageviews {
      canvasView.addSubview(imageview)
    }
    
  }
  // 计算子Imageview的边长
  private func imageViewSideLengthWith(_ canvasViewFrame: CGRect, _ count: Int) {
    var sideLength: CGFloat = 0.0
    
    if count == 1 {
      sideLength = (canvasViewFrame.size.width - margin! * 2) / 1.3

    } else if count >= 2 && count <= 4 {
      sideLength = (canvasViewFrame.size.width - margin! * 3) / 2
      
    } else {
      sideLength = (canvasViewFrame.size.width - margin! * 4) / 3
      
    }
    
    cellImageViewSideLength = sideLength
  }
  // 位置计算
  private func matrixFor(_ imageviews: inout [UIImageView], _ originY: CGFloat) -> [UIImageView] {
    let count = imageviews.count
    
    var cellCount: Int
    var maxRow: Int
    var maxColumn : Int
    var ignoreCountofBegining: Int
    
    if count <= 4 {
      maxRow = 2
      maxColumn = 2
      ignoreCountofBegining = count % 2
      cellCount = 4
      
    } else {
      maxRow = 3
      maxColumn = 3
      ignoreCountofBegining = count % 3
      cellCount = 9
    }
    
    for i in 0..<cellCount {
      if i > imageviews.count - 1 { break }
      if i < ignoreCountofBegining { continue }
      
      let row: CGFloat = floor(CGFloat((i - ignoreCountofBegining) / maxRow))
      let column: CGFloat = CGFloat((i - ignoreCountofBegining) % maxColumn)
      
      let origin_x = margin! + cellImageViewSideLength! * column + margin! * column
      let origin_y = originY + cellImageViewSideLength! * row + margin! * row
      
      let imageview = imageviews[i]
      imageview.frame = CGRect(x: origin_x, y: origin_y, width: cellImageViewSideLength!, height: cellImageViewSideLength!)
      
    }
    
    return imageviews
  }
}

当然还有最重要的上面提到的代理。

protocol NineGridImageViewDelegate {
  /// 图片加载细节让外部使用者处理
  func onDisplayImage(_ imageView: UIImageView, _ url: String?)
}

一般情况下,我们会直接为 Imageview 扩展这个协议

// 给 UIImageView 添加九宫格图片生成功能
extension UIImageView: NineGridImageViewDelegate {
  public func generateNineGrid(urls: [String?]) {
    self.image = nil
    let instance = NineGridImageView()
    instance.delegate = self
    instance.generateNineGridImageViewTo(self, urls)
  }
  
  func onDisplayImage(_ imageView: UIImageView, _ url: String?) {
    if url == nil {
      imageView.image = UIImage(named: "user_icon")
    } else {
      imageView.setImageWithURL(url!, placeholderImageStr: "user_icon")
    }
  }

  /// 设置Imageview的图片,可以看出我用了 Kingfisher
    func setImageWithURL(_ url: String, placeholderImageStr: String? = nil) {
        if let placeholderImageStr = placeholderImageStr {
            // update swift3.0
          self.kf.setImage(with: URL(string: url), placeholder: UIImage(named: placeholderImageStr), options: [.processor(RoundCornerImageProcessor(cornerRadius: 10))])
        } else {
            // update swift3.0
            self.kf.setImage(with: URL(string: url), placeholder: nil, options: [.processor(RoundCornerImageProcessor(cornerRadius: 12))])
        }
    }
}

总结一下实现思路,大体是:给一个要展示九宫格头像的源 Imageview 传入需要展示的图片urls 数组。根据 urls 的个数生成子 Imageview 数组,并且将其添加到源 Imageview。核心逻辑在于子 Imageview 如何排列到源 Imageview 上。子 Imageview 的大小,位置计算应该是最难的地方,建议多了解一下。
这个方案有性能隐患,当需要大量使用九宫格头像的时候,可想而知满屏幕会有多少个 Imageview,刷新界面或重绘也会占用很多内存。

第三种,将第一种方案和第二种方案结合。也就是在本地将九宫格图片绘制出来,并保存在本地待下次使用。
同样的我们先看看代码。

// MARK: - 修改后的方法
// 拼接 image 数组
  private func stitchingOn(_ canvasViewFrame: CGRect, withImages images: [UIImage], marginValue: CGFloat? = nil) -> UIImage? {
    if marginValue == nil {
      margin = canvasViewFrame.size.width / 18.0
    } else if images.count == 4 {// 解决4张图遮挡头像的问题
      margin = canvasViewFrame.size.width / 15.0
    } else {
      margin = marginValue
    }
    
    imageViewSideLengthWith(canvasViewFrame, images.count)
    
    var imageRects: [(UIImage, CGRect)] = []
    
    if images.count == 1 {
      let image = images[0]
      let row_1_origin = (canvasViewFrame.size.width - cellImageViewSideLength!) / 2
      let rect = CGRect(x: row_1_origin, y: row_1_origin, width: cellImageViewSideLength!, height: cellImageViewSideLength!)
      
      imageRects.append((image, rect))
      
    } else if images.count == 2 {
      let row_1_origin_y = (canvasViewFrame.size.width - cellImageViewSideLength!) / 2
      
      imageRects.append(contentsOf: matrixFor(images, row_1_origin_y))
      
    } else if images.count == 3 {
      let row_1_origin_y = (canvasViewFrame.size.height - cellImageViewSideLength! * 2) / 3
      
      let image1 = images[0]
      let rect = CGRect(x: (canvasViewFrame.size.width - cellImageViewSideLength!)/2, y: row_1_origin_y, width: cellImageViewSideLength!, height: cellImageViewSideLength!)
      
      imageRects.append((image1, rect))
      
      imageRects.append(contentsOf: matrixFor(images, row_1_origin_y + cellImageViewSideLength! + margin!))
      
    } else if images.count == 4 {
      let row_1_origin_y = (canvasViewFrame.size.height - cellImageViewSideLength! * 2) / 3
      imageRects.append(contentsOf: matrixFor(images, row_1_origin_y))
      
    } else if images.count == 5 {
      let row_1_origin_y = (canvasViewFrame.size.height - cellImageViewSideLength! * 2 - margin!) / 2
      
      let image1 = images[0]
      let rect1 = CGRect(x: (canvasViewFrame.size.width - 2 * cellImageViewSideLength! - margin!) / 2, y: row_1_origin_y, width: cellImageViewSideLength!, height: cellImageViewSideLength!)
      imageRects.append((image1, rect1))
      
      let image2 = images[1]
      let rect2 = CGRect(x: rect1.origin.x + rect1.size.width + margin!, y: row_1_origin_y, width: cellImageViewSideLength!, height: cellImageViewSideLength!)
      imageRects.append((image2, rect2))
      
      imageRects.append(contentsOf: matrixFor(images, row_1_origin_y + cellImageViewSideLength! + margin!))
      
    } else if images.count == 6 {
      let row_1_origin_y = (canvasViewFrame.size.height - cellImageViewSideLength! * 2 - margin!) / 2
      imageRects.append(contentsOf: matrixFor(images, row_1_origin_y))
      
    } else if images.count == 7 {
      let row_1_origin_y = (canvasViewFrame.size.height - cellImageViewSideLength! * 3) / 4
      
      let image1 = images[0]
      let rect1 = CGRect(x: (canvasViewFrame.size.width - cellImageViewSideLength!) / 2, y: row_1_origin_y, width: cellImageViewSideLength!, height: cellImageViewSideLength!)
      imageRects.append((image1, rect1))
      
      imageRects.append(contentsOf: matrixFor(images, row_1_origin_y + cellImageViewSideLength! + margin!))
      
    } else if images.count == 8 {
      let row_1_origin_y = (canvasViewFrame.size.height - cellImageViewSideLength! * 3) / 4
      
      let image1 = images[0]
      let rect1 = CGRect(x: (canvasViewFrame.size.width - 2 * cellImageViewSideLength! - margin!) / 2, y: row_1_origin_y, width: cellImageViewSideLength!, height: cellImageViewSideLength!)
      imageRects.append((image1, rect1))
      
      let image2 = images[1]
      let rect2 = CGRect(x: rect1.origin.x + rect1.size.width + margin!, y: row_1_origin_y, width: cellImageViewSideLength!, height: cellImageViewSideLength!)
      imageRects.append((image2, rect2))
      
      imageRects.append(contentsOf: matrixFor(images, row_1_origin_y + cellImageViewSideLength! + margin!))
      
    } else if images.count == 9 {
      let row_1_origin_y = (canvasViewFrame.size.height - cellImageViewSideLength! * 3) / 4
      imageRects.append(contentsOf: matrixFor(images, row_1_origin_y))
      
    }
    
    let resultImage = composeImages(canvasViewFrame, imageRects)
    
    return resultImage
  }
  // 计算每个 image 绘制的位置,返回一个包含image和位置的元数组
  private func matrixFor(_ images: [UIImage], _ originY: CGFloat) -> [(UIImage, CGRect)] {
    let count = images.count
    
    var cellCount: Int
    var maxRow: Int
    var maxColumn : Int
    var ignoreCountofBegining: Int
    
    if count <= 4 {
      maxRow = 2
      maxColumn = 2
      ignoreCountofBegining = count % 2
      cellCount = 4
      
    } else {
      maxRow = 3
      maxColumn = 3
      ignoreCountofBegining = count % 3
      cellCount = 9
    }
    
    var result: [(UIImage,CGRect)] = []
    
    for i in 0..<cellCount {
      if i > images.count - 1 { break }
      if i < ignoreCountofBegining { continue }
      
      let row: CGFloat = floor(CGFloat((i - ignoreCountofBegining) / maxRow))
      let column: CGFloat = CGFloat((i - ignoreCountofBegining) % maxColumn)
      
      let origin_x = margin! + cellImageViewSideLength! * column + margin! * column
      let origin_y = originY + cellImageViewSideLength! * row + margin! * row
      
      let rect = CGRect(x: origin_x, y: origin_y, width: cellImageViewSideLength!, height: cellImageViewSideLength!)
      result.append((images[i], rect))
    }
    
    return result
  }
    // 将需要拼接的 image 绘制到一起
  private func composeImages(_ canvasViewFrame: CGRect, _ images: [(UIImage,CGRect)]) -> UIImage? {
    let size = CGSize(width: canvasViewFrame.size.width, height: canvasViewFrame.size.height)
    
    UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
    
    for (image, rect) in images {
      image.draw(in: rect)
    }
    
    let result_image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    
    return result_image
  }

上面代码的核心变化,在于计算的是image的位置。并且加入了新的方法,也就是 image 的绘制 composeImages() 方法。ImageHelper 是我封装的 Kingfisher 工具类,稍后也会贴出相关代码。

  // 生成九宫格图片传入到 Imageview 中。
  func generateNineGridImageViewTo(_ canvasView: UIImageView, _ urls: [String?], _ forkey: String? = nil) {
      // forkey 是新生成的九宫格图片的唯一标识。
      if ImageHelper.sharedInstance.isImageCached(key: forkey!) {
        ImageHelper.sharedInstance.retrieveImage(forkey: forkey!, completionBlock: { (image) in
          canvasView.image = image
        })
        
      } else {
        delegate?.getDrawImages(urls, completionBlock: { (images) in
          let image = self.stitchingOn(canvasView.frame, withImages: images)
          
          if image != nil {
            ImageHelper.sharedInstance.storeImage(image: image!, forkey: forkey!)
          }
          
          DispatchQueue.main.async {
            canvasView.image = image
          }
          
        })
      }
     
  }

在 protocol NineGridImageViewDelegate 中加入 getDrawImages 方法。并在 extension UIImageView 的时候实现它,具体逻辑如下:

public func getDrawImages(_ urls: [String?], completionBlock: @escaping ([UIImage]) -> Void){
    ImageHelper.sharedInstance.downLoadImage(urls: urls) {
      ImageHelper.sharedInstance.retrieveImage(keys: urls, placeholderImageStr: "user_icon", completionBlock: completionBlock)
    }
  }

以上就是全部内容了。回看整个代码,你会发现第三种方案也有不少问题。比如何时更新九宫格图片,更新的时候一样会出现图片闪一下的问题。当然这些问题目前来看还是可以接受的。

彩蛋:ImageHelper,对 Kingfisher 的操作。

class ImageHelper {
    class var sharedInstance: ImageHelper {
        struct Static {
            static let instance: ImageHelper = ImageHelper()
        }
        return Static.instance
    }
    
    /// 自定义KingfisherManager cache system,在这里我们暂时只使用 default 的 cache
    fileprivate func initKingfisherManager() {
        let cache = KingfisherManager.shared.cache
        // Set max disk cache to 100 mb. Default is no limit.
        cache.maxDiskCacheSize = UInt(100 * 1024 * 1024)
        
        // Set max disk cache to duration to 30 days, Default is 1 week.
        cache.maxCachePeriodInSecond = TimeInterval(60 * 60 * 24 * 30)
    }
    
    /// 计算图片使用磁盘大小
    func getDiskCacheSize() -> UInt?{
        let cache = KingfisherManager.shared.cache
        var cachesize: UInt? = nil
        // Get the disk size taken by the cache.
        cache.calculateDiskCacheSize {size in
            cachesize = size
        }
        return cachesize
    }
    
    /// 清理缓存,包括: memory & disk
    func clearCache() {
        let cache = KingfisherManager.shared.cache
        // Clear memory cache right away.
        cache.clearMemoryCache()
        // Clear disk cache. This is an async operation.
        cache.clearDiskCache()
    }
  
  func downLoadImage(urls: [String?], completionBlock: @escaping () -> Void){
    let group = DispatchGroup()

    for url in urls {
      guard url != nil else {
        continue
      }
      
      if isImageCached(key: url!) {
        continue
      }
      
      guard let urlTemp = URL(string: url!) else {
        continue
      }
      
      group.enter()
      
      ImageDownloader.default.downloadImage(with: urlTemp, options: [], progressBlock: nil) { (image, error, url, data) in
        if error == nil, let image = image {
          ImageCache.default.store(image, forKey: (url?.absoluteString)!)
        }
        
        group.leave()
      }
      
    }
    
    group.notify(queue: DispatchQueue.global()) {
      completionBlock()
    }
    
  }
  
  func retrieveImage(keys: [String?], placeholderImageStr: String? = nil, completionBlock: @escaping ([UIImage]) -> Void){
    
    let group = DispatchGroup()
    
    var images: [UIImage] = []
    
    for key in keys {
      group.enter()
      
      if key?.description == nil {
        DispatchQueue.global().async {
          if let holder = placeholderImageStr {
            let placeholder = UIImage(named: holder)
            assert(placeholder != nil, "placeholderImageStr 不存在")
            images.append(placeholder!)
            group.leave()
          }
        }
        
      } else {
        ImageCache.default.retrieveImage(forKey: key!, options: nil) { (image, cacheType) in
          
          if let image = image {
            images.append(image)
            
          } else {
            NSLog("--------- retrieveImage error -- key : \(key!)")
            
            if let holder = placeholderImageStr {
              
              let placeholder = UIImage(named: holder)
              images.append(placeholder!)
              assert(placeholder != nil, "placeholderImageStr 不存在")
            }
            
          }
          
          group.leave()
          
        }
      }
      
    }
    
    group.notify(queue: DispatchQueue.global()) {
      completionBlock(images)
    }
  }
  
  func retrieveImage(forkey: String, completionBlock: @escaping (UIImage?) -> Void) {
    
    ImageCache.default.retrieveImage(forKey: forkey, options: []) { (image, cacheType) in
      completionBlock(image)
    }
    
  }
  
  func isImageCached(key: String) -> Bool{
    let result = ImageCache.default.isImageCached(forKey: key)
    return result.cached
  }
  
  func storeImage(image: Image, forkey: String) {
    if isImageCached(key: forkey) {
      ImageCache.default.removeImage(forKey: forkey, fromDisk: true) {
        ImageCache.default.store(image, forKey: forkey)
      }
    } else {
      ImageCache.default.store(image, forKey: forkey)
    }
  }
  
}

ImageHelper 唯一需要注意的只有 DispatchGroup 的使用。

结束语

最后还是感谢一下第二种方案的原作者。虽然已经找不到原链接,但直接拿来学习使用,并在其基础上做了修改,多少还是要声明感谢一下的。
以上代码均是 swift 编写。只是提供了一种思路,android 开发中也可以用同样的思路来实现。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,612评论 4 59
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,567评论 25 707
  • 真正优秀的人,大都不合群 01.不合群者,独来独往的人,常有过人之处。整天混在朋友之间的人,未必有多大的能力。 0...
    鬼翼龙阅读 279评论 0 2