RecyclerView-為Adapter添加頭部是尖、尾部及事件響應(yīng)(第2篇)

效果圖

添加 頭部 尾部 事件

簡述

這篇文章主要描述如何擴(kuò)展原生的Adapter,讓其支持添加header泥耀、footer饺汹,以及item的事件處理,這將會是一篇很長的文章痰催。

首先的考慮是我需要一個(gè)支持添加任意數(shù)量的header和footer兜辞,并且互相不干擾的Adapter迎瞧。主要表現(xiàn)在客戶端的調(diào)用上數(shù)據(jù)是數(shù)據(jù),header是header逸吵,footer是footer凶硅,而且他們的順序關(guān)系也是很顯然的。另外再將header和footer進(jìn)行一下擴(kuò)展扫皱,將給客戶端使用的和庫內(nèi)部本身需要擴(kuò)展的header足绅、footer進(jìn)行區(qū)分,這樣就產(chǎn)生了下面這個(gè)順序關(guān)系(已經(jīng)在第一篇進(jìn)行了闡述):

  {
      item_sys_header - item_header - 
      item_data - 
      item_footer - item_sys_footer
  }

這個(gè)順序以及每個(gè)域的數(shù)量都是很關(guān)鍵韩脑。
有了上面這個(gè)順序的關(guān)系圖氢妈,這時(shí)候數(shù)據(jù)結(jié)構(gòu)也就定下來了:

protected List<View> headerViews = new ArrayList<>();
protected List<View> footerViews = new ArrayList<>();
//邏輯上設(shè)計(jì)為系統(tǒng)頭部也可以是多個(gè) ,但是實(shí)現(xiàn)上系統(tǒng)頭部實(shí)現(xiàn)為僅有一個(gè)
private List<View> sysHeaderViews = new ArrayList<>();
private View sysFooterView;
//真實(shí)的數(shù)據(jù)部分
protected List<T> datas;

這里需要對上面的數(shù)據(jù)結(jié)構(gòu)補(bǔ)充說明一下:
數(shù)據(jù)結(jié)構(gòu)的訪問權(quán)限是為了擴(kuò)展用段多;系統(tǒng)header有計(jì)劃進(jìn)行擴(kuò)展(聲明中可以看到)首量,但是目前來說只支持一個(gè)(將會在稍后的實(shí)現(xiàn)中看到);系統(tǒng)footer沒有打算擴(kuò)展(聲明中可以看到)衩匣,所以只支持一個(gè)蕾总。
既然有了上面這個(gè)關(guān)系圖,那么接下來的思路就很明顯了琅捏,我們按照這個(gè)順序進(jìn)行生百。

1. 系統(tǒng)頭部

這部分對應(yīng)到item_sys_header部分,起初的考慮是用于內(nèi)部擴(kuò)展用柄延。

數(shù)據(jù)操作

首先目測需要這么幾個(gè)方法對系統(tǒng)頭部的操作

 int getSysHeaderViewCount();
 void setSysHeaderView(View view);
 void removeSysHeaderView(View view);

這里的setSysHeaderView()方法為什么用一個(gè)view參數(shù)而不是一個(gè)layoutId呢蚀浆,我的考慮是如果用了id,那么內(nèi)部就要去創(chuàng)建這個(gè)view以及viewHolder搜吧,這樣客戶端需要通過viewHolder去綁定數(shù)據(jù)市俊,這樣的操作不是我希望的。移除的操作removeSysHeaderView()與設(shè)置操作相對應(yīng)滤奈。

前面已經(jīng)說了摆昧,系統(tǒng)頭部在設(shè)計(jì)的時(shí)候是支持任意數(shù)量的,并且系統(tǒng)頭部的是在最頂層的蜒程。所以調(diào)用設(shè)置方法時(shí)將會清除所有的系統(tǒng)header(這么做的目的是可能在某個(gè)版本中將會進(jìn)行多個(gè)的擴(kuò)充)绅你,然后才進(jìn)行添加(這時(shí)候一定只有一個(gè),索引就是0)昭躺,接著是局部刷新(這些是Adapter的常規(guī)操作)忌锯。下面就是相關(guān)代碼:

final
public int getSysHeaderViewCount(){
    return sysHeaderViews.size();
}
final
public void setSysHeaderView(View view) {
    if (null == view) {
        return;
    }
    int index = getSysHeaderViewCount();
    if (index > 0) {
        sysHeaderViews.clear();
        notifyItemRangeRemoved(0, index);
    }
    sysHeaderViews.add(view);
    notifyItemInserted(0);
}

對于移除操作就不太一樣,因?yàn)槊鎸Φ氖嵌鄠€(gè)领炫,所以具體要?jiǎng)h除哪一個(gè)需要先檢索出在sysHeaderViews中的位置index偶垮,然后進(jìn)行相應(yīng)索引位置的移除操作,接著還是局部刷新。下面就是相關(guān)代碼:

final
public void removeSysHeaderView(View view) {
    if (null == view || !sysHeaderViews.contains(view)) {
        return;
    }
    int index = sysHeaderViews.indexOf(view);
    sysHeaderViews.remove(view);
    notifyItemRemoved(index);
}

onCreateViewHolder過程

系統(tǒng)頭部數(shù)據(jù)的操作已經(jīng)結(jié)束似舵,我們再調(diào)用了刷新操作后脚猾,Adapter將會處理一系列操作,首先我們看看onCreateViewHolder()啄枕,這里將會根據(jù)viewType創(chuàng)建對應(yīng)的viewHolder婚陪。在我們這個(gè)Adapter的擴(kuò)展中很明顯是item的類型已經(jīng)不止一種了,所以我們需要復(fù)寫getItemViewType()方法為系統(tǒng)頭部關(guān)聯(lián)一個(gè)類型频祝。系統(tǒng)header可能很多泌参,每一個(gè)都是單獨(dú)的view對象,在創(chuàng)建viewHolder的時(shí)候也是需要區(qū)分的常空,因此每一個(gè)系統(tǒng)header都是一個(gè)單獨(dú)的類型沽一,所以我們這里采取了將view的hashCode作為與之對應(yīng)的類型。

@Override
final
public int getItemViewType(int position) {
    int shc = getSysHeaderViewCount();
    //處理系統(tǒng)頭部
    if (shc > 0 && position < shc) {
        return sysHeaderViews.get(position).hashCode();
    }
    return 0;
}

onCreateViewHolder()的過程中我們需要根據(jù)viewType創(chuàng)建viewHolder漓糙,首先檢查viewType是否是系統(tǒng)header以及是哪一個(gè)系統(tǒng)header

@Override
final
public BaseViewHolder onCreateViewHolder(ViewGroup parent, final int viewType) {
    //處理系統(tǒng)頭部
    View sysHeaderView = getSysFooterViewByHashCode(viewType);
    if (null != sysHeaderView) {
        return new BaseViewHolder(sysHeaderView);
    }
    return null;
}
private View getSysFooterViewByHashCode(int hashCode) {
    return this.getViewByHashCodeFromList(sysHeaderViews, hashCode);
}
private View getViewByHashCodeFromList(List<View> views, int hashCode) {
    if (null == views) {
        return null;
    }
    for (View v : views) {
        if (v.hashCode() == hashCode) {
            return v;
        }
    }
    return null;
}

onBindViewHolder過程

viewHolder創(chuàng)建完畢之后铣缠,按照流程就到了onBindViewHolder進(jìn)行數(shù)據(jù)的綁定操作,但是看看我們向客戶端提供的設(shè)置方法昆禽,傳入的是一個(gè)view蝗蛙,這就意味著系統(tǒng)header數(shù)據(jù)的綁定操作是在客戶端進(jìn)行的,換句話說就是在這里針對系統(tǒng)header沒有要數(shù)據(jù)綁定的操作(傳入的position在系統(tǒng)頭部范圍內(nèi)不做任何處理)醉鳖。那么代碼就是下面這個(gè)樣子:

@Override
final
public void onBindViewHolder(BaseViewHolder holder, int position) {
    int shc = getSysHeaderViewCount();
    //item_sys_header
    if (0 != shc && position < shc) {
        return;
    }
}

到這里捡硅,系統(tǒng)header的處理部分就結(jié)束了,這時(shí)候已經(jīng)可以添加和刪除一個(gè)系統(tǒng)header了盗棵。這里可能有的同學(xué)就會問了壮韭,最重要的三個(gè)方法為什么都加了final,這樣不是都沒法操作了嗎纹因,至于為什么要這樣做喷屋,將會在稍后慢慢說明。

2. 用戶頭部

這部分對應(yīng)到item_header部分瞭恰,是提供給客戶端使用的屯曹。
流程仍然按照上面系統(tǒng)header部分。

數(shù)據(jù)操作

首先對于這部分功能提供一下部分幾個(gè)方法進(jìn)行頭部的操作

int getHeaderViewCount();
void addHeaderView(View view);
void removeHeaderView(View view);

以上參數(shù)的說明與系統(tǒng)header一致惊畏,這里不再贅述是牢。
這部分在按順序在系統(tǒng)header(item_sys_header)后面,所以在數(shù)據(jù)操作上需要考慮item_sys_header部分陕截。下面是具體的操作代碼:

final
public int getHeaderViewCount() {
    return headerViews.size();
}
final
public void addHeaderView(View view) {
    if (null != view && headerViews.contains(view)) {
        headerViews.remove(view);
    }
    headerViews.add(view);
    int shc = getSysHeaderViewCount();
    int index = shc + headerViews.indexOf(view);
    notifyItemInserted(index);
}
final
public void removeHeaderView(View view) {
    if (null == view || !headerViews.contains(view)) {
        return;
    }
    int shc = getSysHeaderViewCount();
    int index = shc + headerViews.indexOf(view);
    headerViews.remove(view);
    notifyItemRemoved(index);
}

onCreateViewHolder過程

這部分仍然與系統(tǒng)header一樣,item類型也是view對應(yīng)的hashCode批什,這里對getItemViewType()onCreateViewHolder()進(jìn)行完善农曲,添加對用戶header的支持。代碼如下:

@Override
final
public int getItemViewType(int position) {
    int shc = getSysHeaderViewCount();
    int hc = getHeaderViewCount();
    int hAll = shc + hc;
    //處理系統(tǒng)頭部
    if (shc > 0 && position < shc) {
        return sysHeaderViews.get(position).hashCode();
    }
    //處理頭部
    if (hc > 0 && position >= shc && position < hAll) {
        position = position - shc;
        return headerViews.get(position).hashCode();
    }
    return 0;
}
@Override
final
public BaseViewHolder onCreateViewHolder(ViewGroup parent, final int viewType) {
    //處理系統(tǒng)頭部
    View sysHeaderView = getSysFooterViewByHashCode(viewType);
    if (null != sysHeaderView) {
        return new BaseViewHolder(sysHeaderView);
    }
    //處理頭部
    View headerView = getHeaderViewByHashCode(viewType);
    if (null != headerView) {
        return new BaseViewHolder(headerView);
    }
    return null;
}

onBindViewHolder過程

用戶header的數(shù)據(jù)綁定處理仍然與處理系統(tǒng)header一致,增加了用戶header處理的代碼如下:

@Override
final
public void onBindViewHolder(BaseViewHolder holder, int position) {
    int shc = getSysHeaderViewCount();
    //item_sys_header 
   if (0 != shc && position < shc) {
        return;
    }
    int hc = getHeaderViewCount();
    int hAll = shc + hc;
    //處理用戶header
    if (0 != hc && position < hAll) {
        return;
    }
}

3. 正常的數(shù)據(jù)部分

這部分是除header乳规、footer外客戶端處理的真實(shí)數(shù)據(jù)部分形葬。
對于這部分的實(shí)現(xiàn)有以下幾方面的考慮:

多item類型支持

這個(gè)擴(kuò)展庫一定是支持多種item類型的,但是對于header和footer的類型已經(jīng)在內(nèi)部做了處理(本應(yīng)該是這樣)暮的,從上面來看多類型的支持的重載方法被設(shè)置為final笙以,所以需要額外提供一個(gè)方法來只針對數(shù)據(jù)部分多item類型的支持。在安全起見和必要性方面將getItemViewType()方法做了屏蔽冻辩。額外增加的方法如下:

  /**
    * 初始化數(shù)據(jù)域類型,總是從0開始猖腕,已經(jīng)除去頭部
    * @param positionOffsetHeaders 
    * @return 
    */
  protected int mapDataSectionItemViewTypeToItemLayoutId(int positionOffsetHeaders) {
        return 0;
  } 

從方法名來看,除了header和footer外的數(shù)據(jù)域部分item類型對應(yīng)的都是item的布局文件id恨闪,這樣在onCreateViewHolder()內(nèi)部封裝是有好處的倘感,再一次說明viewHolder的創(chuàng)建對于客戶端直接使用api是屏蔽的。

onCreateViewHolder

如果就單一般的展示用咙咽,那么這個(gè)方法也可以被屏蔽了老玛,但是為了更好的擴(kuò)展(在后面的滑動(dòng)菜單以及粘性頭部時(shí)),還是向上面一樣再提供一個(gè)下面這樣的方法供子類使用:

abstract
public BaseViewHolder onCreateHolder(ViewGroup parent, int viewType);

onBindViewHolder

同上面一樣钧敞,我們需要保護(hù)非數(shù)據(jù)域的邏輯蜡豹,但是數(shù)據(jù)的綁定操作一定是客戶端來做的,所以我們還是需要額外提供一個(gè)下面這樣的方法供客戶端使用:

 /** 
    * 綁定數(shù)據(jù)
    * @param holder
    * @param position  數(shù)據(jù)域索引從0開始溉苛,已經(jīng)除去頭部
    */
abstract
public void convert(BaseViewHolder holder, int position);

僅在這篇文章中提及的功能而言镜廉,對于onBindViewHolder()的操作我們只是又提取了一個(gè)convert()方法,但是如果翻看源代碼的話會發(fā)現(xiàn)對這塊的提取操作是下面這樣的(在粘性頭部的擴(kuò)展中會找到原因):

  /**     
     * 為了子類擴(kuò)展     
     * @param holder     
     * @param position     
     */    
@CallSuper    
protected void onBindHolder(BaseViewHolder holder, int position) {
        this.convert(holder, position); 
  }    
  /**     
     * 綁定數(shù)據(jù)    
     * @param holder     
     * @param position  數(shù)據(jù)域索引從0開始炊昆,已經(jīng)除去頭部     
     */    
abstract    
public void convert(BaseViewHolder holder, int position);

經(jīng)過上面的篇幅闡述桨吊,已經(jīng)簡單描述了header的添加刪除以及數(shù)據(jù)域部分的處理,屏蔽了部分非必要方法以及為進(jìn)一步擴(kuò)展等額外添加了一些處理方法凤巨,這么做的目的就是為了簡單视乐、易擴(kuò)展。
大概總結(jié)一下就是:

  1. 多類型的支持分為兩類:數(shù)據(jù)部分映射為item布局id敢茁;非數(shù)據(jù)部分映射為view的hashCode
  2. viewHolder創(chuàng)建分為兩種情況:對于客戶端使用直接屏佑淀;對于擴(kuò)展則有間接的映射方法
  3. 數(shù)據(jù)綁定分為兩類:數(shù)據(jù)域?qū)τ诳蛻舳擞斜仨殞?shí)現(xiàn)的間接映射方法;非數(shù)據(jù)域則直接屏蔽

相信看到這里已經(jīng)知道footer怎么加進(jìn)去了彰檬,與header和數(shù)據(jù)域都是類似的伸刃,主要抓住一點(diǎn)就是正確的計(jì)算索引,具體的就不在往下闡述了逢倍,可以去翻看源代碼捧颅。

4. 為數(shù)據(jù)域的item添加事件

我們知道RecyclerView對于viewHolder有特殊的要求,必須是RecyclerView.ViewHolder的子類才行较雕,而它內(nèi)部有個(gè)itemView屬性碉哑,指的就是我們創(chuàng)建的item根視圖挚币,有了這個(gè)萬事已經(jīng)具備。還記得代碼是這樣子寫的:

@Override
public BaseViewHolder onCreateHolder(ViewGroup parent, int viewType) {
    View itemView = inflater.inflate(viewType, parent, false);
    return new BaseViewHolder(itemView);
}

我們仿照ListView添加以下兩種事件扣典。

/** 
  * 點(diǎn)擊事件 
  */
public interface OnItemClickListener {    
    void onItemClick(View itemView, int position);
}
/** 
  * 長按事件 
  */
public interface OnItemLongClickListener {
    void onItemLongClick(View itemView, int position);
}

接下來只需要在onCreateViewHolder()里面添加事件回調(diào)就可以了妆毕,這里需要說明兩點(diǎn):1. 寫這么復(fù)雜的原因是還是為了擴(kuò)展 2. 計(jì)算正確的索引

@Override
final
public BaseViewHolder onCreateViewHolder(ViewGroup parent, final int viewType) {
    //省略其他操作...

    //事件只針對正常數(shù)據(jù)項(xiàng)
    final BaseViewHolder holder = onCreateHolder(parent, viewType);
    this.initItemListener(holder/*, viewType*/);
    return holder;
}

protected void initItemListener(final BaseViewHolder holder/*, final int viewType*/){
    if (null == holder) {
        return;
    }
    holder.itemView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            HeaderFooterAdapter.this.onItemClick(holder, v);
        }
    });

    holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {
            return HeaderFooterAdapter.this.onItemLongClick(holder, v);
        }
    });
}

protected void onItemClick(final BaseViewHolder holder, View view){
    if (null == onItemClickListener) {
        return;
    }
    int hAll = getHeaderViewCount() + getSysHeaderViewCount();
    onItemClickListener.onItemClick(view, holder.getAdapterPosition() - hAll);
}

protected boolean onItemLongClick(final BaseViewHolder holder, View view){
    if (null == onItemLongClickListener) {
        return false;
    }
    int hAll = getHeaderViewCount() + getSysHeaderViewCount();
    onItemLongClickListener.onItemLongClick(view, holder.getAdapterPosition() - hAll);
    return true;
}

5. 支持GridLayoutManager與StaggeredGridLayoutManager

寫到這里我們擴(kuò)展的Adapter已經(jīng)支持header、footer的增刪操作贮尖、更加簡化的api處理以及為數(shù)據(jù)域添加事件監(jiān)聽響應(yīng)笛粘。
寫一個(gè)demo測試后發(fā)現(xiàn)一切正常,但是這僅限于LayoutManagerLinearLayoutManager的情況湿硝,其他情況會發(fā)現(xiàn)是失效的狀態(tài)薪前。我們需要的是在任何情況下我們加進(jìn)去的header和footer一定要是橫向填充的,顯然還需要處理图柏。
發(fā)現(xiàn)Adapter中并沒有提供相關(guān)可以使用的方法序六,其實(shí)這種布局的處理是交給LayoutManager處理的,所以我們需要從LayoutManager著手處理這些問題蚤吹,最后發(fā)現(xiàn)不同的LayoutManager處理的方式不同例诀,那么我們需要分別對待。首先明確一點(diǎn)LayoutManager是設(shè)置在RecyclerView而不是Adapter裁着,所以第一點(diǎn)我們需要在Adapter中獲取RecyclerView對象繁涂。

@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
    super.onAttachedToRecyclerView(recyclerView);
    if (this.recyclerView == recyclerView) {
        return;
    }
    this.recyclerView = recyclerView;
}
@Override
public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
    super.onDetachedFromRecyclerView(recyclerView);
    this.recyclerView = null;
}

獲取到RecyclerView也就獲取到了設(shè)置的LayoutManager對象,這樣我們就可以針對不同的LayoutManager進(jìn)行處理二驰。在處理之前扔罪,首先有以下兩點(diǎn)需要考慮:

  1. 除了數(shù)據(jù)域(header和footer)一定是橫跨的,并且這部分處理需要內(nèi)部搞定且對外屏蔽
  2. 客戶端設(shè)置的數(shù)據(jù)域有可能有些item也是需要橫跨的桶雀,這時(shí)候我們需要額外提供一個(gè)方法供客戶端使用矿酵。很簡單方法,如下:
  /**
     * 設(shè)置是否橫跨
     * @param position
     * @return
     */
protected boolean isFullSpanWithItemView(int position) {
      return false;
}

所以我們就有了以下的代碼來分別針對GridLayoutManagerStaggeredGridLayoutManager進(jìn)行處理矗积。

private void adapterGridLayoutManager() {
    final RecyclerView.LayoutManager layoutManager = null == recyclerView ? null : recyclerView.getLayoutManager();
    if (null == layoutManager) {
        return;
    }
    if (layoutManager instanceof GridLayoutManager) {
        final GridLayoutManager glm = (GridLayoutManager) layoutManager;
        final GridLayoutManager.SpanSizeLookup ssl = glm.getSpanSizeLookup();
        glm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {
                return !isDataItemView(position) ? glm.getSpanCount() : ssl.getSpanSize(position);
            }
        });
    }}

private void adapterStaggeredGridLayoutManager(BaseViewHolder holder) {
    final RecyclerView.LayoutManager layoutManager = null == recyclerView ? null : recyclerView.getLayoutManager();
    if (null == layoutManager) {
        return;
    }
    if (layoutManager instanceof StaggeredGridLayoutManager) {
        ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
        int position = holder.getAdapterPosition();
        if (null != lp && lp instanceof StaggeredGridLayoutManager.LayoutParams && !isDataItemView(position)) {
            ((StaggeredGridLayoutManager.LayoutParams) lp).setFullSpan(true);
        }
    }
}

/**
 * 用來判斷item是否為真實(shí)數(shù)據(jù)項(xiàng)全肮,除了頭部、尾部棘捣、系統(tǒng)尾部等非真實(shí)數(shù)據(jù)項(xiàng)辜腺,結(jié)構(gòu)為: * item_header - item_data - item_footer - item_sys_footer
 * @param position
 * @return true:將保留LayoutManager的設(shè)置  false:該item將會橫跨整行(對GridLayoutManager,StaggeredLayoutManager將很有用)
 */
private boolean isDataItemView(int position) {
    int shc = getSysHeaderViewCount();
    int hc = shc + getHeaderViewCount();
    int dc = getDataSectionItemCount();
    boolean isHeaderOrFooter = position >= 0 && position >= hc && position < (hc + dc);
    if (!isHeaderOrFooter) {
        return isHeaderOrFooter;
    }
    return this.isFullSpanWithItemView(position - (shc + hc));
}

最后將以上處理加進(jìn)去,注意一下兩種方式在注冊的位置不太一樣:

@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
    super.onAttachedToRecyclerView(recyclerView);
    if (this.recyclerView == recyclerView) {
        return;
    }
    this.recyclerView = recyclerView;
    this.adapterGridLayoutManager();
}
@Override
public void onViewAttachedToWindow(BaseViewHolder holder) {
    super.onViewAttachedToWindow(holder);
    this.adapterStaggeredGridLayoutManager(holder);
}

到這里乍恐,為Adapter添加header评疗、footer、事件以及適配LayoutManager就結(jié)束了茵烈,下一篇將闡述下數(shù)據(jù)域的操作百匆。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市呜投,隨后出現(xiàn)的幾起案子胧华,更是在濱河造成了極大的恐慌寄症,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件矩动,死亡現(xiàn)場離奇詭異,居然都是意外死亡释漆,警方通過查閱死者的電腦和手機(jī)悲没,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來男图,“玉大人示姿,你說我怎么就攤上這事⊙钒剩” “怎么了栈戳?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長难裆。 經(jīng)常有香客問我子檀,道長,這世上最難降的妖魔是什么乃戈? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任褂痰,我火速辦了婚禮,結(jié)果婚禮上症虑,老公的妹妹穿的比我還像新娘缩歪。我一直安慰自己,他們只是感情好谍憔,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布匪蝙。 她就那樣靜靜地躺著,像睡著了一般习贫。 火紅的嫁衣襯著肌膚如雪逛球。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天沈条,我揣著相機(jī)與錄音需忿,去河邊找鬼。 笑死蜡歹,一個(gè)胖子當(dāng)著我的面吹牛屋厘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播月而,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼汗洒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了父款?” 一聲冷哼從身側(cè)響起溢谤,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤瞻凤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后世杀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體阀参,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年瞻坝,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蛛壳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,919評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡所刀,死狀恐怖衙荐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情浮创,我是刑警寧澤忧吟,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站斩披,受9級特大地震影響溜族,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜雏掠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一斩祭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧乡话,春花似錦摧玫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至闸婴,卻和暖如春坏挠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背邪乍。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工降狠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人庇楞。 一個(gè)月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓榜配,卻偏偏與公主長得像,于是被迫代替她去往敵國和親吕晌。 傳聞我的和親對象是個(gè)殘疾皇子蛋褥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評論 2 354

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