一、初识Transition—实现两个场景的变换

诞生于4.4的transition框架为在不同的UI状态之间产生动画效果提供了非常方便的API。5.0中Activity和Fragment 转场变换也是建立在Transitions框架的新特性之上的。

该框架主要基于两个概念:scenes(场景)和transitions(变换)

1. Secene

Transition Framework 核心就是根据Scene的不同帮助开发者们自动生成动画

官方文档
A scene represents the collection of values that various properties in the View hierarchy will have when the scene is applied. A Scene can be configured to automatically run a Transition when it is applied, which will animate the various property changes that take place during the scene change.

通俗的解释就是这个类存储着一个根view下的各种view的属性。

创建Scene

创建一个 Scene有两种方法

// sceneRoot是Scene的 Container,也可以说是它的根布局
1. Scene.getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context) ;
2. new Scene(ViewGroup sceneRoot, View layout);

例子如下:

    protected Scene scene1;
    protected Scene scene2;

    protected void initScene(@IdRes int sceneRoot, @LayoutRes int scene1_layout, @LayoutRes int scene2_layout) {
        ViewGroup sceneRoot= (ViewGroup) findViewById(rootView);
        scene1= Scene.getSceneForLayout(sceneRoot,scene1_layout,this);
        scene2=Scene.getSceneForLayout(sceneRoot,scene2_layout,this);
        TransitionManager.go(scene1); //先把初始状态设置为scene1
    }

或者

    protected Scene scene1;
    protected Scene scene2;

        ViewGroup sceneRoot=findViewById(R.id.rootView);

        View view1= LayoutInflater.from(this).inflate(R.layout.changeclipbounds_scene,null);
        View view2= LayoutInflater.from(this).inflate(R.layout.changeclipbounds_scene,null);
        ImageView iv1=view1.findViewById(R.id.imageView);
        ImageView iv2=view2.findViewById(R.id.imageView);
        iv1.setClipBounds(new Rect(0,0,100,100));
        iv2.setClipBounds(new Rect(100,100,200,200));

        scene1=new Scene(sceneRoot,view1);
        scene2=new Scene(sceneRoot,view2);
        TransitionManager.go(scene1);//先把初始状态设置为scene1

sceneRoot 在动画开始时,会将sceneRoot中的所有子View都remove掉,然后在sceneRoot 中加载我们的end Scene。

所以,对于end Scene,如果是通过代码new Scene(mSceneRoot, view)创建的Scene其实对于view是有要求的:view是没有parentview的,不然在addview的时候会报错
验证代码如下:

//测试该段代码
        LinearLayout container=new LinearLayout(appContext);
        View view= LayoutInflater.from(appContext).inflate(R.layout.layout_temp,null);
        FrameLayout frameLayout=new FrameLayout(appContext);
        frameLayout.addView(view);
        container.addView(view);
//log
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
at android.view.ViewGroup.addViewInner(ViewGroup.java:4917)
at android.view.ViewGroup.addView(ViewGroup.java:4748)

例子

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


    <ImageView
        android:id="@+id/imageView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:srcCompat="@drawable/bangtang"
        tools:layout_editor_absoluteX="128dp"
        tools:layout_editor_absoluteY="58dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        android:layout_marginTop="58dp"
        app:layout_constraintEnd_toEndOf="parent" />

    <ImageView
        android:id="@+id/imageView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:srcCompat="@drawable/shengdanlaoren"
        tools:layout_editor_absoluteX="49dp"
        tools:layout_editor_absoluteY="226dp"
        app:layout_constraintTop_toTopOf="@+id/imageView3"
        app:layout_constraintStart_toStartOf="parent"
        android:layout_marginStart="49dp" />

    <ImageView
        android:id="@+id/imageView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:srcCompat="@drawable/xueren"
        tools:layout_editor_absoluteX="210dp"
        tools:layout_editor_absoluteY="226dp"
        android:layout_marginTop="40dp"
        app:layout_constraintTop_toBottomOf="@+id/imageView1"
        android:layout_marginEnd="46dp"
        app:layout_constraintEnd_toEndOf="parent" />
</android.support.constraint.ConstraintLayout>

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


    <ImageView
        android:id="@+id/imageView2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:srcCompat="@drawable/shengdanlaoren"
        tools:layout_editor_absoluteX="128dp"
        tools:layout_editor_absoluteY="58dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        android:layout_marginTop="58dp"
        app:layout_constraintEnd_toEndOf="parent" />

    <ImageView
        android:id="@+id/imageView3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:srcCompat="@drawable/xueren"
        tools:layout_editor_absoluteX="49dp"
        tools:layout_editor_absoluteY="226dp"
        app:layout_constraintTop_toTopOf="@+id/imageView1"
        app:layout_constraintStart_toStartOf="parent"
        android:layout_marginStart="49dp" />

    <ImageView
        android:id="@+id/imageView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:srcCompat="@drawable/bangtang"
        tools:layout_editor_absoluteX="210dp"
        tools:layout_editor_absoluteY="226dp"
        android:layout_marginTop="40dp"
        app:layout_constraintTop_toBottomOf="@+id/imageView2"
        android:layout_marginEnd="46dp"
        app:layout_constraintEnd_toEndOf="parent" />

</android.support.constraint.ConstraintLayout>
scene1和scene2的效果对比图

Activity代码:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_base_scene);
        Button btn=findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                clickChange();
            }
        });
                
        ViewGroup sceneRoot= (ViewGroup) findViewById(rootView);
        scene1= Scene.getSceneForLayout(sceneRoot,scene1_layout,this);
        scene2=Scene.getSceneForLayout(sceneRoot,scene2_layout,this);
        TransitionManager.go(scene1);//先把初始状态设置为scene1
    }

    protected void clickChange(){
        TransitionManager.go(isScene1?scene2:scene1,getTransition());
        isScene1=!isScene1;
    }

    Transition getTransition() {
        return new ChangeBounds();
    }

getTransition方法提供的是两个Scene的切换效果,下面会讲到,所以先不管它。
最后出来的效果是:



scene1和scene2的 Id 对比图

根据效果图和Id对比图可以发现切换时是相同id的View之间互相切换,那么如果两个Scene之间View的Id不对等(id不相同 或者 一多一少)呢?


View的Id不对等

这个时候可以试着运行一下就会发现:仍然可以实现两个场景的切换,但是切换过程没有任何动画效果(即Transition没有起作用),就只是简单的替换。


上面例子getTransition方法返回ChangeBounds(),其实就是一种Transition的实现,下面来详细了解一下:

2. transitions

当一个Scene发生改变时,transition主要负责:

  1. 捕捉每个View在开始场景和结束场景时的状态。
  2. 根据两个场景(开始和结束)之间的区别创建一个Animator

2.1 API 21之后,框架层提供的Transitions:

2.1.1 ChangeBounds :检测view的位置边界创建移动和缩放动画

捕获共享元素的layout bound,然后播放layout bound变化动画。ChangeBounds 是共享元素变换中用的最多的,因为前后两个activity中共享元素的大小和位置一般都是不同的。

根据始末位置画出Path,再根据Path创造Animator

GIFchangebounds.gif

/**
 * This transition captures the layout bounds of target views before and after
 * the scene change and animates those changes during the transition.
 *
 * <p>A ChangeBounds transition can be described in a resource file by using the
 * tag <code>changeBounds</code>, using its attributes of
 * {@link android.R.styleable#ChangeBounds} along with the other standard
 * attributes of {@link android.R.styleable#Transition}.</p>
 */
public class ChangeBounds extends Transition {
    ...
}
2.1.2 ChangeClipBounds :检测view的剪切区域的位置边界,和ChangeBounds类似。不过ChangeBounds针对的是view而ChangeClipBounds针对的是view的剪切区域(setClipBound(Rect rect) 中的rect)。如果没有设置则没有动画效果

捕获共享元素clip bounds,然后播放clip bounds变化动画。

GIFchangeClipBounds.gif
/**
 * ChangeClipBounds captures the {@link android.view.View#getClipBounds()} before and after the
 * scene change and animates those changes during the transition.
 */
public class ChangeClipBounds extends Transition {
      
    ...
    @Override
    public Animator createAnimator(final ViewGroup sceneRoot, TransitionValues startValues,
         ...
        Rect start = (Rect) startValues.values.get(PROPNAME_CLIP);
        Rect end = (Rect) endValues.values.get(PROPNAME_CLIP);
        boolean endIsNull = end == null;
        ...
        endValues.view.setClipBounds(start);
        RectEvaluator evaluator = new RectEvaluator(new Rect());
        ObjectAnimator animator =
                ObjectAnimator.ofObject(endValues.view, "clipBounds", evaluator, start, end);
        if (endIsNull) {
            final View endView = endValues.view;
            animator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    endView.setClipBounds(null);
                }
            });
        }
        return animator;
    }
}
2.1.3 ChangeImageTransform :检测ImageView(这里是专指ImageView)的尺寸,位置以及ScaleType,并创建相应动画。

捕获共享元素(ImageView)的transform matrices 属性,然后播放ImageViewtransform matrices 属性变化动画。与ChangeBounds相结合,这个变换可以让ImageView在动画中高效实现大小,形状或者ImageView.ScaleType

属性平滑过度。

/**
 * This Transition captures an ImageView's matrix before and after the
 * scene change and animates it during the transition.
 *
 * <p>In combination with ChangeBounds, ChangeImageTransform allows ImageViews
 * that change size, shape, or {@link android.widget.ImageView.ScaleType} to animate contents
 * smoothly.</p>
 */
public class ChangeImageTransform extends Transition {

    ...
    /**
     * Creates an Animator for ImageViews moving, changing dimensions, and/or changing
     * {@link android.widget.ImageView.ScaleType}.
     *
     * @param sceneRoot   The root of the transition hierarchy.
     * @param startValues The values for a specific target in the start scene.
     * @param endValues   The values for the target in the end scene.
     * @return An Animator to move an ImageView or null if the View is not an ImageView,
     * the Drawable changed, the View is not VISIBLE, or there was no change.
     */
    @Override
    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
            TransitionValues endValues) {
        if (startValues == null || endValues == null) {
            return null;
        }
        Rect startBounds = (Rect) startValues.values.get(PROPNAME_BOUNDS);
        Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS);
        if (startBounds == null || endBounds == null) {
            return null;
        }
        Matrix startMatrix = (Matrix) startValues.values.get(PROPNAME_MATRIX);
        Matrix endMatrix = (Matrix) endValues.values.get(PROPNAME_MATRIX);
        boolean matricesEqual = (startMatrix == null && endMatrix == null) ||
                (startMatrix != null && startMatrix.equals(endMatrix));
        if (startBounds.equals(endBounds) && matricesEqual) {
            return null;
        }
        ImageView imageView = (ImageView) endValues.view;
        Drawable drawable = imageView.getDrawable();
        int drawableWidth = drawable.getIntrinsicWidth();
        int drawableHeight = drawable.getIntrinsicHeight();
        ObjectAnimator animator;
        if (drawableWidth == 0 || drawableHeight == 0) {
            animator = createNullAnimator(imageView);
        } else {
            if (startMatrix == null) {
                startMatrix = Matrix.IDENTITY_MATRIX;
            }
            if (endMatrix == null) {
                endMatrix = Matrix.IDENTITY_MATRIX;
            }
            ANIMATED_TRANSFORM_PROPERTY.set(imageView, startMatrix);
            animator = createMatrixAnimator(imageView, startMatrix, endMatrix);
        }
        return animator;
    }
    ...
}
2.1.4 ChangeScroll :滑动的属性发生了变化

/**
 * This transition captures the scroll properties of targets before and after
 * the scene change and animates any changes.
 */
public class ChangeScroll extends Transition {

     ...
    @Override
    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
            TransitionValues endValues) {
        if (startValues == null || endValues == null) {
            return null;
        }
        final View view = endValues.view;
        int startX = (Integer) startValues.values.get(PROPNAME_SCROLL_X);
        int endX = (Integer) endValues.values.get(PROPNAME_SCROLL_X);
        int startY = (Integer) startValues.values.get(PROPNAME_SCROLL_Y);
        int endY = (Integer) endValues.values.get(PROPNAME_SCROLL_Y);
        Animator scrollXAnimator = null;
        Animator scrollYAnimator = null;
        if (startX != endX) {
            view.setScrollX(startX);
            scrollXAnimator = ObjectAnimator.ofInt(view, "scrollX", startX, endX);
        }
        if (startY != endY) {
            view.setScrollY(startY);
            scrollYAnimator = ObjectAnimator.ofInt(view, "scrollY", startY, endY);
        }
        return TransitionUtils.mergeAnimators(scrollXAnimator, scrollYAnimator);
    }
}
2.1.5 ChangeTransform :检测view的scale和rotation创建缩放和旋转动画

捕获共享元素的缩放(scale)与旋转(rotation)属性 ,然后播放缩放(scale)与旋转(rotation)属性变化动画。

GIFchangetransform.gif
/**
 * This Transition captures scale and rotation for Views before and after the
 * scene change and animates those changes during the transition.
 *
 * A change in parent is handled as well by capturing the transforms from
 * the parent before and after the scene change and animating those during the
 * transition.
 */
public class ChangeTransform extends Transition {

    ...
    @Override
    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
            TransitionValues endValues) {
        if (startValues == null || endValues == null ||
                !startValues.values.containsKey(PROPNAME_PARENT) ||
                !endValues.values.containsKey(PROPNAME_PARENT)) {
            return null;
        }
        ViewGroup startParent = (ViewGroup) startValues.values.get(PROPNAME_PARENT);
        ViewGroup endParent = (ViewGroup) endValues.values.get(PROPNAME_PARENT);
        boolean handleParentChange = mReparent && !parentsMatch(startParent, endParent);
        Matrix startMatrix = (Matrix) startValues.values.get(PROPNAME_INTERMEDIATE_MATRIX);
        if (startMatrix != null) {
            startValues.values.put(PROPNAME_MATRIX, startMatrix);
        }
        Matrix startParentMatrix = (Matrix)
                startValues.values.get(PROPNAME_INTERMEDIATE_PARENT_MATRIX);
        if (startParentMatrix != null) {
            startValues.values.put(PROPNAME_PARENT_MATRIX, startParentMatrix);
        }
        // First handle the parent change:
        if (handleParentChange) {
            setMatricesForParent(startValues, endValues);
        }
        // Next handle the normal matrix transform:
        ObjectAnimator transformAnimator = createTransformAnimator(startValues, endValues,
                handleParentChange);
        if (handleParentChange && transformAnimator != null && mUseOverlay) {
            createGhostView(sceneRoot, startValues, endValues);
        }
        return transformAnimator;
    }

    ...
    /**
     * PathAnimatorMatrix allows the translations and the rest of the matrix to be set
     * separately. This allows the PathMotion to affect the translations while scale
     * and rotation are evaluated separately.
     */
    private static class PathAnimatorMatrix {
      ...
    }
}

2.2 如何使用Transition

2.2.1 TransitionManager.go(Scene scene, Transition transition)

直接在切换Scene时,设置效果

2.2.2 根据设置的 transition文件 自动生成Animator效果

在XML中或者在代码中设置,举个例子就是: res->transition文件下创建transition文件

//xxx.xml Fade、Slide、Explode :渐入、滑动、爆炸

<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<changeBounds/>
    <explode/>
    <fade/>
</transitionSet>

//或者直接new Fade()等等
fade_and_slide.gif

设置 transition文件 自动生成Animator效果,通常在两种情况下使用:

  1. 切换Activity/Fragment时设置场景切换效果(这个部分在后续的文章会讲到)
  2. beginDelayedTransition()设置延时动画
beginDelayedTransition

为每一个结束关键帧都专门设置一个xml的Scene布局岂不是很麻烦?如图所示,点击每个图片的切换效果都差不多,那是不是就要设置4个Scene,然后对应的点击某个View就跳转到某个Scene呢?

GIFbegindelayed.gif

所以,就有了 : 延时动画 :beginDelayedTransition()

例子:(四个ImageView,点击每一个,那个就会放大,其他三个就会消失)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/rootView"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/imageView1"
        android:layout_width="120dp"
        android:layout_height="120dp"
        android:layout_marginStart="72dp"
        android:layout_marginTop="71dp"
        app:srcCompat="@drawable/bangtang"
        android:layout_alignParentTop="true"
        android:layout_alignParentStart="true" />

    <ImageView
        android:id="@+id/imageView2"
        android:layout_width="120dp"
        android:layout_height="120dp"
        app:srcCompat="@drawable/shengdanshu"
        android:layout_alignTop="@+id/imageView1"
        android:layout_alignParentEnd="true"
        android:layout_marginEnd="30dp" />

    <ImageView
        android:id="@+id/imageView3"
        android:layout_width="120dp"
        android:layout_height="120dp"
        app:srcCompat="@drawable/xueren"
        android:layout_alignTop="@+id/imageView4"
        android:layout_alignStart="@+id/imageView1" />

    <ImageView
        android:id="@+id/imageView4"
        android:layout_width="120dp"
        android:layout_height="120dp"
        app:srcCompat="@drawable/xunlu"
        android:layout_marginTop="24dp"
        android:layout_below="@+id/imageView2"
        android:layout_alignStart="@+id/imageView2" />
</RelativeLayout>

// explode_fade_changebounds.xml
<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<changeBounds/>
    <explode/>
    <fade/>
</transitionSet>
public class BeginDelayedActivity extends AppCompatActivity implements View.OnClickListener{

    ImageView iv1,iv2,iv3,iv4;
    ViewGroup rootView;

    boolean isBig=false;
    int primarySize;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_begin_delayed);
        rootView=findViewById(R.id.rootView);
        iv1=findViewById(R.id.imageView1);
        iv2=findViewById(R.id.imageView2);
        iv3=findViewById(R.id.imageView3);
        iv4=findViewById(R.id.imageView4);
        primarySize=iv1.getLayoutParams().width;
        iv1.setOnClickListener(this);
        iv2.setOnClickListener(this);
        iv3.setOnClickListener(this);
        iv4.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        //start scene 是当前的scene
        TransitionManager.beginDelayedTransition(rootView, TransitionInflater.from(this).inflateTransition(R.transition.explode_fade_changebounds));
        //next scene 此时通过代码已改变了scene statue
        changeScene(v);
    }

    private void changeScene(View v) {
        changeSize(v);
        changeVisibility(iv1,iv2,iv3,iv4);
        v.setVisibility(View.VISIBLE);
    }


    private void changeSize(View v) {

        isBig=!isBig;
        ViewGroup.LayoutParams layoutParams = v.getLayoutParams();
        if(isBig){
            layoutParams.width=(int)(1.5*primarySize);
            layoutParams.height=(int)(1.5*primarySize);
        }else {
            layoutParams.width=primarySize;
            layoutParams.height=primarySize;
        }
        v.setLayoutParams(layoutParams);
    }

    /**
     * VISIBLE和INVISIBLE状态切换
     * @param ivs
     */
    private void changeVisibility(ImageView ... ivs) {
        for (View view:ivs){
            view.setVisibility(view.getVisibility()==View.VISIBLE?View.INVISIBLE:View.VISIBLE);
        }
    }
}

我们在这做详细的分析 :
假设最开始每个view都是可见的:

  1. 当点击事件发生之后调用TransitionManager的beginDelayedTransition()方法,并且传递了mRootView和一个Fade对象最为参数。之后,framework会立即调用transition类的captureStartValues()方法为每个view保存其当前的可见状态(visibility)。
  2. 当beginDelayedTransition返回之后,在上面的代码中将每个view设置为不可见。
  3. 在接下来的显示中framework会调用transition类的captureEndValues()方法,记录每个view最新的可见状态。
  4. 接着,framework调用transition的createAnimator()方法。transition会分析每个view的开始和结束时的数据发现view在开始时是可见的,结束时是不可见的。Fade(transition的子类)会利用这些信息创建一个用于把view的alpha属性变为0的AnimatorSet,并且将此AnimatorSet对象返回。
  5. framework会运行返回的Animator,导致所有的View都渐渐消失。

这样就达到了:通过属性的改变,就发生动画...达到了代码的精简


3. 总结

  1. 创建两个Scene(起始关键帧 和 结束关键帧)
  2. 利用系统内置的或自定义的transitions创建Animator
  3. 开启动画

这样就是实现了简单的一个动画效果,这个过程我们只关心 开始状态和结束状态,并为状态的变化规定了变化规律(transitions),然后自动帮我们生成效果


transitions_diagram.png

由此可见 :transition框架的两个主要优点
第一、Transitions抽象和封装了属性动画,Animator的概念对开发者来说是透明的,因此它极大的精简了代码量。开发者所做的所有事情只是改变一下view前后的状态数据,Transition就会自动的根据状态的区别去生成动画效果。第二、不同场景之间变换的动画效果可以简单的通过使用不同的Transition类来改变

基础的介绍就先讲到这里!!!
上面讲到了基础的 TransitionManager.go()beginDelayedTransition() 开启动画。其实还有一种开启方式更为常见 :setEnterTransition()/setSharedElementEnterTransition() //当然,这得看下回分解

Transition系列文章
一、初识Transition—实现两个场景的变换
二、番外篇 Transition之ViewOverlay
三、定义 界面指定元素 或界面间共享元素 的转场动画基础
四、Content Transition实现非共享元素转场
五、SharedElementTransition之Activity间的转场
六、SharedElementTransition之Fragment间的转场
七、番外篇- 自定义Visibility
八、5.0以下实现共享转场

本篇参考:
Activity和Fragment Transition介绍
Android 过渡(Transition)动画解析之基础篇
animatedTransitionsLearn-master

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

推荐阅读更多精彩内容