利用Android自带嵌套滑动控件解决滑动冲突(NestingScroll,CoordinatorLayout与Behavior)

实现嵌套滑动有三种方案:

1. 纯事件拦截与派发方案
2. 基于NestingScroll机制的实现方案
3. 基于CoordinatorLayout与Behavior的实现方案

第一种方案:灵活性最高,也最繁琐。因为事件的拦截是一锤子买卖,谁拦截了事件,当前手势接下来的事件都会交给拦截者来处理,除非等到下一次Down事件触发。这很不方便多个View对同一个事件进行处理。

第二种方案:其实就是对原始的事件拦截机制做了一层封装,通过子View实现NestedScrollingChild接口,父View实现NestedScrollingParent 接口,并且在子View和父View中都分别有一个NestedScrollingChildHelper、NestedScrollingParentHelper来代理了父子之间的联动,开发者不用关心具体是怎么联动的,这一点很方便。

第三种方案:其实就是对原始的NestedScrolling机制再次做了一层封装。CoordinatorLayout默认实现了NestedScrollingParent接口。第二种方案只能由子View通知父View,但有时候除了需要通知父View,还需要通知兄弟View,这个时候就该是Behavior出场了。

第一种方案:纯事件拦截与派发方案

文章中有事件分发的相关内容 View事件冲突和解决实例

第二种方案:NestingScroll机制

使用这种方式,一般需要继承NestedScrollingChild和NestedScrollingParent接口。同时NestedScrollingChildHelper和NestedScrollingScrollingParentHelper进行辅助。像RecyclerView,CoordinatorLayout等一些控件已经实现NestedScrollingChild,NestedScrollingParent接口。下面介绍一下接口中方法的作用。

NestedScrollingChild接口

public interface NestedScrollingChild {  
    /** 
     * 设置嵌套滑动是否能用
     *  @param enabled true to enable nested scrolling, false to disable
     */  
    public void setNestedScrollingEnabled(boolean enabled);  
    /** 
     * 判断嵌套滑动是否可用 
     * @return true if nested scrolling is enabled
     */  
    public boolean isNestedScrollingEnabled();  
    /** 
     * 开始嵌套滑动
     * @param axes 表示方向轴,有横向和竖向
     */  
    public boolean startNestedScroll(int axes);  
    /** 
     * 停止嵌套滑动 
     */  
    public void stopNestedScroll();  
    /** 
     * 判断是否有父View 支持嵌套滑动 
     * @return whether this view has a nested scrolling parent
     */  
    public boolean hasNestedScrollingParent();  
    /** 
     * 在子View的onInterceptTouchEvent或者onTouch中,调用该方法通知父View滑动的距离
     * @param dx  x轴上滑动的距离
     * @param dy  y轴上滑动的距离
     * @param consumed 父view消费掉的scroll长度
     * @param offsetInWindow   子View的窗体偏移量
     * @return 支持的嵌套的父View 是否处理了 滑动事件 
     */  
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);  
    /** 
     * 子view处理scroll后调用
     * @param dxConsumed x轴上被消费的距离(横向) 
     * @param dyConsumed y轴上被消费的距离(竖向)
     * @param dxUnconsumed x轴上未被消费的距离 
     * @param dyUnconsumed y轴上未被消费的距离 
     * @param offsetInWindow 子View的窗体偏移量
     * @return  true if the event was dispatched, false if it could not be dispatched.
     */  
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,  
          int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);  
    /** 
     * 滑行时调用 
     * @param velocityX x 轴上的滑动速率
     * @param velocityY y 轴上的滑动速率
     * @param consumed 是否被消费 
     * @return  true if the nested scrolling parent consumed or otherwise reacted to the fling
     */  
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);  
    /** 
     * 进行滑行前调用
     * @param velocityX x 轴上的滑动速率
     * @param velocityY y 轴上的滑动速率 
     * @return true if a nested scrolling parent consumed the fling
     */  
    public boolean dispatchNestedPreFling(float velocityX, float velocityY);  
}

NestedScrollingParent接口

public interface NestedScrollingParent {
    /**
     * 调用child的startNestedScroll()来发起嵌套滑动流程(实质上是寻找能够配合child进行嵌套滚动的parent)。
     * parent的onStartNestedScroll()会被调用,若此方法返回true,则OnNestScrollAccepted()也会被调用。
     * */
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);

    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);

    public void onStopNestedScroll(View target);
    /**
     * 后于child滚动
     * @param target
     * @param dxConsumed
     * @param dyConsumed
     * @param dxUnconsumed
     * @param dyUnconsumed
     */
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed);
    /**
     * 先于child滚动
     * 前3个为输入参数,最后一个是输出参数
     * @param target
     * @param dx
     * @param dy  向上是正的,向下是负的 是差值
     * @param consumed
     */
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);

    public boolean onNestedFling(View target, float velocityX, 
                                                      float velocityY,boolean consumed);

    public boolean onNestedPreFling(View target, float velocityX, float velocityY);

    public int getNestedScrollAxes();
}

当 NestedScrollingChild(下文用Child代替) 要开始滑动的时候会调用 onStartNestedScroll ,然后交给代理类NestedScrollingChildHelper(下文ChildHelper代替)的onStartNestedScroll请求给最近的NestedScrollingParent(下文Parent代替).
当ChildHelper的onStartNestedScroll方法 返回 true 表示同意一起处理 Scroll 事件的时候时候,ChildHelper会通知Parent回调onNestedScrollAccepted 做一些准备动作
当Child 要开始滑动的时候,会先发送onNestedPreScroll,交给ChildHelper的onNestedPreScroll 请求给Parent ,告诉它我现在要滑动多少距离,你觉得行不行,这时候Parent 根据实际情况告诉Child 现在只允许你滑动多少距离.然后 ChildHelper根据 onNestedPreScroll 中回调回来的信息对滑动距离做相对应的调整.
在滑动的过程中 Child 会发送onNestedScroll通知ChildeHelpaer的onNestedScroll告知Parent 当前 Child 的滑动情况.
当要进行滑行的时候,会先发送onNestedFling 请求给Parent,告诉它 我现在要滑行了,你说行不行, 这时候Parent会根据情况告诉 Child 你是否可以滑行.
Child 通过onNestedFling 返回的 Boolean 值来觉得是否进行滑行.如果要滑行的话,会在滑行的时候发送onNestedFling 通知告知 Parent 滑行情况.
当滑动事件结束就会child发送onStopNestedScroll通知 Parent 去做相关操作.

NestedScroll嵌套滑动事件流程图

NestedScroll嵌套滑动事件.png

最后来总结一下整个流程,分为四个步骤:

  • 步骤一:子View的ACTION_DOWN调用startNestedScroll---->父View的onStartNestedScroll判断是否要一起滑动,父ViewonNestedScrollAccepted同意协同滑动
  • 步骤二:子View的ACTION_MOVE调用dispatchNestedPreScroll---->父View的onNestedPreScroll在子View滑动之前先进行滑动并消耗需要的距离---->父View完成该次滑动之后返回消耗的距离,子View在剩下的距离中再完成自己需要的滑动
  • 步骤三:子View滑动完成之后调用dispatchNestedScroll---->父View的onNestedScroll处理父View和子View之前滑动剩余的距离
  • 步骤四:子View的ACTION_UP调用stopNestedScroll---->父View的onStopNestedScroll完成滑动收尾工作

这样,子View和父View的一系列嵌套滑动就完成了,可以看出来整个嵌套滑动还是靠子View来推动父View进行滑动的,这也解决了在传统的滑动事件中一旦事件被子View处理了就很难再分享给父View共同处理的问题,这也是嵌套滑动的一个特点。
NestedScrolling 机制能够让父 View 和子 View 在滚动式进行配合,而要实现这样的交互机制,首先父 view 要实现 NestedScrollingParent 接口,而子 View 需要实现 NestedScrollingChild 接口,在这套机制中子 View是发起者,父 view 是接受回调并做出响应的。

自定义MyNestedScrollingParent实现下面效果

NestedScroll.gif
布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.example.chenpeng.julyapplication.NestedScrolling.MyNestedScrollParent
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <ImageView
            android:id="@+id/imageView"
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:src="@mipmap/ic_launcher"/>
        <View
            android:layout_width="match_parent"
            android:layout_height="30dp"
            android:background="#009987"/>
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </android.support.v7.widget.RecyclerView>
    </com.example.chenpeng.julyapplication.NestedScrolling.MyNestedScrollParent>

</LinearLayout>

自定义MyNestedScrollingParent实现NestedScrollingParent接口

在源码中已经相关解释。主要讲一个方法ViewCompat.canScrollVertically(target, -1),判断target控件是否可以上滑。

在APi14之后,官方提供了canScrollVertically(int direction)方法,所以API14之后判断非常方便,官方文档的解释如下:
image.png

翻译下来就是:当direction>0时,判断是否可以下滑,当direction<0时,判断是否可以上滑

public class MyNestedScrollParent extends LinearLayout implements NestedScrollingParent {

    private ImageView mImageView;
    private RecyclerView mRecyclerView;
    private int mImageViewHeight;
    private NestedScrollingParentHelper mNestedScrollingParentHelper;
    private OverScroller mScroller;
    private static final String TAG = "MyNestedScrollParent";

    public MyNestedScrollParent(Context context) {
        this(context,null);
        Log.i(TAG, "MyNestedScrollParent: 1");
    }

    public MyNestedScrollParent(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
        Log.i(TAG, "MyNestedScrollParent: 2");
    }

    public MyNestedScrollParent(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mNestedScrollingParentHelper = new NestedScrollingParentHelper(this);
        mScroller = new OverScroller(context);
        Log.i(TAG, "MyNestedScrollParent: 3");
    }


    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mImageView = (ImageView) getChildAt(0);
        mRecyclerView = (RecyclerView) getChildAt(2);
        mImageView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                mImageViewHeight = mImageView.getMeasuredHeight();
                Log.i(TAG, "onGlobalLayout: getMeasuredHeight = " + mImageViewHeight + " , getHeight = " + mImageView.getHeight());
            }
        });
    }

    /**
     * 调用child的startNestedScroll()来发起嵌套滑动流程(实质上是寻找能够配合child进行嵌套滚动的parent)。
     * parent的onStartNestedScroll()会被调用,若此方法返回true,则OnNestScrollAccepted()也会被调用。
     * */
    @Override
    public boolean onStartNestedScroll(@NonNull View child, @NonNull View target, int axes) {
        //在此可以判断参数target是哪一个子view以及滚动的方向,然后决定是否要配合其进行嵌套滚动
        Log.i(TAG, "onStartNestedScroll: ");
        //垂直滑动返回true
        return (axes & SCROLL_AXIS_VERTICAL) != 0;
    }

    @Override
    public void onNestedScrollAccepted(@NonNull View child, @NonNull View target, int axes) {
        mNestedScrollingParentHelper.onNestedScrollAccepted(child,target,axes);
        Log.i(TAG, "onNestedScrollAccepted: ");
    }

    @Override
    public void onStopNestedScroll(@NonNull View target) {
        mNestedScrollingParentHelper.onStopNestedScroll(target);
        Log.i(TAG, "onStopNestedScroll: ");
    }

    /**
     * 后于child滚动
     * @param target
     * @param dxConsumed
     * @param dyConsumed
     * @param dxUnconsumed
     * @param dyUnconsumed
     */
    @Override
    public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {

    }

    /**
     * 先于child滚动
     * 前3个为输入参数,最后一个是输出参数
     * @param target
     * @param dx
     * @param dy  向上是正的,向下是负的 是差值
     * @param consumed
     */
    @Override
    public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed) {
        Log.i(TAG, "onNestedPreScroll: dy = " + dy + " , getScrollY() = " + getScrollY() + ", mRecyclerView.getChildAt(0).getTop() = "+ mRecyclerView.getChildAt(0).getTop());
        if((dy>0 && getScrollY() < mImageViewHeight ||
                (dy < 0 && getScrollY() > 0 && (!ViewCompat.canScrollVertically(target, -1))))){//!ViewCompat.canScrollVertically(target, -1)可以判断是否可继续下滑
            scrollBy(0,dy);
            consumed[1] = dy;//告诉child我消费了多少
        }
    }



    /**
     *scrollBy内部会调用scrollTo , 限制滚动范围
     */
    @Override
    public void scrollTo(int x, int y) {
        if (y < 0) {
            y = 0;
        }
        if (y > mImageViewHeight) {
            y = mImageViewHeight;
        }

        super.scrollTo(x, y);
    }

    @Override
    public boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed) {
        //是否消费了fling
        if (getScrollY() >= mImageViewHeight)
            return false;
        fling((int) velocityY);
        return true;
    }

    @Override
    public boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY) {
        return false;
    }

    public void fling(int velocityY)
    {
        mScroller.fling(0, getScrollY(), 0, velocityY, 0, 0, 0, mImageViewHeight);
        invalidate();
    }

    @Override
    public void computeScroll()
    {
        if (mScroller.computeScrollOffset())
        {
            scrollTo(0, mScroller.getCurrY());
            invalidate();
        }
    }


    @Override
    public int getNestedScrollAxes() {
        return mNestedScrollingParentHelper.getNestedScrollAxes();
    }
}

第三种方案:基于CoordinatorLayout与Behavior的实现方案

通过依赖compile'com.android.support:design:26.1.0',中的CoordinatorLayout,AppBarLayout,CollapsingToolbarLayout,FloatingActionButton中控件实现下面的例子。


CoordinatorLayout.gif
AppBarLayou:

1.AppbarLayout:继承了LinearLayout(默认的AppBarLayout是垂直方向),它是为了Material Design设计的App Bar,它的作用是把AppBarLayout包裹的内容都作为AppBar。说白了它的出现就是为了和CoordinatorLayout搭配使用,实现一些炫酷的效果的。没有CoordinatorLayout,它和Linearlayout没区别。

2.app:layout_scrollFlags=”scroll|enterAlways”
AppBarLayout的直接子控件可以设置的属性:layout_scrollFlags

  • scroll|exitUntilCollapsed 如果AppBarLayout的直接子控件设置该属性,该子控件可以滚动,向上滚动NestedScrollView出父布局(一般为CoordinatorLayout)时,会折叠到顶端,向下滚动时NestedScrollView必须滚动到最上面的时候才能拉出该布局
  • scroll|enterAlways:只要向下滚动该布局就会显示出来,只要向上滑动该布局就会向上收缩
  • scroll|enterAlwaysCollapsed:向下滚动NestedScrollView到最底端时该布局才会显示出来
    如果不设置改属性,则改布局不能滑动

3.细心的朋友可能在NestedScrollView中发现这么一条属性

app:layout_behavior="@string/appbar_scrolling_view_behavior"

它的核心就在于Behavior类。我们给CoordinatorLayout的子View设置一个Behavior,就可以接管该View自身的一些事件与其他的View之间的交互事件。包括 touch,measure,layout等事件。我们在NestedScrollView设定的Android提供的Behavior,app:layout_behavior="@string/appbar_scrolling_view_behavior">,其中的string是Behavior的绝对路径。
CoordinatorLayout中Behavior介绍与简单使用

CollapsingToolbarLayout

我们在AppBarLayout中添加CollapsingToolbarLayout。Collapsing是折叠的意思。见名知意,CollapsingToolbarLayout的作用是提供了一个可以折叠的Toolbar,它继承至FrameLayout。
1.大家一定要弄清楚,它们之间的包含关系:
AppBarLayout >CollapsingToolbarLayout>Toolbar

2.CollapsingToolbarLayout作为AppBarLayout的直接子控件,也要设置app:layout_scrollFlags属性

3.app:layout_collapseMode属性是折叠模式,它是CollapsingToolbarLayout的直接子控件需要设置的,它的取值如下:

  • pin:在滑动过程中,此自布局会固定在它所在的位置不动,直到CollapsingToolbarLayout全部折叠或者全部展开
  • parallax:视察效果,在滑动过程中,不管上滑还是下滑都会有视察效果,不知道什么事视察效果自己看gif图(layout_collapseParallaxMultiplier视差因子 0~1之间取值,当设置了parallax时可以配合这个属性使用,调节自己想要的视差效果)
  • 不设置:跟随NestedScrollView的滑动一起滑动,NestedScrollView滑动多少距离他就会跟着走多少距离
contentScrim折叠后的颜色也是展开时的渐变颜色,效果超赞. 
title标题,如果设置在折叠时会动画的显示到折叠后的部分上面,拉开时会展开,很好玩的 
expandedTitleMargin当title文字展开时文字的margin,当然还有marginTop等属性,脑补吧 
app:collapsedTitleTextAppearance=”@style/Text”折叠时title的样式里面可以定义字体大小颜色等 
app:collapsedTitleTextAppearance=”@style/Text1”折叠时title的样式里面可以定义字体大小颜色等 
当然还有一些,自己试试吧,现在的这些已经完全够用了
FloatingActionButton
app:layout_anchor="@id/app_bar" //在哪个控件上
app:layout_anchorGravity="bottom|right" //具体位置
实现上面效果的布局文件
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <android.support.design.widget.CollapsingToolbarLayout
            android:layout_width="match_parent"
            android:layout_height="170dp"
            app:contentScrim="#ff2077ff"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">
            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@mipmap/bg"/>

            <android.support.v7.widget.Toolbar
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="parallax"
                app:title="Title"/>
        </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>
    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="30dp"
                android:text="测试数据1"
                android:textSize="20sp" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="30dp"
                android:text="测试数据2"
                android:textSize="20sp" />
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="30dp"
                android:text="测试数据1"
                android:textSize="20sp" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="30dp"
                android:text="测试数据2"
                android:textSize="20sp" />
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="30dp"
                android:text="测试数据1"
                android:textSize="20sp" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="30dp"
                android:text="测试数据2"
                android:textSize="20sp" />
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="30dp"
                android:text="测试数据1"
                android:textSize="20sp" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="30dp"
                android:text="测试数据2"
                android:textSize="20sp" />
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="30dp"
                android:text="测试数据1"
                android:textSize="20sp" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="30dp"
                android:text="测试数据2"
                android:textSize="20sp" />
            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="30dp"
                android:text="测试数据1"
                android:textSize="20sp" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="30dp"
                android:text="测试数据2"
                android:textSize="20sp" />
        </LinearLayout>

    </android.support.v4.widget.NestedScrollView>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_anchor="@id/app_bar"
        app:layout_anchorGravity="bottom|right"
        app:srcCompat="@android:drawable/ic_dialog_email" />

</android.support.design.widget.CoordinatorLayout>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容