Glide遇坑記---Glide與CircleImageView (二)

Glide遇坑記之決戰(zhàn)

上述三種解決方法都避免了與真正BOSS---TransitionDrawable的正面交鋒。TransitionDrawable繼承自LayerDrawable岛心。LayerDrawable是一個特殊的Drawable瓦宜,它內(nèi)部保持著一個Drawable數(shù)組降宅,其中每一個Drawable都是視圖中的一層擂橘。通過多個Drawable的疊加、漸變二驰、旋轉(zhuǎn)等組合顯示出與單一Drawable不同的效果每强。

@Override
public boolean animate(T current, ViewAdapter adapter) {
    Drawable previous = adapter.getCurrentDrawable();
    if (previous != null) {
        TransitionDrawable transitionDrawable = new TransitionDrawable(new Drawable[] { previous, current });
        transitionDrawable.setCrossFadeEnabled(true);
        transitionDrawable.startTransition(duration);
        adapter.setDrawable(transitionDrawable);
        return true;
    } else {
        defaultAnimation.animate(current, adapter);
        return false;
    }
}
public void startTransition(int durationMillis) {
    mFrom = 0;
    mTo = 255;
    mAlpha = 0;
    mDuration = mOriginalDuration = durationMillis;
    mReverse = false;
    mTransitionState = TRANSITION_STARTING;
    invalidateSelf();
}

DrawableCrossFadeViewAnimation.animate()方法先是獲取TransitionDrawable對象實例,接著調(diào)用setCrossFadeEnabled()startTransition()方法對TransitionDrawable的成員變量進行設置娇掏。并在startTransition()方法的最后一行呕寝,調(diào)用invalidateSelf()方法嘗試進行視圖重繪。

public void invalidateSelf() {
    final Callback callback = getCallback();
    if (callback != null) {
        callback.invalidateDrawable(this);
    }
}

invalidateSelf()方法先是獲取TransitionDrawable注冊的Callback實例婴梧,如果無則返回null下梢。通過Callback接口,一個Drawable實例可以回調(diào)其客戶端來執(zhí)行動畫塞蹭。為了動畫可以被執(zhí)行孽江,所有的客戶端都應該支持這個Callback接口。 View類正是實現(xiàn)了Callback接口番电,所以callback.invalidateDrawable()其實調(diào)用的就是View中的invalidateDrawable()方法岗屏。 但此時TransitionDrawable實例未注冊任何Callback接口,invalidateSelf()方法直接返回漱办。


緊接著animation()中執(zhí)行adapter.setDrawable()方法这刷,方法內(nèi)部通過view.setImageDrawable(drawable)來更新Drawable

public void setImageDrawable(@Nullable Drawable drawable) {
    if (mDrawable != drawable) {
        mResource = 0;
        mUri = null;

        final int oldWidth = mDrawableWidth;
        final int oldHeight = mDrawableHeight;

        updateDrawable(drawable);

        if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
            requestLayout();
        }
        invalidate();
    }
}

view.setImageDrawable(drawable)方法內(nèi)部先是通過updateDrawable(drawable)更新成員變量mDrawable娩井,同時修改其屬性暇屋。 接著調(diào)用invalidate()方法正式開始視圖重繪。

private void updateDrawable(Drawable d) {
    if (mDrawable != null) {
        sameDrawable = mDrawable == d;
        mDrawable.setCallback(null);
        unscheduleDrawable(mDrawable);
        if (!sCompatDrawableVisibilityDispatch && !sameDrawable && isAttachedToWindow()) {
                mDrawable.setVisible(false, false);
        }
    }

    mDrawable = d;

    if (d != null) {
        d.setCallback(this);
        d.setLayoutDirection(getLayoutDirection());
        d.setLevel(mLevel);
        configureBounds();
    } else {
        mDrawableWidth = mDrawableHeight = -1;
    }
}

updateDrawble()首先對mDrawable做了一些檢查撞牢,并將與ImageView關(guān)聯(lián)的Drawable實例mDrawableCallback置空率碾。接著把傳進來的Drawable對象賦給成員變量mDrawable。如果參數(shù)d不為空的話屋彪,那么設置dCallbackImageView實例所宰。通過d.getIntrinsicWidth()獲取drawablewidth賦值全局變量mDrawableWidth

Android視圖重繪機制

View的源碼中會有數(shù)個invalidate()方法的重載和一個invalidateDrawable()方法畜挥,最終都是通過invalidateInternal()方法來實現(xiàn)視圖重制仔粥。

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) {
    if (mGhostView != null) {
        mGhostView.invalidate(true);
        return;
    }

    if (skipInvalidate()) {
        return;
    }

    if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
                || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
                || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED|| (fullInvalidate && isOpaque() != mLastIsOpaque)) {
        if (fullInvalidate) {
            mLastIsOpaque = isOpaque();
            mPrivateFlags &= ~PFLAG_DRAWN;
        }

        mPrivateFlags |= PFLAG_DIRTY;

        if (invalidateCache) {
            mPrivateFlags |= PFLAG_INVALIDATED;
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
        }

        final AttachInfo ai = mAttachInfo;
        final ViewParent p = mParent;
        if (p != null && ai != null && l < r && t < b) {
            final Rect damage = ai.mTmpInvalRect;
            damage.set(l, t, r, b);
            p.invalidateChild(this, damage);
        }
    }
}

在這個方法中首先會調(diào)用skipInvalidate()方法來判斷當前 View是否需要重繪,判斷的邏輯也比較簡單,如果View是不可見的且沒有執(zhí)行任何動畫躯泰,就認為不需要重繪了谭羔。之后會進行透明度的判斷,并給View添加一些標記位麦向,然后調(diào)用ViewParent的invalidateChild()方法瘟裸,這里的ViewParent其實就是當前視圖的父視圖,因此會調(diào)用到ViewGroupinvalidateChild()方法中诵竭。省略若干循環(huán)調(diào)用话告。最終經(jīng)過多次輾轉(zhuǎn)的調(diào)用,最終會走到視圖繪制的入口方法performTraversals()中卵慰,然后重新執(zhí)行繪制流程沙郭。

invalidate()方法雖然最終會調(diào)用到performTraversals()方法中,但這時measurelayout流程是不會重新執(zhí)行的裳朋,因為視圖沒有強制重新測量的標志位病线,而且大小也沒有發(fā)生過變化,所以這時只有draw流程可以得到執(zhí)行鲤嫡。而如果你希望視圖的繪制流程可以完完整整地重新走一遍送挑,就不能使用invalidate()方法,而應該調(diào)用requestLayout()泛范。

繪制流程始于ViewRootImplperformDraw()方法让虐,里面又調(diào)用了ViewRootImpldraw()方法紊撕,經(jīng)過一系列調(diào)用罢荡,然后實例化Canvas對象,鎖定該canvas的區(qū)域并進行一系列的屬性賦值对扶,最后調(diào)用了mView.draw(canvas)方法区赵,這個mView就是DecorView,也就是說從DecorView開始繪制浪南。由于ViewGroup沒有重寫draw方法笼才,因此所有的View都是通過調(diào)用Viewdraw()方法實現(xiàn)繪制。

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;
    }
    ...
}

draw過程比較復雜络凿,但是邏輯十分清晰骡送,一般是遵循下面幾個步驟:

  • 繪制背景 -- drawBackground()
  • 繪制內(nèi)容 -- onDraw()
  • 繪制孩子 -- dispatchDraw()
  • 繪制裝飾 -- onDrawScrollbars()

View中的onDraw()方法是一個空實現(xiàn),不同的View有著不同的內(nèi)容絮记,這需要我們自己去實現(xiàn)摔踱,即在自定義View中重寫該方法來實現(xiàn)。

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    if (mDrawable == null) {
        return; // couldn't resolve the URI
    }

    if (mDrawableWidth == 0 || mDrawableHeight == 0) {
        return;     // nothing to draw (empty bounds)
    }

    if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
        mDrawable.draw(canvas);
    } else {
        final int saveCount = canvas.getSaveCount();
        canvas.save();

        canvas.translate(mPaddingLeft, mPaddingTop);

        if (mDrawMatrix != null) {
            canvas.concat(mDrawMatrix);
        }
        mDrawable.draw(canvas);
        canvas.restoreToCount(saveCount);
    }
}

ImageViewonDraw()方法首先對mDrawable進行檢查怨愤,mDrawable是否為空派敷、寬高是否具有意義。在設置了mDrawMatrix的一系列方法后,onDraw()在繪制前會根據(jù)mDrawMatrix設置的值對圖片資源進行相應的變換操作篮愉。無論Drawable縮放與否在滿足mDrawable != null && mDrawableWidth != 0 && mDrawableHeight != 0的繪制條件下腐芍,最終都是通過mDrawable.draw(canvas)方法對mDrawable進行繪制。這里的mDrawableTransitionDrawable對象實例试躏。

public void draw(Canvas canvas) {
    boolean done = true;

    switch (mTransitionState) {
        case TRANSITION_STARTING:
            mStartTimeMillis = SystemClock.uptimeMillis();
            done = false;
            mTransitionState = TRANSITION_RUNNING;
            break;

        case TRANSITION_RUNNING:
            if (mStartTimeMillis >= 0) {
                float normalized = (float)(SystemClock.uptimeMillis() - mStartTimeMillis) / mDuration;
                done = normalized >= 1.0f;
                normalized = Math.min(normalized, 1.0f);
                mAlpha = (int) (mFrom  + (mTo - mFrom) * normalized);                                                
            }
            break;
    }

    final int alpha = mAlpha;
    final boolean crossFade = mCrossFade;
    final ChildDrawable[] array = mLayerState.mChildren;

    if (done) {
        if (!crossFade || alpha == 0) {
            array[0].mDrawable.draw(canvas);
        }
        if (alpha == 0xFF) {
            array[1].mDrawable.draw(canvas);
        }
        return;
    }

    Drawable d;
    d = array[0].mDrawable;
    if (crossFade) {
        d.setAlpha(255 - alpha);
    }
    d.draw(canvas);
    if (crossFade) {
        d.setAlpha(0xFF);
    }

    if (alpha > 0) {
        d = array[1].mDrawable;
        d.setAlpha(alpha);
        d.draw(canvas);
        d.setAlpha(0xFF);
    }

    if (!done) {
        invalidateSelf();
    }
}

調(diào)用adapter.setDrawable(transitionDrawable)猪勇,進行視圖重繪的流程中,實質(zhì)還是調(diào)用TransitionDrawable.draw()方法完成自身繪制颠蕴。TransitionDrawable.draw()方法的邏輯也是簡單明了埠对,d.setAlpha(alpha)d.draw(canvas),在不同階段為兩張Drawable設置對應透明度以此實現(xiàn)兩個Drawable之間的淡入淡出效果裁替。Drawable.draw()本身是個抽象方法项玛,繪制具體邏輯由其子類實現(xiàn)。這里的drawable分別為GlideBitmapDrawableBitmapDrawable對象實例弱判,具體為什么襟沮,在了解Drawable源碼之后你就清楚了。(GlideBitmapDrawable則隱藏在Glide網(wǎng)絡請求部分的源碼之中)

Drawable源碼分析

Drawable是一個用于處理各種可繪制資源的抽象類昌腰。我們使用Drawable最常見的情況就是將獲取到的資源繪制到屏幕上开伏。

Drawable實例可能存在以下多種形式

  • Bitmap:最簡單的Drawable形式,PNG或者JPEG圖片遭商。
  • .9圖PNG的一個擴展固灵,可以支持設置其如何填充內(nèi)容,如何被拉伸劫流。
  • Shape:包含簡單的繪制指令巫玻,用于替代Bitmap,某些情況下對大小調(diào)整有更好表現(xiàn)祠汇。
  • Layers:一個復合的Drawable仍秤,按照層級進行繪制,單個Drawable實例繪制于其下層Drawable實例集合之上可很。
  • States:一個復合的Drawable诗力,根據(jù)它的state選擇一個Drawable集合。
  • Levels:一個復合的Drawable我抠,根據(jù)它的level選擇一個Drawable集合苇本。
  • Scale:一個復合的Drawable和單個Drawable實例構(gòu)成,它的總體尺寸由它的當前level值決定菜拓。

Drawable常見使用步驟

  • 通過Resource獲取Drawable實例
  • 將獲取的Drawable實例當做背景設置給View或者作為ImageViewsrc進行顯示:

getResources().getDrawable()方法經(jīng)過多次輾轉(zhuǎn)的調(diào)用最終會通過ResourcesImpl實例的drawableFromBitmap()方法加載資源圖片瓣窄。

private static Drawable drawableFromBitmap(Resources res, Bitmap bm, byte[] np, Rect pad, Rect layoutBounds, String srcName) {

    if (np != null) {
        return new NinePatchDrawable(res, bm, np, pad, layoutBounds, srcName);
    }

    return new BitmapDrawable(res, bm);
}

drawableFromBitmap()方法對于.9圖返回1個NinePatchDrawable實例,普通圖片返回1個BitmapDrawable實例尘惧。


public boolean animate(T current, ViewAdapter adapter) {
    Drawable previous = adapter.getCurrentDrawable();
    if (previous != null) {
        TransitionDrawable transitionDrawable = new TransitionDrawable(new Drawable[] { previous, current });
        return true;
    } 
} 

TransitionDrawable在實例化時傳入的previous康栈、current分別來自adapter.getCurrentDrawable()方法和animate()方法傳入的current參數(shù)。adapter.getCurrentDrawable()方法內(nèi)部通過view.getDrawable()來獲取與ImageView關(guān)聯(lián)的Drawable實例。這個Drawable則來自getPlaceholderDrawable()方法啥么。

private Drawable getPlaceholderDrawable() {
        if (placeholderDrawable == null && placeholderResourceId > 0) {
            placeholderDrawable = context.getResources().getDrawable(placeholderResourceId);
        }
        return placeholderDrawable;
}

getPlaceholderDrawable()方法登舞,通過Resource實例加載占位符placeHolder圖片資源。沒錯悬荣,getPlaceholderDrawable()方法就像??Drawable常見使用步驟菠秒,最終會通過ResourcesImpl.drawableFromBitmap()加載資源圖片,返回1個BitmapDrawable實例氯迂。通過target.onLoadStarted(getPlaceholderDrawable())將獲取的Drawable實例當做背景設置給ImageView践叠。

LayerDrawable&Callback

Android LayerDrawable and Drawable.Callback

文章中之所以提到Callback是因為為ImageView設置占位符時ImageView的Callback指向當前的Drawable。
當使用占位符作為子圖層創(chuàng)建LayerDrawable實例時

    LayerDrawable(@NonNull Drawable[] layers, @Nullable LayerState state) {
        this(state, null);

        final int length = layers.length;
        final ChildDrawable[] r = new ChildDrawable[length];
        for (int i = 0; i < length; i++) {
            r[i] = new ChildDrawable(mLayerState.mDensity);
            r[i].mDrawable = layers[i];
            layers[i].setCallback(this);
            mLayerState.mChildrenChangingConfigurations |= layers[i].getChangingConfigurations();
        }
    }
BitmapDrawable&GlideBitmapDrawable

Drawable.draw()本身是個抽象方法嚼蚀,繪制具體邏輯由其子類實現(xiàn)禁灼。TransitionDrawable.draw() 方法最終還是通過d.setAlpha(alpha)d.draw(canvas),在不同階段為Drawable設置對應透明度以此實現(xiàn)兩個Drawable之間的淡入淡出效果轿曙。

public void setAlpha(int alpha) {
    final int oldAlpha = mBitmapState.mPaint.getAlpha();
    if (alpha != oldAlpha) {
        mBitmapState.mPaint.setAlpha(alpha);
        invalidateSelf();
    }
}

LayerDrawable中弄捕,每層視圖(Drawable)都會將LayerDrawable注冊為它的Drawable.Callback。從而允許Drawable在需要重繪自己的時候能夠告知LayerDrawable重繪它导帝。LayerDrawable最終調(diào)用到View中的invalidateDrawable()方法守谓,之后就會按照我們前面分析的流程執(zhí)行重繪邏輯,以此改變視圖背景您单。

public void draw(Canvas canvas) {
    final Bitmap bitmap = mBitmapState.mBitmap;
    if (bitmap == null) {
        return;
    }

    final BitmapState state = mBitmapState;
    final Paint paint = state.mPaint;

    final Shader shader = paint.getShader();
    if (shader == null) {
        if (needMirroring) {
            canvas.save();
            canvas.translate(mDstRect.right - mDstRect.left, 0);
            canvas.scale(-1.0f, 1.0f);
        }

        canvas.drawBitmap(bitmap, null, mDstRect, paint);

    } else {
        updateShaderMatrix(bitmap, paint, shader, needMirroring);
        canvas.drawRect(mDstRect, paint);
    }
}

BitmapDrawable.draw()方法先是對畫筆Paint和畫布Canvas進行相應設置斋荞,接著將Drawable實例中的Bitmap繪制到View實例關(guān)聯(lián)的畫布上。

GlideBitmapDrawable繪制邏輯與BitmapDrawable基本相同虐秦,便不再贅述平酿。


至此,我們已經(jīng)了解了TransitionDrawable實現(xiàn)漸變的原理羡疗,及與相關(guān)知識(Android視圖重繪機制染服、Drawable及其實現(xiàn)類源碼)。是不是覺得頭昏腦脹叨恨,不知所云~~~ 不不不,應該是雖然學到了很多知識挖垛,并沒有發(fā)現(xiàn)問題的存在~~~

還記得嗎痒钝,這個坑只會在使用CircleImageView的情況下出現(xiàn),對于ImageView痢毒,即便是在使用占位符和默認動畫的情況下Glide仍可以正常工作送矩。那CircleImageViewImageView兩者之間又是存在何種差異導致了問題的出現(xiàn)?

protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    if (mDrawable == null) {
        return; // couldn't resolve the URI
    }

    if (mDrawableWidth == 0 || mDrawableHeight == 0) {
        return;     // nothing to draw (empty bounds)
    }

    if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
        mDrawable.draw(canvas);
    } else {
        final int saveCount = canvas.getSaveCount();
        canvas.save();

        canvas.translate(mPaddingLeft, mPaddingTop);

        if (mDrawMatrix != null) {
            canvas.concat(mDrawMatrix);
        }
        mDrawable.draw(canvas);
        canvas.restoreToCount(saveCount);
    }
}

ImageView.onDraw()方法通過mDrawable.draw()方法對mDrawable進行繪制并實現(xiàn)淡入淡出效果哪替。??有對ImageView.onDraw()方法更為詳細的分析過程栋荸,沒錯就是Android視圖重繪機制哪里~

protected void onDraw(Canvas canvas) {
    if (mDisableCircularTransformation) {
        onDraw(canvas);
        return;
    }

    if (mBitmap == null) {
        return;
    }

    if (mFillColor != Color.TRANSPARENT) {
        canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mFillPaint);
    }
    canvas.drawCircle(mDrawableRect.centerX(), mDrawableRect.centerY(), mDrawableRadius, mBitmapPaint);
    if (mBorderWidth > 0) {
        canvas.drawCircle(mBorderRect.centerX(), mBorderRect.centerY(), mBorderRadius, mBorderPaint);
    }
}

通過上面的分析,我們終于可以得出只顯示占位符placeHolder的原因。Glide在網(wǎng)絡中獲取圖片并經(jīng)過解碼晌块、邏輯操作(包括對圖片的壓縮爱沟,甚至還有旋轉(zhuǎn)、圓角等邏輯處理)之后匆背,會最終回調(diào)到GlideDrawableImageViewTarget.onResourceReady()方法來設置ImageView呼伸。一番處理之后,會調(diào)用父類ImageViewTargetonResourceReady()方法钝尸。

onResourceReady()方法通過對glideAnimation進行判空括享,對glideAnimation.animate()返回值進行分析,來決定是否執(zhí)行setResource()方法珍促。根據(jù)animationFactory引用工廠對象的不同铃辖,onResourceReady()方法可能傳入DrawableCrossFadeViewAnimationNoAnimation對象實例。

DrawableCrossFadeViewAnimation.animate()方法內(nèi)部先是獲取先前通過placeHolder()設置占位符占位符previous猪叙。如previous不為空澳叉,則通過TransitionDrawable設置動畫并添加圖片至ImageView。否則通過defaultAnimation展示圖片沐悦。

CircleImageView.onDraw()方法僅是通過canvas.drawCircle()方法將Drawable實例中的 Bitmap經(jīng)過裁剪之后繪制到CircleImageView實例關(guān)聯(lián)的畫布上成洗。沒錯,與ImageView.onDraw()方法相比缺少了對mDrawable.draw()方法的調(diào)用藏否,而mDrawable.draw()方法則會不斷調(diào)用invalidateSelf()方法獲取其關(guān)聯(lián)的View進行重復的視圖重繪操作瓶殃,通過不斷調(diào)用TransitionDrawable.draw()方法,設置兩個Drawable透明度從而實現(xiàn)漸入漸出效果的實現(xiàn)副签。

除上述我個人的結(jié)論之外遥椿,網(wǎng)上也有一些分析的文章說:根本原因就是你的placeholder圖片和你要加載顯示的圖片寬高比不一樣,而Android的TransitionDrawable無法很好地處理不同寬高比的過渡問題淆储,這的確是個Bug冠场,是Android的也是Glide的。 文章實在是太長了??本砰,對此就先挖個坑碴裙,回頭再填~

至此Glide遇坑記之Glide與CircleImageView的分析就告一段落~~
以上分析均是個人見解。如果錯誤或疏忽請及時指出点额,O(∩_∩)O謝謝舔株!

參考文章

Glide v4快速高效的Android圖片加載庫

Android圖片加載框架最全解析,從源碼的角度理解Glide的執(zhí)行流程

詳談高大上的圖片加載框架Glide -源碼篇

Android Drawable完全解析

Android LayerDrawable and Drawable.Callback

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末还棱,一起剝皮案震驚了整個濱河市载慈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌珍手,老刑警劉巖办铡,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辞做,死亡現(xiàn)場離奇詭異,居然都是意外死亡寡具,警方通過查閱死者的電腦和手機秤茅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來晒杈,“玉大人嫂伞,你說我怎么就攤上這事≌辏” “怎么了帖努?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長粪般。 經(jīng)常有香客問我拼余,道長,這世上最難降的妖魔是什么亩歹? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任匙监,我火速辦了婚禮,結(jié)果婚禮上小作,老公的妹妹穿的比我還像新娘亭姥。我一直安慰自己,他們只是感情好顾稀,可當我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布达罗。 她就那樣靜靜地躺著,像睡著了一般静秆。 火紅的嫁衣襯著肌膚如雪粮揉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天抚笔,我揣著相機與錄音扶认,去河邊找鬼。 笑死殊橙,一個胖子當著我的面吹牛辐宾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蛀柴,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼螃概,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鸽疾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤训貌,失蹤者是張志新(化名)和其女友劉穎制肮,沒想到半個月后冒窍,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡豺鼻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年综液,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片儒飒。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡谬莹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出桩了,到底是詐尸還是另有隱情附帽,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布井誉,位于F島的核電站蕉扮,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏颗圣。R本人自食惡果不足惜喳钟,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望在岂。 院中可真熱鬧奔则,春花似錦、人聲如沸蔽午。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽祠丝。三九已至疾呻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間写半,已是汗流浹背岸蜗。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留叠蝇,地道東北人璃岳。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像悔捶,于是被迫代替她去往敵國和親铃慷。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,976評論 2 355

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