庖丁解牛之ScrollView

前言

ScrollView可以說是android里最簡單的滑動控件岖常,但是其中也蘊(yùn)含了很多的知識點(diǎn)。今天嘗試通過ScrollView的源碼來了解ScrollView內(nèi)部的細(xì)節(jié)。本文在介紹ScrollView時(shí)會忽略以下內(nèi)容:嵌套滑動,崩潰保存,Accessibility奋岁。
ScrollView是一種控件,繼承自 FrameLayout荸百,他的子控件遠(yuǎn)遠(yuǎn)大于ScrollView本身闻伶,所以ScrollView展現(xiàn)出來的只有子控件的一部分,通過滑動的形式來呈現(xiàn)出子控件的內(nèi)容够话。

基本用法與功能剖析

先來回顧下ScrollView的基本用法蓝翰,超級簡單光绕。我們通常在ScrollView內(nèi)部放一個(gè)LinearLayout,然后在LinearLayout放各種元素畜份,ScrollView滾動時(shí)就可以看到這些元素诞帐。附帶一句,LinearLayout的width通常是match_parent(也可以是warp_content爆雹,這里有個(gè)坑停蕉,我們暫且不管,后面會提)钙态。

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:id="@+id/linear"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

        </LinearLayout>
    </ScrollView>

從測試的角度來看下慧起,ScrollView的功能是怎么樣的?

第一册倒,滑動的時(shí)候有2種情況蚓挤,如果滑的慢,ScrollView的滑動會隨著手指的離開而停止(簡單滑動)剩失;如果滑的快屈尼,在手指離開后册着,ScrollView還會再滑一段時(shí)間(這段時(shí)間內(nèi)的狀態(tài)我們稱為fling)拴孤。
第二,fling的時(shí)候甲捏,手指碰一下演熟,就立刻停止fling
第三,ScrollView到頂部的時(shí)候司顿,下拉有光影效果芒粹。底部同理

子窗口大小超出父窗口

我們知道,一般情況下子view都是沒有父view大的大溜,因?yàn)閙easure的時(shí)候子view的大小會受到父view的制約化漆,那什么情況下,子view會超出父view大小呢钦奋?

要想子view超出父view大小座云,大概有2種方式,一種是父view對子view的要求為MeasureSpec.EXACTLY,子view的size設(shè)置為某個(gè)固定值付材,另一種是父view對子view的要求為UNSPECIFIED,然后子view就可以隨便搞了朦拖。可以參考getChildMeasureSpec代碼就能大概看出來厌衔。

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);


        int size = Math.max(0, specSize - padding);


        int resultSize = 0;
        int resultMode = 0;


        switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
               //此時(shí)為case1璧帝,resultSize可能大于specSize
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;


        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;


        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
        //此時(shí)為case2,parent不做限制富寿,大小就可以亂來了
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

EXACTLY+固定值

對于case1睬隶,我們舉個(gè)例子锣夹,可以這么寫

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"

    tools:context="com.fish.a.MainActivity">

    <TextView
        android:id="@+id/aa"
        android:layout_width="4000dp"
        android:layout_height="wrap_content"
        android:text="Hello World!" />
</LinearLayout>

此時(shí)TextView的就比parent的大,這是一種方式讓子view超出了父view的大小理疙。
ScrollView重寫了android.widget.ScrollView#measureChildWithMargins

UNSPECIFIED

而ScrollView的child能比ScrollView本身還大晕城,用的是第二種方法,量的時(shí)候把specMode改為UNSPECIFIED,具體代碼如下所示窖贤,關(guān)鍵看這句
final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
MeasureSpec.getSize(parentHeightMeasureSpec), MeasureSpec.UNSPECIFIED);
直接把childHeightMeasureSpec變?yōu)榱薓easureSpec.UNSPECIFIED砖顷,此時(shí)parent傳過來的高度其實(shí)已經(jīng)毫無意義了。而子view的高度一般寫為wrap_content赃梧,就可以非常大了滤蝠。

   @Override
    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
                MeasureSpec.getSize(parentHeightMeasureSpec), MeasureSpec.UNSPECIFIED);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

嵌套滑動(NestedScrolling)

本文雖然不介紹嵌套滑動,但是嵌套滑動的相關(guān)代碼頻繁出現(xiàn)在onTouchevent里面授嘀,所以還是要簡單說下物咳。

NestedScrolling 提供了一套父 View 和子 View 滑動交互機(jī)制。要完成這樣的交互蹄皱,父 View 需要實(shí)現(xiàn) NestedScrollingParent 接口览闰,而子 View 需要實(shí)現(xiàn) NestedScrollingChild 接口。


NestedScrollingChild

更多知識可以參考
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0822/3342.html
https://segmentfault.com/a/1190000002873657

ScrollView默認(rèn)支持了嵌套滑動巷折,既可作為父view压鉴,也可作為子view
我們在看代碼的時(shí)候暫時(shí)忽略和嵌套滑動相關(guān)的(帶nest的函數(shù)),后面我會寫篇文章專門介紹嵌套滑動

滑動觸發(fā)

首先看下锻拘,怎么觸發(fā)ScrollView的滑動呢油吭?有2條路徑。

滑動觸發(fā)前-down事件

我們先從down事件開始看署拟,對照android事件分發(fā)里的down的流程圖來看婉宰,ScrollView會少幾個(gè)分支。

滑動觸發(fā)之DOWN

down事件分發(fā)到ScrollView之后推穷,會走ScrollView的dispatchTouchEvent()心包,然后進(jìn)入onInterceptTouchEvent(),onInterceptTouchEvent里面關(guān)于down的代碼馒铃,我們看一下蟹腾,此時(shí)必定返回false.分析下,如果L4的inChild為false,那么就直接break骗露,返回mIsBeingDragged岭佳,此時(shí)必定false;如果inChild為true萧锉,那就會到L24珊随,mIsBeingDragged必定是false,所以還是返回false。所以無論inChild是true還是false叶洞,此時(shí)onInterceptTouchEvent必定返回false鲫凶,因此onInterceptTouchEvent返回true的分支就被剪掉了。

            ...
            case MotionEvent.ACTION_DOWN: {
                final int y = (int) ev.getY();
                if (!inChild((int) ev.getX(), (int) y)) {
                    mIsBeingDragged = false;
                    recycleVelocityTracker();
                    break;
                }

                /*
                 * Remember location of down touch.
                 * ACTION_DOWN always refers to pointer index 0.
                 */
                mLastMotionY = y;
                mActivePointerId = ev.getPointerId(0);

                initOrResetVelocityTracker();
                mVelocityTracker.addMovement(ev);
                /*
                * If being flinged and user touches the screen, initiate drag;
                * otherwise don't.  mScroller.isFinished should be false when
                * being flinged.
                */
                mIsBeingDragged = !mScroller.isFinished();
                if (mIsBeingDragged && mScrollStrictSpan == null) {
                    mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
                }
                startNestedScroll(SCROLL_AXIS_VERTICAL);
                break;
            }
            ...
 return mIsBeingDragged;

圖中還有一個(gè)很明顯的分支被減掉了衩辟,那就是p:super.dispatchTouchEvent()返回false的分支螟炫,為什么這里不可能返回false呢?我們知道ScrollView的super.dispatchTouchEvent()會調(diào)用onTouchEvent艺晴,我們在看看onTouchEvent的代碼昼钻,down事件下一般都返回true。(只有g(shù)etChildCount為0封寞,返回false)
所以ScrollView處理down事件之后然评,必定返回true,mFirstTouchTarget可能空狈究,也可能非空碗淌。說的直白一點(diǎn),那就是down事件傳遞到ScrollView之后抖锥,如果他的子view消費(fèi)了亿眠,那ok,如果子view不消費(fèi)磅废,那ScrollView自己消費(fèi)纳像。

滑動觸發(fā)中-MOVE事件

前面說了down事件后的結(jié)果,這是滑動觸發(fā)的一個(gè)前置條件还蹲,真正觸發(fā)滑動肯定是MOVE引起的爹耗,那么MOVE如何引起滑動呢耙考?down事件的結(jié)果是谜喊,要么ScrollView的子類消費(fèi)掉,要么ScrollView消費(fèi)掉倦始。我們對照著2種情況分別分析

ScrollView親自消費(fèi)down事件

此時(shí)ScrollView親自消費(fèi)了down事件斗遏,那么ScrollView的mFirstTouchTarget為null,(對照android事件分發(fā)的move流程圖分析) 此時(shí)move事件進(jìn)入ScrollView直接被攔截鞋邑,傳遞給ScrollView的onTouchEvent诵次。來看onTouchEvent的move

這里我們看到個(gè)變量mIsBeingDragged,這個(gè)代表的是ScrollView是否正在被拖拽枚碗,手指抬起逾一,mIsBeingDragged就會變?yōu)閒alse,初始化的時(shí)候也為false肮雨∽穸拢看L4可知如果deltaY(滑動的距離)超過mTouchSlop,那就表示觸發(fā)了ScrollView的滑動,mIsBeingDragged 置為true陌宿,mTouchSlop是一個(gè)固定閾值锡足。然后會執(zhí)行L17 overScrollBy進(jìn)行滾動。

            case MotionEvent.ACTION_MOVE:
                 ...
                
                if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
                    final ViewParent parent = getParent();
                    if (parent != null) {
                        parent.requestDisallowInterceptTouchEvent(true);
                    }
                    mIsBeingDragged = true;
                    if (deltaY > 0) {
                        deltaY -= mTouchSlop;
                    } else {
                        deltaY += mTouchSlop;
                    }
                }
                壳坪。舶得。。
                   if (overScrollBy(0, deltaY, 0, mScrollY, 0, range, 0, mOverscrollDistance, true)
                            && !hasNestedScrollingParent()) {
                        // Break our velocity if we hit a scroll barrier.
                        mVelocityTracker.clear();
                    }

overScrollBy這是View的方法爽蝴,會觸發(fā)onOverScrolled回調(diào)沐批。此時(shí)只是普通的滑動,所以走L18蝎亚,就是調(diào)super.scrollTo珠插,根據(jù)手指滑動的距離進(jìn)行移動。非常簡單颖对。

   @Override
    protected void onOverScrolled(int scrollX, int scrollY,
            boolean clampedX, boolean clampedY) {
        // Treat animating scrolls differently; see #computeScroll() for why.
        if (!mScroller.isFinished()) {
             //fling走這里
            final int oldX = mScrollX;
            final int oldY = mScrollY;
            mScrollX = scrollX;
            mScrollY = scrollY;
            invalidateParentIfNeeded();
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (clampedY) {
                mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange());
            }
        } else {
              //普通的滑動走這里
            super.scrollTo(scrollX, scrollY);
        }

        awakenScrollBars();
    }

ScrollView子類消費(fèi)down事件

此時(shí)ScrollView的子view消費(fèi)了down事件捻撑,那么ScrollView的mFirstTouchTarget非空,(對照android事件分發(fā)的move流程圖分析) 此時(shí)move事件進(jìn)入ScrollView會執(zhí)行onInterceptTouchEvent缤底,如果返回false就交給子view處理顾患。如果返回true就向子view發(fā)一個(gè)cancel消息,并且把mFirstTouchTarget設(shè)置為null个唧,這樣下次move事件來就會直接攔截并進(jìn)入onTouchEvent江解。那什么情況下,onInterceptTouchEvent會返回true呢徙歼?下面是onInterceptTouchEvent的move部分的代碼犁河,其實(shí)跟前面類似的,yDiff > mTouchSlop 觸發(fā)滑動

 case MotionEvent.ACTION_MOVE: {
                /*
                 * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
                 * whether the user has moved far enough from his original down touch.
                 */

                /*
                * Locally do absolute value. mLastMotionY is set to the y value
                * of the down event.
                */
                final int activePointerId = mActivePointerId;
                if (activePointerId == INVALID_POINTER) {
                    // If we don't have a valid id, the touch down wasn't on content.
                    break;
                }

                final int pointerIndex = ev.findPointerIndex(activePointerId);
                if (pointerIndex == -1) {
                    Log.e(TAG, "Invalid pointerId=" + activePointerId
                            + " in onInterceptTouchEvent");
                    break;
                }

                final int y = (int) ev.getY(pointerIndex);
                final int yDiff = Math.abs(y - mLastMotionY);
                if (yDiff > mTouchSlop && (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
                    mIsBeingDragged = true;
                    mLastMotionY = y;
                    initVelocityTrackerIfNotExists();
                    mVelocityTracker.addMovement(ev);
                    mNestedYOffset = 0;
                    if (mScrollStrictSpan == null) {
                        mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
                    }
                    final ViewParent parent = getParent();
                    if (parent != null) {
                        parent.requestDisallowInterceptTouchEvent(true);
                    }
                }
                break;
            }
             return mIsBeingDragged;
            

滑動觸發(fā)小結(jié)

滑動觸發(fā)的地方可能是在onTouchEvent也可能在onInterceptTouchEvent內(nèi)魄梯。
觸發(fā)的原因就是手指移動的距離超過了mTouchSlop
可能是一次move超過了mTouchSlop桨螺,也可能是多次move加起來超過了mTouchSlop。

多次move是怎么樣的呢酿秸?注意灭翔,這里說的多次move是在一個(gè)cycle內(nèi)的,舉個(gè)例子比如mTouchSlop21辣苏,第一次move了10肝箱,第二次move了15,第三次move了5稀蟋,會怎么樣呢煌张?
第一次move了10,此時(shí)未達(dá)到mTouchSlop退客,所以不會觸發(fā)滑動
第二次move了15骏融,此時(shí)10+15>21,所以會觸發(fā)滑動,滾多少呢绎谦?滾的距離為10+15-21=4管闷,為啥,看下邊這段代碼,第一次觸發(fā)滾動窃肠,滾的距離要減掉一個(gè)mTouchSlop包个。
然后第三次滾動距離5,那ScrollView滾動5冤留,后面的move都跟第三次一致

          if (deltaY > 0) {
                        deltaY -= mTouchSlop;
                    } else {
                        deltaY += mTouchSlop;
                    }

fling(慣性滑動)

怎么實(shí)現(xiàn)手指離開之后碧囊,還能滑動一段距離呢?
onTouchEvent里有這么段代碼

           case MotionEvent.ACTION_UP:
                if (mIsBeingDragged) {
                    final VelocityTracker velocityTracker = mVelocityTracker;
                    velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                    int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);

                    if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
                        flingWithNestedDispatch(-initialVelocity);
                    } else if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0,
                            getScrollRange())) {
                        postInvalidateOnAnimation();
                    }

                    mActivePointerId = INVALID_POINTER;
                    endDrag();
                }
                break;

只要速度超過mMinimumVelocity纤怒,那就會調(diào)用flingWithNestedDispatch()糯而,實(shí)際上就是調(diào)用mScroller.fling()。mScroller.fling是一個(gè)OverScroller泊窘,OverScroller的相關(guān)知識可以參考 View的滾動與Scroller

fling的時(shí)候點(diǎn)擊一下熄驼,立刻停止

這是怎么做到的?總的來說烘豹,是通過onInterceptTouchEvent和onTouchEvent的配合瓜贾,調(diào)用 mScroller.abortAnimation();來停止?jié)L動的。
分2種case來討論

case1 ScrollView內(nèi)部的LinearLayout的width為match_parent

此時(shí)隨便點(diǎn)一下就點(diǎn)到了LinearLayout內(nèi)部携悯。
先來看fling時(shí)的狀態(tài)祭芦,此時(shí)手指已經(jīng)抬起,endDrag()被調(diào)用憔鬼,mIsBeingDragged為false龟劲。此時(shí)點(diǎn)擊一下,會到onInterceptTouchEvent()方法轴或。此時(shí)在LinearLayout內(nèi)部昌跌,所以inChild返回true,會走到mIsBeingDragged = !mScroller.isFinished();侮叮,因?yàn)樵趂ling避矢,所以mScroller.isFinished()必定false悼瘾,所以mIsBeingDragged為true囊榜,那么down事件就被攔截起來了。
下一步會走到onTouchEvent里亥宿。

     case MotionEvent.ACTION_DOWN: {
                final int y = (int) ev.getY();
                if (!inChild((int) ev.getX(), (int) y)) {
                    mIsBeingDragged = false;
                    recycleVelocityTracker();
                    break;
                }

                /*
                 * Remember location of down touch.
                 * ACTION_DOWN always refers to pointer index 0.
                 */
                mLastMotionY = y;
                mActivePointerId = ev.getPointerId(0);

                initOrResetVelocityTracker();
                mVelocityTracker.addMovement(ev);
                /*
                * If being flinged and user touches the screen, initiate drag;
                * otherwise don't.  mScroller.isFinished should be false when
                * being flinged.
                */
                mIsBeingDragged = !mScroller.isFinished();
                if (mIsBeingDragged && mScrollStrictSpan == null) {
                    mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
                }
                startNestedScroll(SCROLL_AXIS_VERTICAL);
                break;
            }

再來看onTouchEvent如何處理down事件,有下面這段代碼卸勺,如果在fling,那么立刻終止烫扼,達(dá)到目的曙求。

      /*
                 * If being flinged and user touches, stop the fling. isFinished
                 * will be false if being flinged.
                 */
                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                    if (mFlingStrictSpan != null) {
                        mFlingStrictSpan.finish();
                        mFlingStrictSpan = null;
                    }
                }

case2 ScrollView內(nèi)部的LinearLayout的width較小,點(diǎn)擊到LinearLayout外部

此時(shí)inChild返回false,那么onInterceptTouchEvent返回false悟狱,不攔截静浴。但是注意,此時(shí)點(diǎn)到了LinearLayout外部挤渐,那么這個(gè)down事件苹享,沒有child去處理,所以還是交給ScrollView來處理浴麻,還是會走到onTouchEvent內(nèi)得问,一樣會調(diào)用mScroller.abortAnimation();方法

R.attr.scrollViewStyle是什么

在構(gòu)造函數(shù)里,我們可以看到這么一段代碼软免,默認(rèn)給ScrollView宫纬,配置了scrollViewStyle,這有什么意義呢膏萧?其實(shí)就是設(shè)置了scrollbars和fadingEdge為vertical漓骚。看下邊代碼

  public ScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, com.android.internal.R.attr.scrollViewStyle);
    }

attrs.xml內(nèi)有

<attr name="scrollViewStyle" format="reference" />

themes.xml內(nèi)有

<item name="scrollViewStyle">@style/Widget.ScrollView</item>

styles.xml內(nèi)有

    <style name="Widget.ScrollView">
        <item name="scrollbars">vertical</item>
        <item name="fadingEdge">vertical</item>
    </style>

其他

  1. 因?yàn)橛昧薕verScroller,所以mScrollY可能是負(fù)值
  2. Scrollview到頂部的時(shí)候下拉的暈影效果榛泛,主要是用EdgeEffect實(shí)現(xiàn)
  3. 我們會在下篇文章從0開始寫一個(gè)ScrollView
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末认境,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子挟鸠,更是在濱河造成了極大的恐慌叉信,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件艘希,死亡現(xiàn)場離奇詭異硼身,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)覆享,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門佳遂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人撒顿,你說我怎么就攤上這事丑罪。” “怎么了凤壁?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵吩屹,是天一觀的道長。 經(jīng)常有香客問我拧抖,道長趣惠,這世上最難降的妖魔是什么绑嘹? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任可帽,我火速辦了婚禮,結(jié)果婚禮上嘲驾,老公的妹妹穿的比我還像新娘。我一直安慰自己迹卢,他們只是感情好辽故,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著腐碱,像睡著了一般榕暇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上喻杈,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天彤枢,我揣著相機(jī)與錄音,去河邊找鬼筒饰。 笑死缴啡,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的瓷们。 我是一名探鬼主播业栅,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼谬晕!你這毒婦竟也來了碘裕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤攒钳,失蹤者是張志新(化名)和其女友劉穎帮孔,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體不撑,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡文兢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了焕檬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片姆坚。...
    茶點(diǎn)故事閱讀 39,795評論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖实愚,靈堂內(nèi)的尸體忽然破棺而出兼呵,到底是詐尸還是另有隱情,我是刑警寧澤腊敲,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布击喂,位于F島的核電站,受9級特大地震影響兔仰,放射性物質(zhì)發(fā)生泄漏茫负。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一乎赴、第九天 我趴在偏房一處隱蔽的房頂上張望忍法。 院中可真熱鬧,春花似錦榕吼、人聲如沸饿序。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽原探。三九已至,卻和暖如春顽素,著一層夾襖步出監(jiān)牢的瞬間咽弦,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工胁出, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留型型,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓全蝶,卻偏偏與公主長得像闹蒜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子抑淫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評論 2 354

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