UIScrollView实现循环轮播Banner(自定义Cell实现复用方案)

效果图

实现循环轮播图的各种方案

  • 轮播图的实现方案有很多种,大体上分为CollectionView和ScrollView实现的两个方向。其中CollectionView实现的方案较多利用了CollectionView的特性,实现比较简单,但是比较受限于CollectionView的这个框架,而且出于学习的目的出发会比较不够深入。ScrollView实现的比较大众比较令人满意的方案是使用三个UIImageView实现轮播效果。原理这里有位同学有提到,iOS无限轮播图片的两种方式,就不赘述啦。然后其实使用两个UIImageView也是可以实现的。但是不管是使用两个还是三个UIImageView,都是只限于可见范围内只存在一个banner的情况,如果可见范围内有n个banner,就需要n+2个UIImageView。所以这种方法还是不够灵活,看看有没有其他方案。

  • 本文描述的方案是通过UIScrollView实现Cell的重用机制,然后实现循环轮播的功能。这样既可以解决使用2或者3个UIImageView的不灵活的问题,也可以突破UICollectionView框架的限制,提高组件的可扩展性。

如何实现

用ScrollView实现类似CollectionView的Cell重用机制
  • 首先为什么要用ScrollView去实现Cell的重用机制呢。除了出于学习的角度考虑之外,我们知道Cell的重用机制其实就是建立一个Cell的复用池,当可见的Cell滑动出屏幕外的时候将其回收,下一个Cell将要显示于屏幕上时从复用池中拿一个Cell进行复用,如果没有就new一个。所以,假如静止时可见范围内只存在一个banner页的话,那么最多滑动时可见的就是两个,一共只需要new两个Cell出来。
  1. 我们先用一个继承于UIView的View来承载这个banner组件,叫KiraBanner。它的结构很简单,一个UIScrollView用于滑动,一个Cell的集合作为Cell的重用池,一个PageControl。然后我们在init方法中进行初始化。当然,在init方法中需要对一些KiraBanner的一些默认属性进行初始化设置,如isCircle(是否循环)、topBottomSpace(上下边距)、leftRightSpace(左右边距)等等。
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) NSMutableSet *reuseCells;
@property (nonatomic,retain)  UIPageControl *pageControl;
- (instancetype)init {
    self = [super init];
    if (self) {
        [self commonInit];
    }
    return self;
}

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self commonInit];
    }
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super initWithCoder:aDecoder];
    if (self) {
        [self commonInit];
    }
    return self;
}

- (void)commonInit {
    self.clipsToBounds = YES;
    self.pageCount = 0;
    self.isAutoScroll = YES;
    
    //默认左右间距为20,上下间距为30
    self.leftRightSpace = 20;
    self.topBottomSpace = 30;
    _currentIndex = 0;
    
    //默认自动滚动时间间隔为5s
    _autoTime = 5.0;
    _visibleRange = NSMakeRange(0, 0);
    self.reuseCells = [[NSMutableSet alloc] init];
    
    self.scrollView.scrollsToTop = NO;
    self.scrollView.clipsToBounds = NO;
    self.scrollView.showsVerticalScrollIndicator = NO;
    self.scrollView.showsHorizontalScrollIndicator = NO;
    self.scrollView.backgroundColor = [UIColor redColor];
    _currentIndex = 0;
    [self.scrollView setFrame:self.bounds];
    [self addSubview:self.scrollView];
}

2.仿照CollectionView的使用,我们需要两个委托方法来告诉KiraBanner中banner的数量以及每个banner展示的内容是什么,也就是KiraBanner的DataSource。

@protocol KiraBannerDataSource <NSObject>
@required
/**
 *  设置banner的数量
 */
- (NSInteger)numberOfItemsInKiraBanner:(KiraBanner *)banner;

/**
 *  设置某一页banner的内容
 */
- (UIView *)kiraBanner: (KiraBanner *)banner viewForItemAtIndex:(NSInteger)index;

@optional

@end

3.然后我们在KiraBanner中增加一个Class的属性,对外提供regiseterClassForCells的方法来注册Cell。

/**
 *  注册的cell类型
 */
@property (nonatomic, strong) Class cellClass;
- (void)regiseterClassForCells: (Class) cellClass {
    self.cellClass = cellClass;
}

4.dequeueReusableCell 方法拿到可复用Cell,也就是即将进入屏幕中的cell的来源

- (UIView *)dequeueReusableCell {
    UIView *cell = [self.reuseCells anyObject];
    if (cell) {
        [self.reuseCells removeObject:cell];
        NSLog(@"add a cell");
    }
    if (!cell) {
        cell = [[self.cellClass alloc] init];
        NSLog(@"produce a new cell");
    }
    return  cell;
}
  1. recycleCell方法,用于滑动时将消失于屏幕外的cell回收。
- (void)recycleCell: (UIView *)cell {
    [self.reuseCells addObject:cell];
    [cell removeFromSuperview];
}

6、我们将每页banner的size作为一个委托方法交给外部设置,同时增加私有属性pageSize。包括banner的点击等方法,都需要作为代理给外部调用。

@protocol KiraBannerDelegate <UIScrollViewDelegate>

/**
 *  设置一个page的size
 */
- (CGSize)sizeForPageInKiraBanner:(KiraBanner *)banner;

/**
 *  当前banner滚动到了哪一页
 */
- (void)didScrollToIndex:(NSInteger)index inKiraBanner:(KiraBanner *)banner;

/**
 *  点击某个cell
 */
- (void)didSelectCell:(UIView *)cell inKiraBannerAtIndex:(NSInteger)index;

/**
 *  当前page滚动过了整页的百分比
 */
- (void)didScrollPercent:(float)percent OfPageInScrollView:(UIScrollView *)scrollView;

@end

7、在外部ViewController中简单配置使用一下KiraBanner,并实现KiraBannerDataSource和Delegate的委托方法。

- (void)viewDidLoad {
    [super viewDidLoad];
    self.dataArray = @[@"1.jpg",@"2.jpg",@"3.jpg",@"4.jpg"];
    self.banner = [[KiraBanner alloc] initWithFrame:CGRectMake(0, 72, Width, Width * 9 / 16)];
    [self.view addSubview:self.banner];
    self.banner.backgroundColor = [UIColor blackColor];
    self.banner.isCircle = YES;
    self.banner.leftRightSpace = 50;
    self.banner.topBottomSpace = 30;
    self.banner.clipsToBounds = YES;
    self.automaticallyAdjustsScrollViewInsets = NO;
    [self.banner regiseterClassForCells:[UIImageView class]];
    self.banner.bannerType = KiraBannerTypeHorizontal;
    self.banner.minimumPageAlpha = 1;
    self.banner.dataSource = self;
    self.banner.delegate = self;
}
- (UIView *)kiraBanner:(KiraBanner *)banner viewForItemAtIndex:(NSInteger)index {
    UIImageView *cell = (UIImageView *)[self.banner dequeueReusableCell];
    cell.image = [UIImage imageNamed:self.dataArray[index]];
    [cell setContentMode:UIViewContentModeScaleAspectFill];
    return cell;
}

- (CGSize)sizeForPageInKiraBanner:(KiraBanner *)banner {
    return CGSizeMake(Width - 60, (Width - 60) * 9 / 16);
}

- (NSInteger)numberOfItemsInKiraBanner:(KiraBanner *)banner {
    return self.dataArray.count;
}

- (void)didSelectCell:(UIView *)cell inKiraBannerAtIndex:(NSInteger)index {
    NSLog(@"banner of index : %ld is clicked.",(long)index);
}
  1. 前置项已经准备好了,接下来就是在didScrollView的代理方法中去做cell复用的逻辑。这里封装一个方法setVisibleCellsAtContentOffset在didScrollView中调用,顾名思义就是根据contentoffset添加可见cell。那怎么做复用的逻辑呢。首先我们遍历scrollView中的每个子Cell,根据cell的frame.origin.x(拿横向滑动举例)和frame.size.width计算并与contentoffset作比较,判断该cell是否在显示区域外,如果是,则调用recycleCell方法对cell进行回收。如果不是,则调用fillPageAtIndex将cell添加到scrollView上。
- (void)setVisibleCellsAtContentOffset:(CGPoint)offset {
    
    CGPoint startPoint = CGPointMake(offset.x - _scrollView.frame.origin.x, offset.y - _scrollView.frame.origin.y);
    CGPoint endPoint = CGPointMake(startPoint.x + self.bounds.size.width, startPoint.y + self.bounds.size.height);
    
    switch (self.bannerType) {
        case KiraBannerTypeHorizontal: {
            
            for (UIView *cellView in [self cellSubView]) {
                if (cellView.frame.origin.x + cellView.frame.size.width < startPoint.x) {
                    [self recycleCell:cellView];
                }
                if (cellView.frame.origin.x > endPoint.x) {
                    [self recycleCell:cellView];
                }
            }
            
            NSInteger startIndex = MAX(0, floor(startPoint.x / _pageSize.width));
            NSInteger endIndex = MIN(_pageCount, ceil(endPoint.x / _pageSize.width));
            
            _visibleRange = NSMakeRange(startIndex, endIndex - startIndex + 1);
        
            for (NSInteger i = startIndex; i < endIndex ; i++) {
                [self fillPageAtIndex:i];
            }
        }
            break;
        case KiraBannerTypeVertical: {
            
            for (UIView *cellView in [self cellSubView]) {
                if (cellView.frame.origin.y + cellView.frame.size.height < startPoint.y) {
                    [self recycleCell:cellView];
                }
                if (cellView.frame.origin.y > endPoint.y) {
                    [self recycleCell:cellView];
                }
            }
            
            NSInteger startIndex = MAX(0, floor(startPoint.y / _pageSize.height));
            NSInteger endIndex = MIN(_pageCount, ceil(endPoint.y / _pageSize.height));
            //visibleRange表示可见的banner的index范围
            _visibleRange = NSMakeRange(startIndex, endIndex - startIndex + 1);
            
            for (NSInteger i = startIndex; i < endIndex ; i++) {
                [self fillPageAtIndex:i];
            }
        }
            break;
        default:
            break;
    }
    
}

- (NSArray *) cellSubView {
    NSMutableArray * cells = [[NSMutableArray alloc] init];
    for (UIView *subView in self.scrollView.subviews) {
        if ([subView isKindOfClass:[_cellClass class]]) {
            [cells addObject:subView];
        }
    }
    return [cells copy];
}

- (void)fillPageAtIndex:(NSInteger)index {
    //cellForIndex的实现在后面给出,现在只需要知道这是根据index取到对应的cell的方法
    UIView *cell = [self cellForIndex:index];
    
    if (!cell) {
        UIView *cell = [self.dataSource kiraBanner:self viewForItemAtIndex:index % self.numberOfItems];
        cell.clipsToBounds = YES;
        switch (self.bannerType) {
            case KiraBannerTypeHorizontal: {
                float originX = index * self.pageSize.width;
                cell.frame = CGRectMake(originX,
                                        self.topBottomSpace,
                                        self.pageSize.width,
                                        self.pageSize.height);
            }
                break;
            case KiraBannerTypeVertical: {
                float originY = index * self.pageSize.height;
                cell.frame = CGRectMake(self.leftRightSpace,
                                        originY,
                                        self.pageSize.width,
                                        self.pageSize.height);
            }
                break;
            default:
                break;
        }
    }
}
  1. cellForIndex 在上述的fillPageAtIndex方法中,我们看到需要将index对应的cell拿出来重用。但是我们的重用池是Set不是Array,无法用下标直接访问,所以需要将index和cell以某种关系联系起来。这边我们采用的是runtime的关联对象方法,将index转换为NSNumber并将其与cell对象关联在一起。关联的代码写在fillPageAtIndex中,然后在cellForIndex方法中读取,最后在recycleCell的时候需要进行remove操作。
//在fillPageAtIndex中补充
   if (cell) {
            //将cellforindex方法的地址作为objc_setAssociatedObject的key,保证唯一性
            objc_setAssociatedObject(cell, @selector(cellForIndex:),[NSNumber numberWithInteger:index], OBJC_ASSOCIATION_COPY);
            [self.scrollView insertSubview:cell atIndex:0];
        }
- (UIView *) cellForIndex: (NSInteger)index {
    for (UIView *cellView in [self cellSubView]) {
        NSNumber *value = objc_getAssociatedObject(cellView, @selector(cellForIndex:));
        if (value.integerValue == index) {
            return cellView;
        }
    }
    return nil;
}
- (void)recycleCell: (UIView *)cell {
    objc_removeAssociatedObjects(cell);
    [self.reuseCells addObject:cell];
    [cell removeFromSuperview];
}

10.最后在scrollViewDidScroll中调用setVisibleCellsAtContentOffset方法,理论上到这里是已经完成cell的复用逻辑了。

实现KiraBanner的循环轮播

1.在上述过程中已经实现了Cell的复用逻辑,其实就是非循环banner的实现。但是现在的页面没有单页滑动的效果,所以我们需要对ScrollView进行设置。

    self.scrollView.pagingEnabled = YES;

2.reloadData方法 我们需要在reloadData方法中判断是否循环,如果是,则将scrollView的contentSize设置为3组banner的大小,并且设置scrollView的初始contentOffset为第二组banner第一个的位置。

{
    _needsReload = YES;
    //reload的时候移除所有cell
    for (UIView *view in self.scrollView.subviews) {
        if ([NSStringFromClass(view.class) isEqualToString:NSStringFromClass(_cellClass.class)]) {
            [view removeFromSuperview];
        }
    }
    //停止计时器
    [self stopTimer];
    
    if (_needsReload) {
        if (self.dataSource && [self.dataSource respondsToSelector:@selector(numberOfItemsInKiraBanner:)]) {
            _numberOfItems = [self.dataSource numberOfItemsInKiraBanner:self];
            
            if (self.isCircle) {
                //如果是循环banner,则把scrollView的长度设为3组
                _pageCount = self.numberOfItems == 1 ? 1 : self.numberOfItems * 3;
            } else {
                _pageCount = self.numberOfItems == 1 ? 1 : self.numberOfItems;
            }
            
            if (_pageCount == 0) {
                return;
            }
            
            if (self.pageControl && [self.pageControl respondsToSelector:@selector(setNumberOfPages:)]) {
                [self.pageControl setNumberOfPages:self.numberOfItems];
            }
        }
        
        //重置page的宽度
        CGFloat width = _scrollView.bounds.size.width - 4 * self.leftRightSpace;
        
        _pageSize = CGSizeMake(width, width * 9 / 16);
        if (self.delegate && [self.delegate respondsToSelector:@selector(sizeForPageInKiraBanner:)]) {
            _pageSize = [self.delegate sizeForPageInKiraBanner:self];
        }
        
        [_reuseCells removeAllObjects];
        _visibleRange = NSMakeRange(0, 0);
        
        switch (self.bannerType) {
            case KiraBannerTypeHorizontal: {
                [self.scrollView setFrame:CGRectMake(0, 0, _pageSize.width, _pageSize.height)];
                [self.scrollView setContentSize:CGSizeMake(_pageSize.width * _pageCount, 0)];
                _scrollView.center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
                
                if (self.numberOfItems > 1) {
                    if (self.isCircle) {
//设置contentOffset为第二组第一个banner的位置
                        [_scrollView setContentOffset:CGPointMake(_pageSize.width * self.numberOfItems, 0) animated:NO];
                        self.page = self.numberOfItems;
                        [self startTimer];
                    } else {
                        [_scrollView setContentOffset:CGPointMake(0, 0) animated:NO];
                        self.page = self.numberOfItems;
                    }
                }
                
            }
                break;
            case KiraBannerTypeVertical: {
                [self.scrollView setFrame:CGRectMake(0, 0, _pageSize.width, _pageSize.height)];
                [self.scrollView setContentSize:CGSizeMake(0, _pageSize.height * _pageCount)];
                _scrollView.center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
                
                if (self.numberOfItems > 1) {
                    if (self.isCircle) {
                        [_scrollView setContentOffset:CGPointMake(_pageSize.height * self.numberOfItems, 0) animated:NO];
                        self.page = self.numberOfItems;
                        [self startTimer];
                    } else {
                        [_scrollView setContentOffset:CGPointMake(0, 0) animated:NO];
                        self.page = self.numberOfItems;
                    }
                }
            }
                
                break;
            default:
                break;
        }
        _needsReload = NO;
    }
    
    [self setVisibleCellsAtContentOffset:_scrollView.contentOffset];
//refreshView是做cell在移动过程中变化的函数,下面会给出
    [self refreshView];
}

3.在scrollViewDidScroll方法中对scrollView的contentOffset进行判断。由offset除以cell的宽度计算出当前是第几个cell。如果右滑超出第二组最后一个cell的范围,就将offset设置到第二组第一个cell处,同理如果左滑超出第二组第一个cell,就将offset设置到第二组最后一个cell处,从而达到循环的效果。

case KiraBannerTypeHorizontal:
{
     if (scrollView.contentOffset.x / _pageSize.width >= 2 * self.numberOfItems) {
           [scrollView setContentOffset:CGPointMake(_pageSize.width * self.numberOfItems, 0) animated:NO];
           self.page = self.numberOfItems;
     }
                    
     if (scrollView.contentOffset.x / _pageSize.width <= self.numberOfItems - 1) {
           [scrollView setContentOffset:CGPointMake((2 * self.numberOfItems - 1) * _pageSize.width, 0) animated:NO];
           self.page = 2 * self.numberOfItems - 1;
     }
 }
 break;

4.refreshView 在滑动过程中根据offset做transform形变。

- (void)refreshView {
    if (CGRectIsNull(self.scrollView.frame)) {
        return;
    }
    switch (self.bannerType) {
        case KiraBannerTypeHorizontal: {
            CGFloat offset = _scrollView.contentOffset.x;
            for (NSInteger i = self.visibleRange.location; i < self.visibleRange.location + self.visibleRange.length ; i++) {
                UIView *cell = [self cellForIndex:i];
                CGFloat origin = cell.frame.origin.x;
                CGFloat delta = fabs(origin - offset);
                CGRect originCellFrame = CGRectMake(_pageSize.width * i, 0, _pageSize.width, _pageSize.height);
                //TODO:透明度渐变
                
                if (delta < _pageSize.width) {
                    
                    CGFloat leftRightInset = self.leftRightSpace * delta / _pageSize.width;
                    CGFloat topBottomInset = self.topBottomSpace * delta / _pageSize.width;
                    
                    cell.layer.transform = CATransform3DMakeScale((_pageSize.width-leftRightInset*2)/_pageSize.width,(_pageSize.height-topBottomInset*2)/_pageSize.height, 1.0);
                    cell.frame = UIEdgeInsetsInsetRect(originCellFrame, UIEdgeInsetsMake(topBottomInset, leftRightInset, topBottomInset, leftRightInset));
                } else {
                    
                    cell.layer.transform = CATransform3DMakeScale((_pageSize.width-self.leftRightSpace * 2)/_pageSize.width,(_pageSize.height-self.topBottomSpace * 2)/_pageSize.height, 1.0);
                    
                    cell.frame = UIEdgeInsetsInsetRect(originCellFrame, UIEdgeInsetsMake(self.topBottomSpace,
                                                                                         self.leftRightSpace,
                                                                                         self.topBottomSpace,
                                                                                         self.leftRightSpace));
                }
            }
        }
            break;
        case KiraBannerTypeVertical: {
            CGFloat offset = _scrollView.contentOffset.y;
            for (NSInteger i = self.visibleRange.location; i < self.visibleRange.location + self.visibleRange.length ; i++) {
                UIView *cell = [self cellForIndex:i];
                CGFloat origin = cell.frame.origin.y;
                CGFloat delta = fabs(origin - offset);
                CGRect originCellFrame = CGRectMake(0, _pageSize.width * i, _pageSize.width, _pageSize.height);
                //TODO:透明度渐变
                
                if (delta < _pageSize.height) {
                    
                    CGFloat leftRightInset = self.leftRightSpace * delta / _pageSize.height;
                    CGFloat topBottomInset = self.topBottomSpace * delta / _pageSize.height;
                    
                    cell.layer.transform = CATransform3DMakeScale((_pageSize.width-leftRightInset*2)/_pageSize.width,(_pageSize.height-topBottomInset*2)/_pageSize.height, 1.0);
                    cell.frame = UIEdgeInsetsInsetRect(originCellFrame, UIEdgeInsetsMake(topBottomInset, leftRightInset, topBottomInset, leftRightInset));
                } else {
                    
                    cell.layer.transform = CATransform3DMakeScale((_pageSize.width-self.leftRightSpace * 2)/_pageSize.width,(_pageSize.height-self.topBottomSpace * 2)/_pageSize.height, 1.0);
                    
                    cell.frame = UIEdgeInsetsInsetRect(originCellFrame, UIEdgeInsetsMake(self.topBottomSpace,
                                                                                         self.leftRightSpace,
                                                                                         self.topBottomSpace,
                                                                                         self.leftRightSpace));
                }
            }
        }
            break;
            
        default:
            break;
    }
}

5 .自动轮播。自动轮播就是通过设置timer来控制自动播放。其实自动播放也是通过设置scrollview的contentOffset来实现。需要注意在stoptimer的时候将time置为nil,并且在合适的时机调用start及stoptimer方法。

- (void)startTimer {
    if (self.numberOfItems > 1 && self.isAutoScroll && self.isCircle) {
        NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:self.autoTime target:self selector:@selector(autoPlay) userInfo:nil repeats:YES];
        self.timer = timer;
        [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    }
}

- (void)stopTimer {
    [self.timer invalidate];
    self.timer = nil;
}

- (void)autoPlay {
    self.page ++;
    switch (self.bannerType) {
        case KiraBannerTypeHorizontal: {
            [_scrollView setContentOffset:CGPointMake(self.page * _pageSize.width, 0) animated:YES];
        }
            break;
        case KiraBannerTypeVertical: {
             [_scrollView setContentOffset:CGPointMake(0, self.page * _pageSize.height) animated:YES];
        }
            break;
        default:
            break;
    }
}

6.Cell的点击回调。cell的点击回调通过在fillPageAtIndex方法中,对cell添加UITapGestureRecognizer手势,并且在@selector方法中,通过sender.view来拿到对应的cell。然后通过objc_getAssociatedObject拿到该cell关联的index进行回调。

//在fillPageAtIndex函数中,完善以下代码
 UIView *cell = [self.dataSource kiraBanner:self viewForItemAtIndex:index % self.numberOfItems];
 UITapGestureRecognizer * tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(cellTapped:)];
[cell addGestureRecognizer:tap];
cell.userInteractionEnabled = YES;
- (void)cellTapped:(UITapGestureRecognizer *)sender {
    UIView * cell = sender.view;
    NSInteger index = -1;
    NSNumber *value = objc_getAssociatedObject(cell, @selector(cellForIndex:));
    if (value) {
        index = value.integerValue % self.numberOfItems;
    }
    if ([self.delegate respondsToSelector:@selector(didSelectCell:inKiraBannerAtIndex:)]) {
        [self.delegate didSelectCell:cell inKiraBannerAtIndex:index];
    }
}

END

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

推荐阅读更多精彩内容