Android Scroll詳解(二):OverScroller實(shí)戰(zhàn)

作者: ztelur
聯(lián)系方式:segmentfault缓溅,csdn蛇损,github

本文僅供個(gè)人學(xué)習(xí),不用于任何形式商業(yè)目的坛怪,轉(zhuǎn)載請(qǐng)注明原作者淤齐、文章來源,鏈接袜匿,版權(quán)歸原文作者所有更啄。

本文是android滾動(dòng)相關(guān)的系列文章的第二篇,主要總結(jié)一下使用手勢(shì)相關(guān)的代碼邏輯居灯。主要是單點(diǎn)拖動(dòng)祭务,多點(diǎn)拖動(dòng)内狗,fling和OveScroll的實(shí)現(xiàn)。每個(gè)手勢(shì)都會(huì)有代碼片段义锥。
?對(duì)android滾動(dòng)相關(guān)的知識(shí)還不太了解的同學(xué)可以先閱讀一下文章:

為了節(jié)約你的時(shí)間其屏,我特地將文章大致內(nèi)容總結(jié)如下:

  • 手勢(shì)Drag的實(shí)現(xiàn)和原理
  • 手勢(shì)Fling的實(shí)現(xiàn)和原理
  • OverScroll效果和EdgeEffect效果的實(shí)現(xiàn)和原理。

詳細(xì)代碼請(qǐng)查看我的github

Drag

Drag是最為基本的手勢(shì):用戶可以使用手指在屏幕上滑動(dòng)缨该,以拖動(dòng)屏幕相應(yīng)內(nèi)容移動(dòng)。實(shí)現(xiàn)Drag手勢(shì)其實(shí)很簡(jiǎn)單川背,步驟如下:

  • ACTION_DOWN事件發(fā)生時(shí)贰拿,調(diào)用getXgetY函數(shù)獲得事件發(fā)生的x,y坐標(biāo)值,并記錄在mLastXmLastY變量中熄云。
  • ACTION_MOVE事件發(fā)生時(shí)膨更,調(diào)用getXgetY函數(shù)獲得事件發(fā)生的x,y坐標(biāo)值,將其與mLastXmLastY比較,如果二者差值大于一定限制(ScaledTouchSlop),就執(zhí)行scrollBy函數(shù)缴允,進(jìn)行滾動(dòng),最后更新mLastXmLastY的值荚守。
  • ACTION_UPACTION_CANCEL事件發(fā)生時(shí),清空mLastX练般,mLastY矗漾。
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int actionId = MotionEventCompat.getActionMasked(event);
        switch (actionId) {
            case MotionEvent.ACTION_DOWN:
                mLastX = event.getX();
                mLastY = event.getY();
                mIsBeingDragged = true;
                if (getParent() != null) {
                    getParent().requestDisallowInterceptTouchEvent(true);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                float curX = event.getX();
                float curY = event.getY();
                int deltaX = (int) (mLastX - curX);
                int deltaY = (int) (mLastY - curY);
                if (!mIsBeingDragged && (Math.abs(deltaX)> mTouchSlop ||
                                                        Math.abs(deltaY)> mTouchSlop)) {
                    mIsBeingDragged = true;
                    // 讓第一次滑動(dòng)的距離和之后的距離不至于差距太大
                    // 因?yàn)榈谝淮伪仨?gt;TouchSlop,之后則是直接滑動(dòng)
                    if (deltaX > 0) {
                        deltaX -= mTouchSlop;
                    } else {
                        deltaX += mTouchSlop;
                    }
                    if (deltaY > 0) {
                        deltaY -= mTouchSlop;
                    } else {
                        deltaY += mTouchSlop;
                    }
                }
                // 當(dāng)mIsBeingDragged為true時(shí),就不用判斷> touchSlopg啦薄料,不然會(huì)導(dǎo)致滾動(dòng)是一段一段的
                // 不是很連續(xù)
                if (mIsBeingDragged) {
                        scrollBy(deltaX, deltaY);
                        mLastX = curX;
                        mLastY = curY;
                }
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                mIsBeingDragged = false;
                mLastY = 0;
                mLastX = 0;
                break;
            default:
        }
        return mIsBeingDragged;
    }

多觸點(diǎn)Drag

上邊的代碼只適用于單點(diǎn)觸控的手勢(shì)敞贡,如果你是兩個(gè)手指觸摸屏幕,那么它只會(huì)根據(jù)你第一個(gè)手指滑動(dòng)的情況來進(jìn)行屏幕滾動(dòng)摄职。更為致命的是誊役,當(dāng)你先松開第一個(gè)手指時(shí),由于我們少監(jiān)聽了ACTION_POINTER_UP事件谷市,將會(huì)導(dǎo)致屏幕突然滾動(dòng)一大段距離蛔垢,因?yàn)榈诙€(gè)手指移動(dòng)事件的x,y值會(huì)和第一個(gè)手指移動(dòng)時(shí)留下的mLastXmLastY比較,導(dǎo)致屏幕滾動(dòng)迫悠。

如果我們要監(jiān)聽并處理多觸點(diǎn)的事件鹏漆,我們還需要對(duì)ACTION_POINTER_DOWNACTION_POINTER_UP事件進(jìn)行監(jiān)聽,并且在ACTION_MOVE事件時(shí)及皂,要記錄所有觸摸點(diǎn)事件發(fā)生的x,y值甫男。

  • 當(dāng)ACTION_POINTER_DOWN事件發(fā)生時(shí),我們要記錄第二觸摸點(diǎn)事件發(fā)生的x,y值為mSecondaryLastXmSecondaryLastY验烧,和第二觸摸點(diǎn)pointer的id為mSecondaryPointerId
  • 當(dāng)ACTION_MOVE事件發(fā)生時(shí)板驳,我們除了根據(jù)第一觸摸點(diǎn)pointer的x,y值進(jìn)行滾動(dòng)外碍拆,也要更新mSecondayLastXmSecondaryLastY
  • 當(dāng)ACTION_POINTER_UP事件發(fā)生時(shí)若治,我們要先判斷是哪個(gè)觸摸點(diǎn)手指被抬起來啦慨蓝,如果是第一觸摸點(diǎn),那么我們就將坐標(biāo)值和pointer的id都更換為第二觸摸點(diǎn)的數(shù)據(jù)端幼;如果是第二觸摸點(diǎn)礼烈,就只要重置一下數(shù)據(jù)即可。
        switch (actionId) {
            .....
            case MotionEvent.ACTION_POINTER_DOWN:
                activePointerIndex = MotionEventCompat.getActionIndex(event);
                mSecondaryPointerId = MotionEventCompat.findPointerIndex(event,activePointerIndex);
                mSecondaryLastX = MotionEventCompat.getX(event,activePointerIndex);
                mSecondaryLastY = MotionEventCompat.getY(event,mActivePointerId);
                break;
            case MotionEvent.ACTION_MOVE:
                ......
                // handle secondary pointer move
                if (mSecondaryPointerId != INVALID_ID) {
                    int mSecondaryPointerIndex = MotionEventCompat.findPointerIndex(event, mSecondaryPointerId);
                    mSecondaryLastX = MotionEventCompat.getX(event, mSecondaryPointerIndex);
                    mSecondaryLastY = MotionEventCompat.getY(event, mSecondaryPointerIndex);
                }
                break;
            case MotionEvent.ACTION_POINTER_UP:
                //判斷是否是activePointer up了
                activePointerIndex = MotionEventCompat.getActionIndex(event);
                int curPointerId  = MotionEventCompat.getPointerId(event,activePointerIndex);
                Log.e(TAG, "onTouchEvent: "+activePointerIndex +" "+curPointerId +" activeId"+mActivePointerId+
                                        "secondaryId"+mSecondaryPointerId);
                if (curPointerId == mActivePointerId) { // active pointer up
                    mActivePointerId = mSecondaryPointerId;
                    mLastX = mSecondaryLastX;
                    mLastY = mSecondaryLastY;
                    mSecondaryPointerId = INVALID_ID;
                    mSecondaryLastY = 0;
                    mSecondaryLastX = 0;
                    //重復(fù)代碼婆跑,為了讓邏輯看起來更加清晰
                } else{ //如果是secondary pointer up
                    mSecondaryPointerId = INVALID_ID;
                    mSecondaryLastY = 0;
                    mSecondaryLastX = 0;
                }
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                mIsBeingDragged = false;
                mActivePointerId = INVALID_ID;
                mLastY = 0;
                mLastX = 0;
                break;
            default:
        }

Fling

當(dāng)用戶手指快速劃過屏幕此熬,然后快速立刻屏幕時(shí),系統(tǒng)會(huì)判定用戶執(zhí)行了一個(gè)Fling手勢(shì)滑进。視圖會(huì)快速滾動(dòng)犀忱,并且在手指立刻屏幕之后也會(huì)滾動(dòng)一段時(shí)間。Drag表示手指滑動(dòng)多少距離扶关,界面跟著顯示多少距離阴汇,而fling是根據(jù)你的滑動(dòng)方向與輕重,還會(huì)自動(dòng)滑動(dòng)一段距離节槐。Filing手勢(shì)在android交互設(shè)計(jì)中應(yīng)用非常廣泛:電子書的滑動(dòng)翻頁(yè)搀庶、ListView滑動(dòng)刪除item、滑動(dòng)解鎖等铜异。所以如何檢測(cè)用戶的fling手勢(shì)是非常重要的哥倔。
?在檢測(cè)Fling時(shí),你需要檢測(cè)手指在屏幕上滑動(dòng)的速度熙掺,這是你就需要VelocityTrackerScroller這兩個(gè)類啦未斑。

  • 我們首先使用VelocityTracker.obtain()這個(gè)方法獲得其實(shí)例
  • 然后每次處理觸摸時(shí)間時(shí),我們將觸摸事件通過addMovement方法傳遞給它
  • 最后在處理ACTION_UP事件時(shí)币绩,我們通過computeCurrentVelocity方法獲得滑動(dòng)速度;
  • 我們判斷滑動(dòng)速度是否大于一定數(shù)值(MinFlingSpeed),如果大于蜡秽,那么我們調(diào)用Scrollerfling方法。然后調(diào)用invalidate()函數(shù)缆镣。
  • 我們需要重載computeScroll方法芽突,在這個(gè)方法內(nèi)袍患,我們調(diào)用ScrollercomputeScrollOffset()方法啦計(jì)算當(dāng)前的偏移量牺汤,然后獲得偏移量,并調(diào)用scrollTo函數(shù),最后調(diào)用postInvalidate()函數(shù)麸折。
  • 除了上述的操作外钠糊,我們需要在處理ACTION_DOWN事件時(shí)挟秤,對(duì)屏幕當(dāng)前狀態(tài)進(jìn)行判斷,如果屏幕現(xiàn)在正在滾動(dòng)(用戶剛進(jìn)行了Fling手勢(shì))抄伍,我們需要停止屏幕滾動(dòng)艘刚。

具體這一套流程是如何運(yùn)轉(zhuǎn)的,我會(huì)在下一篇文章中詳細(xì)解釋截珍,大家也可以自己查閱代碼或者google來搞懂其中的原理攀甚。

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        .....
        if (mVelocityTracker == null) {
            //檢查速度測(cè)量器箩朴,如果為null,獲得一個(gè)
            mVelocityTracker = VelocityTracker.obtain();
        }
        int action = MotionEventCompat.getActionMasked(event);
        int index = -1;
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                ......
                                if (!mScroller.isFinished()) { //fling
                    mScroller.abortAnimation();
                }
                .....
                break;
            case MotionEvent.ACTION_MOVE:
                ......
                break;
            case MotionEvent.ACTION_CANCEL:
                endDrag();
                break;
            case MotionEvent.ACTION_UP:
                if (mIsBeingDragged) {
                //當(dāng)手指立刻屏幕時(shí)秋度,獲得速度炸庞,作為fling的初始速度     mVelocityTracker.computeCurrentVelocity(1000,mMaxFlingSpeed);
                    int initialVelocity = (int)mVelocityTracker.getYVelocity(mActivePointerId);
                    if (Math.abs(initialVelocity) > mMinFlingSpeed) {
                        // 由于坐標(biāo)軸正方向問題,要加負(fù)號(hào)荚斯。
                        doFling(-initialVelocity);
                    }
                    endDrag();
                }
                break;
            default:
        }
        //每次onTouchEvent處理Event時(shí)埠居,都將event交給時(shí)間
        //測(cè)量器
        if (mVelocityTracker != null) {
            mVelocityTracker.addMovement(event);
        }
        return true;
    }
    private void doFling(int speed) {
        if (mScroller == null) {
            return;
        }
        mScroller.fling(0,getScrollY(),0,speed,0,0,-500,10000);
        invalidate();
    }
    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
            postInvalidate();
        }
    }

OverScroll

在Android手機(jī)上,當(dāng)我們滾動(dòng)屏幕內(nèi)容到達(dá)內(nèi)容邊界時(shí)事期,如果再滾動(dòng)就會(huì)有一個(gè)發(fā)光效果拐格。而且界面會(huì)進(jìn)行滾動(dòng)一小段距離之后再回復(fù)原位,這些效果是如何實(shí)現(xiàn)的呢刑赶?我們需要使用ScrollerscrollTo的升級(jí)版OverScrolleroverScrollBy了,還有發(fā)光的EdgeEffect類懂衩。
?我們先來了解一下相關(guān)的API撞叨,理解了這些接口參數(shù)的含義,你就可以輕松使用這些接口來實(shí)現(xiàn)上述的效果啦浊洞。

protected boolean overScrollBy(int deltaX, int deltaY,
            int scrollX, int scrollY,
            int scrollRangeX, int scrollRangeY,
            int maxOverScrollX, int maxOverScrollY,
            boolean isTouchEvent)
  • int deltaX,int deltaY : 偏移量牵敷,也就是當(dāng)前要滾動(dòng)的x,y值法希。
  • int scrollX,int scrollY : 當(dāng)前的mScrollX和mScrollY的值枷餐。
  • int maxOverScrollX,int maxOverScrollY: 標(biāo)示可以滾動(dòng)的最大的x,y值,也就是你視圖真實(shí)的長(zhǎng)和寬苫亦。也就是說毛肋,你的視圖可視大小可能是100,100,但是視圖中的內(nèi)容的大小為200,200,所以,上述兩個(gè)值就為200,200
  • int maxOverScrollX,int maxOverScrollY:允許超過滾動(dòng)范圍的最大值屋剑,x方向的滾動(dòng)范圍就是0maxOverScrollX,y方向的滾動(dòng)范圍就是0maxOverScrollY润匙。
  • boolean isTouchEvent:是否在onTouchEvent中調(diào)用的這個(gè)函數(shù)。所以唉匾,當(dāng)你在computeScroll中調(diào)用這個(gè)函數(shù)時(shí)孕讳,就可以傳入false。
protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY)
  • int scrollX,int scrollY:就是x巍膘,y方向的滾動(dòng)距離厂财,就相當(dāng)于mScrollXmScrollY。你既可以直接把二者賦值給相應(yīng)的成員變量峡懈,也可以使用scrollTo函數(shù)璃饱。
  • boolean clampedX,boolean clampY:表示是否到達(dá)超出滾動(dòng)范圍的最大值。如果為true逮诲,就需要調(diào)用OverScrollspringBack函數(shù)來讓視圖回復(fù)原來位置帜平。
public boolean springBack(int startX, int startY, int minX, int maxX, int minY, int maxY)
  • int startX,int startY:標(biāo)示當(dāng)前的滾動(dòng)值幽告,也就是mScrollXmScrollY的值。
  • int minX,int maxX:標(biāo)示x方向的合理滾動(dòng)值
  • int minY,int maxY:標(biāo)示y方向的合理滾動(dòng)值裆甩。

相信看完上述的API之后冗锁,大家會(huì)有很多的疑惑,所以這里我來舉個(gè)例子嗤栓。
?假設(shè)視圖大小為100*100冻河。當(dāng)你一直下拉到視圖上邊緣,然后在下拉茉帅,這時(shí)叨叙,mScrollY已經(jīng)達(dá)到或者超過正常的滾動(dòng)范圍的最小值了,也就是0堪澎,但是你的maxOverScrollY傳入的是10,所以擂错,mScrollY最小可以到達(dá)-10,最大可以為110。所以樱蛤,你可以繼續(xù)下拉钮呀。等到mScrollY到達(dá)或者超過-10時(shí),clampedY就為true昨凡,標(biāo)示視圖已經(jīng)達(dá)到可以O(shè)verScroll的邊界爽醋,需要回滾到正常滾動(dòng)范圍,所以你調(diào)用springBack(0,0,0,100)便脊。

然后我們?cè)賮砜匆幌掳l(fā)光效果是如何實(shí)現(xiàn)的蚂四。
?使用EdgeEffect類。一般來說哪痰,當(dāng)你只上下滾動(dòng)時(shí)遂赠,你只需要兩個(gè)EdgeEffect實(shí)例,分別代表上邊界和下邊界的發(fā)光效果晌杰。你需要在下面兩個(gè)情景下改變EdgeEffect的狀態(tài)解愤,然后在draw()方法中繪制EdgeEffect

  • 處理ACTION_MOVE時(shí),如果發(fā)現(xiàn)y方向的滾動(dòng)值超過了正常范圍的最小值時(shí)乎莉,你需要調(diào)用上邊界實(shí)例的onPull方法送讲。如果是超過最大值,那么就是調(diào)用下邊界的onPull方法惋啃。
  • computeScroll函數(shù)中哼鬓,也就是說Fling手勢(shì)執(zhí)行過程中,如果發(fā)現(xiàn)y方向的滾動(dòng)值超過正常范圍時(shí)的最小值時(shí)边灭,調(diào)用onAbsorb函數(shù)异希。

然后就是重載draw方法,讓EdgeEffect實(shí)例在畫布上繪制自己绒瘦。你會(huì)發(fā)現(xiàn)称簿,你必須對(duì)畫布進(jìn)行移動(dòng)或者旋轉(zhuǎn)來讓EdgeEffect繪制出上邊界或者下邊界的發(fā)光的效果扣癣,因?yàn)?code>EdgeEffect對(duì)象自己是沒有上下左右的概念的。

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        if (mEdgeEffectTop != null) {
            final int scrollY = getScrollY();
            if (!mEdgeEffectTop.isFinished()) {
                final int count = canvas.save();
                final int width = getWidth() - getPaddingLeft() - getPaddingRight();
                canvas.translate(getPaddingLeft(),Math.min(0,scrollY));
                mEdgeEffectTop.setSize(width,getHeight());
                if (mEdgeEffectTop.draw(canvas)) {
                    postInvalidate();
                }
                canvas.restoreToCount(count);
            }

        }
        if (mEdgeEffectBottom != null) {
            final int scrollY = getScrollY();
            if (!mEdgeEffectBottom.isFinished()) {
                final int count = canvas.save();
                final int width = getWidth() - getPaddingLeft() - getPaddingRight();
                canvas.translate(-width+getPaddingLeft(),Math.max(getScrollRange(),scrollY)+getHeight());
                canvas.rotate(180,width,0);
                mEdgeEffectBottom.setSize(width,getHeight());
                if (mEdgeEffectBottom.draw(canvas)) {
                    postInvalidate();
                }
                canvas.restoreToCount(count);
            }

        }
    }
    
 @Override
    public boolean onTouchEvent(MotionEvent event) {
            ......
            case MotionEvent.ACTION_MOVE:
                .....
                if (mIsBeingDragged) {
                    overScrollBy(0,(int)deltaY,0,getScrollY(),0,getScrollRange(),0,mOverScrollDistance,true);
                    final int pulledToY = (int)(getScrollY()+deltaY);
                    mLastY = y;
                    if (pulledToY<0) {
                        mEdgeEffectTop.onPull(deltaY/getHeight(),event.getX(mActivePointerId)/getWidth());
                        if (!mEdgeEffectBottom.isFinished()) {
                            mEdgeEffectBottom.onRelease();
                        }
                    } else if(pulledToY> getScrollRange()) {
                        mEdgeEffectBottom.onPull(deltaY/getHeight(),1.0f-event.getX(mActivePointerId)/getWidth());
                        if (!mEdgeEffectTop.isFinished()) {
                            mEdgeEffectTop.onRelease();
                        }
                    }
                    if (mEdgeEffectTop != null && mEdgeEffectBottom != null &&(!mEdgeEffectTop.isFinished()
                                        || !mEdgeEffectBottom.isFinished())) {
                        postInvalidate();
                    }
                }
                .....
        }
        ....
    }
    @Override
    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
        if (!mScroller.isFinished()) {  
            int oldX = getScrollX();
            int oldY = getScrollY();
            scrollTo(scrollX,scrollY);
            onScrollChanged(scrollX,scrollY,oldX,oldY);
            if (clampedY) {
                Log.e("TEST1","springBack");
                mScroller.springBack(getScrollX(),getScrollY(),0,0,0,getScrollRange());
            }
        } else {
            // TouchEvent中的overScroll調(diào)用
            super.scrollTo(scrollX,scrollY);
        }
    }

    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            int oldX = getScrollX();
            int oldY = getScrollY();
            int x = mScroller.getCurrX();
            int y = mScroller.getCurrY();

            int range = getScrollRange();
            if (oldX != x || oldY != y) {
                overScrollBy(x-oldX,y-oldY,oldX,oldY,0,range,0,mOverFlingDistance,false);
            }
            final int overScrollMode = getOverScrollMode();
            final boolean canOverScroll = overScrollMode == OVER_SCROLL_ALWAYS ||
                    (overScrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
            if (canOverScroll) {
                if (y<0 && oldY >= 0) {
                    mEdgeEffectTop.onAbsorb((int)mScroller.getCurrVelocity());
                } else if (y> range && oldY < range) {
                    mEdgeEffectBottom.onAbsorb((int)mScroller.getCurrVelocity());
                }
            }
        }
    }

后記

本篇文章是系列文章的第二篇憨降,大家可能已經(jīng)知道如何實(shí)現(xiàn)各類手勢(shì)父虑,但是對(duì)其中的機(jī)制和原理還不是很了解,之后的第三篇會(huì)講解從本篇代碼的視角講解一下android視圖繪制的原理和Scroller的機(jī)制授药,希望大家多多關(guān)注士嚎。

參考文章

http://stackoverflow.com/questions/22843671/android-swipe-vs-fling

https://www.google.com/design/spec/patterns/gestures.html#gestures-drag-swipe-or-fling-details

http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1212/2145.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市悔叽,隨后出現(xiàn)的幾起案子莱衩,更是在濱河造成了極大的恐慌,老刑警劉巖娇澎,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件笨蚁,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡趟庄,警方通過查閱死者的電腦和手機(jī)赚窃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來岔激,“玉大人,你說我怎么就攤上這事是掰÷嵌Γ” “怎么了?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵键痛,是天一觀的道長(zhǎng)炫彩。 經(jīng)常有香客問我,道長(zhǎng)絮短,這世上最難降的妖魔是什么江兢? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮丁频,結(jié)果婚禮上杉允,老公的妹妹穿的比我還像新娘。我一直安慰自己席里,他們只是感情好叔磷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著奖磁,像睡著了一般改基。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上咖为,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天秕狰,我揣著相機(jī)與錄音稠腊,去河邊找鬼。 笑死鸣哀,一個(gè)胖子當(dāng)著我的面吹牛架忌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播诺舔,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鳖昌,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了低飒?” 一聲冷哼從身側(cè)響起许昨,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎褥赊,沒想到半個(gè)月后糕档,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拌喉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年速那,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尿背。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡端仰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出田藐,到底是詐尸還是另有隱情荔烧,我是刑警寧澤,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布汽久,位于F島的核電站鹤竭,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏景醇。R本人自食惡果不足惜臀稚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望三痰。 院中可真熱鬧吧寺,春花似錦、人聲如沸散劫。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽舷丹。三九已至抒钱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谋币。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工仗扬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蕾额。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓早芭,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親诅蝶。 傳聞我的和親對(duì)象是個(gè)殘疾皇子退个,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

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