Texture/AsyncDisplayKit

AsyncDisplayKit

查看github发现:AsyncDisplayKit has been moved and renamed: Texture所以我们之后将直接介绍Texture

Texture

通过阅读介绍:
Texture lets you move image decoding, text sizing and rendering, layout, and other expensive UI operations off the main thread, to keep the main thread available to respond to user interaction.我们可以看出来Texture这一库主要是帮助我们将图片解码/文本大小计算/渲染/布局等一些需要耗费主线程昂贵资源的UI操作移动到主线程之外处理,以保持主线程可以更好的去进行用户交互。也就是做到了an iOS framework for smooth and responsive interfaces。那么Texture这一开源库究竟是怎样实现这一技术点的呢?我们接着往下看

Node

介绍里说Texture的基础是node,那么node又是个什么呢?直接上图吧(官方盗图😛)


层级

结合介绍我们可以很容易的理解到其实ASDisplayNode是对UIView的抽象,就好像UIView是对CALayer的抽象,但是不同于Views只能在主线程使用,但是Nodes是线程安全的,你可以在异步线程对其进行实例化,布局,配置他们整体的层次结构等,这三个层次的详细区别如下:

CALayer
1.CALayer专注负责一切关于渲染绘制的事情
2.CALayer只能在主线程使用

UIView
1.UIView内部持有了CALayer,将layer封装起来
2.UIView充当CALayer的delegate,渲染动画时产生的时间会通知view
3.UIView可以通过.layer直接访问CALayer,从而更进一步操作渲染
4.UIView管理着CALayer的渲染,同时还管理着其他诸如点击事件等渲染之外的事情
5.UIView只能在主线程使用

ASDisplayNode
1.ASDisplayNode内部持有了UIView,将view封装起来
2.ASDisplayNode充当UIView的delegate,原本view产生的各种事件,由于已经不直接操作UIView,因此会delegate通知node进行处理
3.ASDiplayNode可以通过.view直接访问UIView
ASDiplayNode管理着UIView,接管了UIView的一些处理操作
4.ASDiplayNode通过对异步处理的改造,让使用者可以在安全的在线程进行操作

tip

使用Texture的时候常犯的一个错误是直接添加node到一个已经存在的view层,这将会造成node闪烁。正确的做法是你应该将node加入到node container classes中,这些容器负责告诉包含的节点当前处于什么状态,以便node加载数据和尽可能高效地渲染。你应该将这些类视为UIKit和Texture的结合。Texture给我们提供的容器如下node container classes大家可以自行查阅。

然后我们来看一下Texture中的node继承关系图(可以说是相当庞大了😂):


多个node类继承关系图

其中ASDisplayNode即为其基类,其他类在其基础上做相应扩展。下面我们结合实际例子来看一下Texture的具体使用:

实际使用

因为Texture中子类繁多,所以我们用在实际项目中最常用的类比于UITableView的ASTableNode来做讲解。

ASTableDataSource

相对于UITableView的UITableDataSource

// 返回table中有多少组数据
- (NSInteger)numberOfSectionsInTableNode:(ASTableNode *)tableNode;
// 相当于UITableView的下列方法
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; 
// 返回table中每组有多少条数据
- (NSInteger)tableNode:(ASTableNode *)tableNode numberOfRowsInSection:(NSInteger)section;
// 相当于UITableView的下列方法
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
// function1:返回table中对应indexPath的node
- (ASCellNode *)tableNode:(ASTableNode *)tableNode nodeForRowAtIndexPath:(NSIndexPath *)indexPath;
// function2:此外Texture还提供了下列的一个方法,作用是返回table中创建对应indexPath的node的nodeBlock
- (ASCellNodeBlock)tableNode:(ASTableNode *)tableNode nodeBlockForRowAtIndexPath:(NSIndexPath *)indexPath;

需要注意的是function1和function2可以在主线程或者其他线程被调用,Texture保证了它的线程安全,另外相比较下面的tableView对应的方法,它还有一个特性是:不应该实现重用,而是每绘制一行就调用一次。这与Texture进行绘制的逻辑有关,在后面我们在详细介绍

// 相当于UITableView的下列方法
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

ASTableViewDelegate

相对于UITableView的UITableDataDelegate

// 点击某一行的触发操作
- (void)tableNode:(ASTableNode *)tableNode didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
// 相当于UITableView的下列方法
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;

// 在UITableDataDelegate中计算每一行的高度的方法,在ASTableViewDelegate中并没有对应声明,因为你所使用的ASCellNode子类的布局需要实现- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize;这个方法去做node中的布局
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;

此处我们举一个示例代码(示例遵循MVC结构):

【M】

@interface AnimalModel : NSObject

/**
 动物名称
 */
@property (nonatomic, copy) NSString *animalName;

/**
 动物图片地址
 */
@property (nonatomic, copy) NSString *animalImageUrl;

/**
 动物详细描述
 */
@property (nonatomic, copy) NSString *animalInfo;

+ (instancetype)animailModelWithName:(NSString *)name
                            imageUrl:(NSString *)imageUrl
                                info:(NSString *)info;

@end

#import "AnimalModel.h"

@implementation AnimalModel

+ (instancetype)animailModelWithName:(NSString *)name
                            imageUrl:(NSString *)imageUrl
                                info:(NSString *)info {
    AnimalModel *model = [[AnimalModel alloc]init];
    model.animalName = name;
    model.animalImageUrl = imageUrl;
    model.animalInfo = info;
    return model;
}

@end

【V】

@interface AnimalCellNode : ASCellNode

@property (nonatomic, strong) AnimalModel *model;

@end

@interface AnimalCellNode ()<ASNetworkImageNodeDelegate>

@property (nonatomic, strong) ASTextNode *nameTextNode;

@property (nonatomic, strong) ASNetworkImageNode *photoNetImageNode;

@property (nonatomic, strong) ASTextNode *infoTextNode;

@end

@implementation AnimalCellNode
- (instancetype)init {
    if (!(self = [super init])) {
        return nil;
    }
    self.backgroundColor = [UIColor lightGrayColor];
    self.clipsToBounds = YES;
    
    self.nameTextNode = [[ASTextNode alloc]init];
    self.infoTextNode = [[ASTextNode alloc]init];
    
    self.photoNetImageNode = [[ASNetworkImageNode alloc]init];
    self.photoNetImageNode.delegate = self;
    self.photoNetImageNode.clipsToBounds = YES;
    self.photoNetImageNode.placeholderFadeDuration = 0.15;
    self.photoNetImageNode.contentMode = UIViewContentModeScaleAspectFill;
    
    // 此处要注意添加的顺序,后添加的会被加在视觉更上层
    [self addSubnode:self.photoNetImageNode];
    [self addSubnode:self.nameTextNode];
    [self addSubnode:self.infoTextNode];
    
    return self;
}

- (void)setModel:(AnimalModel *)model {
    _model = model;
    NSMutableAttributedString *nameAtt = [[NSMutableAttributedString alloc] initWithString:model.animalName];
    [nameAtt addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(0, model.animalName.length)];
    self.nameTextNode.attributedText = nameAtt;
    
    NSMutableAttributedString *infoAtt = [[NSMutableAttributedString alloc] initWithString:model.animalInfo];
    [infoAtt addAttribute:NSForegroundColorAttributeName value:[UIColor blackColor] range:NSMakeRange(0, model.animalInfo.length)];
    self.infoTextNode.attributedText = infoAtt;
    
    self.photoNetImageNode.URL = [NSURL URLWithString:model.animalImageUrl];
}


- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize {

    // 设置宽高比
    CGFloat ratio = constrainedSize.min.height/constrainedSize.min.width;

    // 设置图片的布局为刚才得出的宽高比
    ASRatioLayoutSpec *imageRatioSpec = [ASRatioLayoutSpec
                                         ratioLayoutSpecWithRatio:ratio
                                         child:self.photoNetImageNode];
    // 设置名称node在水平开始垂直结束,即左下方
    ASRelativeLayoutSpec *relativeSpec = [ASRelativeLayoutSpec
                                          relativePositionLayoutSpecWithHorizontalPosition:ASRelativeLayoutSpecPositionStart
                                          verticalPosition:ASRelativeLayoutSpecPositionEnd
                                          sizingOption:ASRelativeLayoutSpecSizingOptionDefault
                                          child:self.nameTextNode];
    // 设置名称node内边距
    ASInsetLayoutSpec *nameInsetSpec = [ASInsetLayoutSpec
                                        insetLayoutSpecWithInsets:UIEdgeInsetsMake(0, 16.0, 8.0, 0.0) child:relativeSpec];
    // 设置名称node相对于图片node的位置
    ASOverlayLayoutSpec *nameOverlaySpec = [ASOverlayLayoutSpec overlayLayoutSpecWithChild:imageRatioSpec overlay:nameInsetSpec];
    
    // 设置描述node内边距
    ASInsetLayoutSpec *descriptionTextInsetSpec = [ASInsetLayoutSpec
                                                   insetLayoutSpecWithInsets:UIEdgeInsetsMake(10.0, 20.0, 12.0, 20.0)  child:self.infoTextNode];
    
    // 组合上部(名字node+图片node)和下部(描述node)
    ASStackLayoutSpec *verticalStackSpec = [[ASStackLayoutSpec alloc] init];
    verticalStackSpec.direction = ASStackLayoutDirectionVertical;
    verticalStackSpec.children = @[nameOverlaySpec, descriptionTextInsetSpec];
    
    // 返回
    return verticalStackSpec;
}

#pragma mark - ASNetworkImageNodeDelegate
- (void)imageNode:(ASNetworkImageNode *)imageNode didFailWithError:(NSError *)error {
    NSLog(@"Image failed to load with error: \n%@", error);
}

@end

【C】

#import "AnimalViewController.h"
#import <AsyncDisplayKit/AsyncDisplayKit.h>
#import "AnimalModel.h"
#import "AnimalCellNode.h"

@interface AnimalViewController () <ASTableDataSource, ASTableDelegate>

@property (nonatomic, strong) ASTableNode *tableNode;

@property (nonatomic, strong) NSMutableArray *sourceMArr;

// 数据源
@property (nonatomic, strong) NSArray *nameArr;
@property (nonatomic, strong) NSArray *urlArr;
@property (nonatomic, strong) NSArray *infoArr;

@end

@implementation AnimalViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.title = @"十二生肖";
    self.view.backgroundColor = [UIColor whiteColor];
    // 初始化tableNode
    self.tableNode = [[ASTableNode alloc]initWithStyle:UITableViewStylePlain];
    // 设置dataSource和delegate
    self.tableNode.dataSource = self;
    self.tableNode.delegate = self;
    // 有网络列表数据请求时-设置当用户滚动还剩多少个全屏就到达数据末尾时开始抓取新的一批数据,此处设置为1屏,默认值是2,
    self.tableNode.leadingScreensForBatching = 1.0;
    // 添加node到页面上
    [self.view addSubnode:self.tableNode];
}

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];
    
    self.tableNode.frame = self.view.bounds;
}

#pragma mark - ASTableDataSource
- (NSInteger)tableNode:(ASTableNode *)tableNode numberOfRowsInSection:(NSInteger)section {
    return self.sourceMArr.count;
}

#pragma mark - ASTableDelegate
- (ASCellNode *)tableNode:(ASTableNode *)tableNode nodeForRowAtIndexPath:(NSIndexPath *)indexPath {
    AnimalCellNode *mCell = [[AnimalCellNode alloc]init];
    mCell.model = self.sourceMArr[indexPath.row];
    return mCell;
}

// 有网络列表数据请求时-表示在这次批抓取之后是否还可以进行新的批抓取
- (BOOL)shouldBatchFetchForTableNode:(ASTableNode *)tableNode {
    // 设置始终能够进行新的批抓取
    return YES;
}

// 有网络列表数据请求时-tableView展示的数据快要被展示结束,需要抓取更多数据时调用下列方法
// 当网络请求结束后必须调用-completeBatchFetching:方法通知context抓取结束,以便进行更多数据的抓取
// 需要注意:这个方法在后台队列中被调用而不是主队列。另外目前仅支持结尾数据的加载,如果需要开始数据的加载请自行考虑实现UIRefreshControl
- (void)tableNode:(ASTableNode *)tableNode willBeginBatchFetchWithContext:(ASBatchContext *)context {
    // 进行网络请求,注意接收后必须调用-completeBatchFetching:传参为YES通知抓取结束
}

// 设置ASCellNode的宽高
- (ASSizeRange)tableNode:(ASTableNode *)tableNode constrainedSizeForRowAtIndexPath:(NSIndexPath *)indexPath {
    CGFloat width = [UIScreen mainScreen].bounds.size.width;
    CGSize min = CGSizeMake(width, ([UIScreen mainScreen].bounds.size.height/3) * 2);
    CGSize max = CGSizeMake(width, INFINITY);
    return ASSizeRangeMake(min, max);
}


#pragma mark - setter && getter
- (NSMutableArray *)sourceMArr {
    if (!_sourceMArr) {
        _sourceMArr = [[NSMutableArray alloc]init];
        for (int i = 0; i < self.nameArr.count; i++) {
            AnimalModel *model = [AnimalModel animailModelWithName:self.nameArr[i] imageUrl:self.urlArr[i] info:self.infoArr[i]];
            [_sourceMArr addObject:model];
        }
    }
    return _sourceMArr;
}

- (NSArray *)nameArr {
    if (!_nameArr) {
        _nameArr = @[@"鼠",@"牛",@"虎",@"兔",@"龙",@"蛇",@"马",@"羊",@"猴",@"鸡",@"狗",@"猪"];
    }
    return _nameArr;
}

- (NSArray *)urlArr {
    if (!_urlArr) {
        _urlArr = @[@"https://hellorfimg.zcool.cn/provider_image/preview260/2236972505.jpg",
                    @"https://hellorfimg.zcool.cn/provider_image/preview260/2236972506.jpg",
                    @"https://hellorfimg.zcool.cn/provider_image/preview260/2236972507.jpg",
                    @"https://hellorfimg.zcool.cn/provider_image/preview260/2236972510.jpg",
                    @"https://hellorfimg.zcool.cn/provider_image/preview260/2236972511.jpg",
                    @"https://hellorfimg.zcool.cn/provider_image/preview260/2236972513.jpg",
                    @"https://hellorfimg.zcool.cn/provider_image/preview260/2236972515.jpg",
                    @"https://hellorfimg.zcool.cn/provider_image/preview260/2236972529.jpg",
                    @"https://hellorfimg.zcool.cn/provider_image/preview260/2236972533.jpg",
                    @"https://hellorfimg.zcool.cn/provider_image/preview260/2236972542.jpg",
                    @"https://hellorfimg.zcool.cn/provider_image/preview260/2236972552.jpg",
                    @"https://hellorfimg.zcool.cn/provider_image/preview260/2236972567.jpg"];
    }
    return _urlArr;
}

- (NSArray *)infoArr {
    if (!_infoArr) {
        _infoArr = @[@"子鼠-农历正月廿五为“填仓节”,粮商米贩祭“仓神”老鼠。清代潘荣升《帝京岁时纪胜》载: “当此新正节过,仓凛为虚,应复置而实之”,填仓节当晚不许点灯,当晚是老鼠嫁女。但老鼠嫁女日,各地并不划一,当天人们炒黄豆拌以红糖,撤于屋隅。陕西一带在屋角撒盐巴米粒,称“老鼠分钱”。 苏南则脱鞋当迎亲花轿,果皮当礼盒。",
                     @"丑牛-鞭春牛又称“打春”,意在劝民农耕。《周礼·月今》载“出土牛以送寒气”,后固定于立春。人扮“句芒神”鞭打土牛,地方官行香主礼,宣告新年劳作开始。后用纸牛,牛肚事先装入五谷,鞭后散落,象征“五谷丰登,谷流满地”。 清代每年给地方下发《春牛芒神图》。图中春牛各部位颜色根据当年干支与五行阴阳的关系设计,芒神的年纪、服饰、姿态也是如此,起到历书的作用。",
                     @"寅虎-人们认为虎是孩子的保护神。新生儿用虎骨水洗身以祛除疾病。孩子们戴虎头帽,穿虎头鞋。陕西,外甥满月舅舅送黄布虎,进门时折断虎尾寓意丢掉坎坷。山西,外甥生日舅舅送虎枕,也能当玩具。端午节还盛行把布老虎给孩子当玩具,布老虎需突出老虎的勇猛.东北鄂伦春族,小孩佩戴虎爪和虎牙以驱鬼辟邪。",
                     @"卯兔-兔与中秋祭月联系起来。明人纪坤《花王阁剩稿》载: “京中秋节多以泥传兔形,衣冠踞坐如人状,儿女把而拜之。”兔儿爷大的三尺,小的一寸,兔首人身,手执药杆,造型多属模印,施彩绘,衣着华丽",
                     @"辰龙-舞龙又称龙灯会,有竹龙、布龙、纸龙、铁皮龙等品种。程自牧的《梦梁录》载,“元宵之夜……草缚成龙,用青幕遮草上,密制灯烛万盏,望之婉蜒如双龙之状。”重庆铜梁,舞龙队伍到民居前向主人问好,主人放鞭炮欢迎,以糖果答谢。佛山彩龙以竹篾、铁丝做骨架,头尾用纸糊,龙身蒙丝绸,以剪纸、绒球装饰。",
                     @"巳蛇-福建简称“闽”,便是门里奉蛇的造型。《闽杂记》载:“福建漳州府城南门外,有南台庙,俗称蛇王庙,其神乃一僧像。”遭蛇咬者到庙中投诉即能消灾,出庙后见死蛇表明蛇神已施刑",
                     @"午马-祭马风俗古已有之。春祭马祖(马的星宿),夏祭先牧(教人牧马的神灵),秋祭马社(马厩的土地神),冬祭马步(马灾害的神)。汉族民间信仰马王爷,农家于农历六月廿三祭招,祭品为全羊一只",
                     @"未羊-羊头敬客流行于新疆哈萨克族,主人端熟羊头朝客,客人持刀先割羊头,割肋肉献长者,再割羊耳给幼者,然后任意割一块给自己. 西域民族流行“叼羊”游戏,骑手们分成几队在几百米外争夺羊,以叼羊到终点者为胜,获胜者当场把羊烧熟分给参与者。",
                     @"申猴-耍猴表演可溯至东汉,《西京赋》绘百戏,”猿狖超而高援“。唐昭宗酷爱猴戏,”赐以排袍,号孙供奉“。宋后猴戏在市井大行。明宰相胡惟庸驯养猴子十余只供驱使、歌舞。《清稗类钞》载凤阳艺人韩七,全用猴子串演戏剧,从敲锣打鼓到生旦净末丑都由猴子充当。现代动物园和马戏团也有猴表演踏单车、跳火圈、走钢丝、翻筋斗……",
                     @"酉鸡-雄鸡勇斗,古人想象其有辟邪神力。清初陈昊子《花镜》:“雄鸡能角胜,目能辟邪”。南朝宗慎《荆楚岁时记》载: “正月一日……贴画鸡户上,悬苇索于其上,插桃符其傍,百鬼畏之”。正月初一不杀鸡,这天是鸡的生日。成都一带春节期间仍流传在门楣贴鸡画",
                     @"戌狗-正月十六是瑶族“盘王节”。这一天以祭奠瑶族先祖盘瓠为主:跳祭祀舞蹈盘王舞; 举行还盘王愿的祭仪,宰牛祭盘王; 颂唱“盘王大歌” 。瑶族人上衣前短后长,女子腰带故意后坠一截,意在模仿狗尾巴",
                     @"亥猪-汉族凡重大祭祀必用猪祭品,并以猪头为重,俗称“猪头三牲” 。吴谷人《新年杂咏》:“杭俗,岁终祀神尚猪首……选皱纹如寿字者,谓之‘寿字猪头’ ”现今江浙一带在腊月仍储备腌制咸猪头为年货。清明节广东人爱用烤猪祭祖,俗语“太公分猪肉,人人有份”,形容祭后全家分食祭品"];
    }
    return _infoArr;
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

@end

效果如下所示:


示例

由于我们使用了ASTableNode,就再多扩展下:ASTableNode 也有“页”的概念,但和我们向服务器进行分页请求时的“页”不同,这里的“页”是一屏的概念。也就是说,我们在 viewDidLoad 中设置 self.tableNode.view.leadingScreensForBatching = 1.0; 这一句时,表示当屏幕中还剩下一屏(页)的数据就要显示完的时候,ASTableNode 会自动进行抓取。

但是我们一次向服务器能够请求的页大小并不一定能够填满一屏。比如分页查询的页大小是 4,然而 4 条数据并不足以填满一个屏幕,因此 ASTableNode 还会再请求一次分页查询,然后检查(会进行一个预布局,计算数据显示时的尺寸)是否填满一屏,如果不够,会再次请求,直至填满一屏。

也就是说,数据在真正得到显示之前就已经进行了布局(异步的)。当需要显示的时候,仅仅是一个绘制而已,这样绘制的速度就会非常快,滚动体验会无比顺滑。

注意到一个细节没有?不管是 ASNetworkImageNode 还是 ASTextNode,它们都不需要设置框架(frame)。这是因为它们的构建和布局是分开进行的(这就是原框架名字中 Async 异步的由来了),在初始化方法中,你只管构建好了,布局在另一个方法中进行:

  • (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize

异步布局

Texture到底是怎样实现异步布局的呢?ASDisplayNode 子类会实现layoutSpecThatFits: 方法。一个 ASLayoutSpec 对象负责循环计算所有子节点的大小和位置。然后ASLayoutSpec还会计算其父节点的大小和位置。layoutSpecThatFits:方法参数是一个 ASSizeRange。它有两个 CGSize 属性,一个最小 size,一个最大 size。分别定义该节点的最小尺寸和最大尺寸。

typedef struct {
  CGSize min;
  CGSize max;
} ASSizeRange;

Texture提供了很多种ASLayoutSpec,它们是:

ASStackLayoutSpec: 允许你定义一个水平或垂直的子节点栈。它的 justifyContent 属性决定栈在相应方向上的子节点之间的间距。alignItems 属性决定了它们在另一个坐标轴上的间距。
ASOverlayLayoutSpec: 允许你拉伸一个元素横跨到另一个元素。被覆盖的对象必须要有一个固定的 content size,否则无法工作。
ASRelativeLayoutSpec: 一种相对布局,允许将一个东西以相对位置放置在它的有效空间内。
ASInsetLayoutSpec: 一个 inset 布局,允许你在一个已有的对象的基础上添加某些间距。你想在你的 cell 四周加上一些像素的边距吗?用这个就对了。

通过把布局在后台线程计算,不影响用户交互,从而实现【异步】的概念。

参考链接:
1
2
3

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