ConstraintLayout学起来!

说点大家的观点,有点啰嗦

1、ConstraintLayout允许通过无嵌套视图方式创建大型而复杂的布局。类似于RelativeLayout,所有视图均根据同级视图和父级布局之间的关系进行布局,但是它比RelativeLayout更灵活,更易于使用。

当然,这里有人是有不同意见的,所有控件都是同一个父View,会显得比较散,分模块操作时效率较低。毕竟就目前来说,也就只有Group来控制一组控件的显示与否。
2.0之后添加的Layer会改善这种情况。

而且就正常情况下,在ConstaintLayout里面添加一些其它ViewGroup有时也是无可避免的嘛

2、Android Studio 同时还提供特有的布局编辑器,ConstraintLayout的布局内容均可以通过拖拉拽(以及编辑器的右边属性栏)达成。

不过现在阶段,惯性思维下,对于拖拉拽的不习惯以至于大多人还在观望,即便使用上了,也会习惯性的使用编写XML的方式!
当然有时候只需要修改一行或几行属性,手写会来得快。

哦对了,喜欢手写的直接在编辑器的右边属性栏一个个添加约束,也未尝不可。

另外2.0的基于ConstaintLayout的MotionLayout据说是特强大的动画布局,Android Studio 4.0 版本也提供了拖拉拽来实现,到时候动画可能就看你的想象力了。

3、毕竟Google对于约束布局的支持是很大的,ConstaintLayout之于RelativeLayout,就像RecycleView之于ListView,终究强者是要上位的。大势所趋!大家都在学,你不学,落后就要挨打咯。

都9102年了,别装睡了,你还能学。

共勉。

使用方式

1. 导入包:
dependencies {
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    // androidx:
    // implementation "androidx.constraintlayout:constraintlayout:1.1.3"
}

目前最新版本是1.1.3。 2.0版本已经在测试中了,等到2.0,新的特性就更多了!
什么Layer、Flow、MotionLayout等。

基础使用

2. 相对定位

定义一个控件的位置,起码要使其在纵横方向各至少拥有一个相对约束---即相对于其它控件的位置。看下面这个图

相对约束

布局编辑器显示控件 C 在 A下面, 但是 C并没有设置垂直方向的约束,运行时会默认在父布局的顶端,与我们所预想的发生偏差。

相对约束的基本属性格式是

layout_constraintDirection1_toDirection2Of

Direction1和Direction2可以是Left、Top、Right、Bottom其中任意的左右或者上下的组合,也可以是Start、End组合(根据从左向右布局,Start == Left,End == Right)。后续出现的Direction均代表这个属性。

相对约束-方向

从属性名我们就可直译出其代表的意思,比如:

layout_constraintTop_toBottomOf="@id/btn1",约束该控件的上边界在btn1的下边界下面,且若不设置边距(margin),则与btn1下边界在同一水平线上。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <Button
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        android:text="button1"/>
    <Button
        android:id="@+id/btn2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/btn1"
        app:layout_constraintLeft_toRightOf="@id/btn1"
        android:text="button2"/>

</android.support.constraint.ConstraintLayout>

那么例子中btn2的相对约束就是在btn1的右下角。(btn1中的parent表示相对父布局的位置)

相对约束-例子

当然还有一种常用的文字基线对齐,属于垂直方向的约束,与RelativeLayout的alignBaseLine属性相似

layout_constraintBaseline_toBaselineOf
相对约束-基线
3. Margin

用于设置与其它控件的边距。
与其它Layout类型不同的是,Margin的设置依赖于控件是否有添加相应方向的相对约束。

当设置了某个方向的边界的相对约束之后,该方向设置的margin才能生效!
否则margin无效。

    <Button
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        android:layout_marginRight="20dp"
        android:text="button1"/>

上述android:layout_marginRight="20dp"无效。当然如果你通过布局编辑器操作的话,本身就无法写入这一条无效语句,如果你用的是xml写入的这一条,然后再去编辑器编辑该控件的时候,会发现这条语句也会被优化而删除!

特殊情况:设置了Margin的控件的visibility属性变为View.Gone
  • View.Gone 的控件在约束布局中,依然可以通过findViewById()找到,只是宽高都为0dp,即视为一个点,且其每个方向的margin也都变为0
View.Gone
  • 由于View A 已经Gone,则其他依赖于View A的,如View B的位置会有相应的变化,防止出现显示异常,View B通过可以设置layout_goneMarginDireaction来设置当View A Gone时候的间距。如
      app:layout_goneMarginLeft="20dp"

4.圆形定位

相较于相对定位,圆形定位的属性就很简单了,只有如下三个约束

<Button android:id="@+id/buttonA" ... />
<Button android:id="@+id/buttonB" ...
  app:layout_constraintCircle="@+id/buttonA"
  app:layout_constraintCircleRadius="100dp"
  app:layout_constraintCircleAngle="45" />

解读下就是:以ButtonA的中心点作为原点,从原点处以Y轴正半轴向右偏离45度画一条长度为100dp的线段,线段的另一个顶点为ButtonB的中心点!

圆形定位

值得注意的是,圆形定位优先于相对定位。

Android Studio 当前版本(3.5)并没有直接支持拖拽来写这些角度。。不写相对约束居然还飘红,有点过分

5. 居中与倾向(Biaz)

这个就有点意思

<!--水平方向添加左右两条约束-->
<TextView
    android:id="@+id/tv1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="TextView"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintStart_toEndOf="@+id/btn1"
    app:layout_constraintEnd_toStartOf="@+id/btn2"
/>

当控件在水平(或垂直)方向左右(上下)同时使用了相对约束,那么控件会位于两个约束控件的正中间。

比如说上述代码,tv1的约束条件是,在btn1的右边,btn2的左边,按照规定,三个控件在水平线上应该是紧紧相邻的,但是这里不可能做到,除非tv1的宽度刚好等于btn1与btn2的间距。

所以在这种约束规则下,tv1的表现为位于btn1和btn2的正中间。

居中

当然有时候需要的不仅是居中而是中间偏左,或者偏上之类的。
那么要需要设置:

//居中默认为 0.5,取值0.0-1.0
 //小于0.5即偏左(也不一定,就比如上述例子,若btn2与btn1间距小于tv的宽度
 //那么小于0.5就偏右了)
app:layout_constraintHorizontal_bias = "0.5"  

//垂直方向同理
app:layout_constraintVertical_bias = "0.5"

另外1,在此规则下,若将相对应的宽高设置为0dp,则控件会撑满间距!同时bias设置无效。

另外2,上述这个例子中,根据tv1的约束,若btn2在btn1的左边会发生什么呢?

实际上tv1的中心点依然会在这btn1和btn2的两条约束边界的中间,此时设置bias小于0.5时tv1会偏右。

6.宽高比

作为ConstraintLayout的子控件,其宽高一般是不支持设置为match_parent的,而是使用match_constraint代替(xml中使用0dp表示match_constraint)。之所以是“一般不支持”,控件有在比较简单约束条件下,match_parent是和0dp等效的,所以还是用0dp就可以了。

使用match_constraint的控件最好同时有设置其左右/上下的约束组合!否则,有可能会真的是0dp。

进入正题,当有宽高至少有一边设置为0dp时,我们可以设置该控件的宽高比!

  1. 当只有一边设置为0dp:
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintDimensionRatio="1:2"
// 默认格式为,宽:高,可通过添加W或H来改变,如 "H,1:2" 为高:宽 = 2:1
此时"1:2" == "W,1:2" == "H,2:1", W/H 用于指定分子与分母

该控件宽高比会变为1:2,由于layout_width="0dp",则宽度随着高度变化而变化。

  1. 如若宽高都为0dp。

在这种情况下,系统将设置满足所有约束并维持指定长宽比的最大尺寸。
(这句话翻译自文档,要细品。)

假设控件A在水平的左右方向都存在约束,垂直方向只有一条约束。相对于垂直方向,A在水平方向上的宽度比较固定(等于屏幕宽度),所以高度会根据比例跟着变化。

假设控件A在水平方向以及垂直方向都有存在约束,那就可以通添加w或h来指定约束方向。

    app:layout_constraintDimensionRatio="w,1:2"  //或  "h,1:2"
    "w,1:2"表示 宽度根据高度变化而变化,且宽高比依旧是1:2
    "h,1:2"表示 高度根据宽度变化而变化,且宽高比依旧是1:2
    W/H 是用于指定约束方向

7.尺寸约束

定义layout_width和layout_height的时候,同样是有三种方式:固定值、wrap_content、0dp

  1. 使用 wrap_content的时候,可以使用如下来限制控件大小
    android:minWidth 设置布局的最小宽度
    android:minHeight 设置布局的最小高度
    android:maxWidth 设置布局的最大宽度
    android:maxHeight 设置布局的最大高度
  1. 使用0dp时则可以使用:
    layout_constraintWidth_min、layout_constraintHeight_min:将为此控件设置最小尺寸
    layout_constraintWidth_max、layout_constraintHeight_max:将为此控件设置最大尺寸
    layout_constraintWidth_percent、layout_constraintHeight_percent:将此控件的尺寸设置为父控件的百分比
辅助工具类

终于到了重中之重了,这些拓展的辅助工具类才是ConstaintLayout真香于RelativeLayout的地方。

8. Chain

链虽然没有一个具体的类,比如Chain.java,但是也算一种特殊的约束,就也归入辅助工具类吧。在2.0版本将见到更强大的Flow辅助类。

链,两个及以上的控件两两相互约束。且头尾两边的控件受约束于同一水平轴的其他非此链成员控件(比如parent),链才能正常生效。约束效果如下图

通过链头(最靠左边或上边的控件)设置如下属性来达到不同分布效果

//layout_constraintHorizontal_chainStyle
layout_constraintVertical_chainStyle = "spread_inside|spread|packed"
链类型

下述便于解说,就图上的例子,链两边控件(A和C)的约束控件为父控件parent

  1. Spread

默认的类型,在充分考虑了margin之后,链上的控件均匀分布(在考虑margin之后的,布局剩余的空间,均匀分配给在各个控件的间隙,包括与parent的间隙。若剩余空间为负值,即控件总长度大于父控件两边界的间距,则间隙为0,此时链居中,两边超出屏幕外的控件自生自灭)

  1. Spread inside

A和C控件固定在链的两端的约束上,即贴着parent,其余控件均匀分布。

相对于Spread布局,不同的是,布局剩余的空间不考虑两边控件与parent的间隙。当然若是空隙为负,表现则同Spread模式

  1. Weighted

加权分布,在上述这两种模式中,若有一控件将宽度设置为0dp,那么该控件将充满剩余的空间。而且,类似于LinearLayout,对于剩余的空间可以通过设置每个控件的权重属性:

app:layout_constraintHorizontal_weight = 1
//app:layout_constraintVertical_weight = 1 

根据权重为不同的控件分配不同比例的空间。

  1. Packed

将每个控件紧贴(需要考虑margin)在一起,剩余的空间间隙分配在两边控件与parent之间。而且可以通过调节链头的bias来分配两边间隙。
若间隙小于0,表现如同Spread。

另外生成链的时候,记得使用下面这种简便形式!不然一个一个控件去添加约束,累死个人了。


chain.gif

9. Guideline

指导线,作为其它控件的约束准则。其它控件可以方便的通过GuideLine进行定位。

GuideLine继承于View,但并不会在布局中呈现(View.Gone)

可以通过设置下面三种属性之一来设置GuideLine的位置

app:layout_constraintGuide_begin="100dp" //与parent左边界或上边界(根据GuideLine的方向)的距离
app:layout_constraintGuide_end="100dp" ////与parent右边界或下边界(根据GuideLine的方向)的距离
app:layout_constraintGuide_percent="0.5"  // 百分比, 0~1


android:orientation="vertical|horizontal" //设置方向
GuideLine

10. Barrier

栅栏,类似于GuideLine,设置为View.GONE,也是设置辅助线的作用,不过这个辅助线取决于多个控件的同一侧边界。当所依赖的控件大小有所变化的时候,Barrier也有可能跟着变化。

假设Barrier定义如下:

    <androidx.constraintlayout.widget.Barrier
        android:id="@+id/barrier"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:barrierDirection="right"  // 或left、top、bottom
        app:constraint_referenced_ids="buttonA,buttonB" //引用多个控价,用逗号隔开
        />
        
    <Button
        android:id="@+id/buttonC"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintStart_toEndOf="@+id/barrier"
        app:layout_constraintTop_toTopOf="parent"
        />

即Barrier的位置取决于buttonA和buttonB谁的右边界更靠右,而ButtonC在Barrier的右边。

Barrier

这里还有个知识点:Barrier 继承于 ConstraintHelper,而ConstraintHelper继承于View.

ConstraintHelper是用于管理一组控件的行为,与ViewGroup不同的是:1.不增加层级;2. 不同的Helper可以引用同一个控件

在2.0版本支持自定义Helper。

11. Group

这个比较简单了,也是继承于ConstraintHelper, 用于控制一组控件的显示与否。

<androidx.constraintlayout.widget.Group
              android:id="@+id/group"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:visibility="visible"
              app:constraint_referenced_ids="button1,button2" />

值得注意的是,由于控件被包含在Group中,通过 View.setVisibility(int)来控制控件的显示与否,是无效的。

另外,当一个控件被添加在不同的Group中,此时这根据布局文件中排最后一个的Group将具有一票否决权。

12. PlaceHolder

占位是指提前设置一个绘制内容为空的控件,根据约束完成定位后,在恰当的时候将这个PlaceHolder的位置提供给其它控件使用!

设置占位并绑定指定的控件的方式有两种:

<android.support.constraint.Placeholder
    android:id="@+id/pl"
    android:layout_width="50dp"
    android:layout_height="50dp"
    app:content="@id/btn1"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

或者

placeHolder.setContentId(R.id.btn1)

当一个控件A被绑定至PlaceHolder,有如下反应。

  1. A在原位置上会被当做View.Gone. 其它依赖于A的控件会把A当做一个点来处理。
  2. PlaceHolder的其它约束条件不变,宽高变成了A的宽高
  3. 在PlaceHolder的位置显示出A的内容

虽然目前这个功能对我来说很鸡肋,但对这个功能实现感兴趣,所以我觉得这个可以稍微了解更深点的

在源码中我们看到这几处代码:

PlaceHolder.class
//根据绑定的控件,更新PlaceHolder测量后的宽高
public void updatePostMeasure(ConstraintLayout container) {
    if (this.mContent != null) {
        LayoutParams layoutParams = (LayoutParams)this.getLayoutParams();
        LayoutParams layoutParamsContent = (LayoutParams)this.mContent.getLayoutParams();
        layoutParamsContent.widget.setVisibility(0);
        // 这里
        layoutParams.widget.setWidth(layoutParamsContent.widget.getWidth());
        layoutParams.widget.setHeight(layoutParamsContent.widget.getHeight());
        layoutParamsContent.widget.setVisibility(8);  //控件不可见
    }
}

//PlaceHolder.class
// 在layout()之前将绑定的控件 layoutParamsContent.isInPlaceholder = true
public void updatePreLayout(ConstraintLayout container) {
    if (this.mContentId == -1 && !this.isInEditMode()) {
        this.setVisibility(this.mEmptyVisibility);
    }

    this.mContent = container.findViewById(this.mContentId);
    if (this.mContent != null) {
        LayoutParams layoutParamsContent = (LayoutParams)this.mContent.getLayoutParams();
        layoutParamsContent.isInPlaceholder = true; //这个属性
        this.mContent.setVisibility(0);
        this.setVisibility(0);
    }

}


ConstraintLayout.class
// 更新绑定的控件的位置
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    int widgetsCount = this.getChildCount();
    boolean isInEditMode = this.isInEditMode();

    int helperCount;
    for(helperCount = 0; helperCount < widgetsCount; ++helperCount) {
        View child = this.getChildAt(helperCount);
        ...
            if (child instanceof Placeholder) {
                Placeholder holder = (Placeholder)child;
                View content = holder.getContent();
                if (content != null) {
                    content.setVisibility(0);
                    content.layout(l, t, r, b); //更改所绑定控件的显示位置
                }
            }
        }
    }
    ...
}

用文字来描述就是,当一个控件A被添加到PlaceHolder后,会被标记为isInPlaceholder=true,且A被设置为View.Gone, 那么其它依赖于A的控件就会把A当成一个点。

同时在测量完成后(layout之前),将PlaceHolder的宽高修改为A的宽高

接着在onLayout过程时,A设为View.VISIABLE, PlaceHolder的布局位置让给A,即执行A.layout(l,t,r,b)(参数来自PlaceHolder)

此时虽然A可见了,但依赖于A的其它控件的大小及位置已经与A无关了。(这里没深究,大小是因为,测量时因为A为Gone,布局位置应当是与A.layoutParams.isInPlaceholder == true相关)

Optimizer

优化器,系统会自动尝试减少视图的约束,从而提高布局速度

官方还没啥使用文档。。

给出了这么个使用方式:

添加:app:layout_optimizationLevel 到 ConstraintLayout的标签中

app:layout_optimizationLevel="direct|barrier|chain"

这个属性值有六种,standard为默认形式,即会优化direct和barrier这两种类型的约束

none : no optimizations are applied  // 不优化
standard : Default. Optimize direct and barrier constraints only
direct : optimize direct constraints
barrier : optimize barrier constraints
chain : optimize chain constraints (experimental) // 实验性
dimensions : optimize dimensions measures (experimental), reducing the number of measures of match constraints elements // 实验性

关于这个几个属性,在这个问答中有比较详细的解释。大家自己看看吧。
https://stackoverflow.com/questions/49802490/what-is-constraintlayout-optimizer

猜测:上文设置无效margin约束,会自动被优化删除,可能就是这个触发的?

ConstrainsSet 与 ConstaintLayout.LayoutParams

ConstaintLayout.LayoutParams 顾名思义,就是我们布局参数了。

通常我们可以通过修改布局参数值来控制一个控件的呈现形式。

但是ConstaintLayout的特殊性,如果要做一些比较复杂的变更步骤就会变得繁琐,比如说在Chain中加入一个控件,你觉得还行?那将几个控件组成一条链呢?

所以谷歌的建议是,使用ConstrainsSet来进行动态修改控件的参数。ConstrainsSet.createHorizontalChain(...) 就可以创建一条链,不过这里更多是需要理解方法中的参数

接着了解下使用方式:

  1. 生成ConstaintSet对象。
无参对象
val c = new ConstraintSet(); 

从已存在的layout中导出所有子控件的约束形成约束集
c.clone(context, R.layout.layout1);
c.clone(cLayout);
  1. 修改指定的控件的约束条件
    c.setAlpha(int viewId, float alpha)
    c.constrainHeight(int viewId, int height)
    
    //设置控件间的相对约束,side的取值为1~7 
    //即:ConstaintSet.LEFT、ConstaintSet.RIGHT... 等上述相对布局可使用的7个Dreaction
    c.connect(int startID, int startSide, int endID, int endSide)
    ...
    
    //基本可通过xml设置的属性,在ConstraintSet中都能找相对应的方法
  1. 使步骤2中的修改生效
c.applyTo(cLayout);

// cLayout的所有控件必须都设置有viewId,因为该方法有如下判断:

    if (id == -1) {
         throw new RuntimeException("All children of ConstraintLayout must have ids to use ConstraintSet");
    }

// 不过我注意到2.0版本是这样的,可以通过setForceId(boolean b) 来控制是否都需要设置id
    if (this.mForceId && id == -1) {
         throw new RuntimeException("All children of ConstraintLayout must have ids to use ConstraintSet");
    }


这里有个关键地方需要提下:

  • 若在第一步ConstraintSet使用的是无参数的构造方法
val set = ConstraintSet()
set.setMargin(R.id.btn1, ConstraintSet.LEFT, 300)
set.constrainWidth(R.id.btn1, 300)
set.applyTo(cLayout)

那么在setMargin()时会生成一个Constraints对象用来存R.id.btn1的这条Margin约束。(ConstraintSet使用Map关联viewId和Constraints)

在appleTo(cLayout)的时候上述的Constraints替换cLayout中的R.id.btn1原本定义在xml的所有约束条件!

另外还有个可能就是,cLayout不存在id为R.id.btn1的子控件。那一般就什么都不会发生。

从源码来看,对于这个cLayout中不存在的id,若我们使用了这样的代码。。

val set = ConstraintSet()
set.createBarrier(R.id.btn1, ...)
set.applyTo(cLayout)

那么btn1会被作为一个新的Barrier控件加入cLayout中(addView()的方式),同理的还有GuideLine。

本文最重要的点

  1. 实践出真章。多实战,你会发现还挺好用的,然后发现文中一些错误的主观观点...
  1. 参考自官方文档:

https://developer.android.com/reference/android/support/constraint/ConstraintLayout

https://developer.android.com/training/constraint-layout

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