ViewAnimator源碼分析

我每周會寫一篇源代碼分析的文章,以后也可能會有其他主題.
如果你喜歡我寫的文章的話,歡迎關(guān)注我的新浪微博@達(dá)達(dá)達(dá)達(dá)sky
地址: http://weibo.com/u/2030683111
每周我會第一時間在微博分享我寫的文章,也會積極轉(zhuǎn)發(fā)更多有用的知識給大家.謝謝關(guān)注_,說不定什么時候會有福利哈.


項(xiàng)目地址:ViewAnimator豫缨,本文分析版本: dfa45e0

1.簡介

ViewAnimator.png

在項(xiàng)目開發(fā)中我們應(yīng)該都接觸過動畫效果的開發(fā).我們知道在Andorid中實(shí)現(xiàn)動畫大致分為兩類,一種是Tween/Frame動畫,另一種是Property Animation也就是屬性動畫.關(guān)于這兩種動畫的使用方法我們這篇文章就不多做討論了。可以從這篇文章了解更多寂屏。我們這篇文章只涉及屬性動畫相關(guān)知識洽故。我們今天要介紹的ViewAnimator就是用來簡化我們寫屬性動畫的的代碼量的,它可以通過非常簡潔的代碼通過建造者模式調(diào)用來組合各種動畫.讓我們的代碼簡潔易讀咒循。如果你的APP里需要各種動畫組合,ViewAnimator一定是你的最佳選擇隅茎。

2.使用方法

想必大家都使用過屬性動畫了欣孤。我們來做一個最簡單的位移動畫:


        ObjectAnimator animator = ObjectAnimator.ofFloat(textView, "translationX",0, 500);
        animator.setDuration(2000);
        animator.setRepeatCount(1);
        animator.setInterpolator(new BounceInterpolator());
        animator.start();

上面的代碼執(zhí)行之后就可以使textView從當(dāng)前位置水平移動500px,整個動畫過程是2s,并且添加了一個彈性插值器,而且使動畫再重復(fù)執(zhí)行一遍。這樣看起來整個代碼還是很清晰的,使用起來也很方便,但是如果我們要多個View相互組合再加上各種動畫,可想而知代碼量會有多少了呀潭。下面我們就用屬性動畫來寫一個下面這張圖里的動畫:

ViewAnimatorGif.gif

這張圖里包含了:1.文字顏色的漸變以及背景的漸變,然后同時又textView放大動畫,和兩張圖片的下落動畫,第一組動畫結(jié)束后,圓形的圖片開始旋轉(zhuǎn),然后textView不斷的顯示進(jìn)度.這就是所有動畫,下面是我們實(shí)現(xiàn)的代碼:


        ObjectAnimator mountainTransY = ObjectAnimator.ofFloat(mountain, "translationY", - dip2px(500), 0);
        ObjectAnimator mountainAlpha = ObjectAnimator.ofFloat(mountain, "alpha", 0, 1);
        ObjectAnimator imageTransY = ObjectAnimator.ofFloat(image, "translationY", - dip2px(500), 0);
        ObjectAnimator imageAlpha = ObjectAnimator.ofFloat(image, "alpha", 0, 1);
        ObjectAnimator percentScaleX = ObjectAnimator.ofFloat(percent, "scaleX", 0, 1);
        ObjectAnimator percentScaleY = ObjectAnimator.ofFloat(percent, "scaleY", 0, 1);
        ObjectAnimator textColorAnimator = ObjectAnimator.ofInt(text, "textColor", Color.BLACK, Color.WHITE, Color.RED);
        textColorAnimator.setEvaluator(new ArgbEvaluator());
        ObjectAnimator textBackgroundAnimator = ObjectAnimator.ofInt(text, "backgroundColor", Color.WHITE, Color.BLACK, Color.YELLOW);
        textBackgroundAnimator.setEvaluator(new ArgbEvaluator());

        ObjectAnimator imageRotation = ObjectAnimator.ofFloat(image, "rotation", 0, 360);
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                percent.setText(String.format(Locale.US, "%.02f%%", animation.getAnimatedValue()));
            }
        });

        AnimatorSet firstSet = new AnimatorSet();
        firstSet.playTogether(mountainTransY, mountainAlpha, imageTransY, imageAlpha, percentScaleX,
                percentScaleY, textColorAnimator, textBackgroundAnimator);
        firstSet.setInterpolator(new AccelerateDecelerateInterpolator());
        firstSet.setDuration(5000);

        final AnimatorSet secondSet = new AnimatorSet();
        secondSet.playTogether(imageRotation, valueAnimator);
        secondSet.setDuration(5000);

        firstSet.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                secondSet.start();
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });
        firstSet.start();

上面就是實(shí)現(xiàn)這個效果的所有代碼.借用岳云鵬的一句話就是:"我的天哪"(請腦補(bǔ)配音)钉迷。這么一大坨代碼。钠署。我整整寫了十幾分鐘糠聪。。而且從這么多代碼來看上去,如果以后需要調(diào)整動畫的話,無論如何也得先整個看一遍才能找到怎么調(diào)節(jié)谐鼎。這樣維護(hù)成本就增加了舰蟆。那么如何解決這個問題呢?這就要用到我們今天要介紹的主角ViewAnimator,下面是用ViewAnimator來實(shí)現(xiàn)相同動畫的代碼:


        ViewAnimator.animate(mountain, image)
                .dp().translationY(-500, 0)
                .alpha(0, 1)

                .andAnimate(percent)
                .scale(0, 1)

                .andAnimate(text)
                .textColor(Color.BLACK, Color.WHITE)
                .backgroundColor(Color.WHITE, Color.BLACK)
                .waitForHeight()
                .interpolator(new AccelerateDecelerateInterpolator())
                .duration(2000)

                .thenAnimate(percent)
                .custom(new AnimationListener.Update<TextView>() {
                    @Override
                    public void update(TextView view, float value) {
                        view.setText(String.format(Locale.US, "%.02f%%", value));
                    }
                }, 0, 1)
                .andAnimate(image)
                .rotation(0, 360)
                .duration(2000)
                .start();

真是又簡潔又易讀。簡直"完美"(請?jiān)倌X補(bǔ)配音)狸棍∩砗Γ可以看到從上到下,我們需要先通過animate(View... view)方法將我們要進(jìn)行動畫的View傳入,然后通過建造者模式調(diào)用我們需要做的動畫,方法名代表我們需要動畫的屬性,方法參數(shù)里直接傳入數(shù)值即可。andAnimate(View... view)表示同時做該view的動畫但是具體的動畫可以不一樣草戈。然后通過thenAnimate(View... view)方法就可以表示前面的動畫執(zhí)行完畢后再執(zhí)行的動畫.具體每個方法代表的意思也很清楚就是我們需要操作的屬性的意思塌鸯。所以整體來看代碼簡潔易讀又好維護(hù)。

此外ViewAnimator還封裝了不少動畫組合讓我們拿來即用,例如:standUp(),wave(),shake()等等動畫唐片。此外還支持Path以及SVG Path動畫.更多的使用方法可以參照ViewAnimatorREADME.md丙猬。下面我們就具體來看看如此好用的ViewAnimator是如何實(shí)現(xiàn)的。

3.類關(guān)系圖

classes-relation.png

從類圖上來看ViewAnimator的結(jié)構(gòu)也很簡單明了,ViewAnimatorAnimationBuilder雙向關(guān)聯(lián).AnimationListener.StartAnimationListener.Stop兩個接口是單獨(dú)定義出來,分別用來在動畫開始和結(jié)束時的回調(diào)牵触。下面我們就來看看具體是如何實(shí)現(xiàn)的:

4.源碼分析

ViewAnimator的實(shí)現(xiàn)并不復(fù)雜,我相信大家都應(yīng)該能看懂,但是作者的實(shí)現(xiàn)思路非常值得我們學(xué)習(xí),所以我們還是按照我們一直以來的方式來看,根據(jù)我們的使用方法,來分析ViewAnimator的調(diào)用流程來看具體的實(shí)現(xiàn)淮悼。

由于ViewAnimator類和AnimationBuilder是相互調(diào)用,所以我這里為了防止理解錯誤,在我們看到的執(zhí)行的方法都寫在了對應(yīng)的類里,并省略了其他方法。

1.ViewAnimator.animate(mountain, image);的實(shí)現(xiàn):


public class ViewAnimator {
    ...
    private List<AnimationBuilder> animationList = new ArrayList<>();

    public static AnimationBuilder animate(View... view) {
        //創(chuàng)建一個ViewAnimator對象.
        ViewAnimator viewAnimator = new ViewAnimator();
        //通過addAnimationBuilder方法返回一個AnimationBuilder對象
        return viewAnimator.addAnimationBuilder(view);
    }

    public AnimationBuilder addAnimationBuilder(View... views) {
        //創(chuàng)建一個animationBuilder對象并添加到animationList中去
        AnimationBuilder animationBuilder = new AnimationBuilder(this, views);
        animationList.add(animationBuilder);
        return animationBuilder;
    }
    ...
}

public class AnimationBuilder {
    ...
    private final ViewAnimator viewAnimator;
    private final View[] views;

    public AnimationBuilder(ViewAnimator viewAnimator, View... views) {
        //分別賦值viewAnimator和views
        this.viewAnimator = viewAnimator;
        this.views = views;
    }
    ...
}

可以看到這一步初始化了一個ViewAnimator對象和一個AnimationBuilder,并將AnimationBuilder對象保存在了ViewAnimatoranimationList數(shù)組里,Views則保存在了AnimationBuilder對象里.

2.dp().translationY(-500, 0).alpha(0, 1);的實(shí)現(xiàn):

由于返回了一個AnimationBuilder對象,所以dp()方法肯定在AnimationBuilder里實(shí)現(xiàn):


public class AnimationBuilder {
    ...
    public AnimationBuilder dp() {
        //標(biāo)記nextValueWillBeDp
        nextValueWillBeDp = true;
        return this;
    }

    public AnimationBuilder translationY(float... y) {
        return property("translationY", y);
    }
    
    public AnimationBuilder alpha(float... alpha) {
        return property("alpha", alpha);
    }

    public AnimationBuilder property(String propertyName, float... values) {
        //遍歷views中的所有view,依次實(shí)例化ObjectAnimator對象
        //并添加到AnimationBuilder的animatorList對象中.
        for (View view : views) {
            this.animatorList.add(ObjectAnimator.ofFloat(view, propertyName, getValues(values)));
        }
        return this;
    }
    ...
}

先是標(biāo)記了nextValueWillBeDp,然后translationY(float... y)alpha(float... alpha)方法都是調(diào)用了property(String propertyName, float... values)方法,然后在這個方法里去實(shí)例化對應(yīng)的ObjectAnimator對象,這樣就避免了我們重復(fù)寫很多創(chuàng)建ObjectAnimator對象的代碼了,所以我們類似的操作下面這些屬性時都會調(diào)用這個方法:

  • translationY
  • translationX
  • alpha
  • scaleX
  • scaleY
  • rotationX
  • rotationY
  • rotation

3.andAnimate(text).textColor(Color.BLACK, Color.WHITE).backgroundColor(Color.WHITE, Color.BLACK)的實(shí)現(xiàn):


public class AnimationBuilder {
    ...
    public AnimationBuilder andAnimate(View... views) {
        return viewAnimator.addAnimationBuilder(views);
    }
    ...
}

public class ViewAnimator {
    ...
    public AnimationBuilder addAnimationBuilder(View... views) {
        //創(chuàng)建一個animationBuilder對象并添加到animationList中去
        AnimationBuilder animationBuilder = new AnimationBuilder(this, views);
        animationList.add(animationBuilder);
        return animationBuilder;
    }
    ...
}

注意這里是先在AnimationBuilderviewAnimator初始化了一個新的AnimationBuilder對象并返回了,當(dāng)然也同樣添加進(jìn)了animationList,所以下面的textColor(Color.BLACK, Color.WHITE).backgroundColor(Color.WHITE, Color.BLACK)就會實(shí)例化text對應(yīng)的ObjectAnimator對象了,代碼這里我們就不貼了,我們繼續(xù)往下看揽思。

4.waitForHeight().interpolator(new Interpolator()).duration(2000);方法的實(shí)現(xiàn)


public class AnimationBuilder {
    ...
    public AnimationBuilder waitForHeight() {
        //waitForHeight表示當(dāng)View開始繪制的時候再開始動畫.
        waitForHeight = true;
        return this;
    }

    public AnimationBuilder interpolator(Interpolator interpolator) {
        //賦值插值器,直接賦值給了viewAnimator中的interpolator對象
        viewAnimator.interpolator(interpolator);
        return this;
    }

    public AnimationBuilder duration(long duration) {
        //設(shè)定動畫持續(xù)時間,也是直接賦值給了viewAnimator的duration對象
        viewAnimator.duration(duration);
        return this;
    }
    ...
}

這些都很簡單,我們繼續(xù)往下看thenAnimate(percent).custom(...);方法袜腥。

5.thenAnimate(percent).custom(...);方法的實(shí)現(xiàn)


public class AnimationBuilder {
    ...
    public AnimationBuilder thenAnimate(View... views) {
        //直接調(diào)用viewAnimator的thenAnimate()方法
        return viewAnimator.thenAnimate(views);
    }
    ...
}

public class ViewAnimator {
    ...
    public AnimationBuilder thenAnimate(View... views) {
        //再創(chuàng)建一個nextViewAnimator對象
        ViewAnimator nextViewAnimator = new ViewAnimator();
        //將nextViewAnimator賦值給當(dāng)前ViewAnimator對象的next.
        this.next = nextViewAnimator;
        nextViewAnimator.prev = this;
        //return一個nextViewAnimator創(chuàng)建的AnimationBuilder對象
        return nextViewAnimator.addAnimationBuilder(views);
    }
    ...
}

可以看到這里是又創(chuàng)建了一個新的ViewAnimator對象和一個新的AnimationBuilder對象,注意這里的nextprev賦值,其實(shí)就是數(shù)據(jù)結(jié)構(gòu)中雙向鏈表的思想,這里我們在動畫開始的時候就可以根據(jù)prev來找到最早的ViewAnimator對象,然后再用next就可以將動畫順序執(zhí)行了。

5.custom(...);方法的實(shí)現(xiàn)

在使用方法里我們是這樣調(diào)用的:

    .custom(new AnimationListener.Update<TextView>() {
        @Override
        public void update(TextView view, float value) {
            view.setText(String.format(Locale.US, "%.02f%%", value));
        }
    }, 0, 1)

這樣就實(shí)現(xiàn)了textView從0-1的進(jìn)度顯示.原理如下:


public class AnimationListener {
    ...
    public interface Update<V extends View>{
        void update(V view, float value);
    }
    ...
}

public class AnimationBuilder {
    ...
    public AnimationBuilder custom(final AnimationListener.Update update, float... values) {
        //遍歷所有view,實(shí)例化valueAnimator,并在onAnimationUpdate()回調(diào)接口里,回調(diào)
        //update接口,最后把valueAnimator添加到animatorList中去
        for (final View view : views) {
            ValueAnimator valueAnimator = ValueAnimator.ofFloat(getValues(values));
            if (update != null)
                valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        //noinspection unchecked
                        update.update(view, (Float) animation.getAnimatedValue());
                    }
                });
            add(valueAnimator);
        }
        return this;
    }
    ...
}

我們可以看到update接口里可以傳入任何View的子類,但是其實(shí)在AnimationBuilder里會遍歷所有當(dāng)前動畫View的并全部添加了這個valueAnimator,這樣做有一個問題就是,雖然作者想傳入泛型是想不在回調(diào)方法里強(qiáng)制轉(zhuǎn)換從而直接做操作,但是這樣做是不安全的,如果現(xiàn)在我把thenAnimate(percent).custom(...);方法寫成thenAnimate(percent, image).custom(...);運(yùn)行時立馬會報ClassCastException:

    java.lang.ClassCastException: android.support.v7.widget.AppCompatImageView cannot be cast to android.widget.TextView

因?yàn)橛捎诿總€做動畫的view都添加了這個回調(diào),再回調(diào)處理的時候又會直接當(dāng)成TextView來處理所以會崩潰。所以我們在使用的時候一定要注意這個羹令。當(dāng)然在下文的個人評價中我也會給出解決辦法,這里我們知道就好了鲤屡。

下面的調(diào)用很相似我們就不看了,我們直接來看start()方法.

5.start();方法的實(shí)現(xiàn)

首先是在AnimationBuilder中直接調(diào)用了ViewAnimatorstart()方法:

    public void start() {
        viewAnimator.start();
    }

再來看ViewAnimatorstart()方法:


public class ViewAnimator {
    ...
    public ViewAnimator start() {
        if (prev != null) {
            //如果有上一個ViewAnimator則先調(diào)用上一個的start()方法
            prev.start();
        } else {
            //創(chuàng)建AnimatorSet對象
            animatorSet = createAnimatorSet();
            //如果需要等待view繪制則監(jiān)聽onPreDraw()方法,
            if (waitForThisViewHeight != null) {
                waitForThisViewHeight.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                    @Override
                    public boolean onPreDraw() {
                        animatorSet.start();
                        waitForThisViewHeight.getViewTreeObserver().removeOnPreDrawListener(this);
                        return false;
                    }
                });
            } else {
                //直接開始
                animatorSet.start();
            }
        }
        return this;
    }

    protected AnimatorSet createAnimatorSet() {
        //新建一個animators列表
        List<Animator> animators = new ArrayList<>();
        //將所有的animationBuilder對象中的Animator對象添加
        for (AnimationBuilder animationBuilder : animationList) {
            animators.addAll(animationBuilder.createAnimators());
        }

        //如果標(biāo)記了waitForHeight,
        //則返回animationBuilder里View數(shù)組的第一個view
        for (AnimationBuilder animationBuilder : animationList) {
            if (animationBuilder.isWaitForHeight()) {
                waitForThisViewHeight = animationBuilder.getView();
                break;
            }
        }
        //如果有ValueAnimator 則單獨(dú)設(shè)置重復(fù)模式和重復(fù)次數(shù)
        for (Animator animator : animators) {
            if (animator instanceof ValueAnimator) {
                ValueAnimator valueAnimator = (ValueAnimator) animator;
                valueAnimator.setRepeatCount(repeatCount);
                valueAnimator.setRepeatMode(repeatMode);
            }
        }

        //設(shè)置AnimatorSet的參數(shù)
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playTogether(animators);

        animatorSet.setDuration(duration);
        animatorSet.setStartDelay(startDelay);
        if (interpolator != null)
            animatorSet.setInterpolator(interpolator);

        animatorSet.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                //回調(diào)Start接口
                if (startListener != null) startListener.onStart();
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                //回調(diào)Stop接口
                if (stopListener != null) stopListener.onStop();
                //如果有下一個ViewAnimator則繼續(xù)執(zhí)行
                if (next != null) {
                    next.prev = null;
                    next.start();
                }
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });

        return animatorSet;
    }
    ...
}

從上到下應(yīng)該很清晰的看出,其實(shí)就是在內(nèi)部創(chuàng)建了AnimatorSet對象,然后設(shè)置一些參數(shù),最后執(zhí)行,然后再在onAnimationEnd(Animator animation)的接口里檢查是否還有動畫,從而一直鏈?zhǔn)降膱?zhí)行。

上面就是整個的ViewAnimator主要的實(shí)現(xiàn)了,雖然看上去并不難,但是也不是很容易就能寫出來的福侈。值得我們好好學(xué)習(xí)酒来。

5.個人評價

最近在我負(fù)責(zé)的項(xiàng)目里,我們設(shè)計了大量的組合動畫與交互,如果使用原生的方法會使代碼量特別大,而且相當(dāng)難維護(hù),因此我使用了ViewAnimator簡化了大量的動畫代碼,而且使代碼更易讀,可維護(hù)性就更好了。如果你的項(xiàng)目里也有比較多的動畫,強(qiáng)烈推薦ViewAnimator而且這個庫并沒有幾個類,占用的體積非常小,推薦成為項(xiàng)目標(biāo)配肪凛。

最后還有兩點(diǎn)要說的問題就是:

1.AnimationListener.Update的問題

這個問題我們在上面已經(jīng)說過了,使用不當(dāng)會造成崩潰,而且我們把泛型作為參數(shù)傳入之后,我覺得意義并不大,完全可以在回調(diào)接口里直接根據(jù)value直接操作我們的View,但是由于ViewAnimator里還有其他地方依賴Update接口,所以我把AnimationListener.Update修改成了下面這樣堰汉。經(jīng)測試使用完全沒有問題。


public class AnimationListener {

    public interface Update {
        void update(View view, float value);
    }
}

2.為單獨(dú)的AnimationBuilder添加Interpolator的問題.

在使用中發(fā)現(xiàn)如果我同時組合了好幾個動畫之后,只能為這些同時動畫的屬性添加同一個Interpolator這樣不滿足我想同時動畫多個View但又要不同的Interpolator需求.所以我就在ViewAnimator的基礎(chǔ)上添加了單個Interpolator的功能,而且給ViewAnimator的作者發(fā)了pull request.詳細(xì)原理我就不講了,比較簡單,大家可以在我fork的分支上查看具體的實(shí)現(xiàn)方法:
Commit地址在這. 好了今天就寫到這吧伟墙。

我每周會寫一篇源代碼分析的文章,以后也可能會有其他主題.
如果你喜歡我寫的文章的話,歡迎關(guān)注我的新浪微博@達(dá)達(dá)達(dá)達(dá)sky
地址: http://weibo.com/u/2030683111
每周我會第一時間在微博分享我寫的文章,也會積極轉(zhuǎn)發(fā)更多有用的知識給大家.謝謝關(guān)注_,說不定什么時候會有福利哈.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末翘鸭,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子戳葵,更是在濱河造成了極大的恐慌就乓,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拱烁,死亡現(xiàn)場離奇詭異生蚁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)戏自,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進(jìn)店門邦投,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人浦妄,你說我怎么就攤上這事尼摹。” “怎么了剂娄?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵蠢涝,是天一觀的道長。 經(jīng)常有香客問我阅懦,道長和二,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任耳胎,我火速辦了婚禮惯吕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘怕午。我一直安慰自己废登,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布郁惜。 她就那樣靜靜地躺著堡距,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上羽戒,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天缤沦,我揣著相機(jī)與錄音,去河邊找鬼易稠。 笑死缸废,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的驶社。 我是一名探鬼主播企量,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼衬吆!你這毒婦竟也來了梁钾?” 一聲冷哼從身側(cè)響起绳泉,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤逊抡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后零酪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冒嫡,經(jīng)...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年四苇,在試婚紗的時候發(fā)現(xiàn)自己被綠了孝凌。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡月腋,死狀恐怖蟀架,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情榆骚,我是刑警寧澤片拍,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站妓肢,受9級特大地震影響捌省,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜碉钠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一纲缓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧喊废,春花似錦祝高、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春斤寂,著一層夾襖步出監(jiān)牢的瞬間耿焊,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工遍搞, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留罗侯,地道東北人。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓溪猿,卻偏偏與公主長得像钩杰,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子诊县,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內(nèi)容