UIKit框架(十七) —— 基于自定義UICollectionViewLayout布局的簡(jiǎn)單示例(三)

版本記錄

版本號(hào) 時(shí)間
V1.0 2019.04.27 星期六

前言

iOS中有關(guān)視圖控件用戶能看到的都在UIKit框架里面,用戶交互也是通過(guò)UIKit進(jìn)行的容诬。感興趣的參考上面幾篇文章。
1. UIKit框架(一) —— UIKit動(dòng)力學(xué)和移動(dòng)效果(一)
2. UIKit框架(二) —— UIKit動(dòng)力學(xué)和移動(dòng)效果(二)
3. UIKit框架(三) —— UICollectionViewCell的擴(kuò)張效果的實(shí)現(xiàn)(一)
4. UIKit框架(四) —— UICollectionViewCell的擴(kuò)張效果的實(shí)現(xiàn)(二)
5. UIKit框架(五) —— 自定義控件:可重復(fù)使用的滑塊(一)
6. UIKit框架(六) —— 自定義控件:可重復(fù)使用的滑塊(二)
7. UIKit框架(七) —— 動(dòng)態(tài)尺寸UITableViewCell的實(shí)現(xiàn)(一)
8. UIKit框架(八) —— 動(dòng)態(tài)尺寸UITableViewCell的實(shí)現(xiàn)(二)
9. UIKit框架(九) —— UICollectionView的數(shù)據(jù)異步預(yù)加載(一)
10. UIKit框架(十) —— UICollectionView的數(shù)據(jù)異步預(yù)加載(二)
11. UIKit框架(十一) —— UICollectionView的重用、選擇和重排序(一)
12. UIKit框架(十二) —— UICollectionView的重用、選擇和重排序(二)
13. UIKit框架(十三) —— 如何創(chuàng)建自己的側(cè)滑式面板導(dǎo)航(一)
14. UIKit框架(十四) —— 如何創(chuàng)建自己的側(cè)滑式面板導(dǎo)航(二)
15. UIKit框架(十五) —— 基于自定義UICollectionViewLayout布局的簡(jiǎn)單示例(一)
16. UIKit框架(十六) —— 基于自定義UICollectionViewLayout布局的簡(jiǎn)單示例(二)

源碼

1. Swift

首先看下代碼組織結(jié)構(gòu)

接著看一下sb文件

1. CustomLayoutAttributes.swift
import UIKit

final class CustomLayoutAttributes: UICollectionViewLayoutAttributes {

  // MARK: - Properties
  var parallax: CGAffineTransform = .identity
  var initialOrigin: CGPoint = .zero
  var headerOverlayAlpha = CGFloat(0)

  // MARK: - Life Cycle
  override func copy(with zone: NSZone?) -> Any {
    guard let copiedAttributes = super.copy(with: zone) as? CustomLayoutAttributes else {
      return super.copy(with: zone)
    }

    copiedAttributes.parallax = parallax
    copiedAttributes.initialOrigin = initialOrigin
    copiedAttributes.headerOverlayAlpha = headerOverlayAlpha
    return copiedAttributes
  }
  
  override func isEqual(_ object: Any?) -> Bool {
    guard let otherAttributes = object as? CustomLayoutAttributes else {
      return false
    }

    if NSValue(cgAffineTransform: otherAttributes.parallax) != NSValue(cgAffineTransform: parallax)
      || otherAttributes.initialOrigin != initialOrigin
      || otherAttributes.headerOverlayAlpha != headerOverlayAlpha {
        return false
    }

    return super.isEqual(object)
  }
}
2. CustomLayoutSettings.swift
import UIKit

struct CustomLayoutSettings {

  // Elements sizes
  var itemSize: CGSize?
  var headerSize: CGSize?
  var menuSize: CGSize?
  var sectionsHeaderSize: CGSize?
  var sectionsFooterSize: CGSize?

  // Behaviours
  var isHeaderStretchy: Bool
  var isAlphaOnHeaderActive: Bool
  var headerOverlayMaxAlphaValue: CGFloat
  var isMenuSticky: Bool
  var isSectionHeadersSticky: Bool
  var isParallaxOnCellsEnabled: Bool

  // Spacing
  var minimumInteritemSpacing: CGFloat
  var minimumLineSpacing: CGFloat
  var maxParallaxOffset: CGFloat
}

extension CustomLayoutSettings {

  init() {
    self.itemSize = nil
    self.headerSize = nil
    self.menuSize = nil
    self.sectionsHeaderSize = nil
    self.sectionsFooterSize = nil
    self.isHeaderStretchy = false
    self.isAlphaOnHeaderActive = true
    self.headerOverlayMaxAlphaValue = 0
    self.isMenuSticky = false
    self.isSectionHeadersSticky = false
    self.isParallaxOnCellsEnabled = false
    self.maxParallaxOffset = 0
    self.minimumInteritemSpacing = 0
    self.minimumLineSpacing = 0
  }
}
3. CustomLayout.swift
import UIKit

final class CustomLayout: UICollectionViewLayout {
  
  enum Element: String {
    case header
    case menu
    case sectionHeader
    case sectionFooter
    case cell
    
    var id: String {
      return self.rawValue
    }
    
    var kind: String {
      return "Kind\(self.rawValue.capitalized)"
    }
  }
  
  override public class var layoutAttributesClass: AnyClass {
    return CustomLayoutAttributes.self
  }
  
  override public var collectionViewContentSize: CGSize {
    return CGSize(width: collectionViewWidth, height: contentHeight)
  }
  
  // MARK: - Properties
  var settings = CustomLayoutSettings()
  private var oldBounds = CGRect.zero
  private var contentHeight = CGFloat()
  private var cache = [Element: [IndexPath: CustomLayoutAttributes]]()
  private var visibleLayoutAttributes = [CustomLayoutAttributes]()
  private var zIndex = 0
  
  private var collectionViewHeight: CGFloat {
    return collectionView!.frame.height
  }
  
  private var collectionViewWidth: CGFloat {
    return collectionView!.frame.width
  }
  
  private var cellHeight: CGFloat {
    guard let itemSize = settings.itemSize else {
      return collectionViewHeight
    }
    
    return itemSize.height
  }
  
  private var cellWidth: CGFloat {
    guard let itemSize = settings.itemSize else {
      return collectionViewWidth
    }
    
    return itemSize.width
  }
  
  private var headerSize: CGSize {
    guard let headerSize = settings.headerSize else {
      return .zero
    }
    
    return headerSize
  }
  
  private var menuSize: CGSize {
    guard let menuSize = settings.menuSize else {
      return .zero
    }
    
    return menuSize
  }
  
  private var sectionsHeaderSize: CGSize {
    guard let sectionsHeaderSize = settings.sectionsHeaderSize else {
      return .zero
    }
    
    return sectionsHeaderSize
  }
  
  private var sectionsFooterSize: CGSize {
    guard let sectionsFooterSize = settings.sectionsFooterSize else {
      return .zero
    }
    
    return sectionsFooterSize
  }
  
  private var contentOffset: CGPoint {
    return collectionView!.contentOffset
  }
}

// MARK: - LAYOUT CORE PROCESS
extension CustomLayout {
  
  override public func prepare() {
    guard let collectionView = collectionView,
      cache.isEmpty else {
        return
    }
    
    prepareCache()
    contentHeight = 0
    zIndex = 0
    oldBounds = collectionView.bounds
    let itemSize = CGSize(width: cellWidth, height: cellHeight)
    
    let headerAttributes = CustomLayoutAttributes(
      forSupplementaryViewOfKind: Element.header.kind,
      with: IndexPath(item: 0, section: 0)
    )
    prepareElement(size: headerSize, type: .header, attributes: headerAttributes)
    
    let menuAttributes = CustomLayoutAttributes(
      forSupplementaryViewOfKind: Element.menu.kind,
      with: IndexPath(item: 0, section: 0))
    prepareElement(size: menuSize, type: .menu, attributes: menuAttributes)
    
    for section in 0 ..< collectionView.numberOfSections {
      
      let sectionHeaderAttributes = CustomLayoutAttributes(
        forSupplementaryViewOfKind: UICollectionElementKindSectionHeader,
        with: IndexPath(item: 0, section: section))
      prepareElement(
        size: sectionsHeaderSize,
        type: .sectionHeader,
        attributes: sectionHeaderAttributes)
      
      for item in 0 ..< collectionView.numberOfItems(inSection: section) {
        let cellIndexPath = IndexPath(item: item, section: section)
        let attributes = CustomLayoutAttributes(forCellWith: cellIndexPath)
        let lineInterSpace = settings.minimumLineSpacing
        attributes.frame = CGRect(
          x: 0 + settings.minimumInteritemSpacing,
          y: contentHeight + lineInterSpace,
          width: itemSize.width,
          height: itemSize.height
        )
        attributes.zIndex = zIndex
        contentHeight = attributes.frame.maxY
        cache[.cell]?[cellIndexPath] = attributes
        zIndex += 1
      }
      
      let sectionFooterAttributes = CustomLayoutAttributes(
        forSupplementaryViewOfKind: UICollectionElementKindSectionFooter,
        with: IndexPath(item: 1, section: section))
      prepareElement(
        size: sectionsFooterSize,
        type: .sectionFooter,
        attributes: sectionFooterAttributes)
    }
    
    updateZIndexes()
  }
  
  override public func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
    if oldBounds.size != newBounds.size {
      cache.removeAll(keepingCapacity: true)
    }
    return true
  }
  
  private func prepareCache() {
    cache.removeAll(keepingCapacity: true)
    cache[.header] = [IndexPath: CustomLayoutAttributes]()
    cache[.menu] = [IndexPath: CustomLayoutAttributes]()
    cache[.sectionHeader] = [IndexPath: CustomLayoutAttributes]()
    cache[.sectionFooter] = [IndexPath: CustomLayoutAttributes]()
    cache[.cell] = [IndexPath: CustomLayoutAttributes]()
  }
  
  private func prepareElement(size: CGSize, type: Element, attributes: CustomLayoutAttributes) {
    guard size != .zero else { return }
    
    attributes.initialOrigin = CGPoint(x: 0, y: contentHeight)
    attributes.frame = CGRect(origin: attributes.initialOrigin, size: size)
    
    attributes.zIndex = zIndex
    zIndex += 1
    
    contentHeight = attributes.frame.maxY
    
    cache[type]?[attributes.indexPath] = attributes
  }
  
  private func updateZIndexes(){
    guard let sectionHeaders = cache[.sectionHeader] else { return }
    
    var sectionHeadersZIndex = zIndex
    for (_, attributes) in sectionHeaders {
      attributes.zIndex = sectionHeadersZIndex
      sectionHeadersZIndex += 1
    }
    
    cache[.menu]?.first?.value.zIndex = sectionHeadersZIndex
  }
}

//MARK: - PROVIDING ATTRIBUTES TO THE COLLECTIONVIEW
extension CustomLayout {
  
  public override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
    switch elementKind {
    case UICollectionElementKindSectionHeader:
      return cache[.sectionHeader]?[indexPath]
      
    case UICollectionElementKindSectionFooter:
      return cache[.sectionFooter]?[indexPath]
      
    case Element.header.kind:
      return cache[.header]?[indexPath]
      
    default:
      return cache[.menu]?[indexPath]
    }
  }
  
  override public func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
    return cache[.cell]?[indexPath]
  }
  
  override public func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
    guard let collectionView = collectionView else { return nil }
    
    visibleLayoutAttributes.removeAll(keepingCapacity: true)
    
    let halfHeight = collectionViewHeight * 0.5
    let halfCellHeight = cellHeight * 0.5
    
    for (type, elementInfos) in cache {
      for (indexPath, attributes) in elementInfos {
        
        attributes.parallax = .identity
        attributes.transform = .identity
        
        updateSupplementaryViews(type, attributes: attributes, collectionView: collectionView, indexPath: indexPath)
        if attributes.frame.intersects(rect) {
          if type == .cell,
            settings.isParallaxOnCellsEnabled {
            updateCells(attributes, halfHeight: halfHeight, halfCellHeight: halfCellHeight)
          }
          visibleLayoutAttributes.append(attributes)
        }
      }
    }
    return visibleLayoutAttributes
  }
  
  private func updateSupplementaryViews(_ type: Element, attributes: CustomLayoutAttributes, collectionView: UICollectionView, indexPath: IndexPath) {
    if type == .sectionHeader,
      settings.isSectionHeadersSticky {
      
      let upperLimit = CGFloat(collectionView.numberOfItems(inSection: indexPath.section)) * (cellHeight + settings.minimumLineSpacing)
      let menuOffset = settings.isMenuSticky ? menuSize.height : 0
      attributes.transform =  CGAffineTransform(
        translationX: 0,
        y: min(upperLimit, max(0, contentOffset.y - attributes.initialOrigin.y + menuOffset)))
      
    } else if type == .header,
      settings.isHeaderStretchy {
      
      let updatedHeight = min(
        collectionView.frame.height,
        max(headerSize.height, headerSize.height - contentOffset.y))
      
      let scaleFactor = updatedHeight / headerSize.height
      let delta = (updatedHeight - headerSize.height) / 2
      let scale = CGAffineTransform(scaleX: scaleFactor, y: scaleFactor)
      let translation = CGAffineTransform(translationX: 0, y: min(contentOffset.y, headerSize.height) + delta)
      attributes.transform = scale.concatenating(translation)
      if settings.isAlphaOnHeaderActive {
        attributes.headerOverlayAlpha = min(settings.headerOverlayMaxAlphaValue, contentOffset.y / headerSize.height)
      }
      
    } else if type == .menu,
      settings.isMenuSticky {
      
      attributes.transform = CGAffineTransform(translationX: 0, y: max(attributes.initialOrigin.y, contentOffset.y) - headerSize.height)
    }
  }
  
  private func updateCells(_ attributes: CustomLayoutAttributes, halfHeight: CGFloat, halfCellHeight: CGFloat) {
    let cellDistanceFromCenter = attributes.center.y - contentOffset.y - halfHeight
    let parallaxOffset = -(settings.maxParallaxOffset * cellDistanceFromCenter) / (halfHeight + halfCellHeight)
    let boundedParallaxOffset = min(max(-settings.maxParallaxOffset, parallaxOffset), settings.maxParallaxOffset)
    attributes.parallax = CGAffineTransform(translationX: 0, y: boundedParallaxOffset)
  }
}
4. MockDataManager.swift
protocol Team {
  var marks: [String] { get }
  var playerPictures: [[String]] { get }
}

struct Owls: Team {
  let marks = ["4/5", "3/5", "4/5", "2/5"]
  let playerPictures = [
    ["Owls-goalkeeper"],
    ["Owls-d1", "Owls-d2", "Owls-d3", "Owls-d4"],
    ["Owls-m1", "Owls-m2", "Owls-m3", "Owls-m4"],
    ["Owls-f1", "Owls-f2"]
  ]
}

struct Tigers: Team {
  let marks = ["1/5", "3/5", "3/5", "5/5"]
  let playerPictures = [
    ["Tigers-goalkeeper"],
    ["Tigers-d1", "Tigers-d2", "Tigers-d3", "Tigers-d4"],
    ["Tigers-m1", "Tigers-m2", "Tigers-m3", "Tigers-m4"],
    ["Tigers-f1", "Tigers-f2"]
  ]
}

struct Parrots: Team {
  let marks = ["3/5", "2/5", "4/5", "5/5"]
  let playerPictures = [
    ["Parrots-goalkeeper"],
    ["Parrots-d1", "Parrots-d2", "Parrots-d3", "Parrots-d4"],
    ["Parrots-m1", "Parrots-m2", "Parrots-m3", "Parrots-m4"],
    ["Parrots-f1", "Parrots-f2"]
  ]
}

struct Giraffes: Team {
  let marks = ["5/5", "4/5", "3/5", "1/5"]
  let playerPictures = [
    ["Giraffes-goalkeeper"],
    ["Giraffes-d1", "Giraffes-d2", "Giraffes-d3", "Giraffes-d4"],
    ["Giraffes-m1", "Giraffes-m2", "Giraffes-m3", "Giraffes-m4"],
    ["Giraffes-f1", "Giraffes-f2"]
  ]
}
5. HeaderView.swift
import UIKit

final class HeaderView: UICollectionReusableView {

  // MARK: - IBOutlets
  @IBOutlet weak var overlayView: UIView!

  // MARK: - Life Cycle
  open override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
    super.apply(layoutAttributes)

    guard let customFlowLayoutAttributes = layoutAttributes as? CustomLayoutAttributes else {
      return
    }

    overlayView?.alpha = customFlowLayoutAttributes.headerOverlayAlpha
  }
}
6. MenuView.swift
import UIKit

protocol MenuViewDelegate {
  func reloadCollectionViewDataWithTeamIndex(_ index: Int)
}

final class MenuView: UICollectionReusableView {

  // MARK: - Properties
  var delegate: MenuViewDelegate?
  
  // MARK: - View Life Cycle
  override func prepareForReuse() {
    super.prepareForReuse()

    delegate = nil
  }
}

// MARK: - IBActions
extension MenuView {

  @IBAction func tappedButton(_ sender: UIButton) {
    delegate?.reloadCollectionViewDataWithTeamIndex(sender.tag)
  }
}
7. SectionHeaderView.swift
import UIKit

final class SectionHeaderView: UICollectionReusableView {

  // MARK: - IBOutlets
  @IBOutlet weak var title: UILabel!
}
8. PlayerCell.swift
import UIKit

final class PlayerCell: UICollectionViewCell {

  // MARK: - IBOutlets
  @IBOutlet weak var picture: UIImageView!

  // MARK: - View Life Cycle
  override func apply(_ layoutAttributes: UICollectionViewLayoutAttributes) {
    super.apply(layoutAttributes)

    guard let attributes = layoutAttributes as? CustomLayoutAttributes else {
      return
    }

    picture.transform = attributes.parallax
  }
  
  override func prepareForReuse() {
    super.prepareForReuse()

    picture.transform = .identity
  }
}
9. SectionFooterView.swift
import UIKit

final class SectionFooterView: UICollectionReusableView {

  // MARK: - IBOutlets
  @IBOutlet weak var mark: UILabel!
}
10. JungleCupCollectionViewController.swift
import UIKit

final class JungleCupCollectionViewController: UICollectionViewController {
 
  // MARK: - Properties
  var customLayout: CustomLayout? {
    return collectionView?.collectionViewLayout as? CustomLayout
  }

  private let teams: [Team] = [Owls(), Giraffes(), Parrots(), Tigers()]
  private let sections = ["Goalkeeper", "Defenders", "Midfielders", "Forwards"]
  private var displayedTeam = 0

  override var prefersStatusBarHidden: Bool {
    return true
  }

  // MARK: - View Life Cycle
  override func viewDidLoad() {
    super.viewDidLoad()

    setupCollectionViewLayout()
  }
}

private extension JungleCupCollectionViewController {

  func setupCollectionViewLayout() {
    guard let collectionView = collectionView, let customLayout = customLayout else { return }

    collectionView.register(
      UINib(nibName: "HeaderView", bundle: nil),
      forSupplementaryViewOfKind: CustomLayout.Element.header.kind,
      withReuseIdentifier: CustomLayout.Element.header.id
    )

    collectionView.register(
      UINib(nibName: "MenuView", bundle: nil),
      forSupplementaryViewOfKind: CustomLayout.Element.menu.kind,
      withReuseIdentifier: CustomLayout.Element.menu.id
    )

    customLayout.settings.itemSize = CGSize(width: collectionView.frame.width, height: 200)
    customLayout.settings.headerSize = CGSize(width: collectionView.frame.width, height: 300)
    customLayout.settings.menuSize = CGSize(width: collectionView.frame.width, height: 70)
    customLayout.settings.sectionsHeaderSize = CGSize(width: collectionView.frame.width, height: 50)
    customLayout.settings.sectionsFooterSize = CGSize(width: collectionView.frame.width, height: 50)
    customLayout.settings.isHeaderStretchy = true
    customLayout.settings.isAlphaOnHeaderActive = true
    customLayout.settings.headerOverlayMaxAlphaValue = CGFloat(0.6)
    customLayout.settings.isMenuSticky = true
    customLayout.settings.isSectionHeadersSticky = true
    customLayout.settings.isParallaxOnCellsEnabled = true
    customLayout.settings.maxParallaxOffset = 60
    customLayout.settings.minimumInteritemSpacing = 0
    customLayout.settings.minimumLineSpacing = 3
  }
}

//MARK: - UICollectionViewDataSource
extension JungleCupCollectionViewController {

  override func numberOfSections(in collectionView: UICollectionView) -> Int {
    return sections.count
  }

  override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return teams[displayedTeam].playerPictures[section].count
  }

  override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CustomLayout.Element.cell.id, for: indexPath)
    if let playerCell = cell as? PlayerCell {
      playerCell.picture.image = UIImage(named: teams[displayedTeam].playerPictures[indexPath.section][indexPath.item])
    }
    return cell
  }

  override func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
    switch kind {
    case UICollectionElementKindSectionHeader:
      let supplementaryView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: CustomLayout.Element.sectionHeader.id, for: indexPath)
      if let sectionHeaderView = supplementaryView as? SectionHeaderView {
        sectionHeaderView.title.text = sections[indexPath.section]
      }
      return supplementaryView

    case UICollectionElementKindSectionFooter:
      let supplementaryView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: CustomLayout.Element.sectionFooter.id, for: indexPath)
      if let sectionFooterView = supplementaryView as? SectionFooterView {
        sectionFooterView.mark.text = "Strength: \(teams[displayedTeam].marks[indexPath.section])"
      }
      return supplementaryView

    case CustomLayout.Element.header.kind:
      let topHeaderView = collectionView.dequeueReusableSupplementaryView(
        ofKind: kind,
        withReuseIdentifier: CustomLayout.Element.header.id,
        for: indexPath
      )
      return topHeaderView

    case CustomLayout.Element.menu.kind:
      let menuView = collectionView.dequeueReusableSupplementaryView(
        ofKind: kind,
        withReuseIdentifier: CustomLayout.Element.menu.id,
        for: indexPath
      )
      if let menuView = menuView as? MenuView {
        menuView.delegate = self
      }
      return menuView

    default:
      fatalError("Unexpected element kind")
    }
  }
}

// MARK: - MenuViewDelegate
extension JungleCupCollectionViewController: MenuViewDelegate {

  func reloadCollectionViewDataWithTeamIndex(_ index: Int) {
    displayedTeam = index
    collectionView?.reloadData()
  }
}

后記

本篇主要講述了基于自定義UICollectionViewLayout布局的簡(jiǎn)單示例瞬内,感興趣的給個(gè)贊或者關(guān)注~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末迷雪,一起剝皮案震驚了整個(gè)濱河市限书,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌章咧,老刑警劉巖倦西,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異赁严,居然都是意外死亡扰柠,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門疼约,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)卤档,“玉大人,你說(shuō)我怎么就攤上這事程剥∪霸妫” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵织鲸,是天一觀的道長(zhǎng)舔腾。 經(jīng)常有香客問(wèn)我,道長(zhǎng)搂擦,這世上最難降的妖魔是什么稳诚? 我笑而不...
    開(kāi)封第一講書人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮瀑踢,結(jié)果婚禮上扳还,老公的妹妹穿的比我還像新娘。我一直安慰自己橱夭,他們只是感情好氨距,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著徘钥,像睡著了一般衔蹲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 52,158評(píng)論 1 308
  • 那天舆驶,我揣著相機(jī)與錄音橱健,去河邊找鬼。 笑死沙廉,一個(gè)胖子當(dāng)著我的面吹牛拘荡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播撬陵,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼珊皿,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了巨税?” 一聲冷哼從身側(cè)響起蟋定,我...
    開(kāi)封第一講書人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎草添,沒(méi)想到半個(gè)月后驶兜,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡远寸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年抄淑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片驰后。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡肆资,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出灶芝,到底是詐尸還是另有隱情郑原,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布监署,位于F島的核電站颤专,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏钠乏。R本人自食惡果不足惜栖秕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望晓避。 院中可真熱鬧簇捍,春花似錦、人聲如沸俏拱。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)锅必。三九已至事格,卻和暖如春惕艳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背驹愚。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工远搪, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人逢捺。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓谁鳍,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親劫瞳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子倘潜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容