自定義控件知識儲備-LayoutParams的那些事

在上一篇文章里,我總結(jié)了一下自定義控件需要了解的基礎(chǔ)知識:View的繪制流程——《自定義控件知識儲備-View的繪制流程》尉辑。其中帆精,在View的測量流程里,View的測量寬高是由父控件的MeasureSpec和View自身的LayoutParams共同決定的隧魄。MeasureSpec是什么卓练,上一篇文章里已經(jīng)說得很清楚了(啥,沒看過购啄?快去路克路克襟企,(??????)??)。而LayoutParams呢狮含?是時候在這里做個了斷了顽悼。

LayoutParams是什么?

LayoutParams辉川,顧名思義表蝙,就是Layout Parameters :布局參數(shù)。
很久很久以前乓旗,我就知道LayoutParams了府蛇,并且?guī)缀跆焯煲娒妗D菚r候在布局文件XML里屿愚,寫的最多的肯定是android:layout_width = "match_parent"之類的了汇跨。比如:

常見布局文件的栗子
<TextView
    style="@style/text_flag_01"
    android:layout_width="match_parent"
    android:layout_height="100dp"
    android:layout_marginLeft="10dp"
    android:layout_gravity="center"
    android:gravity="center"
    android:text="英明神武蘑菇君"
    android:textColor="@color/white"
    android:background="@color/colorAccent"/>

我們都知道layout_widthlayout_height這兩個屬性是為View指定寬度的。不過妆距,當(dāng)時年輕的我心里一直有個疑問:為什么要加上"layout_"前綴修飾呢穷遂?其它的描述屬性,如textColorbackground娱据,都很正常膀胶凇!講道理,應(yīng)該用widthheight描述寬高才對凹纱抒寂?

后來呀,我遇到了LayoutParams掠剑,它說layout_width是它的屬性而非View的屈芜,并且不只是針對這一個,而是所有以"layout_"開頭的屬性都與它有關(guān)朴译!所以井佑,它的東西當(dāng)然要打上自己的標(biāo)識"layout_"。(呵呵眠寿,囂張個啥躬翁,到頭來你自己還不是屬于View的一部分( ̄┰ ̄*))

既然layout_width這樣的屬性是LayoutParams定義的,那為何會出現(xiàn)在描述View的xml屬性里呢盯拱?View和LayoutParams之間有什么恩怨糾纏呢姆另?

不吹不黑,咱們來看看官方文檔是怎么說的:

  1. LayoutParams are used by views to tell their parents how they want to be laid out.
    -- LayoutParams是View用來告訴它的父控件如何放置自己的坟乾。
  1. The base LayoutParams class just describes how big the view wants to be for both width and height.
    -- 基類LayoutParams(也就是ViewGroup.LayoutParams)僅僅描述了這個View想要的寬度和高度。
  1. There are subclasses of LayoutParams for different subclasses of ViewGroup.
    -- 不同ViewGroup的繼承類對應(yīng)著不同的ViewGroup.LayoutParams的子類蝶防。

看著我妙到巔峰的翻譯甚侣,想必大家都看懂了<( ̄▽ ̄)/〖溲В看不懂殷费?那我再來畫蛇添足稍微解釋一下:

  1. 上面我們提到過,描述View直接用它們自己的屬性就好了低葫,如textColorbackground等等详羡,為什么還需要引入LayoutParams呢?在我看來嘿悬,textColorbackground這樣的屬性都是只與TextView自身有關(guān)的实柠,無論這個TextView處于什么環(huán)境,這些屬性都是不變的善涨。而layout_widthlayout_marginLeft這樣的屬性是與它的父控件息息相關(guān)的窒盐,是父控件通過LayoutParams提供這些"layout_"屬性給孩子們用的;是父控件根據(jù)孩子們的要求(LayoutParams)來決定怎么測量钢拧,怎么安放孩子們的蟹漓;是父控件......(寫不下去了,我都快被父控件感動了源内,不得不再感慨一句葡粒,當(dāng)父母的都不容易啊(′⌒`)) )。所以,View的LayoutParams離開了父控件嗽交,就沒有意義了卿嘲。

  2. 基類LayoutParams是ViewGroup類里的一個靜態(tài)內(nèi)部類(看吧,這就證明了LayoutParams是與父控件直接相關(guān)的)轮纫,它的功能很簡單腔寡,只提供了widthheight兩個屬性,對應(yīng)于xml里的layout_widthlayout_height掌唾。所以放前,對任意系統(tǒng)提供的容器控件或者是自定義的ViewGroup,其chid view總是能寫layout_widthlayout_height屬性的糯彬。

  3. 自從有了ViewGroup.LayoutParams后凭语,我們就可以在自定義ViewGroup時,根據(jù)自己的邏輯實現(xiàn)自己的LayoutParams撩扒,為孩子們提供更多的布局屬性似扔。不用說,系統(tǒng)里提供給我們的容器控件辣么多搓谆,肯定也有很多LayoutParams的子類啦炒辉。let us see see:

ViewGroup.LayoutParams的截圖

果然,我們看到了很多ViewGroup.LayoutParams的子類泉手,里面大部分我們應(yīng)該都比較熟悉黔寇。如果你覺得和它們不熟,那就是你一廂情愿啦斩萌,你早就“偷偷摸摸”的用過它們好多次了→_→

ViewGroup.MarginLayoutParams

我們首先來看看ViewGroup.MarginLayoutParams缝裤,看名字我們也能猜到,它是用來提供margin屬性滴颊郎。margin屬性也是我們在布局時經(jīng)常用到的憋飞。看看這個類里面的屬性:

public static class MarginLayoutParams extends ViewGroup.LayoutParams {
      
        public int leftMargin;

        public int topMargin;
       
        public int rightMargin;

        public int bottomMargin;

        private int startMargin = DEFAULT_MARGIN_RELATIVE;

        private int endMargin = DEFAULT_MARGIN_RELATIVE;
        
        ...
    }    

前面4個屬性是我們以前在布局文件里常用的姆吭,而后面的startMarginendMargin是為了支持RTL設(shè)計出來代替leftMarginrightMargin的榛做。

一般情況下,View開始部分就是左邊猾编,但是有的語言目前為止還是按照從右往左的順序來書寫的瘤睹,例如阿拉伯語。在Android 4.2系統(tǒng)之后答倡,Google在Android中引入了RTL布局轰传,更好的支持了從右往左文字布局的顯示。為了更好的兼容RTL布局瘪撇,google推薦使用MarginStart和MarginEnd來替代MarginLeft和MarginRight获茬,這樣應(yīng)用可以在正常的屏幕和從右往左顯示文字的屏幕上都保持一致的用戶體驗港庄。

我們除了在布局文件里用layout_marginLeftlayout_marginTop這樣的屬性來指定單個方向的間距以外,還會用layout_margin來表示四個方向的統(tǒng)一間距恕曲。我們來通過源碼看看這一過程:

 public MarginLayoutParams(Context c, AttributeSet attrs) {
            super();

            TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout);
            setBaseAttributes(a,
                    R.styleable.ViewGroup_MarginLayout_layout_width,
                    R.styleable.ViewGroup_MarginLayout_layout_height);

            int margin = a.getDimensionPixelSize(
                    com.android.internal.R.styleable.ViewGroup_MarginLayout_layout_margin, -1);
            if (margin >= 0) {
                leftMargin = margin;
                topMargin = margin;
                rightMargin= margin;
                bottomMargin = margin;
            } else {
                leftMargin = a.getDimensionPixelSize(
                        R.styleable.ViewGroup_MarginLayout_layout_marginLeft,
                        UNDEFINED_MARGIN);
                if (leftMargin == UNDEFINED_MARGIN) {
                    mMarginFlags |= LEFT_MARGIN_UNDEFINED_MASK;
                    leftMargin = DEFAULT_MARGIN_RESOLVED;
                }
                ... 
            }
            ...
    }        
               

在這個MarginLayoutParams的構(gòu)造函數(shù)里鹏氧,將獲取到的xml布局文件里的屬性轉(zhuǎn)化成了leftMagrinrightMagrin等值。先獲取xml里的layout_margin值佩谣,如果未設(shè)置把还,則再去獲取layout_marginLeftlayout_marginRight等值。所以從這里可以得出一個小結(jié)論:

在xml布局里茸俭,layout_margin屬性的值會覆蓋layout_marginLeftlayout_marginRight等屬性的值吊履。

以前我還很傻很天真的猜測,屬性寫在后面调鬓,就會覆蓋前面的屬性艇炎。雖然經(jīng)過實踐,也能發(fā)現(xiàn)上述的結(jié)論腾窝,但是自己了解了背后的原理缀踪,再去看看源碼實現(xiàn),自然就有更深刻的印象了虹脯。<( ̄ˇ ̄)/

揭開隱藏的LayoutParams

在上文中提到驴娃,我們初學(xué)Android的時候經(jīng)常在“偷偷摸摸”的使用著LayoutParams,而自己卻還

一臉懵逼
循集。
因為我們常用它的方式是在XML布局文件里托慨,使用容器控件的LayoutParams里的各種屬性來給孩子們布局。這種方式直觀方便暇榴,直接就能在預(yù)覽界面看到效果,但是同時布局也被我們寫死了蕉世,無法動態(tài)改變蔼紧。想要動態(tài)變化,那還是得不怕麻煩狠轻,使用代碼來寫奸例。(實際上,我們寫的XML布局最終也是通過代碼來解析滴)

好的向楼,那還是讓我們通過源碼來揭開隱藏在ViewGroup里的LayoutParams吧查吊!<( ̄︶ ̄)↗[GO!]......等會,我們該從哪里開始看源碼呢湖蜕?我認為有句名言說的在理:

脫離場景談源碼逻卖,都是在耍流氓 ——英明神武蘑菇君

上文提到,LayoutParams其實是父控件提供給child view的昭抒,好讓child view選擇如何測量和放置自己评也。所以肯定在child view添加到父控件的那一刻炼杖,child view就應(yīng)該有LayoutParams了。我們來看看幾個常見的添加View的方式:


LinearLayout parent = (LinearLayout) findViewById(R.id.parent);
// 1.直接添加一個“裸”的TextView盗迟,不主動指定LayoutParams
TextView textView = new TextView(this);
textView.setText("紅色蘑菇君");
textView.setTextColor(Color.RED);
parent.addView(textView);

// 2.先手動給TextView設(shè)定好LayoutParams坤邪,再添加
textView = new TextView(this);
textView.setText("綠色蘑菇君");
textView.setTextColor(Color.GREEN);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(300,300);
textView.setLayoutParams(lp);
parent.addView(textView);

// 3.在添加的時候傳遞一個創(chuàng)建好的LayoutParams
textView = new TextView(this);
textView.setText("藍色蘑菇君");
textView.setTextColor(Color.BLUE);
LinearLayout.LayoutParams lp2 = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,300);
parent.addView(textView, lp2);

上面代碼展示的是3種往LinearLayout里動態(tài)添加TextView的方式,其中都涉及到了addView這個方法罚缕。我們來看看addView的幾個重載方法:

//這3個方法都來自于基類ViewGroup

 public void addView(View child) {
        addView(child, -1);
    }
    
 /*
  * @param child the child view to add
  * @param index the position at which to add the child    
  /
public void addView(View child, int index) {
        if (child == null) {
            throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
        }
        LayoutParams params = child.getLayoutParams();
        if (params == null) {
            params = generateDefaultLayoutParams();
            if (params == null) {
                throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
            }
        }
        addView(child, index, params);
    }
    
public void addView(View child, LayoutParams params) {
        addView(child, -1, params);
    }    
    

可以看出addView(View child)是調(diào)用了addView(View child, int index)方法的艇纺,在這個里面對child的LayoutParams做了判斷,如果為null的話邮弹,則調(diào)用了generateDefaultLayoutParams方法為child生成一個默認的LayoutParams黔衡。這也合情合理,畢竟現(xiàn)在這個社會呀肠鲫,像蘑菇君我這么懶的人太多员帮,你要是不給個默認的選項,那別說友誼的小船了导饲,就算泰坦尼克捞高,那也說翻就翻!<( ̄︶ ̄)>......好的渣锦,那讓我們看看LinearLayout為我們這群懶人生成了怎樣的默認LayoutParams:

@Override
protected LayoutParams generateDefaultLayoutParams() {
        if (mOrientation == HORIZONTAL) {
            return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        } else if (mOrientation == VERTICAL) {
            return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        }
        return null;
}

顯然硝岗,LinearLayout是重寫了基類ViewGroup里的generateDefaultLayoutParams方法的:如果布局是水平方向,則孩子們的寬高都是WRAP_CONTENT袋毙,而如果是垂直方向型檀,高仍然是WRAP_CONTENT,但寬卻變成了MATCH_PARENT听盖。所以胀溺,這一點大家得注意,因為很有可能因為我們的懶皆看,導(dǎo)致布局效果和我們理想中的不一樣仓坞。因此呢,第1種添加View的方式是不推薦滴腰吟,像第2或第3種方式无埃,添加的時候指定了LayoutParams,不僅明確毛雇,而且易修改嫉称。(果然還是勤勞致富呀...)

上面三個重載的addView方法最終都調(diào)用了addView(View child, int index, LayoutParams params)這個參數(shù)最多的方法:

public void addView(View child, int index, LayoutParams params) {
        ...
        requestLayout();
        invalidate(true);
        addViewInner(child, index, params, false);
    }
    
private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {
        ...

        if (!checkLayoutParams(params)) {
            params = generateLayoutParams(params);
        }

        if (preventRequestLayout) {
            child.mLayoutParams = params;
        } else {
            child.setLayoutParams(params);
        }
        
        ...
    }    
        

addView方法又調(diào)用了方法addViewInner,在這個私有方法里灵疮,又干了哪些偷偷摸摸的事呢织阅?接著來看看:

//這兩個方法都重寫了基類ViewGroup里的方法
// Override to allow type-checking of LayoutParams.
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
    return p instanceof LinearLayout.LayoutParams;
}
    
@Override
  protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
    return new LayoutParams(p);
}

checkLayoutParams方法的作用是檢查傳遞進來的LayoutParams是不是LinearLayout的LayoutParam。如果不是呢震捣?再通過generateLayoutParams方法根據(jù)你傳遞的LayoutParams的屬性構(gòu)造一個LinearLayout的LayoutParams蒲稳。不得不再次感慨父容器控件的不容易:我們懶得設(shè)置child view的LayoutParams氮趋,甚至是設(shè)置了錯誤的LayoutParams,父控件都在竭盡所能的糾正我們的錯誤江耀,只為了給孩子提供一個舒適的環(huán)境剩胁。(╥╯^╰╥)

不過呀,雖然父控件可以在添加View時幫我們糾正部分錯誤祥国,但我們在其他情況下錯誤的修改child View的LayoutParams昵观,那父控件也愛莫能助了。比如下面這種情況:

LinearLayout parent = (LinearLayout) findViewById(R.id.parent);
textView = new TextView(this);
textView.setText("此處有BUG");
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(200,200);
parent.addView(textView, lp);

textView.setLayoutParams(new ViewGroup.LayoutParams(100,100));

會直接報ClassCastException

java.lang.ClassCastException: android.view.ViewGroup$LayoutParams cannot be cast to android.widget.LinearLayout$LayoutParams

上面這種異常熟悉么舌稀?反正我是相當(dāng)熟悉的〒▽〒......原因就是上面代碼里的textView是LinearLayout的孩子啊犬,而我們調(diào)用textView的setLayoutParams方法強行給它設(shè)置了一個ViewGroup的LayoutParams,所有在LinearLayout重新進行繪制流程的時候壁查,在onMeasure方法里觉至,會進行強制類型轉(zhuǎn)換操作:

LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();

所以App斯巴達了。也許你會說睡腿,我才不會這么傻语御,我知道textView的父控件是LinearLayout了,我肯定會給它設(shè)置相應(yīng)的LayoutParams的席怪!這是當(dāng)然的啦应闯,在這種明確的情況下,我們當(dāng)然不會這么傻挂捻。但是碉纺,很不幸的是,有很多時候我們并不能一眼就看出來一個View的LayoutParams是什么類型的LayoutParams刻撒,這就需要動用你的智慧去分析分析啦骨田,希望這篇文章能給你一些幫助。?(^?^●)?

自定義LayoutParams

在本文的開頭就提到過:每個容器控件幾乎都會有自己的LayoutParams實現(xiàn)声怔,像LinearLayout盛撑、FrameLayout和RelativeLayout等等。所以捧搞,我們在自定義ViewGroup時,幾乎都要自定義相應(yīng)的LayoutParams狮荔。這一節(jié)呢胎撇,就是對如何自定義LayoutParams進行一個總結(jié)。

我以一個簡單的流布局FlowLayout為例殖氏,流布局的簡單定義如下:

FlowLayout:添加到此容器的控件自左往右依次排列晚树,如果當(dāng)前行的寬度不足以容納下一個控件,就會將此控件放置到下一行雅采。

假設(shè)這個FlowLayout可以給它的孩子們提供一個gravity屬性爵憎,效果就是讓孩子能在某一行的垂直方向上選擇三個位置:top(處于頂部)慨亲、center(居中)爹橱、bottom(處于底部)锡移。咦?這個效果是不是和LinearLayout提供給孩子的layout_gravity屬性很像涵紊?那好愚铡,我們來參考一下LinearLayout里的LayoutParams源碼:

public static class LayoutParams extends ViewGroup.MarginLayoutParams {
   
        public float weight;

        public int gravity = -1;
   
        public LayoutParams(Context c, AttributeSet attrs) {
        
            super(c, attrs);
            TypedArray a =
            c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout);
            weight = a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0);
            gravity = a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1);

            a.recycle();
        }
     
     
        public LayoutParams(ViewGroup.LayoutParams p) {
            super(p);
        }

       
        public LayoutParams(ViewGroup.MarginLayoutParams source) {
            super(source);
        }  
        
         public LayoutParams(LayoutParams source) {
            super(source);

            this.weight = source.weight;
            this.gravity = source.gravity;
        }
    }    
    

首先蛉签,LinearLayout里的靜態(tài)內(nèi)部類LayoutParams是繼承ViewGroup.MarginLayoutParams的,所以它的孩子們都可以用margin屬性沥寥。事實上碍舍,絕大部分容器控件都是直接繼承ViewGroup.MarginLayoutParams而非ViewGroup.LayoutParams。所以我們的FlowLayout也直接繼承ViewGroup.MarginLayoutParams邑雅。

其次片橡,LinearLayout支持兩個屬性weightgravity,這兩個屬性在xml對應(yīng)的就是layout_weightlayout_gravity淮野。在它的構(gòu)造函數(shù)LayoutParams(Context c, AttributeSet attrs)里捧书,將獲取到的xml布局文件里的屬性轉(zhuǎn)化成了weightgravity的值。不過com.android.internal.R.styleable.LinearLayout_Layout這個東西是什么鬼录煤?其實這是系統(tǒng)在xml屬性文件里配置的declare-styleable鳄厌,好讓系統(tǒng)知道LinearLayout能為它的孩子們提供哪些屬性支持。我們在布局的時候IDE也會給出這些快捷提示妈踊。而對于自定義的FlowLayout來說了嚎,模仿LinearLayout的寫法,可以在attrs.xml文件里這么寫:

<declare-styleable name="FlowLayout_Layout">
        <attr name="android:layout_gravity"/>
    </declare-styleable>

而剩下的幾個構(gòu)造方法起的作用就是從傳遞的LayoutParams參數(shù)里克隆屬性了廊营。

依葫蘆畫瓢歪泳,F(xiàn)lowLayout的LayoutParams如下:

public static class LayoutParams extends ViewGroup.MarginLayoutParams {
   
        public int gravity = -1;
   
        public LayoutParams(Context c, AttributeSet attrs) {
        
            super(c, attrs);
            TypedArray a =
            c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout);
            weight = a.getFloat(R.styleable.FlowLayout_Layout, 0);
            gravity = a.getInt(R.styleable.FlowLayout_Layout_android_layout_gravity, -1);

            a.recycle();
        }
        
         public LayoutParams(ViewGroup.LayoutParams p) {
            super(p);
        }

       
        public LayoutParams(ViewGroup.MarginLayoutParams source) {
            super(source);
        }  
        
        
         public LayoutParams(LayoutParams source) {
            super(source);
            this.gravity = source.gravity;
        }
     
    }    

看起來還是挺簡單的吧?好露筒,那我們這篇文章到此結(jié)束......等一等呐伞!好像忘記了點什么......

思考一會兒

如果對上面分析ViewGroup的addView方法的流程還有印象,可能你會注意ViewGroup里的這幾個方法:

 public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return p;
    }
    
protected LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }

protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return  p != null;
    }

為了能在添加child view時給它設(shè)置正確的LayoutParams慎式,我們還需要重寫上面幾個方法(還問為啥要重寫伶氢?快翻到前面再see see)。同樣的瘪吏,我們還是先來看看LinearLayout是怎么處理的吧:

 @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LinearLayout.LayoutParams(getContext(), attrs);
    }

   
    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        if (mOrientation == HORIZONTAL) {
            return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        } else if (mOrientation == VERTICAL) {
            return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
        }
        return null;
    }

    @Override
    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p);
    }


    // Override to allow type-checking of LayoutParams.
    @Override
    protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return p instanceof LinearLayout.LayoutParams;
    }

那FlowLayout該如何重寫上面的幾個方法呢癣防?相信聰明的你已經(jīng)知道了。(??????)??

總結(jié)

這一篇文章從自定義控件的角度掌眠,并結(jié)合源碼和表情包生動形象的談了談我所理解的LayoutParams蕾盯。(生動,形象蓝丙?真不要臉...(ˉ﹃ˉ))级遭。不得不說望拖,結(jié)合源碼來學(xué)習(xí)某個知識點,的確是能起到事半功倍的作用挫鸽。蘑菇君初來乍到说敏,文章里如有錯誤和疏漏之處,歡迎指正和補充掠兄。

預(yù)告

下一篇文章打算記錄一個簡單的自定義ViewGroup:流布局FlowLayout的實現(xiàn)過程像云,將自定義控件知識儲備-View的繪制流程里的知識點和本篇文章的LayoutParams結(jié)合起來。

PS:寫博客的初始階段果然是有些艱辛蚂夕,腦海里想寫的很多迅诬,而真到了要以文字表達出來時,卻有一種“愛你在心口難開”的尷尬婿牍。不過侈贷,感覺到艱難也就意味著自己在走上坡路,堅持下去等脂,希望能給自己和大家?guī)砀嗟膸椭?/p>

我是蘑菇君俏蛮,我為自己帶鹽

加油,加油
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末上遥,一起剝皮案震驚了整個濱河市搏屑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌粉楚,老刑警劉巖辣恋,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異模软,居然都是意外死亡伟骨,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門燃异,熙熙樓的掌柜王于貴愁眉苦臉地迎上來携狭,“玉大人,你說我怎么就攤上這事回俐」渫龋” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵仅颇,是天一觀的道長单默。 經(jīng)常有香客問我,道長灵莲,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任殴俱,我火速辦了婚禮政冻,結(jié)果婚禮上枚抵,老公的妹妹穿的比我還像新娘。我一直安慰自己明场,他們只是感情好汽摹,可當(dāng)我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著苦锨,像睡著了一般逼泣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上舟舒,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天拉庶,我揣著相機與錄音,去河邊找鬼秃励。 笑死氏仗,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的夺鲜。 我是一名探鬼主播皆尔,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼币励!你這毒婦竟也來了慷蠕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤食呻,失蹤者是張志新(化名)和其女友劉穎流炕,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體搁进,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡浪感,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了饼问。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片影兽。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖莱革,靈堂內(nèi)的尸體忽然破棺而出峻堰,到底是詐尸還是另有隱情,我是刑警寧澤盅视,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布捐名,位于F島的核電站,受9級特大地震影響闹击,放射性物質(zhì)發(fā)生泄漏镶蹋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望贺归。 院中可真熱鬧淆两,春花似錦、人聲如沸拂酣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽婶熬。三九已至剑勾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間赵颅,已是汗流浹背虽另。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留性含,地道東北人洲赵。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像商蕴,于是被迫代替她去往敵國和親叠萍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,722評論 2 345

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