從 Android 源碼角度分析 View 的狀態(tài)改變?nèi)绾斡绊?Drawable 的表現(xiàn)

在 Android 開發(fā)中芹血,View 和 Drawable 之間關(guān)系十分緊密,例如我們經(jīng)常用 Drawable 作為一個(gè) View 的背景楞慈。View 常常會(huì)有狀態(tài)的改變幔烛,例如被按下、例如禁用囊蓝,而不同的狀態(tài)下 Drawable 也常有不同的表現(xiàn)饿悬。今天要探索的問題是 View 的狀態(tài)改變是如何影響 Drawable 的表現(xiàn)的。
以下將簡(jiǎn)單介紹我們平時(shí)如何在 View 上使用 Drawable聚霜,做到在不同狀態(tài)下表現(xiàn)不一樣狡恬。接著分析系統(tǒng)源碼探索其中的原理。最后以系統(tǒng)的控件和自定義控件 2 個(gè)例子來驗(yàn)證和實(shí)踐在 View 中自定義狀態(tài)的做法蝎宇。

注:

  1. 本文的源碼分析基于 Android API Level 23弟劲,并省略掉部分與本文關(guān)系不大的代碼。
  2. 在代碼中加入了個(gè)人對(duì)源碼的理解姥芥,以注釋形式呈現(xiàn)兔乞。
  3. 本文最后的 DEMO 項(xiàng)目源碼托管到 Github 上。

如何給 View 在不同狀態(tài)下設(shè)置不同背景色

可以對(duì)一個(gè) View 設(shè)置 background 屬性凉唐,傳進(jìn)去的是一個(gè) Drawable庸追。 如果該 Drawable 是一個(gè) StateListDrawable(對(duì)應(yīng)的 xml 標(biāo)簽為 <selctor>),那么它能在不同狀態(tài)下顯示不同的表現(xiàn)台囱。例如一個(gè) Button淡溯,可以在 normal、pressed簿训、disabled 等狀態(tài)下顯示不同的背景色咱娶,像這樣:

<!-- button_bg.xml -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="#CCCCCC" android:state_enabled="false"/>
    <item android:drawable="#666666" android:state_pressed="true"/>
    <item android:drawable="#999999"/>
</selector>
<!-- layout.xml -->
<Button
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/button_bg"
    android:text="Test Button"/>

這樣即可實(shí)現(xiàn)使 Button 在不同狀態(tài)下顏色不一樣,normal 為 #999999强品,pressed 為 #666666豺总,disabled 時(shí)為 #CCCCCC。

原理

以上是經(jīng)常用來設(shè)置 Button 背景的用法择懂,那么實(shí)際上 Button(View)的不同狀態(tài)是如何和 Drawable 關(guān)聯(lián)起來的喻喳?除了上面說的 pressed 和 enabled 狀態(tài),我們可以設(shè)置的狀態(tài)還有哪些困曙?如果系統(tǒng)提供的狀態(tài)不夠用表伦,我們能否自己定義狀態(tài)?帶著這幾個(gè)問題慷丽,我們來看 Android FrameWork 的源碼蹦哼。

// View.java
// 首先,按鈕被按下的時(shí)候要糊,setPressed(boolean pressed) 會(huì)被調(diào)用纲熏。
// 注1:這里以 pressed 狀態(tài)改變?yōu)槔瑥?setPressed 方法為入口。
// 同理當(dāng) enabled 或其他狀態(tài)改變時(shí)局劲,可以看 setEnabled 方法或其他對(duì)應(yīng)方法勺拣。
public void setPressed(boolean pressed) {
    final boolean needsRefresh =
        pressed != ((mPrivateFlags & PFLAG_PRESSED) == PFLAG_PRESSED);

    if (pressed) {
        mPrivateFlags |= PFLAG_PRESSED;
    } else {
        mPrivateFlags &= ~PFLAG_PRESSED;
    }

    // 調(diào)用 refreshDrawableState() 方法來刷新 View 的狀態(tài)
    if (needsRefresh) {
        refreshDrawableState();
    }
    dispatchSetPressed(pressed);
}

// 接著看 refreshDrawableState() 方法。
// 該方法會(huì)使 View 更新它的 Drawable 的狀態(tài)鱼填,并調(diào)用 drawableStateChanged() 方法药有。
public void refreshDrawableState() {
    // 設(shè)置 PFLAG_DRAWABLE_STATE_DIRTY 標(biāo)志位,后面會(huì)用到苹丸,并調(diào)用 drawableStateChanged() 方法
    mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
    drawableStateChanged();

    ViewParent parent = mParent;
    if (parent != null) {
        parent.childDrawableStateChanged(this);
    }
}

// 接著看 drawableStateChanged() 方法愤惰。
protected void drawableStateChanged() {
    // 調(diào)用 getDrawableState() 方法得到當(dāng)前 View 的狀態(tài)合集,以一個(gè) int 數(shù)組的形式存在赘理。
    final int[] state = getDrawableState();

    // 將狀態(tài)合集設(shè)置給 background宦言,那么 Drawable 就會(huì)自己更新狀態(tài)并通知 View 重新繪制它。
    final Drawable bg = mBackground;
    if (bg != null && bg.isStateful()) {
        bg.setState(state);
    }

    // 此處省略其他無關(guān)源代碼...
}

// 接著看 getDrawableState() 方法商模,它會(huì)返回一個(gè) resource ID 數(shù)組來表示 View 的當(dāng)前狀態(tài)蜡励。
public final int[] getDrawableState() {
    // 因?yàn)?PFLAG_DRAWABLE_STATE_DIRTY 標(biāo)志位在上面 refreshDrawableState() 方法中已經(jīng)被設(shè)置,
    // 所以從 refreshDrawableState() 方法調(diào)用進(jìn)來時(shí)肯定會(huì)進(jìn)入下面的 else 分支阻桅,
    // 從 onCreateDrawableState(0) 方法取得 drawableState
    if ((mDrawableState != null) && ((mPrivateFlags & PFLAG_DRAWABLE_STATE_DIRTY) == 0)) {
        return mDrawableState;
    } else {
        mDrawableState = onCreateDrawableState(0);
        mPrivateFlags &= ~PFLAG_DRAWABLE_STATE_DIRTY;
        return mDrawableState;
    }
}

// 接著看 onCreateDrawableState(int extraSpace) 方法凉倚。它的作用是生成這個(gè) View 的 Drawable State。
protected int[] onCreateDrawableState(int extraSpace) {
    // 如果這個(gè) View 設(shè)置了 DUPLICATE_PARENT_STATE 標(biāo)志位(可通過 setDuplicateParentStateEnabled(boolean enabled)方法來設(shè)置)嫂沉,
    // 則直接通過父View的狀態(tài)獲得state稽寒,并返回。一般的 View 都沒有設(shè)置這個(gè)標(biāo)志位趟章,所以這個(gè)條件一般不滿足杏糙。
    if ((mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE &&
            mParent instanceof View) {
        return ((View) mParent).onCreateDrawableState(extraSpace);
    }

    int[] drawableState;

    int privateFlags = mPrivateFlags;

    // 檢查這個(gè) View 的 pressed、enabled蚓土、focuesed 等狀態(tài)(系統(tǒng)提供的 View 的狀態(tài)都會(huì)在這里被檢查一遍)宏侍,
    // 通過位運(yùn)算記錄在 viewStateIndex 這個(gè)整型變量的各個(gè)位上
    int viewStateIndex = 0;
    if ((privateFlags & PFLAG_PRESSED) != 0) viewStateIndex |= StateSet.VIEW_STATE_PRESSED;
    if ((mViewFlags & ENABLED_MASK) == ENABLED) viewStateIndex |= StateSet.VIEW_STATE_ENABLED;
    if (isFocused()) viewStateIndex |= StateSet.VIEW_STATE_FOCUSED;
    if ((privateFlags & PFLAG_SELECTED) != 0) viewStateIndex |= StateSet.VIEW_STATE_SELECTED;
    if (hasWindowFocus()) viewStateIndex |= StateSet.VIEW_STATE_WINDOW_FOCUSED;
    if ((privateFlags & PFLAG_ACTIVATED) != 0) viewStateIndex |= StateSet.VIEW_STATE_ACTIVATED;
    if (mAttachInfo != null && mAttachInfo.mHardwareAccelerationRequested &&
            HardwareRenderer.isAvailable()) {
        // This is set if HW acceleration is requested, even if the current
        // process doesn't allow it.  This is just to allow app preview
        // windows to better match their app.
        viewStateIndex |= StateSet.VIEW_STATE_ACCELERATED;
    }
    if ((privateFlags & PFLAG_HOVERED) != 0) viewStateIndex |= StateSet.VIEW_STATE_HOVERED;

    final int privateFlags2 = mPrivateFlags2;
    if ((privateFlags2 & PFLAG2_DRAG_CAN_ACCEPT) != 0) {
        viewStateIndex |= StateSet.VIEW_STATE_DRAG_CAN_ACCEPT;
    }
    if ((privateFlags2 & PFLAG2_DRAG_HOVERED) != 0) {
        viewStateIndex |= StateSet.VIEW_STATE_DRAG_HOVERED;
    }

    // 將 viewStateIndex 變量中記錄的各個(gè)狀態(tài)轉(zhuǎn)化為一個(gè)數(shù)組,具體如何轉(zhuǎn)化可以看 StateSet.get 方法蜀漆,這里不做延伸討論谅河。
    drawableState = StateSet.get(viewStateIndex);

    // 如果參數(shù) extraSpace 為 0,那么這個(gè)數(shù)組就是最終要返回的數(shù)組了确丢。
    if (extraSpace == 0) {
        return drawableState;
    }

    // 如果 extraSpace 不為 0绷耍,那么會(huì)將 drawableState 數(shù)組的長(zhǎng)度擴(kuò)大 extraSpace 后返回。
    final int[] fullState;
    if (drawableState != null) {
        fullState = new int[drawableState.length + extraSpace];
        System.arraycopy(drawableState, 0, fullState, 0, drawableState.length);
    } else {
        fullState = new int[extraSpace];
    }

    return fullState;
}

到此鲜侥,我們從 View 的 pressed 狀態(tài)改變開始褂始,根據(jù)源碼看完了 View 內(nèi)部如何改變 backgroundDrawable 的狀態(tài)。簡(jiǎn)單總結(jié)一下:

  1. View 的 pressed 狀態(tài)改變會(huì)調(diào)用 setPressed 方法描函。
  2. setPressed 方法會(huì)調(diào)用 refreshDrawableState 方法崎苗。
  3. refreshDrawableState 中會(huì)調(diào)用 drawableStateChanged狐粱,去更新 drawable 的狀態(tài),其中就包括 backgroundDrawable胆数。
  4. drawableStateChanged 方法中肌蜻,通過 getDrawableState 方法得到 DrawableState 并設(shè)置為 backgroundDrawable,那么 Drawable 就會(huì)自己更新狀態(tài)并通知 View 重新繪制幅慌。
  5. getDrawableState 方法是通過 onCreateDrawableState(int extraSpace) 方法來得到 DrawableState 的宋欺。
    所以轰豆,View 的 backgroundDrawable 狀態(tài)其實(shí)是由 onCreateDrawableState(int extraSpace) 方法決定的胰伍,而 setPressed 方法只是作為狀態(tài)改變的整個(gè)流程的起點(diǎn)。

看完了源碼酸休,我們應(yīng)該可以解決上面提出的幾個(gè)問題:

  1. Button(View)的不同狀態(tài)是如何和 Drawable 關(guān)聯(lián)起來的骂租?
    View 在狀態(tài)改變時(shí)調(diào)用 refreshDrawableState 去刷新 Drawable 的狀態(tài),而這些狀態(tài)最終由 onCreateDrawableState(int extraSpace) 方法返回斑司。

  2. 除了上面說的 pressed 和 enabled 狀態(tài)渗饮,我們可以設(shè)置的狀態(tài)還有哪些?
    View#onCreateDrawableState(int extraSpace) 方法宿刮,其中檢查了 pressed互站、enabled、focused僵缺、selected胡桃、window_focused、activated磕潮、hardware_accelerated翠胰、hovered、drag_can_accept自脯、drag_hovered 狀態(tài)之景。所以,對(duì)于 View膏潮,我們可以控制這些狀態(tài)锻狗。

  3. 如果系統(tǒng)提供的狀態(tài)不夠用,我們能否自己定義狀態(tài)焕参?
    當(dāng)然可以屋谭,不可以的話我怎么會(huì)在這篇文章提出這個(gè)問題?其實(shí) View 提供的狀態(tài)很有限龟糕,而很多時(shí)候更底層的控件都需要定義更多狀態(tài)欄滿足特定的需求桐磁。接下來我們看自定義狀態(tài)。

自定義狀態(tài)在系統(tǒng)控件中的使用

我們先來看看系統(tǒng)控件自定義狀態(tài)的做法讲岁。以 CheckBox 為例我擂,CheckBox 是 View 的間接子類(兩者中間還有好幾層繼承關(guān)系)衬以,提供了一個(gè)可勾選框的功能,它可以被 setChecked(boolean checked)校摩,并在 checked 為 true/false 時(shí)有不同的表現(xiàn)看峻,那么 CheckBox 是如何在 View 的基礎(chǔ)上實(shí)現(xiàn) checked 狀態(tài)的?
搜一下 CheckBox 的 setChecked 方法衙吩,實(shí)際上這個(gè)方法在其父類 CompoundButton 實(shí)現(xiàn)互妓。

public void setChecked(boolean checked) {
    if (mChecked != checked) {
        mChecked = checked;
        refreshDrawableState();
        // 此處省略其他無關(guān)源代碼...
    }
}
// 該方法同樣調(diào)用了 refreshDrawableState() 方法,且在這個(gè)類中沒有重寫 refreshDrawableState() 方法坤塞,說明接下來的代碼流程會(huì)與上述流程一樣冯勉。
// 但是這個(gè)類重寫了 drawableStateChanged() 方法和 onCreateDrawableState(int extraSpace) 方法。
@Override
protected void drawableStateChanged() {
    super.drawableStateChanged();
    
    // 除了調(diào)用 super 的方法摹芙,還更新了自己持有的 mButtonDrawable 的狀態(tài)
    if (mButtonDrawable != null) {
        int[] myDrawableState = getDrawableState();
        
        // Set the state of the Drawable
        mButtonDrawable.setState(myDrawableState);
        
        invalidate();
    }
}
// 用數(shù)組保存了要自定義的狀態(tài)的 resource ID灼狰,這里自定義了 *checked* 狀態(tài)
private static final int[] CHECKED_STATE_SET = {
    R.attr.state_checked
};
@Override
protected int[] onCreateDrawableState(int extraSpace) {
    // 調(diào)用 super 的方法時(shí),extraSpace 參數(shù)加了 1浮禾,
    // 實(shí)際上這個(gè) 1 就是 CHECKED_STATE_SET.length,即自定義的狀態(tài)的個(gè)數(shù)
    final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
    if (isChecked()) {
         // 如果當(dāng)前狀態(tài)是 checked盈电,則把 super 返回的 drawableState 數(shù)組與 CHECKED_STATE_SET 數(shù)組合并蝴簇,
         // 合并的結(jié)果是在 super 返回的 drawableState 數(shù)組的基礎(chǔ)上,往數(shù)組后面追加了 CHECKED_STATE_SET 數(shù)組的內(nèi)容匆帚。
         // 最后將數(shù)組返回熬词。
        mergeDrawableStates(drawableState, CHECKED_STATE_SET);
    }
    return drawableState;
}

至此,就完成了對(duì) checked 狀態(tài)的自定義卷扮,并能通過 setChecked(boolean checked) 方法來改變 checked 狀態(tài)荡澎。總結(jié)一下自定義狀態(tài)需要做的幾件事:

  1. 提供一個(gè)改變 View 狀態(tài)的方法晤锹,并在狀態(tài)改變時(shí)調(diào)用 refreshDrawableState() 方法摩幔。
  2. drawableStateChanged() 方法中,調(diào)用自己維護(hù)的 Drawable 的 setState 方法鞭铆,傳入 getDrawableState() 返回的值或衡,從而更新 Drawable 的狀態(tài)。
  3. 定義一個(gè) int 數(shù)組车遂,存放自定義的狀態(tài)封断。
  4. onCreateDrawableState(int extraSpace) 方法中,調(diào)用 super.onCreateDrawableState(int)舶担,傳入 extraSpace 加上上述 int 數(shù)組的長(zhǎng)度坡疼,并將 super 返回的結(jié)果與上述 int 數(shù)組用 mergeDrawableStates() 方法合并,最終返回合并后的結(jié)果衣陶。

實(shí)踐

看完原理和系統(tǒng)控件的例子柄瑰,我們也可以來自定義View的狀態(tài)了闸氮。假設(shè)我們要實(shí)現(xiàn)這樣一個(gè)需求:有一個(gè) ListView,它的每個(gè) Item 左側(cè)有一個(gè) CheckBox 可對(duì)整個(gè)列表進(jìn)行多選操作教沾。
這種情況可以使用自定義狀態(tài)來完成蒲跨,Item 是否被 checked 將影響 Drawable 的表現(xiàn),以下以 Item 的最外層 View 為 LinearLayout 為例授翻,自定義一個(gè) CheckableLinearLayout或悲。

public class CheckableLinearLayout extends LinearLayout implements Checkable {

    private boolean mIsChecked = false;

    private Drawable mCheckboxDrawable;

    private static final int[] CHECKED_STATE_SET = {
            android.R.attr.state_checked
    };

    public CheckableLinearLayout(Context context) {
        super(context);
        init();
    }

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

    public CheckableLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mCheckboxDrawable = getResources().getDrawable(R.drawable.qmui_s_dialog_check_mark);
        // 恢復(fù) ViewGroup 的 draw 功能(默認(rèn)關(guān)閉),使 onDraw 方法會(huì)被調(diào)用
        setWillNotDraw(false);
    }

    @Override
    protected void drawableStateChanged() {
        super.drawableStateChanged();
        // 將 getDrawableState 返回的狀態(tài)數(shù)組設(shè)置給 mCheckboxDrawable堪唐,并觸發(fā)重繪
        if (mCheckboxDrawable != null) {
            int[] drawableState = getDrawableState();
            mCheckboxDrawable.setState(drawableState);
            invalidate();
        }
    }

    @Override
    protected int[] onCreateDrawableState(int extraSpace) {
        // 調(diào)用 super 時(shí)參數(shù)加上狀態(tài)集的長(zhǎng)度
        final int[] drawableState = super.onCreateDrawableState(extraSpace + CHECKED_STATE_SET.length);
        if (isChecked()) {
            // 被 checked 狀態(tài)下巡语,在 super 返回的數(shù)組上追加自己的狀態(tài)集合
            mergeDrawableStates(drawableState, CHECKED_STATE_SET);
        }
        return drawableState;
    }

    @Override
    public void setChecked(boolean checked) {
        if (mIsChecked != checked) {
            mIsChecked = checked;
            // checked 狀態(tài)改變時(shí)調(diào)用 refreshDrawableState()
            refreshDrawableState();
        }
    }

    @Override
    public boolean isChecked() {
        return mIsChecked;
    }

    @Override
    public void toggle() {
        setChecked(!isChecked());
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 將 mCheckboxDrawable 畫到 Canvas 上
        if (mCheckboxDrawable != null) {
            int left = QMUIDisplayHelper.dpToPx(5);
            mCheckboxDrawable.setBounds(left, getPaddingTop(),
                    left + mCheckboxDrawable.getIntrinsicWidth(),
                    getPaddingTop() + mCheckboxDrawable.getIntrinsicHeight());
            mCheckboxDrawable.draw(canvas);
        }
    }

}

以下是 dialog_check_mark.xml 文件的內(nèi)容,設(shè)置了 normal 情況和 checked 情況下的不同表現(xiàn)羔杨。

<!-- dialog_check_mark.xml -->
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/checkbox_checked" android:state_checked="true" />
    <item android:drawable="@drawable/checkbox_normal" />
</selector>

到此捌臊,就完成了對(duì) LinearLayout 加上 Checked 狀態(tài)管理的功能杨蛋,在被調(diào)用 setCheck(boolean checked) 方法時(shí)兜材,Drawable 的表現(xiàn)會(huì)隨之改變。

總結(jié)

我們從 Button 被 pressed 時(shí)的源碼入手逞力,分析了 Button(View)和 Drawable 如何關(guān)聯(lián)起來曙寡,狀態(tài)改變時(shí)如何通知 Drawable 改變。接著分析了系統(tǒng)控件 CompoundButton 的狀態(tài)管理寇荧。最后自定義了一個(gè)包含 Checked 狀態(tài)的 LinearLayout举庶。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市揩抡,隨后出現(xiàn)的幾起案子户侥,更是在濱河造成了極大的恐慌,老刑警劉巖峦嗤,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蕊唐,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡烁设,警方通過查閱死者的電腦和手機(jī)替梨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來装黑,“玉大人副瀑,你說我怎么就攤上這事×堤罚” “怎么了糠睡?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)疚颊。 經(jīng)常有香客問我狈孔,道長(zhǎng)滞谢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任除抛,我火速辦了婚禮狮杨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘到忽。我一直安慰自己橄教,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布喘漏。 她就那樣靜靜地躺著护蝶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪翩迈。 梳的紋絲不亂的頭發(fā)上持灰,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音负饲,去河邊找鬼堤魁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛返十,可吹牛的內(nèi)容都是我干的妥泉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼洞坑,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼盲链!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起迟杂,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤刽沾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后排拷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體侧漓,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年攻泼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了火架。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡忙菠,死狀恐怖何鸡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情牛欢,我是刑警寧澤骡男,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站傍睹,受9級(jí)特大地震影響隔盛,放射性物質(zhì)發(fā)生泄漏犹菱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一吮炕、第九天 我趴在偏房一處隱蔽的房頂上張望腊脱。 院中可真熱鬧,春花似錦龙亲、人聲如沸陕凹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽杜耙。三九已至,卻和暖如春拂盯,著一層夾襖步出監(jiān)牢的瞬間佑女,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工谈竿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留团驱,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓榕订,卻偏偏與公主長(zhǎng)得像店茶,于是被迫代替她去往敵國(guó)和親蜕便。 傳聞我的和親對(duì)象是個(gè)殘疾皇子劫恒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

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