iOS自动布局框架Masonry的使用及其原理

作者: 温桂龙
部门: 新业务支持研发团队

0、iOS布局的发展史

在iOS发展早期,由于iPhone屏幕大小是固定的,开发者不需要考虑因屏幕大小差异而造成的适配的问题,在开发应用进行布局时,采用的是直接通过代码计算控件在其父控件的位置和大小的方式对UI控件进行布局。

再到后面,在iPad推出后,apple公司推出autoresizing用于指定当UI控件的父控件发生变化时如何调整布局,达到屏幕适配的效果。

到了iOS6推出的时候,iPhone以及iPad的屏幕尺寸逐渐变多,为了能更好地对不同的屏幕进行适配,apple公司推出了基于约束的、描述性的autoLayout布局系统对不同屏幕的iOS设备进行屏幕适配。

在iOS8中,apple公司推出了sizeClass布局系统用以支持更多屏幕大小不一样的iOS设备的屏幕适配。

1、iOS布局方式的比较

代码计算frame

无论怎样的布局方式,最基本的原则都是指定控件的位置与大小。如果屏幕的大小是固定不变的,则只有横屏和竖屏两种情况,通过代码计算出控件的位置即可。frame是指控件在其父控件中的位置和大小,在iOS中,可以使用具体的frame初始化view,view被创建后也可以对frame进行修改。

frame示例:

//创建一个view,其相对父控件左边距为10、上边距为10;宽高均为10
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(10, 10, 10, 10)];

//修改view的frame,左、上边距均改为15;宽高分别改为20和30
view.frame = CGRectMake(15, 15, 20, 30);

使用直接计算frame的方式,控件的位置与大小都是直接写死的,基本上是没有适配可言的。

autoresizing

使用autoresizing布局时,可以指定view的autoresizingMask属性,即当UI控件的父控件发生变化时如何调整布局的属性,共有六个枚举值。

autoresizingMask属性及autoresizing布局示例:

UIViewAutoresizingNone//默认值,不自动调整布局
UIViewAutoresizingFlexibleLeftMargin //保持右边距不变,调整左边距
UIViewAutoresizingFlexibleWidth//保持左、右边距不变,调整控件的宽度
UIViewAutoresizingFlexibleRightMargin//保持左边距不变,调整右边距,
UIViewAutoresizingFlexibleTopMargin//保持下边距不变,调整上边距
UIViewAutoresizingFlexibleHeight//保持上、下边距不变,调整控件高度
UIViewAutoresizingFlexibleBottomMargin//保持上边距不变,调整下边距,

UIView *view = [[UIView alloc] init];
//父控件变化时,保持宽度与高度不变,自动调整与父控件的左右边距与上下边距
view.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;

使用autoresizing进行布局,控件调整布局依赖于父控件的布局变化,所以只适用于描述父子控件之间的调整关系,而不适用于描述同一层级之间的控或者没有层级关系的控件之间的关系。使用autoresizing,对屏幕适配而言,依然有比较大的局限性。

autoLayout

autoLayout的主要概念是参照与约束。它关注的不是控件位置与大小的具体数值,而是关注控件属性参照另一个控件的属性的约束关系,该约束关系一般是线性关系。

autoLayout示例:

UIView *view = [[UIView alloc] init];
NSLayoutConstraint *lc =[NSLayoutConstraint constraintWithItem:anotherView//被约束的控件
                                                     attribute:NSLayoutAttributeLeft
                                                     relatedBy:NSLayoutRelationEqual
                                                        toItem:view//参照控件
                                                     attribute:NSLayoutAttributeLeft
                                                    multiplier:2//线性关系倍数
                                                      constant:0];//乘以倍数后需要加的数值

[view addConstraint:lc];//添加约束到参照控件

添加约束的规则:添加到其最近的父控件。

  • 兄弟控件添加到共同的父控件。
  • 父子控件添加到父控件。
  • 非父子控件添加到最近层级的父控件

autoLayout约束属性:

NSLayoutAttributeLeft,
NSLayoutAttributeRight,
NSLayoutAttributeTop,
NSLayoutAttributeBottom,
NSLayoutAttributeLeading,
NSLayoutAttributeTrailing,
NSLayoutAttributeWidth,
NSLayoutAttributeHeight,
NSLayoutAttributeCenterX,
NSLayoutAttributeCenterY,
NSLayoutAttributeLastBaseline,

NSLayoutAttributeBaseline NS_SWIFT_UNAVAILABLE("Use 'lastBaseline' instead") = NSLayoutAttributeLastBaseline,

NSLayoutAttributeFirstBaseline NS_ENUM_AVAILABLE_IOS(8_0),


NSLayoutAttributeLeftMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeRightMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeTopMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeBottomMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeLeadingMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeTrailingMargin NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeCenterXWithinMargins NS_ENUM_AVAILABLE_IOS(8_0),
NSLayoutAttributeCenterYWithinMargins NS_ENUM_AVAILABLE_IOS(8_0),

NSLayoutAttributeNotAnAttribute = 0

使用autoLayout进行布局,不再关注控件的位置与大小的具体数值,而是可以通过描述控件的约束条件对控件进行调整,可以设置约束的属性相当的全面,而且对于不同层级的控件,只需要将约束添加到其最近的父控件即可,这就使得使用autoLayout进行布局,可以很方便地进行屏幕适配工作。但是可以看出,使用纯代码布局的话,autoLayout添加一个约束的代码量将会比较多。这也是使得基于autoLayout的第三方布局框架Masonry流行的原因之一。

sizeClass

在iOS8中推出的sizeClass,将屏幕的宽高抽象为三种 ,即所有的设备分为3*3共9种

  • Compact:紧凑的
  • Regular:正常的
  • Any:任意的

通过指定屏幕的宽高类型,就不再需要根据屏幕的具体尺寸去进行适配,甚至也不再有横竖屏的概念了。但是sizeClass只是对屏幕的宽高进行了分类,舍弃了具体尺寸的概念,具体的适配工作仍然需要autoLayout来实现。

小结

计算frame是最原始也是最直接的布局方式,它直接指定来控件的大小和位置,而这也是布局的根本所在,无论autoresizing或autoLayout最终也只是为了确定在不同大小的屏幕下控件的大小和位置。直接计算frame的方式或autoresizing,在iOS设备越来越多的现状下显然难以优雅地解决屏幕适配问题。而无论是手写代码布局,或者是使用Xib、Stroyboard等可视化布局,autoLayout都是目前比较好选择。但是autoLayout亦存在代码重复且代码量比较多的问题。而基于autoLayout的Masonry框架是对原生autoLayout的一种优化,可以使autoLayout用起来变得相对简洁优雅。本文后面的内容主要探讨的也是Masonry框架的使用及其原理。

2、Masonry的基本使用

项目中使用Masonry:

在项目的podfile文件中添加Masonry,然后相应地pod install即可。

target 'Demo' do
    platform :ios, '8.0'
    project 'Demo.xcodeproj'
        
    pod 'Masonry'
        
end
Masonry布局的基本例子
UIView *redView = [[UIView alloc] init];
redView.backgroundColor = [UIColor redColor];
self.redView = redView;
[self.view addSubview:redView];
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.bottom.mas_equalTo(0);//上下边距均为0
    make.centerX.mas_equalTo(self.view.mas_centerX);//X轴中心对齐self.view的X轴中心。
    make.width.mas_equalTo(self.view.frame.size.width/3.0);//宽度为self.view的宽度1/3。
}];

UIView *greenView = [[UIView alloc] init];
greenView.backgroundColor = [UIColor greenColor];
self.greenView = greenView;
[self.view addSubview:greenView];
[greenView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.top.bottom.mas_equalTo(0);
    make.right.mas_equalTo(redView.mas_left);
}];

UIView *blueView = [[UIView alloc] init];
blueView.backgroundColor = [UIColor blueColor];
self.blueView = blueView;
[self.view addSubview:blueView];
[blueView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.right.top.bottom.mas_equalTo(0);
    make.left.mas_equalTo(redView.mas_right);
}];

上面代码定义一个redView,上下边距为0,宽度为self.view.frame即页面宽度的1/3,水平居中。redView左边和右边的其余空间分别为一个greenView和一个blueView,这时就可以以redView为基准约束greenView的右边与blueView的左边。

make.right.mas_equalTo(redView.mas_left)//约束(greenView的)right对齐redView的left。
make.left.mas_equalTo(redView.mas_right)//约束(blueView的)left对齐redView的right。

同时,Masonry支持链式调用,make.top.bottom.mas_equalTo(0)等同于

make.top.mas_equalTo(0)

make.bottom.mas_equalTo(0)

1.jpg
Masonry框架主要约束
mas_left
mas_top
mas_right
mas_bottom
mas_leading
mas_trailing
mas_width
mas_height
mas_centerX
mas_centerY
mas_baseline
- (MASViewAttribute *(^)(NSLayoutAttribute))mas_attribute

#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000) || (__MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)

mas_firstBaseline
mas_lastBaseline

#endif

#if (__IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (__TV_OS_VERSION_MIN_REQUIRED >= 9000)

mas_leftMargin
mas_rightMargin
mas_topMargin
mas_bottomMargin
mas_leadingMargin
mas_trailingMargin
mas_centerXWithinMargins
mas_centerYWithinMargins

#endif

#if (__IPHONE_OS_VERSION_MAX_ALLOWED >= 110000) || (__TV_OS_VERSION_MAX_ALLOWED >= 110000)

mas_safeAreaLayoutGuide
mas_safeAreaLayoutGuideTop
mas_safeAreaLayoutGuideBottom
mas_safeAreaLayoutGuideLef
mas_safeAreaLayoutGuideRight

#endif

Masonry框架的主要约束是对NSLayoutAttribute的封装,与autoLayout的约束基本是一致的,但是由于Masonry的封装,在使用时,代码量变得更少,而且更加的简练直观。

3、Masonry部分源码分析

添加约束时,调用的为mas_makeConstraints,参数^(MASConstraintMaker *make) 为一个block,

[redView mas_makeConstraints:^(MASConstraintMaker *make) { }];

在View+MASAdditions.m中,可以看到mas_makeConstraints的实现,主要是MASConstraintMaker的声明与install工作

- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
    self.translatesAutoresizingMaskIntoConstraints = NO;
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    block(constraintMaker);
    return [constraintMaker install];
}

这里block(constraintMaker)的block,就是前面传入的block,要搞懂mas_makeConstraints干了什么,我们先看block中做了些什么,前面为redView添加约束时是这么写的:

[redView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.bottom.mas_equalTo(0);
        make.centerX.mas_equalTo(self.view.mas_centerX);
        make.width.mas_equalTo(self.view.frame.size.width/3.0);
    }];

暂且先不管链式调用的原理,这里调用了top、bottom、mas_equalTo、centerX、width等方法,以非链式调用的思路分析Masonry的源码。

- (MASConstraint *)top {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
- (MASConstraint *)centerX {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeCenterX];
}
- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    if ([constraint isKindOfClass:MASViewConstraint.class]) {
        //replace with composite constraint
        NSArray *children = @[constraint, newConstraint];
        MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
        compositeConstraint.delegate = self;
        [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
        return compositeConstraint;
    }
    if (!constraint) {
        newConstraint.delegate = self;
        [self.constraints addObject:newConstraint];
    }
    return newConstraint;
}

top、bottom、centerX、width等方法都是类似的,调用addConstraintWithLayoutAttribute,传入对应的NSLayoutAttribute属性。在addConstraintWithLayoutAttribute中,通过initWithFirstViewAttribute创建一个带有firstViewAttribute的MASViewConstraint对象newConstraint,由于传入的constraint为nil ,所以执行的为将约束添加到self.constraints,并返回newConstraint。

再看mas_equalTo方法:

#define mas_equalTo(...)                 equalTo(MASBoxValue((__VA_ARGS__)))
- (MASConstraint * (^)(id))equalTo {
    return ^id(id attribute) {
        return self.equalToWithRelation(attribute, NSLayoutRelationEqual);
    };
}
- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attribute, NSLayoutRelation relation) {
        if ([attribute isKindOfClass:NSArray.class]) {
            NSAssert(!self.hasLayoutRelation, @"Redefinition of constraint relation");
            NSMutableArray *children = NSMutableArray.new;
            for (id attr in attribute) {
                MASViewConstraint *viewConstraint = [self copy];
                viewConstraint.layoutRelation = relation;
                viewConstraint.secondViewAttribute = attr;
                [children addObject:viewConstraint];
            }
            MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
            compositeConstraint.delegate = self.delegate;
            [self.delegate constraint:self shouldBeReplacedWithConstraint:compositeConstraint];
            return compositeConstraint;
        } else {
            NSAssert(!self.hasLayoutRelation || self.layoutRelation == relation && [attribute isKindOfClass:NSValue.class], @"Redefinition of constraint relation");
            self.layoutRelation = relation;
            self.secondViewAttribute = attribute;
            return self;
        }
    };
}

这里mas_equalTo做的做核心的事情是保证之前未设置过relation的前提下把NSLayoutRelationEqual和参数attribute传入了对应的layoutRelation 和secondViewAttribute中。secondViewAttribute中包含item和attribute,这些参数将会在后面调用autoLayout时被用到。

再回过头来看MASConstraintMaker的install方法:

- (NSArray *)install {
    if (self.removeExisting) {
        NSArray *installedConstraints = [MASViewConstraint installedConstraintsForView:self.view];
        for (MASConstraint *constraint in installedConstraints) {
            [constraint uninstall];
        }
    }
    NSArray *constraints = self.constraints.copy;
    for (MASConstraint *constraint in constraints) {
        constraint.updateExisting = self.updateExisting;
        [constraint install];
    }
    [self.constraints removeAllObjects];
    return constraints;
}

这里的install方法的实现,除了基本的逻辑处理,最终通过[constraint install]调用MASConstraint的install方法。MASConstraint为一个接口,在非链式调用时,我们这里用到的它的实现类为MASViewConstraint。

MASViewConstraint的install方法的代码:

- (void)install {
    if (self.hasBeenInstalled) {
        return;
    }
    
    if ([self supportsActiveProperty] && self.layoutConstraint) {
        self.layoutConstraint.active = YES;
        [self.firstViewAttribute.view.mas_installedConstraints addObject:self];
        return;
    }
    
    //获取firstLayoutItem、firstLayoutAttribute、secondLayoutItem、secondLayoutAttribute
    MAS_VIEW *firstLayoutItem = self.firstViewAttribute.item;
    NSLayoutAttribute firstLayoutAttribute = self.firstViewAttribute.layoutAttribute;
    MAS_VIEW *secondLayoutItem = self.secondViewAttribute.item;
    NSLayoutAttribute secondLayoutAttribute = self.secondViewAttribute.layoutAttribute;
    
    //若不存在secondViewAttribute,则父控件为secondLayoutItem,secontAttribute
    //firstLayoutAttribute保持一致
    if (!self.firstViewAttribute.isSizeAttribute && !self.secondViewAttribute) {
        secondLayoutItem = self.firstViewAttribute.view.superview;
        secondLayoutAttribute = firstLayoutAttribute;
    }
    
    //设置AutoLayout约束
    MASLayoutConstraint *layoutConstraint
        = [MASLayoutConstraint constraintWithItem:firstLayoutItem
                                        attribute:firstLayoutAttribute
                                        relatedBy:self.layoutRelation
                                           toItem:secondLayoutItem
                                        attribute:secondLayoutAttribute
                                       multiplier:self.layoutMultiplier
                                         constant:self.layoutConstant];
    
    layoutConstraint.priority = self.layoutPriority;
    layoutConstraint.mas_key = self.mas_key;
    
    //若存在secondViewAttribute.view,添加约束到最近的父控件
    if (self.secondViewAttribute.view) {
        MAS_VIEW *closestCommonSuperview = [self.firstViewAttribute.view mas_closestCommonSuperview:self.secondViewAttribute.view];
        NSAssert(closestCommonSuperview,
                 @"couldn't find a common superview for %@ and %@",
                 self.firstViewAttribute.view, self.secondViewAttribute.view);
        self.installedView = closestCommonSuperview;
    } else if (self.firstViewAttribute.isSizeAttribute) {//如果size attribute,添加到则自身
        self.installedView = self.firstViewAttribute.view;
    } else {//其他情况添加到父控件
        self.installedView = self.firstViewAttribute.view.superview;
    }


    MASLayoutConstraint *existingConstraint = nil;
    if (self.updateExisting) {//需要更新约束
        existingConstraint = [self layoutConstraintSimilarTo:layoutConstraint];
    }
    if (existingConstraint) {//已存在,替换约束
        // just update the constant
        existingConstraint.constant = layoutConstraint.constant;
        self.layoutConstraint = existingConstraint;
    } else {//其他情况直接添加约束
        [self.installedView addConstraint:layoutConstraint];
        self.layoutConstraint = layoutConstraint;
        [firstLayoutItem.mas_installedConstraints addObject:self];
    }
}

这里最终就到了真正的autoLayout了,调用autoLayout设置约束的主要参数的来源是前面的top、mas_equalTo等方法赋的值。

 MASLayoutConstraint *layoutConstraint
        = [MASLayoutConstraint constraintWithItem:firstLayoutItem
                                        attribute:firstLayoutAttribute
                                        relatedBy:self.layoutRelation
                                           toItem:secondLayoutItem
                                        attribute:secondLayoutAttribute
                                       multiplier:self.layoutMultiplier
                                         constant:self.layoutConstant];

以上就是再非链式调用时,Masonry源码调用方法分析。

4、链式调用原理分析

如果需要将上下边距同时设置为0,我们可以使用

make.top.bottom.mas_equalTo(0)

如果需要将左边对齐anotherView的右边,但同时保持一定的offset,可以使用

make.left.equalTo(anotherView.mas_right).offset(8)

这种用法就是所谓的链式调用。

- (MASConstraint *)top {
    return [self addConstraintWithLayoutAttribute:NSLayoutAttributeTop];
}
@property (nonatomic, strong, readonly) MASConstraint *left;
@property (nonatomic, strong, readonly) MASConstraint *top;
@property (nonatomic, strong, readonly) MASConstraint *right;
@property (nonatomic, strong, readonly) MASConstraint *bottom;
@property (nonatomic, strong, readonly) MASConstraint *leading;
@property (nonatomic, strong, readonly) MASConstraint *trailing;
......

留意一个细节,top等方法,返回值类型是MASConstraint,而MASConstraintMaker中,定义了一系列的属性,也就是说,make.top可以视为top属性的getter方法,而该方法返回的是一个带参数的block,于是可以在block中接受这个参数,再次调用left等其它方法,这个是链式调用的基础。

再看回addConstraintWithLayoutAttribute方法

- (MASConstraint *)constraint:(MASConstraint *)constraint addConstraintWithLayoutAttribute:(NSLayoutAttribute)layoutAttribute {
    MASViewAttribute *viewAttribute = [[MASViewAttribute alloc] initWithView:self.view layoutAttribute:layoutAttribute];
    MASViewConstraint *newConstraint = [[MASViewConstraint alloc] initWithFirstViewAttribute:viewAttribute];
    if ([constraint isKindOfClass:MASViewConstraint.class]) {
        //replace with composite constraint
        NSArray *children = @[constraint, newConstraint];
        MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
        compositeConstraint.delegate = self;
        [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
        return compositeConstraint;
    }
    if (!constraint) {
        newConstraint.delegate = self;
        [self.constraints addObject:newConstraint];
    }
    return newConstraint;
}

当链式调用时如make.top.left时

  • 第一步,make.top,走的为if (!constraint),正常添加约束,并执行了newConstraint.delegate = self。
  • 第二步,make.top.left,此时,由于第一步执行过了newConstraint.delegate = self,constraint不再为nil,进入的为下面代码,将constraint的delegate设置到compositeConstraint,将constraint存入_childConstraints。
if ([constraint isKindOfClass:MASViewConstraint.class]) {
        NSArray *children = @[constraint, newConstraint];
        MASCompositeConstraint *compositeConstraint = [[MASCompositeConstraint alloc] initWithChildren:children];
        compositeConstraint.delegate = self;
         //以新的compositeConstraint替换原来的constraint
        [self constraint:constraint shouldBeReplacedWithConstraint:compositeConstraint];
        return compositeConstraint;
    }
- (id)initWithChildren:(NSArray *)children {
    self = [super init];
    if (!self) return nil;

    _childConstraints = [children mutableCopy];
    for (MASConstraint *constraint in _childConstraints) {
        constraint.delegate = self;
    }

    return self;
}

如果是继续链式调用,如make.top.left.right,则会有

  • 第三步,不再进入if ([constraint isKindOfClass:MASViewConstraint.class])和 if (!constraint) ,因为在第二步中以新的compositeConstraint替换原来的constraint,已经不再满足第二步条件了,而是直接返回newConstraint对象,并将将constraint存入_childConstraints。

后面继续链式调用,都是继续执行第三步了。

这样就不断保存了链式调用的约束,知道前面所述的block结束。

而MASConstraint接口的另一个实现类MASCompositeConstraint,即是处理链式调用形成的复合约束的类。

- (MASConstraint * (^)(id, NSLayoutRelation))equalToWithRelation {
    return ^id(id attr, NSLayoutRelation relation) {
        for (MASConstraint *constraint in self.childConstraints.copy) {
            constraint.equalToWithRelation(attr, relation);
        }
        return self;
    };
}

在其equalToWithRelation方法中,对self.childConstraints的约束进行了遍历,并调用了MASConstraint的equalToWithRelation方法进行设置,达到了对通过链式调用添加的各个约束进行设置的效果。

5、总结

本文对代码计算frame、autoresizing、autoLayout、sizeClass这几种布局方式做了简单的概要介绍,并分析了基于autoLayout的Masonry框架源码以及其链式调用的实现。可以看出,Masonry在链式调用、MASConstraint接口等无论在代码规范、设计模式等方面都做得很好,是一个相当优秀的框架。如果是OC纯代码布局的话,使用Masonry框架将会是一个很好的选择。

鉴于水平有限,上文难免会有纰漏之处,希望大家能指出文中纰漏或不足之处。

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

推荐阅读更多精彩内容