探究RecyclerView的設(shè)計和實現(xiàn)

如果你已經(jīng)對RecyclerView熟練運用猾愿,那你是否想過RecyclerView是如何通過設(shè)置如下幾行代碼,就能實現(xiàn)應(yīng)變千變?nèi)f化的UI效果呢

  /*==============探究RecyclerView的設(shè)計和實現(xiàn) ==================*/
    RecyclerView recyclerView = new RecyclerView(this);
    //設(shè)置布局方式為線性布局
    recyclerView.setLayoutManager(new LinearLayoutManager(this));
    recyclerView.setHasFixedSize(true);
    //設(shè)置適配器
    recyclerView.setAdapter(new MyRcAapter());

某個控件或框架大家都說它設(shè)計的如何牛逼账阻,而你也一直在用蒂秘,那我們何嘗不試著深入其源碼的實現(xiàn),看源碼的過程淘太,也在學(xué)習(xí)他人優(yōu)秀的代碼姻僧。

下面我們就一起來一探究竟吧!

首先我們從調(diào)用setAdapter方法開始入手蒲牧,看看里面的具體實現(xiàn)是什么:

  public void setAdapter(Adapter adapter) {
    // bail out if layout is frozen
    setLayoutFrozen(false);
    setAdapterInternal(adapter, false, true);
    requestLayout();
}
 private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
        boolean removeAndRecycleViews) {
    //先移除原來的mAdapter撇贺,注銷觀察者,和從RecyclerView Detached冰抢。
    if (mAdapter != null) {
        mAdapter.unregisterAdapterDataObserver(mObserver);
        mAdapter.onDetachedFromRecyclerView(this);
    }
    //判斷是否清除原有的ViewHolder
    if (!compatibleWithPrevious || removeAndRecycleViews) {
        removeAndRecycleViews();
    }
    mAdapterHelper.reset();
    final Adapter oldAdapter = mAdapter;
    mAdapter = adapter;
    if (adapter != null) {
       //注冊觀察者
        adapter.registerAdapterDataObserver(mObserver);
        adapter.onAttachedToRecyclerView(this);
    }
    if (mLayout != null) {
        mLayout.onAdapterChanged(oldAdapter, mAdapter);
    }
    mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
    mState.mStructureChanged = true;
    //刷新試圖
    setDataSetChangedAfterLayout();
}

這里主要做了幾件事事情
1)判斷是否有Adapter,如果有則先移除原來的松嘶,注銷觀察者,和從RecyclerView Detached挎扰。
2)判斷是否清除原有的ViewHolder
3)注冊觀察者,刷新視圖

具體的實現(xiàn)是RecyclerView內(nèi)部的RecyclerViewDataObserver

private class RecyclerViewDataObserver extends AdapterDataObserver {
    RecyclerViewDataObserver() {
    }
    @Override
    public void onChanged() {
        assertNotInLayoutOrScroll(null);
        mState.mStructureChanged = true;

        setDataSetChangedAfterLayout();
        if (!mAdapterHelper.hasPendingUpdates()) {
            requestLayout();//重新繪制布局
        }
    }
  //....
}

這里的邏輯是翠订,當(dāng)數(shù)據(jù)發(fā)生變化的時候調(diào)用Adapter的notifyDataSetChanged方法之后最終會調(diào)用RecyclerViewDataObserver的onChanged函數(shù)巢音,然后在onChanged又回調(diào)用requestLayout函數(shù)進(jìn)行重新布局。

public abstract static class Adapter<VH extends ViewHolder> {
    //....
    public final void notifyDataSetChanged() {
        mObservable.notifyChanged();
    }
    //...
}  


static class AdapterDataObservable extends Observable<AdapterDataObserver> {
    //...
    public boolean hasObservers() {
        return !mObservers.isEmpty();
    }

    public void notifyChanged() {
        // since onChanged() is implemented by the app, it could do anything, including
        // removing itself from {@link mObservers} - and that could cause problems if
        // an iterator is used on the ArrayList {@link mObservers}.
        // to avoid such problems, just march thru the list in the reverse order.
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onChanged();
        }
    }
     //...
}

關(guān)于如何布局尽超,RecyclerView中把這個職責(zé)則交給了LayoutManager官撼,回到我們調(diào)用 setLayoutManager函數(shù),其內(nèi)部會調(diào)用requestLayout函數(shù)進(jìn)行繪制布局橙弱,然后就會調(diào)用RecyclerView 的onLayout函數(shù)歧寺,再調(diào)用dispatchLayout函數(shù)主儡,

public void setLayoutManager(LayoutManager layout) {
    if (layout == mLayout) {
        return;
    }
    //...
    mChildHelper.removeAllViewsUnfiltered();
    mLayout = layout;
    if (layout != null) {
       //...
        mLayout.setRecyclerView(this);
        if (mIsAttached) {
            mLayout.dispatchAttachedToWindow(this);
        }
    }
    mRecycler.updateViewCacheSize();
    requestLayout();
}


protected void onLayout(boolean changed, int l, int t, int r, int b) {
    this.eatRequestLayout();
    //調(diào)用分發(fā)方法
    this.dispatchLayout();
    this.resumeRequestLayout(false);
    this.mFirstLayoutComplete = true;
}

在dispatchLayout中會調(diào)用Adapter 中的getCount函數(shù)獲取到元素的個數(shù)竭鞍,通過調(diào)用LayoutManager的onLayoutChilden函數(shù),對所有子元素進(jìn)行布局邓夕。
這里有三個函數(shù)dispatchLayoutStep1 蛀缝、dispatchLayoutStep2 顷链、dispatchLayoutStep3都會執(zhí)行,做了簡單的邏輯處理屈梁,避免重復(fù)執(zhí)行某個方法嗤练,其中dispatchLayoutStep2是真正起布局作用的
(如果大家看過ListView的源碼,就會知道ListView是在添加到窗口時調(diào)用其父類absListView的onAttachedAToWindow函數(shù)在讶,然后獲取元素的個數(shù)煞抬,再執(zhí)行onLayoutChilden函數(shù),并在ListView實現(xiàn)這個函數(shù))

 void dispatchLayout() {
   //...
    mState.mIsMeasuring = false;
    if (mState.mLayoutStep == State.STEP_START) {//沒有執(zhí)行過布局
        dispatchLayoutStep1();
        mLayout.setExactMeasureSpecsFrom(this);
        
        dispatchLayoutStep2();
    } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
            || mLayout.getHeight() != getHeight()) {
        //  執(zhí)行過布局构哺,但是寬高發(fā)生改變
        
        mLayout.setExactMeasureSpecsFrom(this);
        //真正的視圖的布局 
        dispatchLayoutStep2();
    } else {// 執(zhí)行過布局革答,寬高沒有改變
        // always make sure we sync them (to ensure mode is exact)
        mLayout.setExactMeasureSpecsFrom(this);
    }
    dispatchLayoutStep3();
}

  private void dispatchLayoutStep2() {
   //...
    //獲取Item數(shù)量
    mState.mItemCount = mAdapter.getItemCount();
    mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;

    // Step 2: Run layout
    mState.mInPreLayout = false;
    //執(zhí)行布局,調(diào)用LayoutManager的onLayoutChilden函數(shù)
    mLayout.onLayoutChildren(mRecycler, mState);

    mState.mStructureChanged = false;
    mPendingSavedState = null;
    //...
}

那么在LayoutManager的onLayoutChildren方法中具體做了什么,則是根據(jù)布局模式來布局ItemView,例如從上到下布局曙强,還是從下到上布局残拐,在每一種布局方式中都會調(diào)用fill函數(shù),在fill函數(shù)中又回循環(huán)的layoutChunk函數(shù)進(jìn)行布局碟嘴,每次布局完之后判斷溪食,計算當(dāng)前屏幕剩余的空間和是否需還有Item View。

  public void onLayoutChildren(Recycler recycler, State state) {
        //...
        if (this.mAnchorInfo.mLayoutFromEnd) {//從下往上布局
        this.updateLayoutStateToFillStart(this.mAnchorInfo);
        this.mLayoutState.mExtra = extraForStart;
        this.fill(recycler, this.mLayoutState, state, false);
        startOffset1 = this.mLayoutState.mOffset;
        if (this.mLayoutState.mAvailable > 0) {
            extraForEnd += this.mLayoutState.mAvailable;
        }

        this.updateLayoutStateToFillEnd(this.mAnchorInfo);
        this.mLayoutState.mExtra = extraForEnd;
        this.mLayoutState.mCurrentPosition += this.mLayoutState.mItemDirection;
        this.fill(recycler, this.mLayoutState, state, false);//填充ItemView
        endOffset = this.mLayoutState.mOffset;
    } else {//從上到下部劇
        this.updateLayoutStateToFillEnd(this.mAnchorInfo);
        this.mLayoutState.mExtra = extraForEnd;
        this.fill(recycler, this.mLayoutState, state, false);
        endOffset = this.mLayoutState.mOffset;
        if (this.mLayoutState.mAvailable > 0) {
            extraForStart += this.mLayoutState.mAvailable;
        }
        this.updateLayoutStateToFillStart(this.mAnchorInfo);
        this.mLayoutState.mExtra = extraForStart;
        this.mLayoutState.mCurrentPosition += this.mLayoutState.mItemDirection;
        this.fill(recycler, this.mLayoutState, state, false);//填充ItemView
        startOffset1 = this.mLayoutState.mOffset;
    }
    //...
  }

  int fill(Recycler recycler, LayoutState layoutState, State state, boolean stopOnFocusable) {
    //存儲當(dāng)前可用空間
    int start = layoutState.mAvailable;
    if (layoutState.mScrollingOffset != -2147483648) {
        if (layoutState.mAvailable < 0) {
            layoutState.mScrollingOffset += layoutState.mAvailable;
        }
        this.recycleByLayoutState(recycler, layoutState);
    }
    //計算RecyclerView的可用布局寬或高
    int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
    LayoutChunkResult layoutChunkResult = new LayoutChunkResult();
    //迭代布局Item View
    while (remainingSpace > 0 && layoutState.hasMore(state)) {
        layoutChunkResult.resetInternal();
        //布局ItemView
        this.layoutChunk(recycler, state, layoutState, layoutChunkResult);
        if (layoutChunkResult.mFinished) {
            break;
        }
        //計算布局偏移量
        layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
        if (!layoutChunkResult.mIgnoreConsumed || this.mLayoutState.mScrapList != null || !state.isPreLayout()) {
            layoutState.mAvailable -= layoutChunkResult.mConsumed;
            //計算剩余的可用空間
            remainingSpace -= layoutChunkResult.mConsumed;
        }
        //...
    }

    return start - layoutState.mAvailable;
}

接下來我們看一下layoutChunk函數(shù)

1)首先是從layoutstate中獲取到ItemView的布局參數(shù)娜扇、尺寸信息
2)然后并且根據(jù)布局方式計算出Item View的上下左右坐標(biāo)
3)最后調(diào)用layoutDecoratedWithMargins函數(shù)實現(xiàn)布局错沃,調(diào)用Item View的layout函數(shù)將Item View布局到具體的位置。
這么一處理雀瓢,LayoutManager就把RecyclerView布局的職責(zé)分離了出來枢析,這也使得RecyclerView更靈活。

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LinearLayoutManager.LayoutState layoutState, LayoutChunkResult result) {
    // 1致燥,獲取Item View
    View view = layoutState.next(recycler);
    if (view == null) {
        if (DEBUG && layoutState.mScrapList == null) {
            throw new RuntimeException("received null view when unexpected");
        }
        // if we are laying out views in scrap, this may return null which means there
        // is
        // no more items to layout.
        result.mFinished = true;
        return;
    }
    //2 獲取 Item View的布局參數(shù)
    LayoutParams params = (LayoutParams) view.getLayoutParams();
    if (layoutState.mScrapList == null) {
        if (mShouldReverseLayout == (layoutState.mLayoutDirection == android.support.v7.widget.LinearLayoutManager.LayoutState.LAYOUT_START)) {
            addView(view);
        } else {
            addView(view, 0);
        }
    } else {
        if (mShouldReverseLayout == (layoutState.mLayoutDirection == android.support.v7.widget.LinearLayoutManager.LayoutState.LAYOUT_START)) {
            addDisappearingView(view);
        } else {
            addDisappearingView(view, 0);
        }
    }
    //3 測量Item View的布局參數(shù)
    measureChildWithMargins(view, 0, 0);
    //4 計算該ItemView 需要的寬度或高度
    result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
    //ItemView的上下左右的坐標(biāo)
    int left, top, right, bottom;
    //5 豎直方向 計算上下左右的坐標(biāo)
    if (mOrientation == VERTICAL) {
        if (isLayoutRTL()) {
            right = getWidth() - getPaddingRight();
            left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
        } else {
            left = getPaddingLeft();
            right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
        }
        if (layoutState.mLayoutDirection == android.support.v7.widget.LinearLayoutManager.LayoutState.LAYOUT_START) {
            bottom = layoutState.mOffset;
            top = layoutState.mOffset - result.mConsumed;
        } else {
            top = layoutState.mOffset;
            bottom = layoutState.mOffset + result.mConsumed;
        }
    } else {////水平方向 計算上下左右的坐標(biāo)
        top = getPaddingTop();
        bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);

        if (layoutState.mLayoutDirection == android.support.v7.widget.LinearLayoutManager.LayoutState.LAYOUT_START) {
            right = layoutState.mOffset;
            left = layoutState.mOffset - result.mConsumed;
        } else {
            left = layoutState.mOffset;
            right = layoutState.mOffset + result.mConsumed;
        }
    }
    //6 布局Item View
    layoutDecoratedWithMargins(view, left, top, right, bottom);
    if (DEBUG) {
        Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:" + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:" + (right - params.rightMargin) + ", b:"
                + (bottom - params.bottomMargin));
    }
    // Consume the available space if the view is not removed OR changed
    if (params.isItemRemoved() || params.isItemChanged()) {
        result.mIgnoreConsumed = true;
    }
    result.mFocusable = view.hasFocusable();
}

 public void layoutDecoratedWithMargins(View child, int left, int top, int right,
            int bottom) {
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        final Rect insets = lp.mDecorInsets;
        child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin,
                right - insets.right - lp.rightMargin,
                bottom - insets.bottom - lp.bottomMargin);
    }

看到這里,我們已經(jīng)知道如何布局排截,還有一個特別重要的點沒講嫌蚤,就是如何獲取創(chuàng)建布局和添加數(shù)據(jù)以及它的緩存機(jī)制

那我們需要通過LayoutState對象next這個重要的函數(shù)入手

   View next(RecyclerView.Recycler recycler) {
        //...
        //調(diào)用Recycler的getViewForPosition
        final View view = recycler.getViewForPosition(mCurrentPosition);
        mCurrentPosition += mItemDirection;
        return view;
    }

從上面可以看到辐益,實際上調(diào)用Recycler的getViewForPosition函數(shù),再通過tryGetViewHolderForPositionByDeadline函數(shù)返回ViewHolde獲取其中的Item View

public final class Recycler {
   final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
   ArrayList<ViewHolder> mChangedScrap = null;
   final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
  //...
   public View getViewForPosition(int position) {
       //調(diào)用Recycler的getViewForPosition
       return getViewForPosition(position, false);
   }
   View getViewForPosition(int position, boolean dryRun) {
        //返回ViewHolder的ItemView
        return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
    }
    @Nullable ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
        //...
        boolean fromScrapOrHiddenOrCache = false;
        ViewHolder holder = null;
        // 0)如果有緩存, 從mChangedScrap中獲取ViewHolder緩存
        if (mState.isPreLayout()) {
            holder = getChangedScrapViewForPosition(position);
            fromScrapOrHiddenOrCache = holder != null;
        }
        // 1) 從 mAttachedScrap 中獲取ViewHolder 緩存
        if (holder == null) {
            holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
            //...
            
        }
        if (holder == null) {
            final int offsetPosition = mAdapterHelper.findPositionOffset(position);
            //...
            final int type = mAdapter.getItemViewType(offsetPosition);
            // 2)從其他ViewHolder緩存中檢測是否有緩存
            if (mAdapter.hasStableIds()) {
                holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
                if (holder != null) {
                    // update position
                    holder.mPosition = offsetPosition;
                    fromScrapOrHiddenOrCache = true;
                }
            }
            //...
            // 3)沒有ViewHolder,則需要創(chuàng)建ViewHolder,這里就會調(diào)用createViewHolder函數(shù)
            if (holder == null) {
                long start = getNanoTime();
                if (deadlineNs != FOREVER_NS && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
                    // abort - we have a deadline we can't meet
                    return null;
                }
                holder = mAdapter.createViewHolder(this, type); 
                //...
            }
        }
        //...
        boolean bound = false;
        if (mState.isPreLayout() && holder.isBound()) {
            // do not update unless we absolutely have to.
            holder.mPreLayoutPosition = position;
        } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
            if (DEBUG && holder.isRemoved()) {
                throw new IllegalStateException("Removed holder should be bound and it should" + " come here only in pre-layout. Holder: " + holder + exceptionLabel());
            }
            final int offsetPosition = mAdapterHelper.findPositionOffset(position);
            // 4)綁定數(shù)據(jù),這里會調(diào)用Adapter的onBindViewHolder
            bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
        }
        /*===============設(shè)置Item View的LayoutParams=================*/
        final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
        final RecyclerView.LayoutParams rvLayoutParams;
        if (lp == null) {
            rvLayoutParams = (RecyclerView.LayoutParams) generateDefaultLayoutParams();
            holder.itemView.setLayoutParams(rvLayoutParams);
        } else if (!checkLayoutParams(lp)) {
            rvLayoutParams = (RecyclerView.LayoutParams) generateLayoutParams(lp);
            holder.itemView.setLayoutParams(rvLayoutParams);
        } else {
            rvLayoutParams = (RecyclerView.LayoutParams) lp;
        }
        rvLayoutParams.mViewHolder = holder;
        rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
        //返回ViewHolder
        return holder;
    }

}

在RecyclerView的內(nèi)部類Recycler 中有mAttachedScrap 脱吱、mChangedScrap智政、mCachedViews幾個ViewHolder列表對象,它們用于緩沖ViewHolder箱蝠。

深入tryGetViewHolderForPositionByDeadline函數(shù)
1)首先從幾個ViewHolder緩存對象獲取對應(yīng)位置的ViewHolder
2)如果沒有緩存則調(diào)用RecyclerView.Adapter.createViewHolder函數(shù)創(chuàng)建ViewHolder

/**
 *createViewHolder函數(shù)實際是調(diào)用了onCreateViewHolder函數(shù)創(chuàng)建了ViewHolder
 * 這就是為什么在繼承RecyclerView.Adapter是需要復(fù)寫  onCreateViewHolder函數(shù)续捂,
 *并返回ViewHolder的原因
 */
public final VH createViewHolder(ViewGroup parent, int viewType) {
        TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
        final VH holder = onCreateViewHolder(parent, viewType);
        holder.mItemViewType = viewType;
        TraceCompat.endSection();
        return holder;
    }
//創(chuàng)建ViewHolder,子類需復(fù)寫
public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);

3)在調(diào)用完RecyclerView.Adapter的onCreateViewHolder后宦搬,則執(zhí)行tryBindViewHolderByDeadline牙瓢,調(diào)用Adapter的onBindViewHolder

 //bindViewHolder進(jìn)行數(shù)據(jù)綁定,執(zhí)行完onBindViewHolder函數(shù)之后數(shù)據(jù)就綁定到Item View上
 public final void bindViewHolder(VH holder, int position) {
        //...
        onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
        //...
  }
 public void onBindViewHolder(VH holder, int position, List<Object> payloads) {
        onBindViewHolder(holder, position);
   }
 //綁定數(shù)據(jù)间校,子類需復(fù)寫矾克,
 public abstract void onBindViewHolder(VH holder, int position);

看到這里,我相信大家對RecyclerView整體的設(shè)計有了一定的了解憔足,
這個時候你再使用RecyclerView胁附,繼承Adapter并實現(xiàn)onCreateViewHolder 、onBindViewHolder滓彰、getItemCount這個三個方法控妻,就知道為什么了,而不再是簡單的使用

總結(jié)

最后還是要把這篇博客總結(jié)一下

RecyclerView 通過Adapter 和觀察者模式進(jìn)行數(shù)據(jù)綁定揭绑,在Adapter中封裝了ViewHolder的創(chuàng)建與綁定邏輯弓候,使用起來更加方便,而其緩存單元不同于ListView,而是用ViewHolder代替了View,代替的之前的繁瑣的步驟洗做。并且把布局的工作交給了LayoutManager,在LayoutManager的onLayoutChilden中對ItemView 進(jìn)行布局等一系列操作弓叛,這樣一來也大大的增加了布局的靈活性。把布局責(zé)任獨立出來也更符合設(shè)計模式中的單一職責(zé)原則诚纸,減少代碼的耦合撰筷,使得RecyclerView的布局更具擴(kuò)張性。


風(fēng)后面是風(fēng)畦徘,天空上面是天空毕籽,而你的生活可以與眾不同

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市井辆,隨后出現(xiàn)的幾起案子关筒,更是在濱河造成了極大的恐慌,老刑警劉巖杯缺,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蒸播,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)袍榆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進(jìn)店門胀屿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人包雀,你說我怎么就攤上這事宿崭。” “怎么了才写?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵葡兑,是天一觀的道長。 經(jīng)常有香客問我赞草,道長讹堤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任房资,我火速辦了婚禮蜕劝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘轰异。我一直安慰自己岖沛,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布搭独。 她就那樣靜靜地躺著婴削,像睡著了一般。 火紅的嫁衣襯著肌膚如雪牙肝。 梳的紋絲不亂的頭發(fā)上唉俗,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天,我揣著相機(jī)與錄音配椭,去河邊找鬼虫溜。 笑死,一個胖子當(dāng)著我的面吹牛股缸,可吹牛的內(nèi)容都是我干的衡楞。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼敦姻,長吁一口氣:“原來是場噩夢啊……” “哼瘾境!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起镰惦,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤迷守,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后旺入,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體兑凿,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡凯力,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了礼华。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沮协。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖卓嫂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情聘殖,我是刑警寧澤晨雳,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站奸腺,受9級特大地震影響餐禁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜突照,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一帮非、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧讹蘑,春花似錦末盔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至版仔,卻和暖如春游盲,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蛮粮。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工益缎, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人然想。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓莺奔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親又沾。 傳聞我的和親對象是個殘疾皇子弊仪,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,092評論 2 355

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