Scroller源碼分析

本文分析版本: Android API 22

1.簡(jiǎn)介

Android開(kāi)發(fā)中,如果我們希望使一個(gè)View滑動(dòng)的話,除了使用屬性動(dòng)畫外。我們還可以使用系統(tǒng)提供給我們的兩個(gè)類ScrollerOverScroller用來(lái)實(shí)現(xiàn)彈性滑動(dòng)。在我以前的一篇ViewDragHelper源碼分析中我們有講到過(guò)Scroller的作用。那么我們今天就來(lái)仔細(xì)分析一下Scroller的使用方法以及實(shí)現(xiàn)方式。

2.使用方法

在看Scroller的使用方法之前我們需要先了解一下View中的scrollBy()scrollTo()方法,scrollTo()方法的實(shí)現(xiàn)如下:


    public void scrollTo(int x, int y) {
        //如果當(dāng)前偏移量變化
        if (mScrollX != x || mScrollY != y) {
            int oldX = mScrollX;
            int oldY = mScrollY;
            //賦值偏移量
            mScrollX = x;
            mScrollY = y;
            invalidateParentCaches();
            //回調(diào)onScrollChanged方法
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (!awakenScrollBars()) {
                postInvalidateOnAnimation();
            }
        }
    }

scrollTo()是指將前視圖內(nèi)容橫向偏移x距離,縱向偏移y距離。注意這里是View的內(nèi)容的偏移,而不是View本身。而scrollBy()方法如下:

    public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
    }

scrollBy()方法里直接調(diào)用了scrollTo()方法,表示在當(dāng)前偏移量的基礎(chǔ)上繼續(xù)偏移(x,y)。現(xiàn)在我們來(lái)看看Scroller的用法。SkyScrollerDemo是我寫的一個(gè)ScrollerOverScroller的使用demo。下面的用法都是來(lái)自于這個(gè)demo里,大家可以clone下來(lái)配合本文一起閱讀。本文我們主要研究Scroller。對(duì)于OverScroller我在demo里也寫了相關(guān)的使用方法,在本文的最后我們?cè)僮鲇懻摗?/p>

Scroller一般需要配合重寫computeScroll()一起使用,代碼如下:

public class ScrollTextView extends TextView {
    private Context mContext;
    private Scroller mScroller;

    public ScrollTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;
        init();
    }

    private void init() {
        mScroller = new Scroller(mContext);
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            offsetLeftAndRight(mScroller.getCurrX() - mLeft);
            offsetTopAndBottom(mScroller.getCurrY() - mTop);
            invalidate();
        }
    }
    //以mLeft,mTop為初始點(diǎn),在DEFAULT_DURATION的時(shí)間內(nèi),在Y軸上滑動(dòng)-400的偏移量
    public void startScrollerScroll() {
        mScroller.startScroll(mLeft, mTop, 0, -400, DEFAULT_DURATION);
        invalidate();
    }
    //以mLeft,mTop為初始點(diǎn),并以Y方向上-5000的加速度滑動(dòng),最小Y坐標(biāo)為200,最大Y坐標(biāo)為1200
    public void startScrollerFling() {
        mScroller.fling(mLeft, mTop, 0, -5000, mLeft, mLeft, 200, 1200);
        invalidate();
    }
}

在上面的代碼里,當(dāng)我們調(diào)用startScrollerScroll()startScrollerFling()方法時(shí)我們就發(fā)現(xiàn)View滑動(dòng)了。如果以前沒(méi)了解過(guò)Scroller的同學(xué)可能會(huì)不理解。這里大致分析一下調(diào)用流程,首先我們要知道Scroller其實(shí)只負(fù)責(zé)計(jì)算,它并不負(fù)責(zé)滑動(dòng)View,當(dāng)我們調(diào)用了ScrollerstartScrollerScroll()方法時(shí),我們緊接著調(diào)用了invalidate()方法。invalidate()方法會(huì)使View重新繪制。因此會(huì)調(diào)用Viewdraw()方法,在Viewdraw()方法中又會(huì)去調(diào)用computeScroll()方法,computeScroll()方法在View中是一個(gè)空實(shí)現(xiàn),所以需要我們自己實(shí)現(xiàn)computeScroll()方法。在上面的computeScroll()方法中,我們調(diào)用了mScroller.computeScrollOffset()方法來(lái)計(jì)算當(dāng)前滑動(dòng)的偏移量。如果還在滑動(dòng)過(guò)程中就會(huì)返回true。所以我們就能在if中通過(guò)Scroller拿到當(dāng)前的滑動(dòng)坐標(biāo)從而做任何我們想做的處理。在demo里我們根據(jù)滑動(dòng)的偏移量來(lái)改變了View的坐標(biāo)偏移量。從而形成了滑動(dòng)動(dòng)畫。下面我們解釋一下Scroller的兩個(gè)方法的具體作用:

1.startScroll(int startX, int startY, int dx, int dy, int duration):

通過(guò)起始點(diǎn)、偏移的距離和滑動(dòng)的時(shí)間來(lái)開(kāi)始滑動(dòng)。

  • startX 起始滑動(dòng)點(diǎn)的X坐標(biāo)
  • startY 起始滑動(dòng)點(diǎn)的Y坐標(biāo)
  • dx 滑動(dòng)的水平偏移量。>0 則表示往左滑動(dòng)。
  • dy 滑動(dòng)的垂直偏移量。>0 則表示往上滑動(dòng)。
  • duration 滑動(dòng)執(zhí)行的時(shí)間

2.fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY) :

基于一個(gè)快速滑動(dòng)手勢(shì)下的滑動(dòng)。滑動(dòng)的距離與這個(gè)手勢(shì)最初的加速度有關(guān)。

  • startX 起始滑動(dòng)點(diǎn)的X坐標(biāo)
  • startY 起始滑動(dòng)點(diǎn)的Y坐標(biāo)
  • velocityX X方向上的加速度
  • velocityY Y方向上的加速度
  • minX X方向上滑動(dòng)的最小值,不會(huì)滑動(dòng)超過(guò)這個(gè)點(diǎn)
  • maxX X方向上滑動(dòng)的最大值,不會(huì)滑動(dòng)超過(guò)這個(gè)點(diǎn)
  • minY Y方向上滑動(dòng)的最小值,不會(huì)滑動(dòng)超過(guò)這個(gè)點(diǎn)
  • maxY Y方向上滑動(dòng)的最大值,不會(huì)滑動(dòng)超過(guò)這個(gè)點(diǎn)

3.源碼分析

我們依然通過(guò)調(diào)用流程來(lái)分析Scroller的實(shí)現(xiàn):

1.構(gòu)造方法

public Scroller(Context context) {
    this(context, null);
}

public Scroller(Context context, Interpolator interpolator) {
    this(context, interpolator,
            context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
}

public Scroller(Context context, Interpolator interpolator, boolean flywheel) {
    mFinished = true;
    if (interpolator == null) {
        mInterpolator = new ViscousFluidInterpolator();
    } else {
        mInterpolator = interpolator;
    }
    mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
    mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
    mFlywheel = flywheel;

    mPhysicalCoeff = computeDeceleration(0.84f); // look and feel tuning
}

最終都會(huì)調(diào)用最后一個(gè)構(gòu)造方法。必須傳入Context對(duì)象??梢詡魅胱远x的interpolator和是否支持飛輪flywheel的功能,當(dāng)然這兩個(gè)并不是必須的。如果不傳入interpolator會(huì)默認(rèn)創(chuàng)建一個(gè)ViscousFluidInterpolator,從字面意義上看是一個(gè)粘性流體插值器。對(duì)于flywheel是指是否支持在滑動(dòng)過(guò)程中,如果有新的fling()方法調(diào)用是否累加加速度。如果不傳默認(rèn)在2.3以上都會(huì)支持。剩下就是初始化了一些用于計(jì)算的參數(shù)。這樣就完成了Scroller的初始化了。下面我們來(lái)看看startScroll()方法的實(shí)現(xiàn):

2.startScroll()方法的實(shí)現(xiàn)

public void startScroll(int startX, int startY, int dx, int dy, int duration) {
  // mMode 分兩種方式 1.滑動(dòng):SCROLL_MODE 2. 加速度滑動(dòng):FLING_MODE
  mMode = SCROLL_MODE;
  // 是否滑動(dòng)結(jié)束 這里是開(kāi)始所以設(shè)置為false
  mFinished = false;
  // 滑動(dòng)的時(shí)間
  mDuration = duration;
  // 開(kāi)始的時(shí)間
  mStartTime = AnimationUtils.currentAnimationTimeMillis();
  // 開(kāi)始滑動(dòng)點(diǎn)的X坐標(biāo)
  mStartX = startX;
  // 開(kāi)始滑動(dòng)點(diǎn)的Y坐標(biāo)
  mStartY = startY;
  // 最終滑動(dòng)到位置的X坐標(biāo)
  mFinalX = startX + dx;
  // 最終滑動(dòng)到位置的Y坐標(biāo)
  mFinalY = startY + dy;
  // X方向上滑動(dòng)的偏移量
  mDeltaX = dx;
  // Y方向上滑動(dòng)的偏移量
  mDeltaY = dy;
  // 持續(xù)時(shí)間的倒數(shù) 最終用來(lái)計(jì)算得到插值器返回的值
  mDurationReciprocal = 1.0f / (float) mDuration;
}

很簡(jiǎn)單只是一些變量的賦值。根據(jù)我們前面使用方法里的分析,最終會(huì)調(diào)用computeScrollOffset()方法:

3.computeScrollOffset() 方法中 SCROLL_MODE 的實(shí)現(xiàn)

// 當(dāng)你需要知道新的位置的時(shí)候調(diào)用這個(gè)方法,如果動(dòng)畫還未結(jié)束則返回true
public boolean computeScrollOffset() {
    //如果已經(jīng)結(jié)束 則直接返回false
    if (mFinished) {
        return false;
    }
    //得到以及度過(guò)的時(shí)間
    int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);

    //如果還在動(dòng)畫時(shí)間內(nèi)
    if (timePassed < mDuration) {
        switch (mMode) {
            case SCROLL_MODE:
                // 根據(jù)timePassed * mDurationReciprocal,從mInterpolator中取出當(dāng)前需要偏移量的比例
                final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
                // 賦值給 mCurrX,mCurrY
                mCurrX = mStartX + Math.round(x * mDeltaX);
                mCurrY = mStartY + Math.round(x * mDeltaY);
                break;
            case FLING_MODE:
                ...
                break;
        }
    }
    else {
        mCurrX = mFinalX;
        mCurrY = mFinalY;
        mFinished = true;
    }
    return true;
}

首先的到當(dāng)前時(shí)間與滑動(dòng)開(kāi)始時(shí)間的時(shí)間差,如果還在滑動(dòng)時(shí)間內(nèi)則通過(guò)插值器獲得當(dāng)前的進(jìn)度并乘以總偏移量并賦值給mCurrXmCurrY。如果已經(jīng)結(jié)束則直接將mFinalXmFinalY賦值并將mFinished設(shè)置?為true。所以這樣我們就能通過(guò)getCurrX()getCurrY()來(lái)得到對(duì)應(yīng)的mCurrXmCurrY來(lái)做相應(yīng)的處理了。整個(gè)Scroll的過(guò)程就是這樣了。

4.fling()方法的實(shí)現(xiàn)

    public void fling(int startX, int startY, int velocityX, int velocityY,
                      int minX, int maxX, int minY, int maxY) {
        // 如果前一次滑動(dòng)還未結(jié)束,又調(diào)用了新的fling()方法時(shí),
        // 則累加相同方向上加速度
        if (mFlywheel && !mFinished) {
            float oldVel = getCurrVelocity();

            float dx = (float) (mFinalX - mStartX);
            float dy = (float) (mFinalY - mStartY);
            float hyp = FloatMath.sqrt(dx * dx + dy * dy);

            float ndx = dx / hyp;
            float ndy = dy / hyp;

            float oldVelocityX = ndx * oldVel;
            float oldVelocityY = ndy * oldVel;
            if (Math.signum(velocityX) == Math.signum(oldVelocityX) &&
                    Math.signum(velocityY) == Math.signum(oldVelocityY)) {
                velocityX += oldVelocityX;
                velocityY += oldVelocityY;
            }
        }

        //設(shè)置為FLING_MODE
        mMode = FLING_MODE;
        mFinished = false;
        //根據(jù)勾股定理獲得總加速度
        float velocity = FloatMath.sqrt(velocityX * velocityX + velocityY * velocityY);

        mVelocity = velocity;
        // 通過(guò)加速度得到滑動(dòng)持續(xù)時(shí)間
        mDuration = getSplineFlingDuration(velocity);
        mStartTime = AnimationUtils.currentAnimationTimeMillis();
        mStartX = startX;
        mStartY = startY;

        float coeffX = velocity == 0 ? 1.0f : velocityX / velocity;
        float coeffY = velocity == 0 ? 1.0f : velocityY / velocity;

        double totalDistance = getSplineFlingDistance(velocity);
        mDistance = (int) (totalDistance * Math.signum(velocity));

        mMinX = minX;
        mMaxX = maxX;
        mMinY = minY;
        mMaxY = maxY;

        mFinalX = startX + (int) Math.round(totalDistance * coeffX);
        // Pin to mMinX <= mFinalX <= mMaxX
        mFinalX = Math.min(mFinalX, mMaxX);
        mFinalX = Math.max(mFinalX, mMinX);

        mFinalY = startY + (int) Math.round(totalDistance * coeffY);
        // Pin to mMinY <= mFinalY <= mMaxY
        mFinalY = Math.min(mFinalY, mMaxY);
        mFinalY = Math.max(mFinalY, mMinY);
    }

依然是為計(jì)算需要的各種變量賦值。因?yàn)橐肓思铀俣鹊母拍钏宰兊孟鄬?duì)復(fù)雜,首先先判斷了如果一次滑動(dòng)未結(jié)束又觸發(fā)另一次滑動(dòng)時(shí),是否需要累加加速度。然后是設(shè)置mModeFLING_MODE。然后根據(jù)velocityXvelocityY算出總的加速度velocity,緊接著算出這個(gè)加速度下可以滑動(dòng)的距離mDistance。最后再通過(guò)xy方向上的加速度比值以及我們?cè)O(shè)定的最大值和最小值來(lái)給mFinalXmFinalY賦值。賦值結(jié)束后,通過(guò)調(diào)用invalidate(),最終依然會(huì)調(diào)用computeScrollOffset()方法:

5.computeScrollOffset() 方法中 FLING_MODE 的實(shí)現(xiàn)


    public boolean computeScrollOffset() {
        if (mFinished) {
            return false;
        }

        int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);

        if (timePassed < mDuration) {
            switch (mMode) {
                case SCROLL_MODE:
                    ...
                    break;
                case FLING_MODE:
                    // 當(dāng)前已滑動(dòng)的時(shí)間與總滑動(dòng)時(shí)間的比值
                    final float t = (float) timePassed / mDuration;
                    final int index = (int) (NB_SAMPLES * t);
                    // 距離系數(shù)
                    float distanceCoef = 1.f;
                    // 加速度系數(shù)
                    float velocityCoef = 0.f;
                    if (index < NB_SAMPLES) {
                        final float t_inf = (float) index / NB_SAMPLES;
                        final float t_sup = (float) (index + 1) / NB_SAMPLES;
                        final float d_inf = SPLINE_POSITION[index];
                        final float d_sup = SPLINE_POSITION[index + 1];
                        velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
                        distanceCoef = d_inf + (t - t_inf) * velocityCoef;
                    }

                    // 計(jì)算出當(dāng)前的加速度
                    mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
                    // 計(jì)算出當(dāng)前的mCurrX 與mCurrY
                    mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
                    // Pin to mMinX <= mCurrX <= mMaxX
                    mCurrX = Math.min(mCurrX, mMaxX);
                    mCurrX = Math.max(mCurrX, mMinX);

                    mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
                    // Pin to mMinY <= mCurrY <= mMaxY
                    mCurrY = Math.min(mCurrY, mMaxY);
                    mCurrY = Math.max(mCurrY, mMinY);

                    // 如果到達(dá)了終點(diǎn) 則結(jié)束
                    if (mCurrX == mFinalX && mCurrY == mFinalY) {
                        mFinished = true;
                    }

                    break;
            }
        }
        else {
            mCurrX = mFinalX;
            mCurrY = mFinalY;
            mFinished = true;
        }
        return true;
    }

由于fling()方法中將mMode賦值為FLING_MODE。所以我們直接來(lái)看FLING_MODE中的代碼??梢钥闯龈鶕?jù)當(dāng)前滑動(dòng)時(shí)間與總滑動(dòng)時(shí)間的比例。再根據(jù)一個(gè)SPLINE_POSITION數(shù)組計(jì)算出了距離系數(shù)distanceCoef與加速度系數(shù)velocityCoef。再根據(jù)這兩個(gè)系數(shù)計(jì)算出當(dāng)前加速度與當(dāng)前的mCurrXmCurrY。關(guān)于SPLINE_POSITION的初始化是在下面的靜態(tài)代碼塊里賦值的:

    static {
        float x_min = 0.0f;
        float y_min = 0.0f;
        for (int i = 0; i < NB_SAMPLES; i++) {
            final float alpha = (float) i / NB_SAMPLES;

            float x_max = 1.0f;
            float x, tx, coef;
            while (true) {
                x = x_min + (x_max - x_min) / 2.0f;
                coef = 3.0f * x * (1.0f - x);
                tx = coef * ((1.0f - x) * P1 + x * P2) + x * x * x;
                if (Math.abs(tx - alpha) < 1E-5) break;
                if (tx > alpha) x_max = x;
                else x_min = x;
            }
            SPLINE_POSITION[i] = coef * ((1.0f - x) * START_TENSION + x) + x * x * x;

            float y_max = 1.0f;
            float y, dy;
            while (true) {
                y = y_min + (y_max - y_min) / 2.0f;
                coef = 3.0f * y * (1.0f - y);
                dy = coef * ((1.0f - y) * START_TENSION + y) + y * y * y;
                if (Math.abs(dy - alpha) < 1E-5) break;
                if (dy > alpha) y_max = y;
                else y_min = y;
            }
            SPLINE_TIME[i] = coef * ((1.0f - y) * P1 + y * P2) + y * y * y;
        }
        SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
    }

我并沒(méi)有看懂這段代碼的實(shí)際意義。網(wǎng)上也沒(méi)有找到比較清晰的解釋。通過(guò)debug得知SPLINE_POSITION是一個(gè)長(zhǎng)度為101并且從0-1遞增數(shù)組。猜想這應(yīng)該是一個(gè)函數(shù)模型并且最終用于計(jì)算出滑動(dòng)過(guò)程中的加速度與位置。如果有同學(xué)能詳細(xì)解釋這段代碼的作用,歡迎在這篇文章留言。至此Scroller的兩個(gè)主要方法的實(shí)現(xiàn)我們就分析完了。

4.OverScroller解析

OverScroller是對(duì)Scroller的拓展,它在Scroller的基礎(chǔ)上拓展出了更多的方法。OverScrollerfling方法支持滑動(dòng)到終點(diǎn)之后并超出一段距離并返回,類似于彈性效果。另外一個(gè)springBack()方法是指將指定的點(diǎn)平滑滾動(dòng)到指定的終點(diǎn)上。這個(gè)終點(diǎn)由設(shè)置的參數(shù)決定。原理我們就不再探究了,大家可以自行研究這兩個(gè)類的差別。最后具體的使用方法在文章最上面的demo里都有提供??梢?code>clone下來(lái)幫助理解。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評(píng)論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,615評(píng)論 3 419
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 176,606評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 63,044評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,826評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 55,227評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評(píng)論 3 442
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 42,447評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,992評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,807評(píng)論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,001評(píng)論 1 370
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評(píng)論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,243評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 34,667評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 35,930評(píng)論 1 287
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,709評(píng)論 3 393
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,996評(píng)論 2 374

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