Android中的動畫詳解

Android中動畫分為:View動畫、幀動畫(也屬于View動畫)、屬性動畫。
View動畫是對View做圖形變換(平移、縮放、旋轉、透明度)從而產生動畫效果。
幀動畫就是順序播放一系列圖片來產生動畫效果。
屬性動畫可以動態改變對象的屬性來達到動畫效果。

一、View動畫

View動畫的平移、縮放、旋轉、透明度 分別對應 Animation的的4個子類:TranslateAnimation、ScaleAnimation、RotateAnimation、AlphaAnimation。View可以用xml定義、也可以用代碼創建。推薦使用xml,可讀性好。

1.1 xml方式

如下所示,R.anim.animation_test 是xml定義的動畫。
其中標簽 translate、scale、alpha、rotate,就是對應四種動畫。set標簽是動畫集合,對應AnimationSet類,有多個動畫構成。

其中android:duration是指動畫時間,fillAfter為true是動畫結束后保持,false會回到初始狀態。interpolator是指動畫的執行速度,默認是先加速后減速。其他標簽及屬性較簡單可自行研究驗證。

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="5000"
    android:fillAfter="true"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"> 
    <!--set里面的duration如果有值,會覆蓋子標簽的duration-->

    <translate
        android:duration="1000"
        android:fromXDelta="0"
        android:toXDelta="400" />
    <scale
        android:duration="2000"
        android:fromXScale="0.5"
        android:fromYScale="0.5"
        android:toXScale="1"
        android:toYScale="1" />
    <alpha
        android:duration="3000"
        android:fromAlpha="0.2"
        android:toAlpha="1" />

    <rotate
        android:fromDegrees="0"
        android:toDegrees="90" />
</set>

定義好動畫后,使用也很簡單,調用view的startAnimation方法即可。

        //view動畫使用,方式一:xml,建議使用。
        Animation animation = AnimationUtils.loadAnimation(this, R.anim.animation_test);
        textView1.startAnimation(animation);

1.2 代碼動態創建

代碼創建舉例如下,也很簡單。

        //view動畫使用,方式二:new 動畫對象
        AnimationSet animationSet = new AnimationSet(false);
        animationSet.setDuration(3000);
        animationSet.addAnimation(new TranslateAnimation(0, 100, 0, 0));
        animationSet.addAnimation(new ScaleAnimation(0.1f, 1f, 0.1f, 1f));
        animationSet.setFillAfter(true);
        textView2.startAnimation(animationSet);

        //view動畫使用,方式二:new 動畫對象,使用setAnimation
        AnimationSet animationSet2 = new AnimationSet(false);
        animationSet2.setDuration(3000);
        animationSet2.addAnimation(new TranslateAnimation(0, 100, 0, 0));
        animationSet2.addAnimation(new ScaleAnimation(0.1f, 1f, 0.1f, 1f));
        animationSet2.setFillAfter(true);
        animationSet2.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {

            }
            @Override
            public void onAnimationEnd(Animation animation) {
                MyToast.showMsg(AnimationTestActivity.this, "View動畫:代碼 set:View動畫結束~");
            }
            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });
        textView3.setAnimation(animationSet2);

注意點:

  1. startAnimation方法是立刻播放動畫;setAnimation是設置要播放的下一個動畫。
  2. setAnimationListener可以監聽動畫的開始、結束、重復。

1.3 自定義View動畫

通常我們不需要自定義View動畫,上面4種基本夠用。

自定義View動畫,需要繼承Animation,重寫initialize和applyTransformation方法。在initialize中做初始化工作,在applyTransformation中做相應的矩陣變換(需要用到Camera),需要用到數學知識。這個給出一個例子Rotate3dAnimation,沿Y軸旋轉并沿Z軸平移,到達3d效果。

/**
 * 沿Y軸旋轉并沿Z軸平移,到達3d效果
 * An animation that rotates the view on the Y axis between two specified angles.
 * This animation also adds a translation on the Z axis (depth) to improve the effect.
 */
public class Rotate3dAnimation extends Animation {
    private final float mFromDegrees;
    private final float mToDegrees;
    private final float mCenterX;
    private final float mCenterY;
    private final float mDepthZ;
    private final boolean mReverse;
    private Camera mCamera;

    /**
     * Creates a new 3D rotation on the Y axis. The rotation is defined by its
     * start angle and its end angle. Both angles are in degrees. The rotation
     * is performed around a center point on the 2D space, definied by a pair
     * of X and Y coordinates, called centerX and centerY. When the animation
     * starts, a translation on the Z axis (depth) is performed. The length
     * of the translation can be specified, as well as whether the translation
     * should be reversed in time.
     *
     * @param fromDegrees the start angle of the 3D rotation 沿y軸旋轉開始角度
     * @param toDegrees   the end angle of the 3D rotation 沿y軸旋轉結束角度
     * @param centerX     the X center of the 3D rotation 沿y軸旋轉的軸點x(相對于自身)
     * @param centerY     the Y center of the 3D rotation 沿y軸旋轉的軸點y(相對于自身)
     * @param depthZ      z軸的平移。如果>0,越大就越遠離,視覺上變得越小。
     * @param reverse     true if the translation should be reversed, false otherwise
     */
    public Rotate3dAnimation(float fromDegrees, float toDegrees,
                             float centerX, float centerY, float depthZ, boolean reverse) {
        mFromDegrees = fromDegrees;
        mToDegrees = toDegrees;
        mCenterX = centerX;
        mCenterY = centerY;
        mDepthZ = depthZ;
        mReverse = reverse;
    }

    @Override
    public void initialize(int width, int height, int parentWidth, int parentHeight) {
        super.initialize(width, height, parentWidth, parentHeight);
        mCamera = new Camera();
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        final float fromDegrees = mFromDegrees;
        float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);

        final float centerX = mCenterX;
        final float centerY = mCenterY;
        final Camera camera = mCamera;

        final Matrix matrix = t.getMatrix();

        camera.save();
        if (mReverse) {
            camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);
        } else {
            camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));
        }
        camera.rotateY(degrees);
        camera.getMatrix(matrix);
        camera.restore();

        matrix.preTranslate(-centerX, -centerY);
        matrix.postTranslate(centerX, centerY);
    }
}

1.4 幀動畫

幀動畫對應AnimationDrawable類,用來順序播放多張圖片。使用很簡單,先xml定義一個AnimationDrawable,然后作為背景或資源設置給view并開始動畫即可。
舉例如下:

R.drawable.frame_animation

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item
        android:drawable="@drawable/home_icon_guide00"
        android:duration="50"/>
    <item
        android:drawable="@drawable/home_icon_guide01"
        android:duration="50"/>
    <item
        android:drawable="@drawable/home_icon_guide02"
        android:duration="50"/>
    <item
        android:drawable="@drawable/home_icon_guide03"
        android:duration="50"/>
    ......
</animation-list>
        tvFrameAnimation.setBackgroundResource(R.drawable.frame_animation);
        AnimationDrawable frameAnimationBackground = (AnimationDrawable) tvFrameAnimation.getBackground();
        frameAnimationBackground.start();

1.5 View動畫的特殊使用場景

1.5.1 給ViewGroup指定child的出場動畫

使用LayoutAnimation給ViewGroup指定child的出場動畫,方法如下:

1.先用xml定義標簽LayoutAnimation:

  • android:animation設置child的出場動畫
  • android:animationOrder設置child的出場順序,normal就是順序
  • delay是指:每個child延遲(在android:animation中指定的動畫時間)0.8倍后播放動畫。如果android:animation中的動畫時間是100ms,那么每個child都會延遲800ms后播放動畫。 如果不設置delay,那么所有child同時執行動畫。
<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
    android:animation="@anim/enter_from_left_for_child_of_group"
    android:animationOrder="normal"
    android:delay="0.8">
</layoutAnimation>
R.anim.enter_from_left_for_child_of_group

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="1000"
        android:fromXDelta="-100%p"
        android:toXDelta="0"/>

</set>

2.把LayoutAnimation設置給ViewGroup

    <LinearLayout
        android:id="@+id/ll_layout_animation"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layoutAnimation="@anim/layout_animation">
        <TextView
            android:layout_width="50dp"
            android:layout_height="wrap_content"
            android:textColor="#ff0000"
            android:text="呵呵呵"/>
        <TextView
            android:layout_width="60dp"
            android:layout_height="wrap_content"
            android:textColor="#ff0000"
            android:text="qq"
            android:background="@color/colorPrimary"/>
        <TextView
            android:layout_width="30dp"
            android:layout_height="wrap_content"
            android:textColor="#ff0000"
            android:text="啊啊"/>
    </LinearLayout>

除了xml,當然也可以使用LayoutAnimationController 指定:

        //代碼設置LayoutAnimation,實現ViewGroup的child的出場動畫
        Animation enterAnim = AnimationUtils.loadAnimation(this, R.anim.enter_from_left_for_child_of_group);
        LayoutAnimationController controller = new LayoutAnimationController(enterAnim);
        controller.setDelay(0.8f);
        controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
        llLayoutAnimation.setLayoutAnimation(controller);

1.5.2 Activity的切換效果

Activity默認有切換動畫效果,我們也可以自定義:overridePendingTransition(int enterAnim, int exitAnim) 可以指定activity開大或暫停時的動畫效果。

  • enterAnim,指要打開的activity進入的動畫
  • exitAnim,要暫停的activity退出的動畫

注意 必須在startActivity或finish之后使用才能生效。如下所示:

    public static void launch(Activity activity) {
        Intent intent = new Intent(activity, AnimationTestActivity.class);
        activity.startActivity(intent);
        //打開的activity,從右側進入,暫停的activity退出到左側。
        activity.overridePendingTransition(R.anim.enter_from_right, R.anim.exit_to_left);
    }
......

    @Override
    public void finish() {
        super.finish();
        //打開的activity,就是上一個activity從左側進入,要finish的activity退出到右側
        overridePendingTransition(R.anim.enter_from_left, R.anim.exit_to_right);
    }

R.anim.enter_from_right

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="500"
        android:fromXDelta="100%p"
        android:toXDelta="0"/>
</set>

R.anim.exit_to_left

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="500"
        android:fromXDelta="0"
        android:toXDelta="-100%p"/>
</set>

R.anim.enter_from_left

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="500"
        android:fromXDelta="-100%p"
        android:toXDelta="0"/>

</set>

R.anim.exit_to_right

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="500"
        android:fromXDelta="-100%p"
        android:toXDelta="0"/>

</set>

二、屬性動畫

屬性動畫幾乎無所不能,只要對象有這個屬性,就可以對這個屬性做動畫。甚至還可以沒有對象。可用通過ObjectAnimator、ValueAnimator、AnimatorSet實現豐富的動畫。

2.1 使用方法

屬性動畫可對任意對象做動畫,不僅僅是View。默認動畫時間是300ms,10ms/幀。具體理解就是:可在給定的時間間隔內 實現 對象的某屬性值 從 value1 到 value2的改變。

使用很簡單,可以直接代碼實現(推薦),也可xml實現,舉例如下:

        //屬性動畫使用,方式一:代碼,建議使用。 橫移
        ObjectAnimator translationX = ObjectAnimator
                .ofFloat(textView6, "translationX", 0, 200)
                .setDuration(1000);
        translationX.setInterpolator(new LinearInterpolator());
        setAnimatorListener(translationX);

        //屬性動畫使用,方式二:xml。   豎移
        Animator animatorUpAndDown = AnimatorInflater.loadAnimator(this, R.animator.animator_test);
        animatorUpAndDown.setTarget(textView6);

        //文字顏色變化
        ObjectAnimator textColor = ObjectAnimator
                .ofInt(textView6, "textColor", 0xffff0000, 0xff00ffff)
                .setDuration(1000);
        textColor.setRepeatCount(ValueAnimator.INFINITE);
        textColor.setRepeatMode(ValueAnimator.REVERSE);
        //注意,這里如果不設置 那么顏色就是跳躍的,設置ArgbEvaluator 就是連續過度的顏色變化
        textColor.setEvaluator(new ArgbEvaluator());

        //animatorSet
        mAnimatorSet = new AnimatorSet();
        mAnimatorSet
                .play(animatorUpAndDown)
                .with(textColor)
                .after(translationX);

        mAnimatorSet.start();


    /**
     * 設置屬性動畫的監聽
     * @param translationX
     */
    private void setAnimatorListener(ObjectAnimator translationX) {
        translationX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //每播放一幀,都會調用
            }
        });
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            translationX.addPauseListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationResume(Animator animation) {
                    super.onAnimationResume(animation);
                }
            });
        }

        translationX.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
            }
        });
    }

R.animator.animator_test,是放在res/animator中。

<?xml version="1.0" encoding="utf-8"?>
<!--屬性動畫test,一般建議采用代碼實現,不用xml-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="sequentially">


    <!--repeatCount:默認是0,-1是無限循環-->
    <!--repeatMode:重復模式:restart-從頭來一遍、reverse-反向來一遍-->
    <!--valueType:指定propertyName的類型可選intType、floatType-->

    <!--android:pathData=""
        android:propertyXName=""
        android:propertyYName=""-->
    <objectAnimator
        android:propertyName="translationY"
        android:duration="1000"
        android:valueFrom="0"
        android:valueTo="120"
        android:startOffset="0"
        android:repeatCount="0"
        android:repeatMode="reverse"
        android:valueType="floatType"
        android:interpolator="@android:interpolator/accelerate_decelerate" />

    <!--animator對用vueAnimator,比objectAnimator少了propertyName-->
    <!--<animator-->
        <!--android:duration="2000"-->
        <!--android:valueFrom=""-->
        <!--android:valueTo=""-->
        <!--android:startOffset=""-->
        <!--android:repeatCount=""-->
        <!--android:repeatMode=""-->
        <!--android:valueType=""-->
        <!--android:interpolator=""-->
        <!--android:pathData=""-->
        <!--android:propertyXName=""-->
        <!--android:propertyYName=""/>-->

</set>

translationX是實現橫移,animatorUpAndDown是實現豎移、textColor是實現文字顏色變化。其中animatorUpAndDown是使用xml定義,標簽含義也很好理解。 最后使用AnimatorSet的play、with、after 實現 先橫移,然后 豎移和顏色變化 同時的動畫集合效果。

注意點

  1. 關于View動畫和屬性動畫的平移屬性動畫改變屬性值setTranslationX 的視圖效果像view動畫的平移一樣,都是view實際的layout位置沒變,只改變了視圖位置;不同點是屬性動畫 給觸摸點生效區域增加了位移(而view動畫僅改變了視圖位置)。
  2. 插值器:Interpolator,根據 時間流逝的百分比,計算當前屬性值改變的百分比。 例如duration是1000,start后過了200,那么時間百分比是0.2,那么如果差值器是LinearInterpolator線性差值器,那么屬性值改變的百分比也是0.2
  3. 估值器:Evaluator,就是根據 差值器獲取的 屬性值百分比,計算改變后的屬性值。 ofInt、onFloat內部會自動設置IntEvaluator、FloatEvaluator。如果使用ofInt且是顏色相關的屬性,就要設置ArgbEvaluator。 上面例子中 文字顏色變化動畫 設置了ArgbEvaluator:textColor.setEvaluator(new ArgbEvaluator())。
  4. 動畫監聽:主要是兩個監聽接口,AnimatorUpdateListener、AnimatorListenerAdapter。AnimatorUpdateListener的回調方法在每幀更新時都會調用一次;AnimatorListenerAdapter可以監聽開始、結束、暫停、繼續、重復、取消,重寫你要關注的方法即可。

2.2對任意屬性做動畫

一個問題,針對下面的Button,如何實現 的寬度逐漸拉長的動畫,即文字不變,僅拉長背景寬度?

    <Button
        android:id="@+id/button_animator_test"
        android:layout_width="180dp"
        android:layout_height="wrap_content"
        android:text="任意屬性動畫-寬度拉長"/>

首先,View動畫的ScaleAnimation是無法實現的,因為view的scale是把view的視圖放大,這樣文字也會拉長變形。那么屬性動畫呢?試試~

        ObjectAnimator width1 = ObjectAnimator.ofInt(button, "width", 1000);
        width1.setDuration(2000);
        width1.start();

但是發現,沒有效果!這是為啥呢?解釋如下.

對object 的任意屬性做動畫 要求兩個條件:

  1. object有 對應屬性 的set方法,動畫中沒設置初始值 還要有get方法,系統要去取初始值(不滿足則會crash)。
  2. set方法要對object有所改變,如UI的變化。不滿足則會沒有動畫效果

上面Button沒有動畫效果,就是沒有滿足第二條。看下Button的setWidth方法:

    public void setWidth(int pixels) {
        mMaxWidth = mMinWidth = pixels;
        mMaxWidthMode = mMinWidthMode = PIXELS;
        requestLayout();
        invalidate();
    }

實際就是TextView的setWidth方法,看到設置進去的值僅影響了寬度最大值和最小值。按照官方注釋和實測,發現只有當Button/TextView在xml中設置android:layout_width為"wrap_content"時,才會setWidth改變寬度;而當Button/TextView在xml中設置android:layout_width為固定dp值時,setWidth無效。 而我們上面給出的Button xml中確實是固定值180dp,所以是屬性"width"的setWidth是無效的,即不滿足第二條要求,就沒有動畫效果了。(當修改Button xml中設置android:layout_width為"wrap_content"時,上面執行的屬性動畫是生效的。)

那么,當不滿足條件時,如何解決此問題呢? 有如下處理方法:

  1. 給object添加set、get方法,如果有權限。(一般不行,如TextView是SDK里面的不能直接改)
  2. 給Object包裝一層,在包裝類中提供set、get方法。
  3. 使用ValueAnimator,監聽Value變化過程,自己實現屬性的改變。
    private void testAnimatorAboutButtonWidth() {
        //Button width 屬性動畫:如果xml中寬度是wrap_content,那么動畫有效。
        // 如果設置button確切的dp值,那么無效,因為對應屬性"width"的setWidth()方法就是 在wrap_content是才有效。
        ObjectAnimator width1 = ObjectAnimator.ofInt(button, "width", 1000);
        width1.setDuration(2000);
//        width1.start();

        //那么,想要在button原本有確切dp值時,要能對width動畫,怎么做呢?
        //方法一,包一層,然后用layoutParams
        ViewWrapper wrapper = new ViewWrapper(button);
        ObjectAnimator width2 = ObjectAnimator.ofInt(wrapper, "width", 1000);
        width2.setDuration(2000);
//        width2.start();

        //方法二,使用ValueAnimator,每一幀自己顯示寬度的變化
        ValueAnimator valueAnimator = ValueAnimator.ofInt(button.getLayoutParams().width, 1000);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int animatedValue = (Integer) animation.getAnimatedValue();
                Log.i("hfy", "onAnimationUpdate: animatedValue=" + animatedValue);

//                IntEvaluator intEvaluator = new IntEvaluator();
////                獲取屬性值改變比例、計算屬性值
//                float animatedFraction = animation.getAnimatedFraction();
//                Integer evaluate = intEvaluator.evaluate(animatedFraction, 300, 600);
//                Log.i("hfy", "onAnimationUpdate: evaluate="+evaluate);


                if (button != null) {
                    button.getLayoutParams().width = animatedValue;
                    button.requestLayout();
                }
            }
        });

        valueAnimator.setDuration(4000).start();

    }

    /**
     * 包一層,提供對應屬性的set、get方法
     */
    private class ViewWrapper {

        private final View mView;

        public ViewWrapper(View view) {
            mView = view;
        }

        public int getWidth() {
            return mView.getLayoutParams().width;
        }

        public void setWidth(int width) {
            ViewGroup.LayoutParams layoutParams = mView.getLayoutParams();
            layoutParams.width = width;
            mView.setLayoutParams(layoutParams);
            mView.requestLayout();
        }
    }

2.3 屬性動畫的原理

屬性動畫,要求對象有這個屬性的set方法,執行時會根據傳入的 屬性初始值、最終值,在每幀更新時調用set方法設置當前時刻的 屬性值。隨著時間推移,set的屬性值會接近最終值,從而達到動畫效果。如果沒傳入初始值,那么對象還要有get方法,用于獲取初始值。

在獲取初始值、set屬性值時,都是使用 反射 的方式,進行 get、set方法的調用。
見PropertyValuesHolder的setupValue、setAnimatedValue方法:

    private void setupValue(Object target, Keyframe kf) {
        if (mProperty != null) {
            Object value = convertBack(mProperty.get(target));
            kf.setValue(value);
        } else {
            try {
                if (mGetter == null) {
                    Class targetClass = target.getClass();
                    setupGetter(targetClass);
                    if (mGetter == null) {
                        // Already logged the error - just return to avoid NPE
                        return;
                    }
                }
                Object value = convertBack(mGetter.invoke(target));
                kf.setValue(value);
            } catch (InvocationTargetException e) {
                Log.e("PropertyValuesHolder", e.toString());
            } catch (IllegalAccessException e) {
                Log.e("PropertyValuesHolder", e.toString());
            }
        }
    }
    void setAnimatedValue(Object target) {
        if (mProperty != null) {
            mProperty.set(target, getAnimatedValue());
        }
        if (mSetter != null) {
            try {
                mTmpValueArray[0] = getAnimatedValue();
                mSetter.invoke(target, mTmpValueArray);
            } catch (InvocationTargetException e) {
                Log.e("PropertyValuesHolder", e.toString());
            } catch (IllegalAccessException e) {
                Log.e("PropertyValuesHolder", e.toString());
            }
        }
    }

以上效果圖:

動畫效果

三、使用動畫的注意事項

  1. 使用幀動畫,避免OOM。因為圖片多。
  2. 屬性動畫 如果有循環動畫,在頁面退出時要及時停止,避免內存泄漏。
  3. 使用View動畫后,調用setVisibility(View.GONE)失效時,使用view.clearAnimation()可解決。

附上之前記錄的一些動畫效果
自定義view:TextSwitcher使用
自定義view:信息飄窗/彈幕——AutoSwitchTextView
自定義view:ProgressBar 前景色、背景色、平滑顯示進度

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370