Android 淺談自定義View(1)

做為一名Android開發(fā)者,自定義View應(yīng)該是我們工作中繞不開的話題片部,畢竟系統(tǒng)提供的View有限霜定,有時很難滿足我們的需求望浩,此時就需要結(jié)合具體的場景來編寫自定義View,通過自定義View不僅可以實現(xiàn)特定的效果缘回,還可以簡化代碼典挑。自定義View的過程搔弄,也是我們自己造小輪子的過程,說不定在你的其它項目中就可以用到倒庵,對提高生產(chǎn)力還是大大有幫助的炫刷。

雖說自定義View是工作中繞不開的話題浑玛,但也是Android中最容易把開發(fā)者繞進(jìn)去的知識點,經(jīng)歷過了從入門到放棄的再重新入門的辛酸過程极阅,也該是用正確的姿勢學(xué)習(xí)自定義View了筋搏。

View是什么呢厕隧?......就是Android中所有控件的基類俄周,我們經(jīng)常聽到的ViewGroup也是View的一個子類峦朗。

直接上來就說如何自定義View未免有些空泛排龄,所以我們從View底層的工作原理開始聊起橄维,知其然也要知其所以然。App中我們所用到看到的一個個控件迄埃,都要經(jīng)過measure(測量)兑障、layout(布局)流译、draw(繪制)三大流程才會呈現(xiàn)在我們的眼前。其中measure用來測量View的大小叠赦,layout用來確定被測量后的View最終的位置革砸,draw則是將View渲染繪制出來算利。其實View的工作流程在我們生活中也能找到類似的原型,比如暂吉,我們要畫一個西瓜缎患,首先要確定西瓜的大小挤渔,接下來要確定畫在紙上的那個位置,最后才進(jìn)行繪制低散。

在分析View的工作流程前熔号,我們先要了解一個重要的知識點---MeasureSpec鸟整,MeasureSpec代表一個View的測量規(guī)格篮条,它是一個32位的int值,高兩位代表測量模式(SpecMode)赴恨,低30位代表在對應(yīng)測量模式下的大邪樗ā(SpecSize)钳垮。通過MeasureSpec的getMode()、getSize()方法可以得到對應(yīng)View寬\高的測量模式以及大小歧焦。
SpecMode肚医,即測量模式有以下三種:

  • EXACTLY:父容器已經(jīng)檢測出View所需要的精確大小肠套,此時View的大小就是SpecSize,對應(yīng)于LayoutParams中的具體數(shù)值和match_parent兩種類型舵稠。
  • AT_MOST:父容器指定了一個可用的大小即SpecSize哺徊,View的大小由其具體的實現(xiàn)決定乾闰,但不能大于SpecSize涯肩,對用于LayoutParams中的wrap_content巢钓。
  • UNSPECIFIED:父容器不對View大小做限制症汹,View需要多大就給多大贷腕,這種測量模式一般用于系統(tǒng)內(nèi)容泽裳,在我們自定義View中很少用到。

View的MeasureSpec是如何確定的呢胸囱?其實是由View自身的LayoutParams和父容器的MeasureSpec共同決定的旺矾。具體的細(xì)節(jié)我們來看源碼夺克,在ViewGroup中有一個measureChildWithMargins方法:

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 = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

在measureChildWithMargins()方法中铺纽,通過getChildMeasureSpec()方法得到了View寬\高對應(yīng)的測量模式childWidthMeasureSpec 狡门、childHeightMeasureSpec,接下來重點看getChildMeasureSpec()的實現(xiàn)細(xì)節(jié):

/**
  * @param spec 父View寬/高的測量規(guī)格
  * @param padding 父View在寬/高上已經(jīng)占用的空間大小
  * @param childDimension 子View的寬/高
  */
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);//得到父View在寬/高上的測量模式
        int specSize = MeasureSpec.getSize(spec);//得到父View在對應(yīng)測量模式下的寬/高

        int size = Math.max(0, specSize - padding);//計算子View在寬/高上可用的空間大小

        int resultSize = 0;
        int resultMode = 0;
        // 開始根據(jù)父View的測量規(guī)格以及子View的LayoutParams判斷子View的測量規(guī)格
        switch (specMode) {
        // 當(dāng)父View的測量模式為精確的大小時(包括具體的數(shù)值和match_parent兩種)
        case MeasureSpec.EXACTLY:
            // 如果子View的LayoutParams的寬/高是固定的數(shù)值,那么它的測量模式為MeasureSpec.EXACTLY叛复,
            // 大小為LayoutParams對應(yīng)的寬/高數(shù)值褐奥,這樣測量規(guī)格就確定了。
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } 
            // 如果子View的LayoutParams的寬/高為match_parent儿倒,那么子View的寬/高和父View尺寸相等夫否,即為size,
            // 因為父View的尺寸已經(jīng)確定汞幢,則子View的測量模式為MeasureSpec.EXACTLY溉瓶。
            else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            }
            //  如果子View的LayoutParams的寬/高為wrap_content堰酿,就是說子View想根據(jù)實現(xiàn)方式來自己確定自己的大小张足,
            // 這個當(dāng)然可以为牍, 但是寬/高不能超過父View的尺寸,最大為size抖韩,則對應(yīng)的測量模式為MeasureSpec.AT_MOST茂浮。
            else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // 當(dāng)父View的測量模式為最大模式時席揽,即父View目前也不知道自己的具體大小谓厘,但不能大于size
        case MeasureSpec.AT_MOST:
            // 既然子View的寬/高已經(jīng)確定,雖然父View的尺寸尚未確定也要優(yōu)先滿足子View属桦,
            // 則子View的寬/高為自身大小childDimension地啰,對應(yīng)的測量模式為MeasureSpec.EXACTLY讲逛。
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } 
            // 如果子View的LayoutParams的寬/高為match_parent盏混,雖說父View的大小為size,但具體的數(shù)值并不能確定止喷,
            // 所以子View寬/高不能超過父View的最大尺寸弹谁,即size,
            // 此時子View的寬高為最大為size沟于,則對應(yīng)的測量模式為MeasureSpec.AT_MOST
            else if (childDimension == LayoutParams.MATCH_PARENT) 
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } 
            // 如果子View的LayoutParams的寬/高為wrap_content旷太,即子View想自己來決定自己的大小供璧,這個當(dāng)然可以
            // 同理冻记,因為父View尺寸的不確定性冗栗,所以子View最終自我決定的尺寸不能大于size,
            // 對應(yīng)的測量模式為MeasureSpec.AT_MOST偶房。
            else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // 這種情況下棕洋,View的尺寸不受任何限制乒融,主要用于系統(tǒng)內(nèi)部赞季,在我們?nèi)粘i_發(fā)中幾乎用不到,就不分析了次绘。
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //根據(jù)最終子View的測量大小和測量模式得到相應(yīng)的測量規(guī)格邮偎。
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

所以View的測量規(guī)格是由自身的LayoutParams和父View的MeasureSpec共同決定的禾进,它們之間具體的組合關(guān)系如下圖:


此圖來自互聯(lián)網(wǎng)

既然搞清楚了MeasureSpec是怎么回事泻云,接下來具體來看一下View的工作流程measure宠纯、layout、draw娇哆。

1勃救、measure

首先測量過程要區(qū)分是View還是ViewGroup蒙秒,如果只是單純的View宵统,則是需要測量自身就好了马澈,如果是ViewGroup則需要先測量自身,再去遞歸測量所有的的子View勤婚。
1.1馒胆、當(dāng)自定義View是單純的View時
在View類中有如下方法:

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
}

View通過該方法來進(jìn)行大小測量祝迂,但是這是final方法器净,我們并不能重寫,但是在它內(nèi)部調(diào)用了View類的另外一個方法:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

好熟悉的感覺,這就是我們在自定義View的時候通常重寫的onMeasure()方法四啰。
其中setMeasuredDimension()方法宁玫,用來存儲測量后的View的寬/高,存儲之后柑晒,我們才可以調(diào)用View的getMeasuredWidth()欧瘪、getMeasuredHeight()的到對應(yīng)的測量寬/高。
重點看一下其中的getDefaultSize()方法:

/**
 * @param size View的默認(rèn)尺寸
 * @param measureSpec View的測量規(guī)格
 */
public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

可以發(fā)現(xiàn)匙赞,當(dāng)View的測量模式為MeasureSpec.AT_MOST佛掖、MeasureSpec.EXACTLY時涌庭,它最終的測量尺寸都為specSize 芥被,竟然相等。再結(jié)合上邊的MeasureSpec關(guān)系圖對比下坐榆,可以看到當(dāng)View最終的測量規(guī)格為MeasureSpec.AT_MOST時拴魄,其最終的尺寸為父View的尺寸。所以當(dāng)自定義View在布局中的使用wrap_content和match_parent時的效果是一樣的席镀,View都將占滿父View剩余的空間匹中,但這并不是我們愿意看到的,所以我們需要在View的布局寬/高為wrap_content時豪诲,重新計算View的測量尺寸顶捷,其它情況下直接使用系統(tǒng)的測量值即可,重新測量的模板代碼如下:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureChildren(widthMeasureSpec, heightMeasureSpec);

        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);

        //View的布局參數(shù)為wrap_content時屎篱,需要重新計算的View的測量寬/高
        int measureWidth = 0;
        int measureHeight = 0;

        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
            //確定measureWidth服赎、measureHeight
            setMeasuredDimension(measureWidth, measureHeight);
        } else if (widthSpecMode == MeasureSpec.AT_MOST) {
            //確定measureWidth
            setMeasuredDimension(measureWidth, heightSpecSize);
        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
            //確定measureHeight
            setMeasuredDimension(widthSpecSize, measureHeight);
        }
    }

至于如何確定measureWidth、measureHeight的值交播,就需要結(jié)合具體的業(yè)務(wù)需求了重虑。
1.2、當(dāng)自定義View是一個ViewGroup時
ViewGroup是一個抽象類秦士,繼承與View類缺厉,但它沒有重寫onMeasure()方法,所以需要ViewGroup的子類去實現(xiàn)onMeasure()方法以進(jìn)行具體測量伍宦。既然View類對onMeasure()方法方法做了統(tǒng)一的實現(xiàn)芽死,為什么ViewGroup類沒有呢?因為View類不牽扯子View的布局次洼,而ViewGroup中的子View可能有不同的布局情況关贵,實現(xiàn)細(xì)節(jié)也有差別,所以無法做統(tǒng)一的處理卖毁,只能交給子類根據(jù)業(yè)務(wù)需求來重寫揖曾。
在ViewGroup類里有一個measureChildren()方法:

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

該方法通過遍歷子View落萎,進(jìn)而調(diào)用measureChild()方法得到子View的測量規(guī)格:

protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

可以看到該方法和我們上邊分析的measureChildWithMargins()方法類似,都是通過getChildMeasureSpec()完成對子View規(guī)格的測量炭剪。所以一般情況下练链,我們自定義View如果繼承ViewGroup,則需要在重寫onMeasure()方法時首先進(jìn)行measureChildren()操作來確定子View的測量規(guī)格奴拦。

1.1中我們提到媒鼓,如果View在布局中寬/高為wrap_content時,需要重寫onMeasure()错妖,來重新計算View的測量寬/高绿鸣,同樣的道理,當(dāng)我們自定義的View是一個ViewGroup的話也需要重新計算ViewGroup的測量寬/高暂氯,當(dāng)然這里的計算一般要考慮子View的數(shù)量以及測量規(guī)格等情況潮模。

2、layout

layout的作用是來確定View本身的位置痴施,在View類中源碼如下:

public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    }

其中有這么一段:

boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

用來設(shè)置View四個邊的位置擎厢,即mLeft、mTop辣吃、mBottom动遭、mRight的值,這樣也就確定了View本身的位置齿尽。
接下來通過onLayout(changed, l, t, r, b);來確定子View在父View中的位置:

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }

是個空方法哦沽损,所以當(dāng)我們自定義ViewGroup時需要重寫onLayout()方法灯节,來確定子View的位置循头。View類中的layout方法有這么一段注釋:

Derived classes should not override this method.
Derived classes with children should override onLayout.
In that method, they should call layout on each of their children.

大概的意思是這樣的,View的派生類一般不需要重寫layout方法炎疆,應(yīng)該在其派生類中重寫onLayout()方法卡骂,并在onLayout()方法中調(diào)用layout()方法來確定子View的位置。

其實形入,這也符合我們平時自定義View時如果繼承ViewGroup時的情況全跨,我們一般都會重寫onLayout()方法,然后通過layout()方法確定子View的具體位置亿遂。
當(dāng)我們自定義的View如果繼承View類的話浓若,一般就不需要重寫onLayout()方法了哦,畢竟沒有子View么蛇数。

執(zhí)行完layout方法后挪钓,我們的View具體位置也就確定了,此時可以通過getWidth()耳舅、getHeight()方法得到View的最終寬/高:

public final int getWidth() {
        return mRight - mLeft;
    }

public final int getHeight() {
        return mBottom - mTop;
    }

還記得我們在分析measure過程時提到碌上,通過getMeasuredWidth()、getMeasuredHeight()可以得到View的測量寬/高,這兩組方法有什么區(qū)別呢馏予?其實一般情況下View的測量寬/高和最終的寬/高相等天梧,只是賦值的時間點不同,但在某些特殊的情況下就有差別了霞丧。拿getWidth()方法來說呢岗,它的返回值是mRight - mLeft,即View右邊位置和左邊位置的差值蛹尝,我們假設(shè)一個自定義ViewGroup中某個子View的四邊的位置分別為:l敷燎、t、r箩言、b硬贯,一般情況下我們會這樣確定子View的位置:

childView.layout(l, t, r, b);

這種情況View的測量寬度和最終寬度是相等的,但如果按照如下的寫法:

childView.layout(l, t, r + 100, b);

此時View的最終寬度會比測量寬度大100px的陨收。在measure過程中有一點需要注意饭豹,如果View的結(jié)構(gòu)比較復(fù)雜,則可能需要多次的進(jìn)行測量才能得到最終的測量結(jié)果务漩,這也會導(dǎo)致我們得到的測量尺寸不準(zhǔn)確拄衰。所以,所以要得到View最終的正確尺寸饵骨,應(yīng)該通過getWidth()或者getHeight()方法翘悉。

3、draw

經(jīng)歷了measure居触、layout的過程妖混,View的尺寸和位置已經(jīng)確定,接下來就差最后一步了轮洋,那就是draw制市,具體的繪制流程是什么樣的呢?查看一下View類中draw方法的源碼:

public void draw(Canvas canvas) {
        final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

        /*
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *
         *      1. Draw the background
         *      2. If necessary, save the canvas' layers to prepare for fading
         *      3. Draw view's content
         *      4. Draw children
         *      5. If necessary, draw the fading edges and restore layers
         *      6. Draw decorations (scrollbars for instance)
         */

        // Step 1, draw the background, if needed
        int saveCount;

        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

            // we're done...
            return;
        }
        //此處省略N行代碼......
}

繪制的流程很清晰弊予,基本按照如下幾個步驟:

  • 1祥楣、Draw the background 繪制View的背景 drawBackground(canvas)
  • 2、If necessary, save the canvas' layers to prepare for fading 保存畫布層汉柒,準(zhǔn)備漸變
  • 3误褪、Draw view's content 繪制內(nèi)容,也就是View自身 onDraw(canvas)
  • 4碾褂、Draw children 繪制子View dispatchDraw(canvas)
  • 5兽间、If necessary, draw the fading edges and restore layers 繪制漸變,保存圖層
  • 6斋扰、Draw decorations (scrollbars for instance) 繪制裝飾物 onDrawForeground(canvas)
    我們關(guān)心的是步驟3渡八、4的onDraw()和dispatchDraw()方法啃洋。
    先看onDraw()方法:
protected void onDraw(Canvas canvas) {
    }

是一個空方法,這也可以理解屎鳍,畢竟不同的View呈現(xiàn)的效果不同宏娄,所以需要子類重寫來實現(xiàn)具體的細(xì)節(jié)。當(dāng)我們自定義View繼承View類時逮壁,通常會重寫onDraw()方法孵坚,來繪制線條或各種形狀、圖案等窥淆。

再看一下View類的dispatchDraw()方法:

protected void dispatchDraw(Canvas canvas) {
    }

依然是空方法卖宠,需要子類去重寫,所以ViewGroup類中重寫了dispatchDraw()方法忧饭,遍歷所有的子View扛伍,其中有一行代碼是drawChild(canvas, transientChild, drawingTime);正是用來繪制子View的,再看下細(xì)節(jié):

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }

其中child.draw(canvas, this, drawingTime);是子View調(diào)用了View類的draw()方法词裤,則子View得到了最終的繪制刺洒。同樣的道理ViewGroup中的所有子View得到繪制。所以當(dāng)我們自定義的View是ViewGroup的子類時吼砂,必要時可以考慮重寫dispatchDraw()方法來繪制相應(yīng)的內(nèi)容逆航。

到這里我們View的工作流程就分析完畢了,掌握這些基本的原理只是第一步渔肩,但也是必須的因俐,繼續(xù)加油吧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末周偎,一起剝皮案震驚了整個濱河市抹剩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌栏饮,老刑警劉巖吧兔,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件磷仰,死亡現(xiàn)場離奇詭異袍嬉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)灶平,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門伺通,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人逢享,你說我怎么就攤上這事罐监。” “怎么了瞒爬?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵弓柱,是天一觀的道長沟堡。 經(jīng)常有香客問我,道長矢空,這世上最難降的妖魔是什么航罗? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮屁药,結(jié)果婚禮上粥血,老公的妹妹穿的比我還像新娘。我一直安慰自己酿箭,他們只是感情好复亏,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著缭嫡,像睡著了一般缔御。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上妇蛀,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天刹淌,我揣著相機(jī)與錄音,去河邊找鬼讥耗。 笑死有勾,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的古程。 我是一名探鬼主播蔼卡,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼挣磨!你這毒婦竟也來了雇逞?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤茁裙,失蹤者是張志新(化名)和其女友劉穎塘砸,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體晤锥,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡掉蔬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了矾瘾。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片女轿。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖壕翩,靈堂內(nèi)的尸體忽然破棺而出蛉迹,到底是詐尸還是另有隱情,我是刑警寧澤放妈,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布北救,位于F島的核電站荐操,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏珍策。R本人自食惡果不足惜淀零,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望膛壹。 院中可真熱鬧驾中,春花似錦、人聲如沸模聋。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽链方。三九已至持痰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間祟蚀,已是汗流浹背工窍。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留前酿,地道東北人患雏。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像罢维,于是被迫代替她去往敵國和親淹仑。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

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