仿微信滑動(dòng)按鈕

前言

前幾天寫過一篇文章View的工作原理掷贾,有原理不行枚粘,還要有實(shí)踐,剛好把以前項(xiàng)目寫過的仿微信滑動(dòng)按鈕控件封裝一下际长,所以本文記錄一下我實(shí)現(xiàn)這個(gè)控件的細(xì)節(jié)。

地址:SwitchButton

效果圖

控件使用效果如下:

sb1.gif

除了顏色兴泥,看起來和微信的還是挺像的也颤。

準(zhǔn)備

1、選擇自定義View的方式

自定義View有3種途徑實(shí)現(xiàn):1郁轻、組合控件;2文留、繼承現(xiàn)有控件(如Button)好唯;3、繼承View燥翅。下面分別介紹一下:

  • 1骑篙、組合控件:我們并不需要自己去繪制視圖上顯示的內(nèi)容,而是將幾個(gè)系統(tǒng)原生的控件組合到一起森书,這樣創(chuàng)建出的控件就被稱為組合控件靶端,比如標(biāo)題欄就是個(gè)很常見的組合控件谎势。
  • 2、繼承現(xiàn)有控件:我們并不需要自己重新去實(shí)現(xiàn)一個(gè)控件杨名,只需要去繼承一個(gè)現(xiàn)有的控件脏榆,然后在這個(gè)控件上增加一些新的功能。它的優(yōu)點(diǎn)就是不僅能夠按照我們的需求加入相應(yīng)的功能台谍,并且還可以繼承現(xiàn)有控件已經(jīng)封裝好的屬性须喂,同時(shí)不用自己定義測(cè)量流程。
  • 3趁蕊、繼承View:我們繼承View坞生,重寫相應(yīng)的方法,重新去實(shí)現(xiàn)一個(gè)控件掷伙。它的優(yōu)點(diǎn)就是靈活性高是己,它給你一張白紙,你用畫筆盡情發(fā)揮任柜。

現(xiàn)實(shí)情況使用什么方式根據(jù)實(shí)際情況考慮卒废,我這個(gè)控件的選擇是方式3: 繼承View,重寫onMeasure方法定義它的測(cè)量流程乘盼,重寫onDraw()方法定義它的繪制流程升熊。

2、選擇讓控件內(nèi)容滑動(dòng)的方式

既然是滑動(dòng)按鈕绸栅,肯定有滑動(dòng)级野,當(dāng)我點(diǎn)擊按鈕時(shí),如果是打開粹胯,按鈕的小圓會(huì)滑向右邊蓖柔,如果是關(guān)閉,按鈕的小圓會(huì)滑向左邊风纠。讓控件的內(nèi)容滑動(dòng)起來我想到的有3種方式:

  • 1况鸣、通過Scroller:調(diào)用Scroller的startScroll()方法,傳入起始點(diǎn)坐標(biāo)和終點(diǎn)坐標(biāo)竹观,然后重寫View的computeScroll()方法镐捧,在這個(gè)方法里面調(diào)用Scroller的computeScrollOffset()方法開始滑動(dòng)計(jì)算,然后調(diào)用View的scrollTo()或scrollBy()方法完成View的滑動(dòng)距離的更新臭增,然后調(diào)用View的invalidate()或postInvalidate()方法重繪View懂酱。
  • 2、通過Handler不斷的發(fā)送延時(shí)消息:通過Handler的 sendMessageDelayed(Message msg, long delayMillis)方法不斷的發(fā)送延時(shí)消息誊抛,在Handler的handlerMessage()中收到消息后列牺,完成滑動(dòng)距離的更新,然后調(diào)用View的invalidate()或postInvalidate()方法重繪View拗窃。
  • 3瞎领、通過動(dòng)畫:利用補(bǔ)間動(dòng)畫或?qū)傩詣?dòng)畫的平移動(dòng)畫可以讓View動(dòng)起來泌辫,或者通過ValueAnimator,設(shè)定一個(gè)初始值和結(jié)點(diǎn)值九默,當(dāng)調(diào)用ValueAnimator的start()方法后震放,就可以在回調(diào)中獲取動(dòng)畫的進(jìn)度,然后根據(jù)動(dòng)畫的進(jìn)度更新滑動(dòng)距離荤西,然后調(diào)用View的invalidate()或postInvalidate()方法重繪View澜搅。

對(duì)于方法1,它更適用于自定義ViewGroup的情景邪锌,如果自定義ViewGroup中有許多子View需要滑動(dòng)起來勉躺,就可以考慮使用Scroller,例如Android的ViewPager內(nèi)部就是使用了Scroller觅丰;而對(duì)于自定義View饵溅,可能方法2和3更適用,我這個(gè)控件的選擇是方式3: 通過ValueAnimator動(dòng)畫妇萄,在構(gòu)造ValueAnimator時(shí)傳入起點(diǎn)和終點(diǎn)蜕企,然后開啟動(dòng)畫,根據(jù)動(dòng)畫進(jìn)度計(jì)算滑動(dòng)距離冠句,讓按鈕的小圓滑動(dòng)起來轻掩。

3、要不要考慮padding屬性

如果你在自定義控件中沒有考慮padding屬性懦底,那么用戶定義控件的padding值就會(huì)失效唇牧,我的選擇是不考慮用戶的padding值,因?yàn)榛瑒?dòng)按鈕中的內(nèi)容只有一個(gè)小圓聚唐,且只在一邊丐重,padding的意義不大,考慮padding會(huì)讓很多地方的坐標(biāo)計(jì)算復(fù)雜杆查,我還不如讓用戶直接控制小圓的半徑扮惦,這樣也類似于padding的效果,也簡(jiǎn)化了計(jì)算亲桦。

所以現(xiàn)實(shí)情況要不要考慮padding屬性需要根據(jù)實(shí)際情況考慮崖蜜。而margin值是由父ViewGroup決定,不是由View控制的客峭,我們不用考慮margin值豫领。

實(shí)現(xiàn)

1、定義控件屬性

在自定義滑動(dòng)按鈕之前桃笙,我們先思考可以讓用戶自定義這個(gè)控件的什么屬性,如按鈕顏色沙绝,打開狀態(tài)和關(guān)閉狀態(tài)的顏色等搏明,在 res -> values 中鼠锈,右鍵新建一個(gè)名為attrs的xml文件,在這個(gè)文件中定義控件屬性星著,如下:

<resources>

    <declare-styleable name="SwitchButton" >
        <attr name="sb_openBackground" format="color"/>
        <attr name="sb_closeBackground" format="color"/>
        <attr name="sb_circleColor" format="color"/>
        <attr name="sb_circleRadius" format="dimension"/>
        <attr name="sb_status">
            <enum name="close" value="0"/>
            <enum name="open" value="1"/>
        </attr>
        <attr name="sb_interpolator">
            <enum name="Linear" value="0"/>
            <enum name="Overshoot" value="1"/>
            <enum name="Accelerate" value="2"/>
            <enum name="Decelerate" value="3"/>
            <enum name="AccelerateDecelerate" value="4"/>
            <enum name="LinearOutSlowIn" value="5"/>
        </attr>
    </declare-styleable>

</resources>

這樣用戶在引用這個(gè)控件時(shí)就能使用這些屬性购笆,如下:

    <com.example.library.SwitchButton
            android:id="@+id/sb_button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:sb_interpolator="Accelerate"
            app:sb_status="open"
            app:sb_circleRadius="10dp"
            app:sb_closeBackground="@android:color/black"
            app:sb_openBackground="@android:color/holo_blue_bright"
            app:sb_circleColor="@android:color/white" />

屬性的名稱要做到見名知意,app只是一個(gè)命名空間虚循,取什么名字都可以同欠,不要和系統(tǒng)android相同就行。關(guān)于這些屬性什么意思可以看SwitchButton横缔。

2铺遂、初始化控件屬性

重寫View的3個(gè)構(gòu)造方法,分別在3個(gè)構(gòu)造函數(shù)中調(diào)用init()方法獲取控件屬性并初始化控件茎刚,如下:

public class SwitchButton extends View {
    public SwitchButton(Context context) {
        super(context);
        init(context, null);
    }

    public SwitchButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public SwitchButton(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, @Nullable AttributeSet attrs) {
        TypedArray typedValue = context.obtainStyledAttributes(attrs, R.styleable.SwitchButton);
        mOpenBackground = typedValue.getColor(R.styleable.SwitchButton_sb_openBackground, DEFAULT_OPEN_BACKGROUND);
        mCloseBackground = typedValue.getColor(R.styleable.SwitchButton_sb_closeBackground, DEFAULT_CLOSE_BACKGROUND);
        //...
        typedValue.recycle();
         //...
        //初始畫筆襟锐,動(dòng)畫等
    }
}

我們?cè)赼ttrs中定義的控件屬性都在AttributeSet這個(gè)集合中,然后通過TypedArray這個(gè)類幫助我們把值獲取出來膛锭,最后一定要記得調(diào)用 typedValue.recycle() 方法回收資源粮坞。

為什么要重寫3個(gè)構(gòu)造函數(shù)呢?因?yàn)槟愕目丶锌赡茉诖a中引用或者在xml布局中引用初狰,如果你的控件在xml布局中被引用莫杈,那么系統(tǒng)就會(huì)調(diào)用含有兩個(gè)參數(shù)的構(gòu)造函數(shù)來初始化控件;如果你直接在代碼中 new 一個(gè)控件然后 add 到容器中奢入,那么大多數(shù)情況你會(huì)使用含有一個(gè)參數(shù)的構(gòu)造函數(shù)來初始化控件筝闹,如:SwitchButton button = new SwitchButton(this),而不管一個(gè)參數(shù)的還是兩個(gè)參數(shù)的系統(tǒng)最終都會(huì)調(diào)用含有三個(gè)參數(shù)的構(gòu)造函數(shù)俊马,以防萬一丁存,3個(gè)構(gòu)造函數(shù)都要重寫。

3柴我、重寫onMeasure方法解寝,設(shè)定按鈕的測(cè)量寬高

重寫onMeasure方法在這個(gè)方法設(shè)定滑動(dòng)控件的測(cè)量寬高,如下:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int measuredWidthMode = MeasureSpec.getMode(widthMeasureSpec);
    int measuredHeightMode = MeasureSpec.getMode(heightMeasureSpec);
    //取出系統(tǒng)測(cè)量寬高
    int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
    int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
    
    int defaultWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 60, getResources().getDisplayMetrics());//控件的默認(rèn)寬
    int defaultHeight = (int) (defaultWidth *  0.5f);//控件的默認(rèn)高是默認(rèn)寬的一半
    
    //OFFSET == 6
    int offset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, OFFSET * 2 * 1.0f, getResources().getDisplayMetrics());//控件寬和高的差距不能小于12dp, 否則按鈕就不好看了
    
    //考慮wrap_content情況
    if(measuredWidthMode == MeasureSpec.AT_MOST && measuredHeightMode == MeasureSpec.AT_MOST){
        measuredWidth = defaultWidth;
        measureHeight = defaultHeight;
    }else if(measuredHeightMode == MeasureSpec.AT_MOST){
        measureHeight = defaultHeight;
        if(measuredWidth - measureHeight < offset)
            measuredWidth = defaultWidth;
    }else if(measuredWidthMode == MeasureSpec.AT_MOST){
        measuredWidth = defaultWidth;
        if(measuredWidth - measureHeight < offset)
            measureHeight = defaultHeight;
    }else {
        //處理輸入非法的寬高情況艘儒,即高度大于寬度聋伦,把它們交換就行
        if(measuredWidth < measureHeight){
            int temp = measuredWidth;
            measuredWidth = measureHeight;
            measureHeight = temp;
        }
    }
    
    if(Math.abs(measureHeight - measuredWidth) < offset) throw new IllegalArgumentException("layout_width cannot close to layout_height nearly, the diff must less than 12dp!");
    
    setMeasuredDimension(measuredWidth, measureHeight);
    
}

如果知道View的工作原理,那么理解上面的代碼就很簡(jiǎn)單界睁,主要是考慮wrap_content情況觉增,我們要給滑動(dòng)按鈕設(shè)置一個(gè)默認(rèn)的寬或高,默認(rèn)的寬是60dp翻斟,默認(rèn)高是30dp即寬的一半逾礁,如果不是wrap_content情況就讓View直接使用系統(tǒng)測(cè)量的寬或高,最后一定要記得調(diào)用setMeasuredDimension()設(shè)定View的測(cè)量寬高访惜。

同時(shí)我們還要考慮理輸入非法的寬高情況嘹履,一定要保證寬 > 高腻扇,如果用戶輸入的寬高是 寬 < 高,這樣會(huì)導(dǎo)致按鈕豎起來砾嫉,這種情況幼苛,我直接讓高度與寬度交換;如果用戶輸入的寬高是 寬 > 高焕刮,但是如果高很接近寬甚至相等舶沿,那么導(dǎo)致滑動(dòng)控件就是一個(gè)圓形,按鈕就不好看了配并,所以我們還要控制寬高不能相差得太近括荡,為了美觀,我設(shè)定閾值是12dp荐绝,如果寬高相差小于12dp一汽,我就拋個(gè)異常提示用戶。

4低滩、在onLayout()方法中根據(jù)View的寬高計(jì)算坐標(biāo)

滑動(dòng)控件被分為4個(gè)部分:左圓召夹、矩形、右圓恕沫、小圓监憎,如下:

在onDraw()方法中也會(huì)按順序繪制滑動(dòng)按鈕的4個(gè)部分,在View的工作原理中講到婶溯,onMeasure()有可能會(huì)被系統(tǒng)調(diào)用多次鲸阔,所以最好在onLayout()方法中通過getHeight()和getWidth()方法獲得View的真實(shí)寬高,所以在onLayout()方法中首先根據(jù)View的寬高計(jì)算出左圓的半徑迄委,小圓的半徑褐筛,矩形左邊界的x坐標(biāo),矩形右邊界的x坐標(biāo)叙身,還有小圓圓心的x坐標(biāo)渔扎,如下:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    //得出左圓的半徑
    mLeftSemiCircleRadius = getHeight() / 2;
    //小圓的半徑 = 大圓半徑減OFFER,OFFER = 6
    if(!checkCircleRaduis(mCircleRadius)) mCircleRadius = mLeftSemiCircleRadius - OFFSET;
    //矩形左邊的x坐標(biāo)
    mLeftRectangleBolder = mLeftSemiCircleRadius;
    //矩形右邊的x坐標(biāo)
    mRightRectangleBolder = getWidth() - mLeftSemiCircleRadius;
    //小圓的圓心x坐標(biāo)一直在變化
    mCircleCenter = isOpen ? mRightRectangleBolder : mLeftRectangleBolder;
}

可以看到左圓的半徑等于View高的一半信轿,然后基于左圓的半徑得出其他坐標(biāo)晃痴,小圓與左圓之間會(huì)有一些空隙,所以左圓半徑減去offset值得出小圓半徑财忽,矩形左邊的x坐標(biāo)直接等于左圓的半徑倘核,矩形右邊的x坐標(biāo)View的寬度減左圓的半徑,小圓圓心的x坐標(biāo)根據(jù)初始狀態(tài)是開啟還是關(guān)閉即彪,決定它的圓心的初始坐標(biāo)是在矩形的右邊界還是左邊界紧唱。

在接下來只要你不斷的改變小圓圓心的x坐標(biāo)并重繪View,就可以讓滑動(dòng)按鈕滑動(dòng)起來。

5漏益、重寫onDraw()方法酬凳,繪制按鈕內(nèi)容

View的工作原理中我們知道,View會(huì)在onDraw()方法中繪制自己遭庶,所以我們重寫onDraw()方法,繪制滑動(dòng)按鈕的四個(gè)部分稠屠,如下:

@Override
protected void onDraw(Canvas canvas) {
    //左圓
    canvas.drawCircle(mLeftRectangleBolder, mLeftSemiCircleRadius, mLeftSemiCircleRadius, mPathWayPaint);
    //矩形
    canvas.drawRect(mLeftRectangleBolder, 0, mRightRectangleBolder, getMeasuredHeight(), mPathWayPaint);
    //右圓
    canvas.drawCircle(mRightRectangleBolder, mLeftSemiCircleRadius, mLeftSemiCircleRadius, mPathWayPaint);
    //小圓
    canvas.drawCircle(mCircleCenter, mLeftSemiCircleRadius, mCircleRadius, mCirclePaint);
}

canvas是系統(tǒng)提供給我們的畫布峦睡,在canvas繪制的東西就是View顯示的內(nèi)容,根據(jù)在onLayout中的計(jì)算权埠,我們用畫筆Paint在canvas中繪制出滑動(dòng)按鈕的4個(gè)部分榨了,繪制后顯示如下:

接下來就是讓它滑動(dòng)起來,這樣就能達(dá)到效果圖的效果攘蔽。

6龙屉、重寫onTouchEvent()方法,讓按鈕滑動(dòng)起來

View的事件分發(fā)機(jī)制講到满俗,觸摸事件如果不被攔截转捕,最終會(huì)分發(fā)到View的onTouchEvent()方法中,在這個(gè)方法中我們可以根據(jù)事件的類型做出滑動(dòng)按鈕的不同行為唆垃,我們知道當(dāng)手指按下按鈕然后抬起五芝,滑動(dòng)按鈕的小圓就會(huì)滑動(dòng)到另一邊;當(dāng)手指按下按鈕然后移動(dòng)辕万,滑動(dòng)按鈕的小圓也會(huì)跟隨手指移動(dòng)枢步,知道了這兩個(gè)行為后,我們看onTouchEvent()方法如下:

@Override
public boolean onTouchEvent(MotionEvent event) {
    //不在動(dòng)畫的時(shí)候可以點(diǎn)擊
    if(isAnim) return false;
    switch(event.getAction()){
        case MotionEvent.ACTION_DOWN:
            //開始的x坐標(biāo)
            startX = event.getX();
            break;
        case MotionEvent.ACTION_MOVE:
            float distance = event.getX() - startX;
            //更新小圓圓心坐標(biāo)
            mCircleCenter += distance / 10;
            //控制范圍
            if (mCircleCenter > mRightRectangleBolder) {//最右
                mCircleCenter = mRightRectangleBolder;
            } else if (mCircleCenter < mLeftRectangleBolder) {//最左
                mCircleCenter = mLeftRectangleBolder;
            }
            invalidate();
            break;
        case MotionEvent.ACTION_UP:
            float offset = Math.abs(event.getX() - Math.abs(startX));
            float diff;
            //分2種情況
            if (offset < mMinDistance) { //1.點(diǎn)擊, 按下和抬起的距離小于mMinDistance確定是點(diǎn)擊了
                if(isOpen){
                    diff = mLeftRectangleBolder - mCircleCenter;
                }else{
                    diff = mRightRectangleBolder - mCircleCenter;
                }
            } else {//2.滑動(dòng)
                if (mCircleCenter > getWidth() / 2) {//滑過中點(diǎn)渐尿,滑到最右
                    this.isOpen = false;
                    diff = mRightRectangleBolder - mCircleCenter;
                } else{//沒滑過中點(diǎn),回歸原點(diǎn)
                    this.isOpen = true;
                    diff = mLeftRectangleBolder - mCircleCenter;
                }
            }
            mValueAnimator.setFloatValues(0, diff);
            mValueAnimator.start();
            startX = 0;
            break;
        default:
            break;
    }
    return true;
}

我們先看ACTION_DOWN醉途,當(dāng)手指按下,我們記錄手指按下的x坐標(biāo)砖茸。

接著看ACTION_MOVE隘擎,如果按下后移動(dòng),我們就讓小圓跟隨手指移動(dòng)即可渔彰,所以ACTION_MOVE中先計(jì)算出手指移動(dòng)的距離distance嵌屎,往右移distance是正數(shù),往左移distance是負(fù)數(shù)恍涂,然后加到小圓的圓心坐標(biāo)宝惰,還要控制小圓的圓心坐標(biāo)的范圍,不要超出矩形左右邊界再沧,最后調(diào)用 invalidate()重繪View尼夺,這樣onDraw()方法就會(huì)重新執(zhí)行,更新小圓的位置,就會(huì)讓小圓慢慢滑動(dòng)起來淤堵。

最后看ACTION_UP寝衫,mMinDistance = new ViewConfiguration().getScaledTouchSlop(),它是系統(tǒng)定義的臨界值拐邪,當(dāng)抬起手指時(shí)慰毅,如果移動(dòng)的距離offset大于mMinDistance ,就認(rèn)為抬起手指前扎阶,手指在移動(dòng)汹胃,否則就認(rèn)為在點(diǎn)擊。如果手指在移動(dòng)后抬起东臀,這時(shí)就判斷小圓圓心是否滑過中點(diǎn)算出滑動(dòng)距離着饥,如果滑過中點(diǎn)(getWidth() / 2),就讓小圓滑到最右惰赋,如果沒有滑過中點(diǎn)宰掉,就讓小圓滑到最左;如果手指只是在點(diǎn)擊控件赁濒,這時(shí)就根據(jù)控件目前處于開啟還是關(guān)閉狀態(tài)算出滑動(dòng)距離轨奄,如果目前處于開啟狀態(tài),就讓小圓滑到最左拒炎,如果目前處于關(guān)閉狀態(tài)就讓小圓滑到最右;而這個(gè)滑動(dòng)距離diff就是小圓圓心到矩形邊界的距離枝冀,至于是距離左邊界還是右邊界舞丛,就看上述情況了,計(jì)算出滑動(dòng)距離后設(shè)置給ValueAnimator果漾,最后開啟動(dòng)畫球切,在ValueAnimator的updateListener中接收動(dòng)畫進(jìn)度,如下:

mValueAnimator.addUpdateListener(animation -> {
    float value = (float)animation.getAnimatedValue();
    mCircleCenter -= mPreAnimatedValue;
    //更新小圓圓心坐標(biāo)
    mCircleCenter += value;
    mPreAnimatedValue = value;
    invalidate();
});

在里面根據(jù)動(dòng)畫進(jìn)度更新小圓圓心坐標(biāo)绒障,然后調(diào)用 invalidate()重繪View吨凑,這樣onDraw()方法就會(huì)重新執(zhí)行,更新小圓的位置户辱,這樣重復(fù)執(zhí)行直到動(dòng)畫結(jié)束鸵钝,就會(huì)讓小圓慢慢滑動(dòng)起來。

結(jié)語(yǔ)

到最后就已經(jīng)實(shí)現(xiàn)了效果圖的效果庐镐,整個(gè)過程的原理還是挺簡(jiǎn)單恩商,使用到了動(dòng)畫還有自定義View的基礎(chǔ)知識(shí),趕快動(dòng)手實(shí)踐一下必逆。

地址:SwitchButton

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末怠堪,一起剝皮案震驚了整個(gè)濱河市揽乱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌粟矿,老刑警劉巖凰棉,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異陌粹,居然都是意外死亡撒犀,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門掏秩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绘证,“玉大人,你說我怎么就攤上這事哗讥。” “怎么了胞枕?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵杆煞,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我腐泻,道長(zhǎng)决乎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任派桩,我火速辦了婚禮构诚,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘铆惑。我一直安慰自己范嘱,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布员魏。 她就那樣靜靜地躺著丑蛤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪撕阎。 梳的紋絲不亂的頭發(fā)上受裹,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音虏束,去河邊找鬼棉饶。 笑死,一個(gè)胖子當(dāng)著我的面吹牛镇匀,可吹牛的內(nèi)容都是我干的照藻。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼汗侵,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼岩梳!你這毒婦竟也來了囊骤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤冀值,失蹤者是張志新(化名)和其女友劉穎也物,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體列疗,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡滑蚯,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了抵栈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片告材。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖古劲,靈堂內(nèi)的尸體忽然破棺而出斥赋,到底是詐尸還是另有隱情,我是刑警寧澤产艾,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布疤剑,位于F島的核電站,受9級(jí)特大地震影響闷堡,放射性物質(zhì)發(fā)生泄漏隘膘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一杠览、第九天 我趴在偏房一處隱蔽的房頂上張望弯菊。 院中可真熱鬧,春花似錦踱阿、人聲如沸管钳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蹋嵌。三九已至,卻和暖如春葫隙,著一層夾襖步出監(jiān)牢的瞬間栽烂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工恋脚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留腺办,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓糟描,卻偏偏與公主長(zhǎng)得像怀喉,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子船响,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

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