SwipeRefreshLayout源碼淺析

代碼版本:support-v4 24.1.1 共1165行
將SwipeRefreshLayout在本文中簡(jiǎn)稱(chēng)為SRL

涉及到的知識(shí)點(diǎn)

從控件的使用效果來(lái)看,我們可以了解到關(guān)鍵的知識(shí)點(diǎn)如下:

  • 自定義ViewGroup:可以學(xué)習(xí)一下ViewGroup以及View的一些關(guān)鍵方法的使用
  • 滑動(dòng)事件:觸摸事件傳遞規(guī)則涯捻,簡(jiǎn)單的多點(diǎn)觸控相關(guān)
  • 動(dòng)畫(huà)

本篇也從這三個(gè)方面來(lái)解讀柿祈。

涉及到的關(guān)鍵方法

 SwipeRefreshLayout(Context context, AttributeSet attrs) 
 onMeasure (int widthMeasureSpec, int heightMeasureSpec)
 onLayout(boolean changed, int left, int top, int right, int bottom)
 onInterceptTouchEvent(MotionEvent ev) 
 onTouchEvent(MotionEvent ev)
 moveSpinner(float overscrollTop)

我們可以把整個(gè)下拉刷新過(guò)程分為幾個(gè)關(guān)鍵的過(guò)程部分:

  • 下拉過(guò)程
  • 回彈過(guò)程
  • 轉(zhuǎn)圈刷新過(guò)程

常量和方法
通過(guò)一些常量的定義我們可以認(rèn)識(shí)到SRL的View組成部分氓奈,以及一些關(guān)鍵的時(shí)間點(diǎn)。

CircleImageView mCircleView(繼承自:android.support.v7.widget.AppCompatImageView): 即我們下拉刷新的過(guò)程中可以見(jiàn)到的圈圈控件,在構(gòu)造方法中為其設(shè)置了一系列屬性.

MaterialProgressDrawable mProgress (繼承自:Drawable):這是圓圈控件CircleImageView 的內(nèi)容稚伍,在構(gòu)造方法中可以看到:mCircleView.setImageDrawable(mProgress);對(duì)于圓圈內(nèi)容的控制基本上都是通過(guò)MaterialProgressDrawable 來(lái)實(shí)現(xiàn)的轧叽。

View mTarget:一般即SRL的直接子View比如你嵌入的RecyclerView苗沧;在ensureTarget()方法中完成對(duì)其賦值刊棕。

float mTotalDragDistance:超過(guò)這個(gè)距離值后即認(rèn)定為下拉刷新;也就是觸發(fā)下拉刷新的距離待逞;也就是最終停下來(lái)轉(zhuǎn)圈圈的位置甥角。

int mCurrentTargetOffsetTo:CircleView實(shí)時(shí)距離初始位置滑過(guò)的距離。

private void setTargetOffsetTopAndBottom(int offset, boolean requiresUpdate):通過(guò)調(diào)用mCircleView的offsetTopAndBottom來(lái)移動(dòng)CircleView本身识樱。

public boolean canChildScrollUp():判斷SRL的內(nèi)容是否滑動(dòng)到頂部嗤无,return false 說(shuō)明滑動(dòng)到頂部可以觸發(fā)下拉刷新;return true說(shuō)明未滑動(dòng)到頂部不觸發(fā)下拉刷新怜庸。該方法在onTouchEvent和onInterceptTouchEvent方法中均被調(diào)用來(lái)判斷当犯。

初始及化繪制過(guò)程:

構(gòu)造方法
初始化各種常量字段,諸如mCircleView的大小割疾,動(dòng)畫(huà)插值器嚎卫,mTotalDragDistance,創(chuàng)建并添加mCircleView宏榕,設(shè)置一些屬性拓诸,比如setWillNotDraw(false)可以提高效率。

繪制布局

@Override
    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //確保mTarget不為空
        if (mTarget == null) {
            ensureTarget();
        }
        if (mTarget == null) {
            return;
        }
        //測(cè)量mTarget的寬高麻昼,去掉padding
        mTarget.measure(MeasureSpec.makeMeasureSpec(
                getMeasuredWidth() - getPaddingLeft() - getPaddingRight(),
                MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(
                getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY));
        //測(cè)量mCircleView的寬高
        mCircleView.measure(MeasureSpec.makeMeasureSpec(mCircleWidth, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(mCircleHeight, MeasureSpec.EXACTLY));
        //如果沒(méi)有使用自定義的起始位置奠支,并且起始位置沒(méi)有被計(jì)算過(guò)(一般第一次onMeaure的時(shí)候會(huì)被調(diào)用)
        if (!mUsingCustomStart && !mOriginalOffsetCalculated) {
            mOriginalOffsetCalculated = true;
          //計(jì)算出當(dāng)前CircleView移動(dòng)的位置,即CircleView的自然高度的負(fù)值抚芦,
          //也就是說(shuō)CircleView正好在屏幕上邊倍谜,我們看不到它
            mCurrentTargetOffsetTop = mOriginalOffsetTop = -mCircleView.getMeasuredHeight();
        }
        mCircleViewIndex = -1;
        // Get the index of the circleview.
        // 獲取到圓圈view在當(dāng)前view中的位置;一般情況下mCircleViewIndex都為0燕垃;
        // 這個(gè)值會(huì)在getChildDrawingOrder被調(diào)用
        for (int index = 0; index < getChildCount(); index++) {
            if (getChildAt(index) == mCircleView) {
                mCircleViewIndex = index;
                break;
            }
        }
    }

onMeasure的代碼和解釋如上枢劝,做了兩件事: 完成子View的大小測(cè)量,對(duì)變量進(jìn)行正確賦值卜壕。
接下來(lái)在onLayout方法中來(lái)確定在mTarget和mCircleView在SRL中的位置您旁,onLayout的代碼清晰明了,就做了兩件事:對(duì)mTarget調(diào)用layout方法確定其位置轴捎,對(duì)CircleView調(diào)用layout方法確定其位置鹤盒,在這里就不貼代碼啦。

滑動(dòng)事件處理:

首先我們知道對(duì)于ViewGroup的滑動(dòng)處理流程的偽代碼如下:

public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean result = false;             // 默認(rèn)狀態(tài)為沒(méi)有消費(fèi)過(guò)

    if (!onInterceptTouchEvent(ev)) {   // 如果沒(méi)有攔截交給子View
        result = child.dispatchTouchEvent(ev);
    }

    if (!result) {                      // 如果事件沒(méi)有被消費(fèi),詢(xún)問(wèn)自身onTouchEvent
        result = onTouchEvent(ev);
    }

    return result;
}

在SRL中重寫(xiě)了onInterceptTouchEvent和onTouchEvent方法侦副,通過(guò)完成一次完整的下拉刷新侦锯,打印Log,我們發(fā)現(xiàn)調(diào)用過(guò)程如下:

滑動(dòng)事件

下面看onInterceptTouchEvent的處理秦驯,這里它會(huì)返回一個(gè)mIsBeingDragged (是否被拖拽的布爾值)尺碰,返回true則正在被拖拽,這時(shí)就會(huì)把事件分發(fā)到事件本身的onTouchEvent中,反之就會(huì)交給子View去處理亲桥。

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        ensureTarget();

        final int action = MotionEventCompat.getActionMasked(ev);
        // 設(shè)置準(zhǔn)備開(kāi)始的狀態(tài)
        if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
            mReturningToStart = false;
        }
        // 判斷一系列狀態(tài)洛心,決定是否可以下拉刷新,注意canChildScrollUp()方法题篷,
        // 該方法決定了只有滑動(dòng)到頂部繼續(xù)下來(lái)才能觸發(fā)下拉刷新
        if (!isEnabled() || mReturningToStart || canChildScrollUp()
                || mRefreshing || mNestedScrollInProgress) {
            // Fail fast if we're not in a state where a swipe is possible
            return false;
        }

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                // 默認(rèn)情況下offset為0词身,不移動(dòng);
                // 若初始指定了mOriginalOffsetTop 的大小則意味著番枚,按下的一刻法严,被移動(dòng)到了指定位置。
                setTargetOffsetTopAndBottom(mOriginalOffsetTop - mCircleView.getTop(), true);
                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
                mIsBeingDragged = false;
                final float initialDownY = getMotionEventY(ev, mActivePointerId);
                if (initialDownY == -1) {
                // 不在屏幕范圍內(nèi)不處理
                    return false;
                }
                // mInitialDownY 在ActionMove中用于與移動(dòng)距離比較葫笼,判斷是否被拖拽深啤。
                mInitialDownY = initialDownY;
                //可以看到在當(dāng)前動(dòng)作下,設(shè)置了CircleView的初始位置渔欢;獲取到了多點(diǎn)觸控相關(guān)的手指id墓塌;拖拽狀態(tài)置為false;賦值初始按下的位置值:mInitialDownY
                break;

            case MotionEvent.ACTION_MOVE:
                // 排除無(wú)效觸摸情況
                if (mActivePointerId == INVALID_POINTER) {
                    Log.e(LOG_TAG, "Got ACTION_MOVE event but don't have an active pointer id.");
                    return false;
                }

                final float y = getMotionEventY(ev, mActivePointerId);
                if (y == -1) {
                    return false;
                }
                final float yDiff = y - mInitialDownY;
                // 如果滑動(dòng)的距離大于被視為滑動(dòng)的最小距離奥额,并且之前的狀態(tài)為沒(méi)有被拖動(dòng);
                // 這時(shí)把拖拽狀態(tài)mIsBeingDragged置為true访诱;同時(shí)記下按下的位置:mInitialMotionY
                //如果mIsBeingDragged為true就會(huì)return true垫挨;根據(jù)事件處理偽代碼那么接下來(lái)的操作就交給onTouchEvent處理了
                if (yDiff > mTouchSlop && !mIsBeingDragged) {
                    mInitialMotionY = mInitialDownY + mTouchSlop;
                    mIsBeingDragged = true;
                    mProgress.setAlpha(STARTING_PROGRESS_ALPHA);
                }
                break;

            case MotionEventCompat.ACTION_POINTER_UP:
                onSecondaryPointerUp(ev);
                break;

            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                mIsBeingDragged = false;
                mActivePointerId = INVALID_POINTER;
                break;
        }

        return mIsBeingDragged;
    }

如上就是onInterceptTouchEvent所做的工作,設(shè)置初始位置触菜,進(jìn)行相關(guān)賦值操作九榔,根據(jù)滑動(dòng)的實(shí)際情況來(lái)決定是否把進(jìn)一步操作轉(zhuǎn)交給onTouchEvent。

public boolean onTouchEvent(MotionEvent ev) {
        final int action = MotionEventCompat.getActionMasked(ev);
        int pointerIndex = -1;
        // 首先是根據(jù)狀態(tài)進(jìn)行攔截
        if (mReturningToStart && action == MotionEvent.ACTION_DOWN) {
            mReturningToStart = false;
        }

        if (!isEnabled() || mReturningToStart || canChildScrollUp() || mNestedScrollInProgress) {
            // Fail fast if we're not in a state where a swipe is possible
            return false;
        }

        switch (action) {
            case MotionEvent.ACTION_DOWN:
                // 獲取觸摸id涡相,設(shè)置拖拽狀態(tài)
                mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
                mIsBeingDragged = false;
                break;

            case MotionEvent.ACTION_MOVE: {
                pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
                //排除無(wú)效狀態(tài)
                if (pointerIndex < 0) {
                    Log.e(LOG_TAG, "Got ACTION_MOVE event but have an invalid active pointer id.");
                    return false;
                }
                //獲取滑動(dòng)位置: y哲泊,y減去初始按下的位置在撐拖拽比率才是CircleView真正要?jiǎng)澾^(guò)的距離,
                //DRAG_RATE為0.5催蝗,所以CircleView滑過(guò)的距離切威,要比你手指移動(dòng)的距離短。
                final float y = MotionEventCompat.getY(ev, pointerIndex);
                final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;
                if (mIsBeingDragged) {
                    if (overscrollTop > 0) {
                      //滑動(dòng)距離大于0就去移動(dòng)CircleView丙号,下面這個(gè)方法很重要先朦,就是依靠它來(lái)完成CircleView的狀態(tài)變化和移動(dòng)的
                        moveSpinner(overscrollTop);
                    } else {
                        return false;
                    }
                }
                break;
            }
            case MotionEventCompat.ACTION_POINTER_DOWN: {
                pointerIndex = MotionEventCompat.getActionIndex(ev);
                if (pointerIndex < 0) {
                    Log.e(LOG_TAG, "Got ACTION_POINTER_DOWN event but have an invalid action index.");
                    return false;
                }
                mActivePointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
                break;
            }

            case MotionEventCompat.ACTION_POINTER_UP:
                onSecondaryPointerUp(ev);
                break;

            case MotionEvent.ACTION_UP: {
                pointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
                if (pointerIndex < 0) {
                    Log.e(LOG_TAG, "Got ACTION_UP event but don't have an active pointer id.");
                    return false;
                }
                // 當(dāng)結(jié)束下拉之后這時(shí)把拖拽狀態(tài)置為false,通過(guò)finishSpinner來(lái)進(jìn)行刷新操作或者是不到刷新距離回彈到初始位置的操作
                final float y = MotionEventCompat.getY(ev, pointerIndex);
                final float overscrollTop = (y - mInitialMotionY) * DRAG_RATE;
                mIsBeingDragged = false;
                finishSpinner(overscrollTop);
                mActivePointerId = INVALID_POINTER;
                return false;
            }
            case MotionEvent.ACTION_CANCEL:
                return false;
        }

        return true;
    }

onTouchEvent分析到這里犬缨,我們知道了在這個(gè)方法中根據(jù)用戶(hù)真實(shí)的滑動(dòng)狀況來(lái)調(diào)用相關(guān)方法:moveSpinner方法完成CircleView及其內(nèi)容的變化喳魏,finishSpinner完成手指抬起后的刷新操作,接下來(lái)我們看這兩個(gè)方法

private void moveSpinner(float overscrollTop) {
        mProgress.showArrow(true);// ture,設(shè)置下拉過(guò)程中展示小箭頭
        //移動(dòng)的距離除以刷新位置的距離得出一個(gè)拖拽比率:originalDragPercent 
        float originalDragPercent = overscrollTop / mTotalDragDistance;

        float dragPercent = Math.min(1f, Math.abs(originalDragPercent));
        float adjustedPercent = (float) Math.max(dragPercent - .4, 0) * 5 / 3;
        float extraOS = Math.abs(overscrollTop) - mTotalDragDistance;
        float slingshotDist = mUsingCustomStart ? mSpinnerFinalOffset - mOriginalOffsetTop
                : mSpinnerFinalOffset;
        float tensionSlingshotPercent = Math.max(0, Math.min(extraOS, slingshotDist * 2)
                / slingshotDist);
        float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow(
                (tensionSlingshotPercent / 4), 2)) * 2f;
        float extraMove = (slingshotDist) * tensionPercent * 2;

        int targetY = mOriginalOffsetTop + (int) ((slingshotDist * dragPercent) + extraMove);
        // where 1.0f is a full circle
        if (mCircleView.getVisibility() != View.VISIBLE) {
            mCircleView.setVisibility(View.VISIBLE);
        }
        if (!mScale) {
            ViewCompat.setScaleX(mCircleView, 1f);
            ViewCompat.setScaleY(mCircleView, 1f);
        }

        if (mScale) {
            setAnimationProgress(Math.min(1f, overscrollTop / mTotalDragDistance));
        }
        if (overscrollTop < mTotalDragDistance) {
            if (mProgress.getAlpha() > STARTING_PROGRESS_ALPHA
                    && !isAnimationRunning(mAlphaStartAnimation)) {
                // Animate the alpha
                startProgressAlphaStartAnimation();
            }
        } else {
            if (mProgress.getAlpha() < MAX_ALPHA && !isAnimationRunning(mAlphaMaxAnimation)) {
                // Animate the alpha
                startProgressAlphaMaxAnimation();
            }
        }
       //設(shè)置內(nèi)部圈圈開(kāi)始出現(xiàn)時(shí)的大小
        float strokeStart = adjustedPercent * .8f;
        //設(shè)置小箭頭的樣式        
        mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart));
        mProgress.setArrowScale(Math.min(1f, adjustedPercent));

        float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f;
        mProgress.setProgressRotation(rotation);
        setTargetOffsetTopAndBottom(targetY - mCurrentTargetOffsetTop, true /* requires update */);
    }

在moveSpinner方法中通過(guò)對(duì)overscrollTop(CircileView移動(dòng)的距離)和mTotalDragDistance(刷新位置的距離)進(jìn)行一系列的計(jì)算得出在下拉過(guò)程中其他元素的變化程度怀薛。最后通過(guò)setTargetOffsetTopAndBottom方法來(lái)真是移動(dòng)CircleView本身刺彩。

接下來(lái)我們看finishSpinner(float overscrollTop)方法,在onTouchEvent中攔截到ACTION_UP手勢(shì)后調(diào)用到了 finishSpinner(overscrollTop);傳入的overscrollTop為CircileView移動(dòng)的距離。

private void finishSpinner(float overscrollTop) {
        if (overscrollTop > mTotalDragDistance) {
        //如果超過(guò)了CircleView最終下來(lái)刷新位置的距離后則認(rèn)定為觸發(fā)下拉刷新調(diào)用setRefreshing方法
            setRefreshing(true, true /* notify */);
        } else {
            //否則的話取消本次刷新動(dòng)作
            // cancel refresh
            mRefreshing = false;
            mProgress.setStartEndTrim(0f, 0f);
            Animation.AnimationListener listener = null;
            //mScale默認(rèn)為false 在這里監(jiān)聽(tīng)動(dòng)畫(huà)結(jié)束來(lái)進(jìn)一步操作
            if (!mScale) {
                listener = new Animation.AnimationListener() {

                    @Override
                    public void onAnimationStart(Animation animation) {
                    }

                    @Override
                    public void onAnimationEnd(Animation animation) {
                        if (!mScale) {
                            startScaleDownAnimation(null);
                        }
                    }

                    @Override
                    public void onAnimationRepeat(Animation animation) {
                    }

                };
            }
            // 在這里開(kāi)始真正的回彈動(dòng)畫(huà)
            animateOffsetToStartPosition(mCurrentTargetOffsetTop, listener);
            mProgress.showArrow(false);
        }
    }

在流程上的分析基本上到這里就結(jié)束了创倔,其中一些自定義view和繪制的方法還需要進(jìn)一步理解三热。

動(dòng)畫(huà)

在SRL中的動(dòng)畫(huà)實(shí)現(xiàn)方式都是如下套路:
首先直接繼承View動(dòng)畫(huà)Animation來(lái)實(shí)現(xiàn)在動(dòng)畫(huà)過(guò)程中的相應(yīng)操作。

private final Animation mAnimateToStartPosition = new Animation() {
        @Override
        public void applyTransformation(float interpolatedTime, Transformation t) {
                int targetTop = 0;
        targetTop = (mFrom + (int) ((mOriginalOffsetTop - mFrom) * interpolatedTime));
        int offset = targetTop - mCircleView.getTop();
       //基于Z軸三幻,放到最上層
        mCircleView.bringToFront();
        mCircleView.offsetTopAndBottom(offset);
        mCurrentTargetOffsetTop = mCircleView.getTop();
        if (requiresUpdate && android.os.Build.VERSION.SDK_INT < 11) {
            //將觸發(fā)onDraw方法
            invalidate();
        }
        }
    };

然后設(shè)置動(dòng)畫(huà)的相關(guān)屬性:

  mAnimateToStartPosition.reset();
            mAnimateToStartPosition.setDuration(ANIMATE_TO_START_DsURATION);
            mAnimateToStartPosition.setInterpolator(mDecelerateInterpolator); //減速返回

最后給View設(shè)置動(dòng)畫(huà)

 mCircleView.clearAnimation();
            mCircleView.startAnimation(mAnimateToStartPosition);

源碼中的動(dòng)畫(huà)實(shí)現(xiàn)都是這一個(gè)套路就不多說(shuō)了就漾。

其他

我們看到SwipeRefreshLayout還實(shí)現(xiàn)了兩個(gè)接口:NestedScrollingParent 和NestedScrollingChild來(lái)處理嵌套滑動(dòng),關(guān)于這個(gè)知識(shí)點(diǎn)打算再寫(xiě)一篇博客念搬。

總結(jié)

到這里我們已經(jīng)把SRL的基本實(shí)現(xiàn)方式和調(diào)用細(xì)節(jié)大致分析了一遍抑堡,主要的知識(shí)點(diǎn)還是自定義ViewGroup、 觸摸事件的處理和動(dòng)畫(huà)的使用朗徊。另外關(guān)于CircleImageView和MaterialProgressDrawable的實(shí)現(xiàn)感興趣的話還值得深入挖掘首妖。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市爷恳,隨后出現(xiàn)的幾起案子有缆,更是在濱河造成了極大的恐慌,老刑警劉巖温亲,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件棚壁,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡栈虚,警方通過(guò)查閱死者的電腦和手機(jī)袖外,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)魂务,“玉大人曼验,你說(shuō)我怎么就攤上這事≌辰” “怎么了鬓照?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)孤紧。 經(jīng)常有香客問(wèn)我豺裆,道長(zhǎng),這世上最難降的妖魔是什么坛芽? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任留储,我火速辦了婚禮,結(jié)果婚禮上咙轩,老公的妹妹穿的比我還像新娘获讳。我一直安慰自己,他們只是感情好活喊,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布丐膝。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪帅矗。 梳的紋絲不亂的頭發(fā)上偎肃,一...
    開(kāi)封第一講書(shū)人閱讀 51,198評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音浑此,去河邊找鬼累颂。 笑死,一個(gè)胖子當(dāng)著我的面吹牛凛俱,可吹牛的內(nèi)容都是我干的紊馏。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蒲犬,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼朱监!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起原叮,我...
    開(kāi)封第一講書(shū)人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤赫编,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后奋隶,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體擂送,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年达布,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了团甲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡黍聂,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出身腻,到底是詐尸還是另有隱情产还,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布嘀趟,位于F島的核電站脐区,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏她按。R本人自食惡果不足惜牛隅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望酌泰。 院中可真熱鬧媒佣,春花似錦、人聲如沸陵刹。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至也糊,卻和暖如春炼蹦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背狸剃。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工掐隐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人钞馁。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓虑省,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親指攒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子慷妙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

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