UICollectionView詳解之自定義布局

想看UICollectionView基礎使用的可以先看我的另一篇文章。這篇主寫關于UIollectionViewLayout自定義布局的一些常用方法,瀑布流布局的自定義,包括頭尾試圖的添加,插入刪除動畫,還有9.0后移動動態布局的使用。

UICollectionViewLayout自定義常用的幾個方法

//預布局方法 所有的布局應該寫在這里面
- (void)prepareLayout

//此方法應該返回當前屏幕正在顯示的視圖(cell 頭尾視圖)的布局屬性集合(UICollectionViewLayoutAttributes 對象集合)
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect

//根據indexPath去對應的UICollectionViewLayoutAttributes  這個是取值的,要重寫,在移動刪除的時候系統會調用改方法重新去UICollectionViewLayoutAttributes然后布局
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath

//返回當前的ContentSize
- (CGSize)collectionViewContentSize
//是否重新布局 
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds

//這4個方法用來處理插入、刪除和移動cell時的一些動畫 瀑布流代碼詳解
- (void)prepareForCollectionViewUpdates:(NSArray *)updateItems
- (UICollectionViewLayoutAttributes*)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
- (nullable UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
- (void)finalizeCollectionViewUpdates
//9.0之后處理移動相關
- (UICollectionViewLayoutInvalidationContext *)invalidationContextForInteractivelyMovingItems:(NSArray<NSIndexPath *> *)targetIndexPaths withTargetPosition:(CGPoint)targetPosition previousIndexPaths:(NSArray<NSIndexPath *> *)previousIndexPaths previousPosition:(CGPoint)previousPosition NS_AVAILABLE_IOS(9_0)
- (UICollectionViewLayoutInvalidationContext *)invalidationContextForEndingInteractiveMovementOfItemsToFinalIndexPaths:(NSArray<NSIndexPath *> *)indexPaths previousIndexPaths:(NSArray<NSIndexPath *> *)previousIndexPaths movementCancelled:(BOOL)movementCancelled NS_AVAILABLE_IOS(9_0)

瀑布流布局詳解,先貼代碼

.h文件

#import <UIKit/UIKit.h>

UIKIT_EXTERN NSString *const AC_UICollectionElementKindSectionHeader;
UIKIT_EXTERN NSString *const AC_UICollectionElementKindSectionFooter;

@class AC_WaterCollectionViewLayout;
@protocol AC_WaterCollectionViewLayoutDelegate <NSObject>

//代理取cell 的高
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(AC_WaterCollectionViewLayout *)layout heightOfItemAtIndexPath:(NSIndexPath *)indexPath itemWidth:(CGFloat)itemWidth;

//處理移動相關的數據源
- (void)moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath;

@end

@interface AC_WaterCollectionViewLayout : UICollectionViewLayout

@property (assign, nonatomic) NSInteger numberOfColumns;//瀑布流有列
@property (assign, nonatomic) CGFloat cellDistance;//cell之間的間距
@property (assign, nonatomic) CGFloat topAndBottomDustance;//cell 到頂部 底部的間距
@property (assign, nonatomic) CGFloat headerViewHeight;//頭視圖的高度
@property (assign, nonatomic) CGFloat footViewHeight;//尾視圖的高度

@property(nonatomic, weak) id<AC_WaterCollectionViewLayoutDelegate> delegate;

@end

.h文件沒有太多東西,看注釋應該都清楚。跟UICollectionViewFlowLayout不同的是沒有方向設置,因為瀑布流橫向基本少見,所以所以頭尾視圖也由CGSize改成CGFloat,

.m文件

#import "AC_WaterCollectionViewLayout.h"

NSString *const AC_UICollectionElementKindSectionHeader = @"AC_HeadView";
NSString *const AC_UICollectionElementKindSectionFooter = @"AC_FootView";

@interface AC_WaterCollectionViewLayout()

@property (strong, nonatomic) NSMutableDictionary *cellLayoutInfo;//保存cell的布局
@property (strong, nonatomic) NSMutableDictionary *headLayoutInfo;//保存頭視圖的布局
@property (strong, nonatomic) NSMutableDictionary *footLayoutInfo;//保存尾視圖的布局

@property (assign, nonatomic) CGFloat startY;//記錄開始的Y
@property (strong, nonatomic) NSMutableDictionary *maxYForColumn;//記錄瀑布流每列最下面那個cell的底部y值
@property (strong, nonatomic) NSMutableArray *shouldanimationArr;//記錄需要添加動畫的NSIndexPath


@end

@implementation AC_WaterCollectionViewLayout

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.numberOfColumns = 3;
        self.topAndBottomDustance = 10;
        self.cellDistance = 10;
        _headerViewHeight = 0;
        _footViewHeight = 0;
        self.startY = 0;
        self.maxYForColumn = [NSMutableDictionary dictionary];
        self.shouldanimationArr = [NSMutableArray array];
        self.cellLayoutInfo = [NSMutableDictionary dictionary];
        self.headLayoutInfo = [NSMutableDictionary dictionary];
        self.footLayoutInfo = [NSMutableDictionary dictionary];
    }
    return self;
}

- (void)prepareLayout
{
    [super prepareLayout];
    
    //重新布局需要清空
    [self.cellLayoutInfo removeAllObjects];
    [self.headLayoutInfo removeAllObjects];
    [self.footLayoutInfo removeAllObjects];
    [self.maxYForColumn removeAllObjects];
    self.startY = 0;
    
    
    CGFloat viewWidth = self.collectionView.frame.size.width;
    //代理里面只取了高度,所以cell的寬度有列數還有cell的間距計算出來
    CGFloat itemWidth = (viewWidth - self.cellDistance*(self.numberOfColumns + 1))/self.numberOfColumns;
    
    //取有多少個section
    NSInteger sectionsCount = [self.collectionView numberOfSections];
    
    for (NSInteger section = 0; section < sectionsCount; section++) {
        //存儲headerView屬性
        NSIndexPath *supplementaryViewIndexPath = [NSIndexPath indexPathForRow:0 inSection:section];
        //頭視圖的高度不為0并且根據代理方法能取到對應的頭視圖的時候,添加對應頭視圖的布局對象
        if (_headerViewHeight>0 && [self.collectionView.dataSource respondsToSelector:@selector(collectionView: viewForSupplementaryElementOfKind: atIndexPath:)]) {
            
            UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:AC_UICollectionElementKindSectionHeader withIndexPath:supplementaryViewIndexPath];
            //設置frame
            attribute.frame = CGRectMake(0, self.startY, self.collectionView.frame.size.width, _headerViewHeight);
            //保存布局對象
            self.headLayoutInfo[supplementaryViewIndexPath] = attribute;
            //設置下個布局對象的開始Y值
            self.startY = self.startY + _headerViewHeight + _topAndBottomDustance;
        }else{
            //沒有頭視圖的時候,也要設置section的第一排cell到頂部的距離
            self.startY += _topAndBottomDustance;
        }
        
        //將Section第一排cell的frame的Y值進行設置
        for (int i = 0; i < _numberOfColumns; i++) {
            self.maxYForColumn[@(i)] = @(self.startY);
        }
        
        
        //計算cell的布局
        //取出section有多少個row
        NSInteger rowsCount = [self.collectionView numberOfItemsInSection:section];
        //分別計算設置每個cell的布局對象
        for (NSInteger row = 0; row < rowsCount; row++) {
            NSIndexPath *cellIndePath =[NSIndexPath indexPathForItem:row inSection:section];
            UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:cellIndePath];
            
            //計算當前的cell加到哪一列(瀑布流是加載到最短的一列)
            CGFloat y = [self.maxYForColumn[@(0)] floatValue];
            NSInteger currentRow = 0;
            for (int i = 1; i < _numberOfColumns; i++) {
                if ([self.maxYForColumn[@(i)] floatValue] < y) {
                    y = [self.maxYForColumn[@(i)] floatValue];
                    currentRow = i;
                }
            }
            //計算x值
            CGFloat x = self.cellDistance + (self.cellDistance + itemWidth)*currentRow;
            //根據代理去當前cell的高度  因為當前是采用通過列數計算的寬度,高度根據圖片的原始寬高比進行設置的
            CGFloat height = [(id<AC_WaterCollectionViewLayoutDelegate>)self.delegate collectionView:self.collectionView layout:self heightOfItemAtIndexPath:cellIndePath itemWidth:itemWidth];
            //設置當前cell布局對象的frame
            attribute.frame = CGRectMake(x, y, itemWidth, height);
            //重新設置當前列的Y值
            y = y + self.cellDistance + height;
            self.maxYForColumn[@(currentRow)] = @(y);
            //保留cell的布局對象
            self.cellLayoutInfo[cellIndePath] = attribute;
            
            //當是section的最后一個cell是,取出最后一排cell的底部Y值   設置startY 決定下個視圖對象的起始Y值
            if (row == rowsCount -1) {
                CGFloat maxY = [self.maxYForColumn[@(0)] floatValue];
                for (int i = 1; i < _numberOfColumns; i++) {
                    if ([self.maxYForColumn[@(i)] floatValue] > maxY) {
                        NSLog(@"%f", [self.maxYForColumn[@(i)] floatValue]);
                        maxY = [self.maxYForColumn[@(i)] floatValue];
                    }
                }
                self.startY = maxY - self.cellDistance + self.topAndBottomDustance;
            }
        }
        
        
        //存儲footView屬性
        //尾視圖的高度不為0并且根據代理方法能取到對應的尾視圖的時候,添加對應尾視圖的布局對象
        if (_footViewHeight>0 && [self.collectionView.dataSource respondsToSelector:@selector(collectionView: viewForSupplementaryElementOfKind: atIndexPath:)]) {
            
            UICollectionViewLayoutAttributes *attribute = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:AC_UICollectionElementKindSectionFooter withIndexPath:supplementaryViewIndexPath];
            
            attribute.frame = CGRectMake(0, self.startY, self.collectionView.frame.size.width, _footViewHeight);
            self.footLayoutInfo[supplementaryViewIndexPath] = attribute;
            self.startY = self.startY + _footViewHeight;
        }
        
    }
}

- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
    NSMutableArray *allAttributes = [NSMutableArray array];
    
    //添加當前屏幕可見的cell的布局
    [self.cellLayoutInfo enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *indexPath, UICollectionViewLayoutAttributes *attribute, BOOL *stop) {
        if (CGRectIntersectsRect(rect, attribute.frame)) {
            [allAttributes addObject:attribute];
        }
    }];
    
    //添加當前屏幕可見的頭視圖的布局
    [self.headLayoutInfo enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *indexPath, UICollectionViewLayoutAttributes *attribute, BOOL *stop) {
        if (CGRectIntersectsRect(rect, attribute.frame)) {
            [allAttributes addObject:attribute];
        }
    }];
    
    //添加當前屏幕可見的尾部的布局
    [self.footLayoutInfo enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *indexPath, UICollectionViewLayoutAttributes *attribute, BOOL *stop) {
        if (CGRectIntersectsRect(rect, attribute.frame)) {
            [allAttributes addObject:attribute];
        }
    }];
    
    return allAttributes;
}

//插入cell的時候系統會調用改方法
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewLayoutAttributes *attribute = self.cellLayoutInfo[indexPath];
    return attribute;
}

- (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewLayoutAttributes *attribute = nil;
    if ([elementKind isEqualToString:AC_UICollectionElementKindSectionHeader]) {
        attribute = self.headLayoutInfo[indexPath];
    }else if ([elementKind isEqualToString:AC_UICollectionElementKindSectionFooter]){
        attribute = self.footLayoutInfo[indexPath];
    }
    return attribute;
}

- (CGSize)collectionViewContentSize
{
    return CGSizeMake(self.collectionView.frame.size.width, MAX(self.startY, self.collectionView.frame.size.height));
}

- (void)prepareForCollectionViewUpdates:(NSArray *)updateItems
{
    [super prepareForCollectionViewUpdates:updateItems];
    NSMutableArray *indexPaths = [NSMutableArray array];
    for (UICollectionViewUpdateItem *updateItem in updateItems) {
        switch (updateItem.updateAction) {
            case UICollectionUpdateActionInsert:
                [indexPaths addObject:updateItem.indexPathAfterUpdate];
                break;
            case UICollectionUpdateActionDelete:
                [indexPaths addObject:updateItem.indexPathBeforeUpdate];
                break;
            case UICollectionUpdateActionMove:
                //                [indexPaths addObject:updateItem.indexPathBeforeUpdate];
                //                [indexPaths addObject:updateItem.indexPathAfterUpdate];
                break;
            default:
                NSLog(@"unhandled case: %@", updateItem);
                break;
        }
    }
    self.shouldanimationArr = indexPaths;
}

//對應UICollectionViewUpdateItem 的indexPathBeforeUpdate 設置調用
- (UICollectionViewLayoutAttributes*)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
{

    if ([self.shouldanimationArr containsObject:itemIndexPath]) {
        UICollectionViewLayoutAttributes *attr = self.cellLayoutInfo[itemIndexPath];
        
        attr.transform = CGAffineTransformRotate(CGAffineTransformMakeScale(0.2, 0.2), M_PI);
        attr.center = CGPointMake(CGRectGetMidX(self.collectionView.bounds), CGRectGetMaxY(self.collectionView.bounds));
        attr.alpha = 1;
        [self.shouldanimationArr removeObject:itemIndexPath];
        return attr;
    }
    return nil;
}

//對應UICollectionViewUpdateItem 的indexPathAfterUpdate 設置調用
- (nullable UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
{
    if ([self.shouldanimationArr containsObject:itemIndexPath]) {
        UICollectionViewLayoutAttributes *attr = self.cellLayoutInfo[itemIndexPath];
        
        attr.transform = CGAffineTransformRotate(CGAffineTransformMakeScale(2, 2), 0);
//        attr.center = CGPointMake(CGRectGetMidX(self.collectionView.bounds), CGRectGetMaxY(self.collectionView.bounds));
        attr.alpha = 0;
        [self.shouldanimationArr removeObject:itemIndexPath];
        return attr;
    }
    return nil;
}

- (void)finalizeCollectionViewUpdates
{
    self.shouldanimationArr = nil;
}

- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{

    CGRect oldBounds = self.collectionView.bounds;
    if (!CGSizeEqualToSize(oldBounds.size, newBounds.size)) {
        return YES;
    }
    return NO;
    
//    
//    return YES;
}

//移動相關
- (UICollectionViewLayoutInvalidationContext *)invalidationContextForInteractivelyMovingItems:(NSArray<NSIndexPath *> *)targetIndexPaths withTargetPosition:(CGPoint)targetPosition previousIndexPaths:(NSArray<NSIndexPath *> *)previousIndexPaths previousPosition:(CGPoint)previousPosition NS_AVAILABLE_IOS(9_0)
{
    UICollectionViewLayoutInvalidationContext *context = [super invalidationContextForInteractivelyMovingItems:targetIndexPaths withTargetPosition:targetPosition previousIndexPaths:previousIndexPaths previousPosition:previousPosition];

    if([self.delegate respondsToSelector:@selector(moveItemAtIndexPath: toIndexPath:)]){
        [self.delegate moveItemAtIndexPath:previousIndexPaths[0] toIndexPath:targetIndexPaths[0]];
    }
    return context;
}

- (UICollectionViewLayoutInvalidationContext *)invalidationContextForEndingInteractiveMovementOfItemsToFinalIndexPaths:(NSArray<NSIndexPath *> *)indexPaths previousIndexPaths:(NSArray<NSIndexPath *> *)previousIndexPaths movementCancelled:(BOOL)movementCancelled NS_AVAILABLE_IOS(9_0)
{
    UICollectionViewLayoutInvalidationContext *context = [super invalidationContextForEndingInteractiveMovementOfItemsToFinalIndexPaths:indexPaths previousIndexPaths:previousIndexPaths movementCancelled:movementCancelled];
    
    if(!movementCancelled){

    }
    return context;
}

@end
  • (void)prepareLayout
    方法里面的布局注釋我應該寫的很詳細了,看不懂的多看2遍。這里我再詳細說一下startY跟maxYForColumn這兩個屬性。startY值主要處理下一個視圖對象的Y值。maxYForColumn保存當前已經計算了的最下一列的cell的bottom值。布局cell的時候,cell的Y值
    取maxYForColumn里面的最小值。當section里面的cell全部布局完的時候,接下來布局尾視圖的時候,startY應該取maxYForColumn里面的最大值。

  • (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
    這個方法需要返回當前界面可見的視圖的布局對象集合,很多線性布局的效果都是在這個方法里面處理,在下面的UIollectionViewFlowLayout會有一些常見效果的處理代碼。

  • (void)prepareForCollectionViewUpdates:(NSArray )updateItems
    當調用插入、刪除和移動相關的api的時候回調用該方法(對照上面的代碼看)其中的indexPathBeforeUpdate跟indexPathAfterUpdat分別對應
    (UICollectionViewLayoutAttributes
    )initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath )itemIndexPath
    (UICollectionViewLayoutAttributes
    )finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath
    處理相對應的UICollectionViewLayoutAttributes屬性變動,我的代碼中插入是添加的indexPathAfterUpdate,刪除是添加的indexPathBeforeUpdate。

關于移動相關的,系統提供的只能9.0之后,如果想9.0之前使用必須的自定義,可以查看這篇文章可拖拽重排的CollectionView自己研究。添加移動相關的代碼在ctr處理,回調也在ctr里面處理,先貼上代碼

//添加cell長按手勢
    UILongPressGestureRecognizer *longGest = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longGest:)];
    [self.waterCollectionView addGestureRecognizer:longGest];

//對應的action
- (void)longGest:(UILongPressGestureRecognizer *)gest
{
    switch (gest.state) {
        case UIGestureRecognizerStateBegan:
        {
            NSIndexPath *touchIndexPath = [self.waterCollectionView indexPathForItemAtPoint:[gest locationInView:self.waterCollectionView]];
            if (touchIndexPath) {
                [self.waterCollectionView beginInteractiveMovementForItemAtIndexPath:touchIndexPath];
            }else{
                break;
            }
            
        }
            break;
        case UIGestureRecognizerStateChanged:
        {
            [self.waterCollectionView updateInteractiveMovementTargetPosition:[gest locationInView:gest.view]];
        }
            break;
        case UIGestureRecognizerStateEnded:
        {
            [self.waterCollectionView endInteractiveMovement];
        }
            break;
        default:
            break;
    }
}

//移動對應的回調
//系統的 
- (void)collectionView:(UICollectionView *)collectionView moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath NS_AVAILABLE_IOS(9_0)
{
    
//    if(sourceIndexPath.row != destinationIndexPath.row){
//        NSString *value = self.imageArr[sourceIndexPath.row] ;
//        [self.imageArr removeObjectAtIndex:sourceIndexPath.row];
//        [self.imageArr insertObject:value atIndex:destinationIndexPath.row];
//        NSLog(@"from:%ld      to:%ld", sourceIndexPath.row, destinationIndexPath.row);
//    }
   
}
//自定義的回調
- (void)moveItemAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath
{
    if(sourceIndexPath.row != destinationIndexPath.row){
        NSString *value = self.imageArr[sourceIndexPath.row];
        [self.imageArr removeObjectAtIndex:sourceIndexPath.row];
        [self.imageArr insertObject:value atIndex:destinationIndexPath.row];
        NSLog(@"from:%ld      to:%ld", sourceIndexPath.row, destinationIndexPath.row);
    }
}

當長按后移動手指的時候系統會一直調用
invalidationContextForInteractivelyMovingItems:withTargetPosition:previousIndexPaths: previousPosition:因為瀑布流的每個cell的frame大小不相同所以要通過代理方法不斷的更新數據源的順序,然后系統不斷調用prepareLayout方法進行重新布局,之前我是采用的系統提供的代理collectionView moveItemAtIndexPath: toIndexPath:來處理數據源的,但是發現只有布局的時候是正常的,然是松開手指后,從新加載數據發現亂了,然后打印數據源。發現數據源的順序并沒有改變,還是之前的順序。
后來發現問題出現在當移動手勢結束的時候調用的方法 [self.waterCollectionView endInteractiveMovement];
以下xcode對該方法的介紹
Ends interactive movement tracking and moves the target item to its new location.
Call this method upon the successful completion of movement tracking for a item. For example, when using a gesture recognizer to track user interactions, call this method upon the successful completion of the gesture. Calling this method lets the collection view know to end tracking and move the item to its new location permanently. The collection view responds by calling the collectionView:moveItemAtIndexPath:toIndexPath: method of its data source to ensure that your data structures are updated.
也就是說當手勢結束的時候系統會掉一次collectionView:moveItemAtIndexPath:toIndexPath:,該操作導致移動的時候進行的變換的順序又變回來了,所以只好自己寫了一個代理方法處理數據源,沒管系統的回調。

運行效果

瀑布流.gif

相關代碼下載

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容