HTextView源碼分析

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


HTextView是一個(gè)用來(lái)給TextView里的文字做各種轉(zhuǎn)換動(dòng)畫(huà)的開(kāi)源庫(kù),第一次看到這個(gè)庫(kù)的時(shí)候就被這些動(dòng)畫(huà)吸引了,不僅提供了多種動(dòng)畫(huà)選擇,而且還有重復(fù)字符的位移動(dòng)畫(huà),的確別出心裁,雖然實(shí)現(xiàn)起來(lái)并不是多么復(fù)雜,但是從1700+的star數(shù)上還是可以看出它的受歡迎程度,所以今天我們就來(lái)分析看看它到底是如何實(shí)現(xiàn)的.有哪些值得我們借鑒的地方,又有哪些不完善的地方静秆。

使用方法

HTextView的使用方法還是比較簡(jiǎn)單的,只需要調(diào)用hTextView.setAnimateType();來(lái)設(shè)定一種動(dòng)畫(huà)的類(lèi)型,再調(diào)用hTextView.animateText();將字符串傳入就可以執(zhí)行切換動(dòng)畫(huà)了,此外還提供了hTextView.reset();方法來(lái)重置動(dòng)畫(huà),具體代碼如下:

hTextView.setAnimateType(HTextViewType.SCALE);
hTextView.animateText(sentences[mCounter]);

類(lèi)關(guān)系圖

當(dāng)我們?nèi)シ治鲆粋€(gè)項(xiàng)目的時(shí)候,首先看這個(gè)類(lèi)庫(kù)的UML類(lèi)圖往往是最直觀的,能很清晰的將各個(gè)類(lèi)的關(guān)系用圖的形式展示的很清楚,這里我是使用的Android Studio的插件simpleUMLCE來(lái)自動(dòng)生成的類(lèi)圖,非常方便推薦給大家,另外如果看不懂UML圖可以參照深入淺出UML類(lèi)圖系列,已經(jīng)講得很詳細(xì)我就不再補(bǔ)充了。

從類(lèi)圖上看我們可以很清晰的看出,首先是定義了一個(gè)IHText的接口,然后HText實(shí)現(xiàn)了IHText的接口,然后左邊的那么多類(lèi),可以從名字上大致猜出是各種動(dòng)畫(huà)的具體實(shí)現(xiàn)他們都是繼承了HText類(lèi),值得一提的是PixelateTextBurnText是直接實(shí)現(xiàn)IHText的,我猜測(cè)應(yīng)該是作者后期重構(gòu)代碼的時(shí)候忘記這兩個(gè)類(lèi)了,實(shí)際上這兩個(gè)類(lèi)也是可以繼承自HText來(lái)實(shí)現(xiàn).最后可以看出IHText是和HTextView相互耦合的.好的,類(lèi)圖就講到這里,下面我們來(lái)看具體實(shí)現(xiàn).

源碼分析

在開(kāi)始分析一個(gè)開(kāi)源項(xiàng)目的時(shí)候,我們往往先從其定義的接口來(lái)看,所以我們先看IHText接口是如何定義的:

public interface IHText {
    void init(HTextView hTextView, AttributeSet attrs, int defStyle);
    void animateText(CharSequence text);
    void onDraw(Canvas canvas);
    void reset(CharSequence text);
}

首先init()方法,顧名思義應(yīng)該是進(jìn)行一些初始化的操作,annimateText應(yīng)該就是讓文字開(kāi)始做動(dòng)畫(huà)的方法,onDraw這個(gè)大家應(yīng)該都很熟悉了,因?yàn)樽鰟?dòng)畫(huà),實(shí)際上就是一幀一幀的繪制然后來(lái)組成動(dòng)畫(huà),所以onDraw方法也是必須的.最后一個(gè)reset應(yīng)該就是重置文字以及一些狀態(tài)等等.

看完了接口定義,不用著急,接下來(lái)我們?nèi)タ?code>HTextView,精簡(jiǎn)后的代碼如下:

public class HTextView extends TextView {
    private IHText mIHText = new ScaleText();
    private AttributeSet attrs;
    private int defStyle;

    public HTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs, defStyle);
    }

    private void init(AttributeSet attrs, int defStyle) {
        this.attrs = attrs;
        this.defStyle = defStyle;
        TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.HTextView);
        int animateType = typedArray.getInt(R.styleable.HTextView_animateType, 0);
        switch (animateType) {
            case 0:
                mIHText = new ScaleText();
                break;
            case 1:
                mIHText = new EvaporateText();
                break;
            case 2:
                mIHText = new FallText();
                break;
        }
        typedArray.recycle();
        initHText(attrs, defStyle);
    }

    private void initHText(AttributeSet attrs, int defStyle) {
        mIHText.init(this, attrs, defStyle);
    }

    public void animateText(CharSequence text) {
        mIHText.animateText(text);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        mIHText.onDraw(canvas);
    }

    public void reset(CharSequence text) {
        mIHText.reset(text);
    }

    public void setAnimateType(HTextViewType type) {
        switch (type) {
            case SCALE:
                mIHText = new ScaleText();
                break;
            case EVAPORATE:
                mIHText = new EvaporateText();
                break;
            case FALL:
                mIHText = new FallText();
                break;
        }
        initHText(attrs, defStyle);
    }
}

從代碼中可以看出HTextView繼承自TextView,并在初始化的時(shí)候根據(jù)animateType來(lái)實(shí)例化對(duì)應(yīng)的IHText,然后再對(duì)外暴露的animateText(),onDraw(),reset()這幾個(gè)方法里都是直接調(diào)用IHText來(lái)進(jìn)行處理.值得一提的是HTextView重寫(xiě)了onDraw方法,這樣也就意味著一些TextView的特性就沒(méi)法使用了,比如添加drawable,換行等等..

看到這里我們知道了原來(lái)就是通過(guò)type來(lái)實(shí)例化對(duì)應(yīng)的動(dòng)畫(huà)執(zhí)行類(lèi),然后再做具體的處理.其實(shí)這里就是設(shè)計(jì)模式中的策略模式,我們先引出來(lái),文章后面我們?cè)俳榻B策略模式.這樣我們只需要去找一個(gè)實(shí)例去具體分析它的實(shí)現(xiàn)就能明白整個(gè)庫(kù)的原理了,這里我們就拿ScaleText類(lèi)來(lái)分析具體動(dòng)畫(huà)的實(shí)現(xiàn)方式.去到ScaleText里發(fā)現(xiàn)它繼承自HText類(lèi)的,所以我們先來(lái)看看HText類(lèi)的代碼:

public abstract class HText implements IHText {
    protected Paint mPaint, mOldPaint;
    protected float[] gaps = new float[100];
    protected float[] oldGaps = new float[100];
    protected float mTextSize;
    protected CharSequence mText;
    protected CharSequence mOldText;
    protected List<CharacterDiffResult> differentList = new ArrayList<>();
    protected float oldStartX = 0; // 原來(lái)的字符串開(kāi)始畫(huà)的x位置
    protected float startX = 0; // 新的字符串開(kāi)始畫(huà)的x位置
    protected float startY = 0; // 字符串開(kāi)始畫(huà)的y, baseline
    protected HTextView mHTextView;

    @Override
    public void init(HTextView hTextView, AttributeSet attrs, int defStyle){

        mHTextView = hTextView;
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(mHTextView.getCurrentTextColor());
        mPaint.setStyle(Paint.Style.FILL);

        mOldPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mOldPaint.setColor(mHTextView.getCurrentTextColor());
        mOldPaint.setStyle(Paint.Style.FILL);

        mText = mHTextView.getText();
        mOldText = mHTextView.getText();

        mTextSize = mHTextView.getTextSize();

        initVariables();
        mHTextView.postDelayed(new Runnable() {
            @Override
            public void run() {
                prepareAnimate();
            }
        }, 50);

    }

    @Override
    public void animateText(CharSequence text) {
        mHTextView.setText(text);
        mOldText = mText;
        mText = text;
        prepareAnimate();
        animatePrepare(text);
        animateStart(text);
    }

    @Override
    public void onDraw(Canvas canvas) {
        drawFrame(canvas);
    }

    private void prepareAnimate() {
        mTextSize = mHTextView.getTextSize();

        mPaint.setTextSize(mTextSize);
        for (int i = 0; i < mText.length(); i++) {
            gaps[i] = mPaint.measureText(mText.charAt(i) + "");
        }

        mOldPaint.setTextSize(mTextSize);
        for (int i = 0; i < mOldText.length(); i++) {
            oldGaps[i] = mOldPaint.measureText(mOldText.charAt(i) + "");
        }

        oldStartX = (mHTextView.getMeasuredWidth() - mHTextView.getCompoundPaddingLeft() - mHTextView.getPaddingLeft() - mOldPaint
                .measureText(mOldText.toString())) / 2f;
        startX = (mHTextView.getMeasuredWidth() - mHTextView.getCompoundPaddingLeft() - mHTextView.getPaddingLeft() - mPaint
                .measureText(mText.toString())) / 2f;
        startY = mHTextView.getBaseline();

        differentList.clear();
        differentList.addAll(CharacterUtils.diff(mOldText, mText));
    }

    public void reset(CharSequence text) {
        animatePrepare(text);
        mHTextView.invalidate();
    }

    /**
     * 類(lèi)被實(shí)例化時(shí)初始化
     */
    protected abstract void initVariables();

    /**
     * 具體實(shí)現(xiàn)動(dòng)畫(huà)
     *
     * @param text
     */
    protected abstract void animateStart(CharSequence text);

    /**
     * 每次動(dòng)畫(huà)前初始化調(diào)用
     *
     * @param text
     */
    protected abstract void animatePrepare(CharSequence text);

    /**
     * 動(dòng)畫(huà)每次刷新界面時(shí)調(diào)用
     *
     * @param canvas
     */
    protected abstract void drawFrame(Canvas canvas);
}

首先HText是一個(gè)抽象類(lèi),并且在初始化的時(shí)候,分別初始化了需要畫(huà)舊的文字和新的文字的畫(huà)筆,以及對(duì)新舊文字的賦值,最后調(diào)用了prepareAnimate();方法.在這個(gè)方法里,首先設(shè)置了兩種PaintTextSize,然后計(jì)算了每一個(gè)文字的寬度并保存在了gapsoldGaps兩個(gè)數(shù)組里,最后計(jì)算了mOldTextmText里相同字符的位置信息.這里主要是為了做到當(dāng)兩組Text中有相同字符時(shí)就不執(zhí)行默認(rèn)動(dòng)畫(huà),而進(jìn)行字符的平移動(dòng)畫(huà),使動(dòng)畫(huà)更靈動(dòng).

看完初始化方法以及prepareAnimate();之后,我們留意到類(lèi)的最后的四個(gè)抽象方法.這里作者注釋的比較清楚了,就不再過(guò)多解釋,這里我們就能明白不論是ScaleText類(lèi)還是其他動(dòng)畫(huà)類(lèi)型的類(lèi),實(shí)際上都是實(shí)現(xiàn)了這些方法,然后HText中對(duì)這些方法進(jìn)行了調(diào)用,從而會(huì)執(zhí)行子類(lèi)中的相應(yīng)實(shí)現(xiàn)然后來(lái)實(shí)現(xiàn)具體的某個(gè)動(dòng)畫(huà),實(shí)際上這里就是設(shè)計(jì)模式中的模板方法.這里同樣先不多說(shuō),文章最后會(huì)做總結(jié).

然后我們?cè)倬唧w看這四個(gè)抽象方法分別是在哪里被調(diào)用的,我們就能理解實(shí)現(xiàn)HText的子類(lèi)實(shí)現(xiàn)的這四個(gè)抽象方法會(huì)在什么時(shí)候調(diào)用.從上面的代碼可以看出來(lái)initVariables();方法是在init();方法里調(diào)用用來(lái)初始化,animatePrepare(CharSequence text);animateStart(CharSequence text);是在prepareAnimate();方法里調(diào)用用來(lái)準(zhǔn)備動(dòng)畫(huà)和開(kāi)始動(dòng)畫(huà),最后drawFrame(Canvas canvas);會(huì)在onDraw(Canvas canvas);方法里不斷調(diào)用察郁。所以從文章開(kāi)始的使用方法中我們知道是調(diào)用hTextView.animateText(text);就可以執(zhí)行動(dòng)畫(huà)了,所以最終都是調(diào)用繼承自HText的子類(lèi)的animatePrepare(CharSequence text);animateStart(CharSequence text);方法。然后一定是在這里開(kāi)始動(dòng)畫(huà),不斷的觸發(fā)onDraw()方法來(lái)完成動(dòng)畫(huà)菜谣。

好的,下面我們就來(lái)看看ScaleText的具體實(shí)現(xiàn),由于篇幅原因,我們只具體分析一個(gè)ScaleText的實(shí)現(xiàn),其余效果的實(shí)現(xiàn)只是繪制方法的不同,可以試著自己去閱讀研究桅打。ScaleText具體代碼如下:

public class ScaleText extends HText {
    float mostCount = 20;
    float charTime = 400;
    private long duration;
    private float progress;

    @Override
    protected void initVariables() {

    }

    @Override
    protected void animateStart(CharSequence text) {
        int n = mText.length();
        n = n <= 0 ? 1 : n;
        // 計(jì)算動(dòng)畫(huà)總時(shí)間
        duration = (long) (charTime + charTime / mostCount * (n - 1));

        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, duration).setDuration(duration);
        valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                progress = (float) animation.getAnimatedValue();
                mHTextView.invalidate();
            }
        });
        valueAnimator.start();
    }

    @Override
    protected void animatePrepare(CharSequence text) {

    }

    @Override
    public void drawFrame(Canvas canvas) {
        float offset = startX;
        float oldOffset = oldStartX;

        int maxLength = Math.max(mText.length(), mOldText.length());

        for (int i = 0; i < maxLength; i++) {

            // draw old text
            if (i < mOldText.length()) {

                float percent = progress / duration;
                int move = CharacterUtils.needMove(i, differentList);
                if (move != -1) {
                    mOldPaint.setTextSize(mTextSize);
                    mOldPaint.setAlpha(255);

                    float p = percent * 2f;
                    p = p > 1 ? 1 : p;
                    float distX = CharacterUtils.getOffset(i, move, p, startX, oldStartX, gaps, oldGaps);
                    canvas.drawText(mOldText.charAt(i) + "", 0, 1, distX, startY, mOldPaint);
                } else {
                    mOldPaint.setAlpha((int) ((1 - percent) * 255));
                    mOldPaint.setTextSize(mTextSize * (1 - percent));
                    float width = mOldPaint.measureText(mOldText.charAt(i) + "");
                    canvas.drawText(mOldText.charAt(i) + "", 0, 1, oldOffset + (oldGaps[i] - width) / 2, startY, mOldPaint);
                }
                oldOffset += oldGaps[i];
            }

            // draw new text
            if (i < mText.length()) {

                if (!CharacterUtils.stayHere(i, differentList)) {

                    int alpha = (int) (255f / charTime * (progress - charTime * i / mostCount));
                    if (alpha > 255) alpha = 255;
                    if (alpha < 0) alpha = 0;

                    float size = mTextSize * 1f / charTime * (progress - charTime * i / mostCount);
                    if (size > mTextSize) size = mTextSize;
                    if (size < 0) size = 0;

                    mPaint.setAlpha(alpha);
                    mPaint.setTextSize(size);

                    float width = mPaint.measureText(mText.charAt(i) + "");
                    canvas.drawText(mText.charAt(i) + "", 0, 1, offset + (gaps[i] - width) / 2, startY, mPaint);
                }
                offset += gaps[i];
            }
        }
    }
}

先來(lái)看ScaleText中定義的幾個(gè)變量mostCount是表示最多同時(shí)執(zhí)行動(dòng)畫(huà)的字符個(gè)數(shù),為了實(shí)現(xiàn)順序的動(dòng)畫(huà)執(zhí)行,charTime表示mostCount個(gè)字符的動(dòng)畫(huà)時(shí)間,根據(jù)字符個(gè)數(shù)的不同動(dòng)畫(huà)時(shí)間不同,duration表示動(dòng)畫(huà)總時(shí)間,progress顯然是表示進(jìn)度.所以在animateStart(CharSequence text)页徐;方法中,是根據(jù)字符個(gè)數(shù)的不同來(lái)計(jì)算總時(shí)間,代碼如下:

int n = mText.length();
n = n <= 0 ? 1 : n;
// 計(jì)算動(dòng)畫(huà)總時(shí)間
duration = (long) (charTime + charTime / mostCount * (n - 1));

然后再通過(guò)ValueAnimator設(shè)置好progress的區(qū)間以及動(dòng)畫(huà)的duration,最后在onAnimationUpdate(ValueAnimator animation)的回調(diào)接口里,不斷的拿到當(dāng)前的progress然后調(diào)用mHTextView.invalidate();來(lái)不斷更新,我們都知道最終會(huì)不斷的調(diào)用onDraw();方法所以流轉(zhuǎn)到最后還是調(diào)用ScaleTextdrawFrame(Canvas canvas);方法.所以最終的動(dòng)畫(huà)都是在這里實(shí)現(xiàn)的。

drawFrame(Canvas canvas);來(lái)看,首先是拿到了新舊字符串各自X方向的偏移量,因?yàn)榭葱Ч覀兛梢园l(fā)現(xiàn)ScaleText切換的過(guò)程中總共有三種動(dòng)畫(huà):

1.oldText中不重復(fù)字符的縮小動(dòng)畫(huà).
2.oldText中與newText中重復(fù)字符的位移動(dòng)畫(huà).
3.newText中不重復(fù)字符的放大動(dòng)畫(huà).

這三種動(dòng)畫(huà)都是在drawFrame(Canvas canvas);方法里處理的,首先是循環(huán)繪制每一個(gè)字符,然后先繪制oldText并在oldText首先判斷這個(gè)字符是不是需要平移動(dòng)畫(huà)通過(guò)CharacterUtils.needMove(i, differentList);來(lái)判斷,當(dāng)不返回-1時(shí)表示需要進(jìn)行平移動(dòng)畫(huà),當(dāng)返回-1時(shí)就進(jìn)行縮小和透明動(dòng)畫(huà),然后緊接著繪制newText,通過(guò)!CharacterUtils.stayHere(i, differentList);方法來(lái)跳過(guò)重復(fù)字符的繪制,然后再通過(guò)progress的值來(lái)計(jì)算出當(dāng)前繪制的字符的大小和透明度存谎。所以通過(guò)不斷增加的progressonDraw();方法的調(diào)用再配合這一系列算法,最終實(shí)現(xiàn)了我們要的動(dòng)畫(huà)拔疚。講到這里整個(gè)庫(kù)我們應(yīng)該整體的理解了。

我想有人會(huì)說(shuō),為什么這么一個(gè)簡(jiǎn)單的動(dòng)畫(huà)寫(xiě)這么類(lèi),我用一個(gè)類(lèi)就能寫(xiě)出來(lái),又是定義接口,又是抽象類(lèi)又是繼承煩不煩?可是我們不要忘了,他還有另外9種動(dòng)畫(huà)實(shí)現(xiàn),將來(lái)還可能有幾十種拓展出來(lái)的動(dòng)畫(huà).如果都寫(xiě)在一個(gè)類(lèi)里實(shí)現(xiàn),那就毫無(wú)拓展性可言了,所以這里我們要聊聊設(shè)計(jì)模式的好處了既荚。

設(shè)計(jì)模式

此項(xiàng)目中用到兩種常用的設(shè)計(jì)模式分別是策略模式模板方法設(shè)計(jì)模式.

策略模式

策略模式(點(diǎn)擊關(guān)于策略模式的詳解) 定義了一系列的算法稚失,并將每一個(gè)算法封裝起來(lái),而且使它們還可以相互替換恰聘。策略模式讓算法獨(dú)立于使用它的客戶(hù)而獨(dú)立變化句各。所以在這個(gè)庫(kù)中,每一種動(dòng)畫(huà)都相當(dāng)于一種獨(dú)立算法,又可以相互替換,所以每一個(gè)實(shí)現(xiàn)了HText的子類(lèi)相當(dāng)于組成了策略模式,這樣做使得類(lèi)庫(kù)的結(jié)構(gòu)清晰明了,拓展方便,耦合度低吸占。缺點(diǎn)就是策略越多實(shí)現(xiàn)的子類(lèi)就會(huì)增加。不過(guò)相對(duì)于策略模式的好處這點(diǎn)也不算什么了凿宾。

模板方法

模板方法(點(diǎn)擊關(guān)于模板方法的詳解)定義一個(gè)操作中的算法的框架矾屯,而將一些步驟延遲到子類(lèi)中。使得子類(lèi)可以不改變一個(gè)算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟初厚。模板方法在此庫(kù)中的體現(xiàn)是HText這個(gè)抽象類(lèi)定義的那四個(gè)抽象方法,分別在HText中進(jìn)行調(diào)用,將這些步驟延遲到子類(lèi)中執(zhí)行,所以子類(lèi)可以實(shí)現(xiàn)各種各樣的動(dòng)畫(huà)效果,這是很典型的模板方法設(shè)計(jì)模式件蚕。

個(gè)人評(píng)價(jià)

至此,我們就算是徹底了解了HTextView,雖然并沒(méi)有多么復(fù)雜,但是它使用的這些典型的設(shè)計(jì)模式以及各種動(dòng)畫(huà)的實(shí)現(xiàn)確實(shí)可以從中讓我們學(xué)到不少知識(shí)。尤其是各種動(dòng)畫(huà)的具體實(shí)現(xiàn),能為我們自己在做相關(guān)動(dòng)畫(huà)時(shí)提供不少思路!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末产禾,一起剝皮案震驚了整個(gè)濱河市排作,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌亚情,老刑警劉巖妄痪,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異楞件,居然都是意外死亡衫生,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)履因,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)障簿,“玉大人盹愚,你說(shuō)我怎么就攤上這事栅迄。” “怎么了皆怕?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵毅舆,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我愈腾,道長(zhǎng)憋活,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任虱黄,我火速辦了婚禮悦即,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘橱乱。我一直安慰自己辜梳,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布泳叠。 她就那樣靜靜地躺著作瞄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪危纫。 梳的紋絲不亂的頭發(fā)上宗挥,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天乌庶,我揣著相機(jī)與錄音,去河邊找鬼契耿。 笑死瞒大,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的搪桂。 我是一名探鬼主播糠赦,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼锅棕!你這毒婦竟也來(lái)了拙泽?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤裸燎,失蹤者是張志新(化名)和其女友劉穎顾瞻,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體德绿,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡荷荤,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了移稳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蕴纳。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖个粱,靈堂內(nèi)的尸體忽然破棺而出古毛,到底是詐尸還是另有隱情,我是刑警寧澤都许,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布稻薇,位于F島的核電站,受9級(jí)特大地震影響胶征,放射性物質(zhì)發(fā)生泄漏塞椎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一睛低、第九天 我趴在偏房一處隱蔽的房頂上張望案狠。 院中可真熱鬧,春花似錦钱雷、人聲如沸骂铁。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)从铲。三九已至,卻和暖如春澄暮,著一層夾襖步出監(jiān)牢的瞬間名段,已是汗流浹背阱扬。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留伸辟,地道東北人麻惶。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像信夫,于是被迫代替她去往敵國(guó)和親窃蹋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

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