RecyclerView-為Adapter增加滑動菜單支持(第4篇)

效果圖

滑動菜單

簡述

通過前面的文章已經(jīng)看出現(xiàn)在的Adapter在功能上已經(jīng)比較強(qiáng)大了,并且已經(jīng)做好了一些隨時可被擴(kuò)展的準(zhǔn)備搔啊,這篇就在現(xiàn)有功能的基礎(chǔ)上增加滑動菜單的支持。
首先這一篇描述的功能都是針對非header和footer的缕题。

有了這些前提后我們開始思考绕辖,滑動菜單需要什么樣的表現(xiàn)形式以及需要哪些功能:

  1. 客戶端的使用上不能脓诡,我們只針對Adapter,不針對RecyclerView媒役,不強(qiáng)制客戶端使用自定義控件總是好的
  2. 滑動的效果盡可能的向Ios的效果去靠祝谚,免得測試說為什么和Ios不一樣呢(誰讓Android這方面比較那啥呢)
  3. 允許item菜單在打開時,其他被打開菜單的item是否需要關(guān)閉菜單要有開關(guān)功能
  4. 要支持左菜單酣衷、右菜單(目前的實(shí)現(xiàn)上兩者不能同時存在)
  5. 菜單的個數(shù)要靈活交惯,對數(shù)量不做限制
  6. 菜單要有事件回調(diào)
  7. 其他菜單操作api

有了上述需求之后,我們開始著手開發(fā)穿仪,首先肯定少不了事件處理席爽,同時我們也不應(yīng)該去自定義RecyclerView。那么接下來的操作啊片,也就是實(shí)現(xiàn)這個功能不可缺少的工具ViewDragHelper出場只锻。這個是Android系統(tǒng)增加的針對簡化事件處理的工具類,這里不過多介紹钠龙,不了解的自行g(shù)oogle炬藤。

注:一定要確保上面提到的功能和知識點(diǎn)徹底明確后再接著往下讀

1. 菜單相關(guān)處理接口聲明

我們先定義操作接口御铃。

菜單包裝類

public class MenuItem {
    //菜單布局
    private int menuLayoutId;
    //菜單方向
    @MenuItem.EdgeTrackWhere
    private int edgeTrack;
    //菜單id
    private int menuId;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({EdgeTrack.LEFT, EdgeTrack.RIGHT}) 
    public @interface EdgeTrackWhere {}

    /**
     * 菜單打開方向
     */
    public interface EdgeTrack{
        int LEFT = 0;
        int RIGHT = 1;
    }
}

菜單創(chuàng)建接口

為了不同需求碴里,我們提供兩個方法,需要注意的是上真,如果兩個方法都有數(shù)據(jù)返回咬腋,則會進(jìn)行組合,所以建議根據(jù)場合不同睡互,選用其中一個就好

public interface ICreateMenus {
    /**
     * 創(chuàng)建多個菜單
     * @param viewType  可以針對不同的item類型創(chuàng)建不同的菜單
     * @return
     */
    List<MenuItem> onCreateMultiMenuItem(int viewType);
    /**
     * 創(chuàng)建單個菜單
     * @param viewType  可以針對不同的item類型創(chuàng)建不同的菜單
     * @return
     */
    MenuItem onCreateSingleMenuItem(int viewType);
}

菜單關(guān)閉接口以及配置接口

public interface ICloseMenus {
    /**
     * 關(guān)閉菜單
     */
    void closeMenuItem();
    /**
     * 關(guān)閉其他打開菜單的item
     */
    void closeOtherMenuItems();
    /**
     * 是否有其他打開菜單的item項(xiàng)(不包含當(dāng)前客戶端觸摸的item)
     * @return
     */
    boolean hasOpendMenuItems();
}
public interface IMenuSupport {
    /**
     * 是否關(guān)閉其他已經(jīng)打開menu的items
     * @return
     */
    boolean isCloseOtherItemsWhenThisWillOpen();
}

菜單點(diǎn)擊事件回調(diào)接口

public interface OnItemMenuClickListener {
    /**
     * 菜單點(diǎn)擊回調(diào)
     * @param swipeItemView
     * @param itemView  客戶端所創(chuàng)建的itemview
     * @param menuView
     * @param position  列表中item所在索引(數(shù)據(jù)區(qū)域)
     * @param menuId    客戶端創(chuàng)建item時指定的id
     */
    void onMenuClick(SwipeLayout swipeItemView, View itemView, View menuView, int position, int menuId);
}

2. 開始擴(kuò)展Adapter

操作接口已經(jīng)定義完畢根竿,開始擴(kuò)展。我們?nèi)∑涿麨?code>SwipeAdapter就珠,讓繼承自BaseAdapter寇壳,這樣就擁有上幾篇介紹的所有功能。
這樣先考慮一下在這個Adapter我們需要做什么妻怎,目測需要著手處理以下兩方面的內(nèi)容壳炎,其他的都不需要:
a. viewHolder的創(chuàng)建我們需要去做,因?yàn)樵瓉淼膇tem需要加菜單逼侦,所以item要動
b. 點(diǎn)擊事件需要匿辩,菜單也需要點(diǎn)擊事件,同時如果我們點(diǎn)擊的這個item是菜單打開的狀態(tài)榛丢,那么是需要關(guān)閉的铲球,所以點(diǎn)擊時間需要復(fù)寫

這樣一來我們就有了入手點(diǎn),從復(fù)寫public BaseViewHolder onCreateHolder(ViewGroup parent, int viewType)方法開始(看過之前文章的話會知道這個方法是怎么來的)晰赞。

創(chuàng)建viewHolder

每個item都需要添加菜單稼病,我們需要的效果是原始item在滑動的過程中菜單慢慢顯現(xiàn)出來选侨,本身菜單沒有動,這樣原始item完全覆蓋在菜單的上面然走,所以我們這里用一個FrameLayout容器來包裹菜單和原始item控件侵俗,這個繼承自FrameLayout的控件我們命名為SwipeLayout,我們將用它作為新的item來創(chuàng)建一個viewHolder丰刊。
因此在onCreateHolder()里我們需要處理的內(nèi)容是:創(chuàng)建新的item控件隘谣、創(chuàng)建菜單以及菜單點(diǎn)擊事件處理。SwipeLayout中的邏輯我們之后再說啄巧。
根據(jù)上面的描述看下面onCreateHolder()的邏輯:

@Override
public BaseViewHolder onCreateHolder(ViewGroup parent, int viewType) {
    View itemView = inflater.inflate(viewType, parent, false);
    MenuItem mi = this.onCreateSingleMenuItem(viewType);
    List<MenuItem> mm = this.onCreateMultiMenuItem(viewType);
    //客戶端沒有設(shè)置菜單支持
    if (null == mi && (null == mm || mm.isEmpty())) {
        return new BaseViewHolder(itemView);
    }
    List<MenuItem> menuItems = new ArrayList<>();
    if (null != mi) {
        menuItems.add(mi);
    }
    if (null != mm && !mm.isEmpty()) {
        menuItems.addAll(mm);
    }
    final SwipeLayout swipeLayout = new SwipeLayout(context);
    swipeLayout.setUpView(parent, itemView, menuItems);
    swipeLayout.setIsCloseOtherItemsWhenThisWillOpen(this.isCloseOtherItemsWhenThisWillOpen());
    itemView.setClickable(true);
    BaseViewHolder holder = new BaseViewHolder(swipeLayout, itemView);
    this.initMenusListener(holder);
    return holder;
}

對于上面這部分代碼的處理寻歧,需要做兩點(diǎn)補(bǔ)充:
a. itemView.setClickable(true); 這里的itemView指的是客戶端所創(chuàng)建的最原始的那個view,設(shè)為可點(diǎn)擊是因?yàn)檫@個itemView我們一定要能消耗事件秩仆,不然該item就不能捕捉點(diǎn)擊事件码泛。
b. 對于有菜單的viewHolder我們用了new BaseViewHolder(swipeLayout, itemView);這樣一個構(gòu)造方法,為什么這樣用呢澄耍,因?yàn)樯厦娴谝稽c(diǎn)也說了我們的點(diǎn)擊事件是加載客戶端創(chuàng)建的最原始的item上的噪珊,而不是新創(chuàng)建的SwipeLayout item位衩,所以我們需要另外一個處理事件的view參數(shù)肴捉,這樣一來我們必須更改兩處地方:
BaseViewHolder的構(gòu)造器需要這樣改造

//事件(解決滑動時事件問題)
public View eventItemView;
public BaseViewHolder(View itemView) {
    super(itemView);
    this.eventItemView = itemView;
}
public BaseViewHolder(View itemView, View eventItemView) {
    super(itemView);
    this.eventItemView = eventItemView;
}

HeaderFooterAdapterinitItemListener的事件處理中處理點(diǎn)擊事件的不再是itemView,而是eventItemView,偽代碼如下:

protected void initItemListener(final BaseViewHolder holder/*, final int viewType*/){
       holder.eventItemView.setOnClickListener(xxxx);
       holder.eventItemView.setOnLongClickListener(xxxx);
}

</p>

菜單事件處理

下面只需要知道菜單的點(diǎn)擊事件是怎么添加的就可以了冤吨,菜單與item的關(guān)聯(lián)關(guān)系List<Pair<View, MenuItem>> menus = swipeLayout.getMenus();將會放到SwipeLayout中進(jìn)行描述选酗。

/**
 * 添加菜單點(diǎn)擊監(jiān)聽器
 * @param holder
 */
private void initMenusListener(final BaseViewHolder holder) {
    if (! (holder.itemView instanceof SwipeLayout)) {
        return;
    }
    final SwipeLayout swipeLayout = (SwipeLayout) holder.itemView;
    List<Pair<View, MenuItem>> menus = swipeLayout.getMenus();
    if (null == menus || menus.isEmpty()) {
        return;
    }
    if (null == this.onItemMenuClickListener) {
        return;
    }
    for (final Pair<View, MenuItem> pair:menus) {
        pair.first.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int hAll = getHeaderViewCount() + getSysHeaderViewCount();
                final int position = holder.getAdapterPosition() - hAll;
                onItemMenuClickListener.onMenuClick(swipeLayout, holder.eventItemView, v, position, pair.second.getMenuId());
            }
        });
    }
}

同時需要復(fù)寫父類中的事件響應(yīng)處理阵难,只有在當(dāng)前item的菜單是關(guān)閉的情況下才可以去響應(yīng)事件,代碼就不貼出來了芒填。

3. SwipeLayout

在上面的描述里面已經(jīng)知道這個SwipeLayout就是新的item view了(承載原始item view和菜單)呜叫,同時滑動的操作也是作用在它上面(少不了對事件的處理)。
另外之前也說了我們這里用ViewDragHelper來處理事件殿衰。
那么SwipeLayout大概需要完成下面這些工作:

添加菜單以及原始item view并關(guān)聯(lián)

SwipeAdapterAdapter的onCreateHolder中朱庆,我們調(diào)用了swipeLayout.setUpView(parent, itemView, menuItems);
進(jìn)行SwipeLayout的初始化
這里對菜單的操作做了簡單的優(yōu)化,前面說過菜單支持多個闷祥,那么這里菜單控件的添加操作是這樣處理的娱颊,如果是只有一個菜單那么直接添加,如果是多個菜單蜀踏,那么在菜單外層包裝了一個線性容器维蒙。
菜單處理的這部分代碼比較多,太占篇幅且沒什么技術(shù)含量果覆,所以就不都貼出來了颅痊,只是貼下流程吧:

private List<Pair<View, MenuItem>> leftMenus;
private List<Pair<View, MenuItem>> rightMenus;
public void setUpView(ViewGroup viewGroup, View itemView, List<MenuItem> menuItems) {
    this.viewGroup = viewGroup;
    this.itemView = itemView;
    if (null == menuItems || menuItems.isEmpty()) {
        return;
    }
    //省略菜單處理邏輯
    //1. 左右菜單分組
    //2. 菜單添加
    //3. 原始item view添加
    ....
}

初始化ViewDragHelper

同樣在初始化方法中進(jìn)行初始化,每一個SwipeLayout都需要處理手勢操作局待,所以必須關(guān)聯(lián)ViewDragHelper斑响,同時針對左右菜單做了ViewDragHelper邊界處理

public void setUpView(ViewGroup viewGroup, View itemView, List<MenuItem> menuItems) {
    //省略其他代碼
    ...
    delegate = new SwipeDragHelperDelegate(this);
    this.helper = ViewDragHelper.create(this, 1.0f, delegate);
    delegate.init(helper);
    if (this.EdgeTracking == MenuItem.EdgeTrack.LEFT) {
        helper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
    }else if (this.EdgeTracking == MenuItem.EdgeTrack.RIGHT) {
        helper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);
    }
}

事件處理

我們需要將SwipeLayout的事件委托給ViewDragHelper進(jìn)行處理菱属,這里的邏輯是這樣的:

  1. 在手指按下(ACTION_DOWN操作)的時候,之前有說過setIsCloseOtherItemsWhenThisWillOpen()這樣一個接口方法舰罚,就是說如果我們希望這時候關(guān)閉掉其他的打開菜單的item的話纽门,那么這個事件中我們就需要做關(guān)閉的操作,注:這個事件不做攔截营罢。部分代碼如下:
if (isCloseOtherItemsWhenThisWillOpen) {
    if (MotionEvent.ACTION_DOWN == action) {
        if (hasOpendMenuItems()) {
            closeOtherMenuItems();
        }
    }
}
  1. 在菜單打開的過程中(ACTION_MOVE操作)我們不需要攔截事件赏陵,這些事件需要交給ViewDragHelper處理
  2. 在手指抬起(ACTION_UP操作)的時候,這里比較復(fù)雜饲漾,我們期望這樣的效果:
a. 菜單在關(guān)閉的時候蝙搔,希望能正常響應(yīng)item的其他事件(點(diǎn)擊、長按等)考传,包括子view
b. 菜單在打開的時候吃型,如果點(diǎn)擊的是原始item以及其子view,希望能關(guān)閉菜單僚楞,就算原始item中有button等能消耗事件的控件也要能關(guān)閉菜單勤晚,并且這樣能消耗事件的控件不能讓其響應(yīng)事件
c. 同樣菜單在打開的時候,如果點(diǎn)擊的是菜單項(xiàng)泉褐,該菜單一定能夠響應(yīng)事件
d. 我們還需要知道ACTION_UP這個點(diǎn)作用在哪個區(qū)域

因?yàn)槲覀冎肋@里事件處理的控件是SwipeLayout,子view能不能響應(yīng)事件一方面取決于自身是否有能力消耗事件赐写,另一方面取決于父控件是否攔截了控件。
經(jīng)過上面的描述兴枯,SwipeLayout對于事件的處理邏輯就很清晰了:

private boolean isCloseOtherItemsWhenThisWillOpen = false;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    final int action = MotionEventCompat.getActionMasked(ev);
    switch (action) {
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
            helper.cancel();
            RectF f = calcViewScreenLocation(itemView);
            boolean isIn = f.contains(ev.getRawX(), ev.getRawY());
            if (isIn && delegate.getMenuStatus() == SwipeDragHelperDelegate.MenuStatus.OPEN) {
                delegate.closeMenuItem();
                return true;
            }
            return false;
    }
    if (isCloseOtherItemsWhenThisWillOpen) {
        if (MotionEvent.ACTION_DOWN == action) {
            if (hasOpendMenuItems()) {
                closeOtherMenuItems();
            }
        }
    }
    return helper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
    helper.processTouchEvent(event);
    return true;
}
public static RectF calcViewScreenLocation(View view) {
    int[] location = new int[2];
    view.getLocationOnScreen(location);
    return new RectF(location[0], location[1], location[0] + view.getWidth(), location[1] + view.getHeight());
}

4. SwipeDragHelperDelegate

我們將事件委托給ViewDragHelper后血淌,通過回調(diào)處理view的操作矩欠。
我假設(shè)在讀的你已經(jīng)知道這個類怎么用(還不太清楚的可自行g(shù)oogle)财剖。
照樣先描述下我們期望的效果:

滑動控件設(shè)置

我們期望SwipeLayout中原始item view進(jìn)行滑動而不是菜單控件,因此復(fù)寫下面的方法就有了這樣的邏輯癌淮,其中swipeLayout.getItemView();獲取的就是客戶端創(chuàng)建最原始item view

@Override
public boolean tryCaptureView(View child, int pointerId) {
    final View itemView = swipeLayout.getItemView();
    if (null != itemView && itemView == child) {
        return true;
    }
    return false;
}

滑動邊界

我們需要通過滑動操作處理控件的真實(shí)行為躺坟,以保證在我們預(yù)期的范圍內(nèi)。比如我們拿右菜單舉例乳蓄,能滑動的最大距離就是菜單的寬度咪橙,縱向是不可以滑動的。

@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
    if (swipeLayout.getEdgeTracking() == MenuItem.EdgeTrack.RIGHT) {
        int menuWidth = swipeLayout.getRightMenuWidth();
        if (left > 0 && dx > 0) {
            return 0;
        }
        if (left < -menuWidth && dx < 0) {
            return -menuWidth;
        }
    }
    return left;
}

滑動行為

可滑動的邊界設(shè)置了以后虚倒,我們現(xiàn)在運(yùn)行demo會發(fā)現(xiàn)在可滑動的邊界內(nèi)滑到哪就停到哪美侦,這顯然也不是我們期望的,我們對這里的效果做如下定義:

  1. 當(dāng)手指松開時魂奥,如果滑動之前菜單是關(guān)閉的菠剩,那么這時候如果滑動的距離超過了菜單寬度的20%,則直接打開菜單耻煤,否則認(rèn)為用戶不想打開菜單具壮,則關(guān)閉菜單
  2. 當(dāng)手指松開時准颓,如果滑動之前菜單是打開的,那么這時候直接關(guān)閉菜單
//打開菜單所滑動的邊界百分比,超過將打開菜單,否在則不打開
private float openMenuBoundaryPercent = 0.2f;
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
    final View itemView = swipeLayout.getItemView();
    if (releasedChild != itemView) {
        return;
    }
    final int et = swipeLayout.getEdgeTracking();
    final int l = Math.abs(itemView.getLeft());
    final int menuWidth;
    //獲取菜單寬度
    if (et == MenuItem.EdgeTrack.LEFT) {
        menuWidth = swipeLayout.getLeftMenuWidth();
    }else if (et == MenuItem.EdgeTrack.RIGHT){
        menuWidth = swipeLayout.getRightMenuWidth();
    }else {
        menuWidth = 0;
    }
    final float min = Math.abs(menuWidth * openMenuBoundaryPercent);
    final int left;
    //計(jì)算偏移量
    if (l < min || (MenuStatus.OPEN == this.menuBoundaryStatusOfBeenTo && l < menuWidth)) {
        left = 0;
    } else {
        if (et == MenuItem.EdgeTrack.LEFT) {
            left = +1 * menuWidth;
        }else if (et == MenuItem.EdgeTrack.RIGHT) {
            left = -1 * menuWidth;
        }else {
            left = 0;
        }
    }
    this.helper.settleCapturedViewAt(left, 0);
    this.swipeLayout.invalidate();
}

這里在補(bǔ)充一句棺妓,在上面的代碼里用了this.helper.settleCapturedViewAt(left, 0); this.swipeLayout.invalidate();進(jìn)行位置的設(shè)置攘已,內(nèi)部其實(shí)用的是Scroller,所以需要在SwipeLayout需要復(fù)寫以下方法配合使用:

@Override
public void computeScroll() {
    super.computeScroll();
    if (helper.continueSettling(true)) {
        invalidate();
    }
}

根據(jù)上面的描述以及代碼中都知道怜跑,在滑動的時候是有邊界條件(這里指的是左右邊界)限制的样勃,也就是說在滑動時我們要知道最近到達(dá)過哪個邊界。

@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
    super.onViewPositionChanged(changedView, left, top, dx, dy);
    this.updateMenuStatus(left);
}
private void updateMenuStatus(int left) {
    final int et = swipeLayout.getEdgeTracking();
    int menuWidth = 0;
    if(MenuItem.EdgeTrack.LEFT == et) {
        menuWidth = swipeLayout.getLeftMenuWidth();
    }else if (MenuItem.EdgeTrack.RIGHT == et) {
        menuWidth = swipeLayout.getRightMenuWidth();
    }
    //記錄拖動時到達(dá)過的邊界狀態(tài)
    if (left == 0) {
        this.menuBoundaryStatusOfBeenTo = MenuStatus.CLOSED;    }else if (Math.abs(left) >= menuWidth) {
        this.menuBoundaryStatusOfBeenTo = MenuStatus.OPEN;
    }
    //記錄打開關(guān)閉菜單項(xiàng)的item
    if (left == 0) {
        this.openView.remove(this.swipeLayout);
    }else if (0 != menuWidth && left == menuWidth) {
        if (!openView.contains(swipeLayout)) {
            openView.add(swipeLayout);
        }
    }
}
//記錄拖動之前達(dá)到過的狀態(tài)(只要到達(dá)過菜單開的狀態(tài)性芬,此時再次移動將會關(guān)閉菜單)
@MenuBoundaryStatusOfBeenToWhereprivate int menuBoundaryStatusOfBeenTo = MenuStatus.CLOSED;
@Retention(RetentionPolicy.SOURCE)
@IntDef({MenuStatus.OPEN, MenuStatus.CLOSED})
private @interface MenuBoundaryStatusOfBeenToWhere {}
/**
 * 菜單狀態(tài)
 */
public interface MenuStatus{
    int CLOSED = -1;
    int DRAGING = 0;
    int OPEN = 1;
}

滑動區(qū)域

在有可消耗事件的view存在時彤灶,我們的滑動效果就失效了,這時候鑒于菜單只定義了橫向滑動批旺,所以我們復(fù)寫下面這個方法來限定可拖動的區(qū)域(至于為什么請自行g(shù)oogle):

@Override
public int getViewHorizontalDragRange(View child) {
    return swipeLayout.getItemView() == child ? child.getWidth() : 0;
}

滑動菜單的支持到這里就介紹完了幌陕,寫的是云里霧里的,有些地方應(yīng)該是比較模糊汽煮,只看的話估計(jì)也很難全面了解搏熄,畢竟這個功能需要處理的細(xì)節(jié)很多,所以首先一定要知道所要實(shí)現(xiàn)的效果是什么暇赤,然后再讀心例。
最后還是移駕到源碼中,應(yīng)該一看就懂了鞋囊。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末止后,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子溜腐,更是在濱河造成了極大的恐慌译株,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挺益,死亡現(xiàn)場離奇詭異歉糜,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)望众,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門匪补,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人烂翰,你說我怎么就攤上這事夯缺。” “怎么了甘耿?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵踊兜,是天一觀的道長。 經(jīng)常有香客問我棵里,道長润文,這世上最難降的妖魔是什么姐呐? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮典蝌,結(jié)果婚禮上曙砂,老公的妹妹穿的比我還像新娘。我一直安慰自己骏掀,他們只是感情好鸠澈,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著截驮,像睡著了一般笑陈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上葵袭,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天涵妥,我揣著相機(jī)與錄音,去河邊找鬼坡锡。 笑死蓬网,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鹉勒。 我是一名探鬼主播帆锋,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼禽额!你這毒婦竟也來了锯厢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤脯倒,失蹤者是張志新(化名)和其女友劉穎实辑,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盔憨,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡徙菠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了郁岩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡缺狠,死狀恐怖问慎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情挤茄,我是刑警寧澤如叼,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站穷劈,受9級特大地震影響笼恰,放射性物質(zhì)發(fā)生泄漏踊沸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一社证、第九天 我趴在偏房一處隱蔽的房頂上張望逼龟。 院中可真熱鬧,春花似錦追葡、人聲如沸腺律。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽匀钧。三九已至,卻和暖如春谬返,著一層夾襖步出監(jiān)牢的瞬間之斯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工遣铝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吊圾,地道東北人。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓翰蠢,卻偏偏與公主長得像项乒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子梁沧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評論 2 360

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