RecyclerView的基础使用及其与ListView的部分区别

先说说RecyclerView是怎么使用的,以其基础代码为本,大体以如下顺序进行分析。

  • 组件介绍
  • 点击事件
    • ListView为什么会点击item事件失效?
    • RecyclerView怎么设计点击回调?
  • 与ListView的区别
  • 布局文件
public class testForRecyclerViewActivity{

    // 装载item中数据
    private List<String> mDatas;
    // 初始化item中的数据
    protected void initData() {
        mDatas = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            mDatas.add("" + i);
        }
    }
    //定义点击事件的接口
    public interface OnItemClickListener {
        void onItemClick(View view, int position);
    }

    // 继承RecyclerView.Adapter , 按照其规定好的设计规范,定义具体内容。
    class LogAdapter extends RecyclerView.Adapter<ViewHolder> {

        private OnItemClickListener mOnItemClickListener;

        public void setOnItemClickListener(OnItemClickListener mOnItemClickListener) {
            this.mOnItemClickListener = mOnItemClickListener;
        }

        @NonNull
        @NotNull
        @Override
        public ViewHolder onCreateViewHolder(@NonNull @NotNull ViewGroup viewGroup, int i) {
            View view = LayoutInflater.from(testForRecyclerViewActivity.this).inflate(R.layout.test_rv_item, viewGroup, false);
            ViewHolder viewHolder = ViewHolder.create(view);
            viewHolder.getItemView(R.id.title);
            viewHolder.getItemView(R.id.date);
            
            if (mOnItemClickListener != null) {
                viewHolder.getmConvertView().setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        mOnItemClickListener.onItemClick(view, i);
                    }
                });
            }
            return viewHolder;
        }

        @Override
        public void onBindViewHolder(@NonNull @NotNull ViewHolder viewHolder, int i) {
            viewHolder.setTextView(R.id.title, "我是标题");
            viewHolder.setTextView(R.id.date, "我是日期");
        }

        @Override
        public int getItemCount() {
            return mDatas.size();
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.test_rv);
        initData();
        RecyclerView mRecyclerView = findViewById(R.id.test_item_log);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        LogAdapter mLogAdapter = new LogAdapter();
        mLogAdapter.setOnItemClickListener(new OnItemClickListener() {
            @Override
            public void onItemClick(View view, int position) {
                String webUrl = "https://www.baidu.com/";
                Intent intent = new Intent(testForRecyclerViewActivity.this, newDetailsActivity.class);
                intent.putExtra("ARGS_KEY_URL", webUrl );
                intent.putExtra("ARGS_KEY_TITLE", "标题");
                startActivity(intent);
            }
        });
        mRecyclerView.setAdapter(mLogAdapter);

        //返回按钮
        findViewById(R.id.return_btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                finish();
            }
        });

    }
}

代码介绍

  • 组件:(其包含的组件较多,挑选最常用的介绍)

    1. onCreateViewHolder(ViewGroup viewGroup, int i),该方法旨在创造一个持有者的类,将对应id的view存到内存中,避免每次都需要去布局文件中读取对应的view。需要注意的是,下面的ViewHolder为公司封装后的类,使用更简洁方便。

              public ViewHolder onCreateViewHolder(@NonNull @NotNull ViewGroup viewGroup, int i) {
                  View view = LayoutInflater.from(testForRecyclerViewActivity.this).inflate(R.layout.test_rv_item, viewGroup, false);
                  ViewHolder viewHolder = ViewHolder.create(view);
                  viewHolder.getItemView(R.id.title);
                  viewHolder.getItemView(R.id.date);
                  
                  if (mOnItemClickListener != null) {
                      viewHolder.getmConvertView().setOnClickListener(new View.OnClickListener() {
                          @Override
                          public void onClick(View view) {
                              mOnItemClickListener.onItemClick(view, i);
                          }
                      });
                  }
                  return viewHolder;
              }
      
    2. onBindViewHolder(ViewHolder viewHolder, int i),给持有者包含的组件赋予数据,同时可以给组件添加事件。

            @Override
            public void onBindViewHolder(@NonNull @NotNull ViewHolder viewHolder, int i) {
                viewHolder.setTextView(R.id.title, "我是标题");
                viewHolder.setTextView(R.id.date, "我是日期");
            }
      
    3. getItemCount(),返回item的条目数

              @Override
              public int getItemCount() {
                  return mDatas.size();
              }
      

点击事件(分析RecyclerView和ListView在点击事件的差异)

  1. item点击事件:与ListView不同的是,RecyclerView并没有配备setOnItemClickListener()方法,只能通过配置回调接口来设置对应的点击事件。

    • 可能你会觉得,wc这么不方便,RecyclerView不用也罢。不急,慢慢听我解释,对于ListView的点击事件有很重要的一个弊端,那就是某个时候你给ListView的item组件设置了setOnItemClickListener事件,正准备高高兴兴去测试时,却发现死活都点击无效甚是懊恼,上网一查同仁还不在少数,解释为:当listview中包含button,checkbox等控件的时候,android会默认将focus给了这些控件,也就是说listview的item根本就获取不到focus,所以导致onitemclick时间不能触发。那我们就详细聊聊为什么会产生这种情况,就随着关键部分的代码慢慢探索答案吧。
    • 对于ListView,点击事件发生后,经过事件分发机制判定(默认不拦截),遂调用onTouchEvent()方法去处理该ItemClick时间,该方法处于其继承的AbsListView类中:
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            if (!isEnabled()) {
                // A disabled view that is clickable still consumes the touch
                // events, it just doesn't respond to them.
                return isClickable() || isLongClickable();
            }
    
            if (mPositionScroller != null) {
                mPositionScroller.stop();
            }
    
            if (mIsDetaching || !isAttachedToWindow()) {
                // Something isn't right.
                // Since we rely on being attached to get data set change notifications,
                // don't risk doing anything where we might try to resync and find things
                // in a bogus state.
                return false;
            }
    
            startNestedScroll(SCROLL_AXIS_VERTICAL);
    
            if (mFastScroll != null && mFastScroll.onTouchEvent(ev)) {
                return true;
            }
    
            initVelocityTrackerIfNotExists();
            final MotionEvent vtev = MotionEvent.obtain(ev);
    
            final int actionMasked = ev.getActionMasked();
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                mNestedYOffset = 0;
            }
            vtev.offsetLocation(0, mNestedYOffset);
            switch (actionMasked) {
                case MotionEvent.ACTION_DOWN: {
                    onTouchDown(ev);
                    break;
                }
    
                case MotionEvent.ACTION_MOVE: {
                    onTouchMove(ev, vtev);
                    break;
                }
    
                case MotionEvent.ACTION_UP: {
                    onTouchUp(ev);
                    break;
                }
    
                case MotionEvent.ACTION_CANCEL: {
                    onTouchCancel();
                    break;
                }
    
                case MotionEvent.ACTION_POINTER_UP: {
                    onSecondaryPointerUp(ev);
                    final int x = mMotionX;
                    final int y = mMotionY;
                    final int motionPosition = pointToPosition(x, y);
                    if (motionPosition >= 0) {
                        // Remember where the motion event started
                        final View child = getChildAt(motionPosition - mFirstPosition);
                        mMotionViewOriginalTop = child.getTop();
                        mMotionPosition = motionPosition;
                    }
                    mLastY = y;
                    break;
                }
    
                case MotionEvent.ACTION_POINTER_DOWN: {
                    // New pointers take over dragging duties
                    final int index = ev.getActionIndex();
                    final int id = ev.getPointerId(index);
                    final int x = (int) ev.getX(index);
                    final int y = (int) ev.getY(index);
                    mMotionCorrection = 0;
                    mActivePointerId = id;
                    mMotionX = x;
                    mMotionY = y;
                    final int motionPosition = pointToPosition(x, y);
                    if (motionPosition >= 0) {
                        // Remember where the motion event started
                        final View child = getChildAt(motionPosition - mFirstPosition);
                        mMotionViewOriginalTop = child.getTop();
                        mMotionPosition = motionPosition;
                    }
                    mLastY = y;
                    break;
                }
            }
    
            if (mVelocityTracker != null) {
                mVelocityTracker.addMovement(vtev);
            }
            vtev.recycle();
            return true;
        }
    
    • 看着这么多的事件处理入口,是否一筹莫展?想当初曹孟德东临碣石以观沧海,从大处着眼忽略细节,望水何澹澹山岛竦峙。同样的道理,我们看着一大片的代码段时,应当结合我们的目标找寻与其最有关的方法,其他的大可以不看。比如我们需要的是看处理onItemClick的事件,而这个事件的触发是来自我们的手指从屏幕抬起的那一刻,因此直接就定位到第47行的onTouchUp(ev)方法。现在我们进去该方法,看它是怎么处理事件的:
    private void onTouchUp(MotionEvent ev) {
            switch (mTouchMode) {
            case TOUCH_MODE_DOWN:
            case TOUCH_MODE_TAP:
            case TOUCH_MODE_DONE_WAITING:
                final int motionPosition = mMotionPosition;
                final View child = getChildAt(motionPosition - mFirstPosition);
                if (child != null) {
                    if (mTouchMode != TOUCH_MODE_DOWN) {
                        child.setPressed(false);
                    }
    
                    final float x = ev.getX();
                    final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right;
                    if (inList && !child.hasExplicitFocusable()) {
                        if (mPerformClick == null) {
                            mPerformClick = new PerformClick();
                        }
    
                        final AbsListView.PerformClick performClick = mPerformClick;
                        performClick.mClickMotionPosition = motionPosition;
                        performClick.rememberWindowAttachCount();
    
                        mResurrectToPosition = motionPosition;
    
                        if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
                            removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
                                    mPendingCheckForTap : mPendingCheckForLongPress);
                            mLayoutMode = LAYOUT_NORMAL;
                            if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
                                mTouchMode = TOUCH_MODE_TAP;
                                setSelectedPositionInt(mMotionPosition);
                                layoutChildren();
                                child.setPressed(true);
                                positionSelector(mMotionPosition, child);
                                setPressed(true);
                                if (mSelector != null) {
                                    Drawable d = mSelector.getCurrent();
                                    if (d != null && d instanceof TransitionDrawable) {
                                        ((TransitionDrawable) d).resetTransition();
                                    }
                                    mSelector.setHotspot(x, ev.getY());
                                }
                                if (mTouchModeReset != null) {
                                    removeCallbacks(mTouchModeReset);
                                }
                                mTouchModeReset = new Runnable() {
                                    @Override
                                    public void run() {
                                        mTouchModeReset = null;
                                        mTouchMode = TOUCH_MODE_REST;
                                        child.setPressed(false);
                                        setPressed(false);
                                        if (!mDataChanged && !mIsDetaching && isAttachedToWindow()) {
                                            performClick.run();
                                        }
                                    }
                                };
                                postDelayed(mTouchModeReset,
                                        ViewConfiguration.getPressedStateDuration());
                            } else {
                                mTouchMode = TOUCH_MODE_REST;
                                updateSelectorState();
                            }
                            return;
                        } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
                            performClick.run();
                        }
                    }
                }
                mTouchMode = TOUCH_MODE_REST;
                updateSelectorState();
                break;
            case TOUCH_MODE_SCROLL:
             ......
    
            case TOUCH_MODE_OVERSCROLL:
                ......
            }
    
            setPressed(false);
    
            if (shouldDisplayEdgeEffects()) {
                mEdgeGlowTop.onRelease();
                mEdgeGlowBottom.onRelease();
            }
    
            // Need to redraw since we probably aren't drawing the selector anymore
            invalidate();
            removeCallbacks(mPendingCheckForLongPress);
            recycleVelocityTracker();
    
            mActivePointerId = INVALID_POINTER;
    
            if (PROFILE_SCROLLING) {
                if (mScrollProfilingStarted) {
                    Debug.stopMethodTracing();
                    mScrollProfilingStarted = false;
                }
            }
    
            if (mScrollStrictSpan != null) {
                mScrollStrictSpan.finish();
                mScrollStrictSpan = null;
            }
        }
    
    • 我省略掉了部分代码,那些不重要,定位到15行看到了这个大大的判断语句,if (inList && !child.hasExplicitFocusable()) inList判断触发是否是item范围内的事件为true,重要的是 child.hasExplicitFocusable() 取反,该方法用来判断该节点是否是获取焦点的,如果是则不会触发后续的点击回调。由此变引申出了两种解决方法:
      • 在button/checkbox等控件处设置android:clickable=”false” android:focusableInTouchMode=”false”,使其在点击item时不会因该组件的获取焦点属性而影响了回调事件。
      • 在item最外层添加属性android:descendantFocusability=”blocksDescendants”,该属性使item覆盖所有的子节点获取焦点,故里面的子节点均不可获取焦点。
    • 既然都到这一步了,不如看看他是怎么调用我们的方法的吧~~点击第67行的performClick.run();
            @Override
            public void run() {
                // The data has changed since we posted this action in the event queue,
                // bail out before bad things happen
                if (mDataChanged) return;
    
                final ListAdapter adapter = mAdapter;
                final int motionPosition = mClickMotionPosition;
                if (adapter != null && mItemCount > 0 &&
                        motionPosition != INVALID_POSITION &&
                        motionPosition < adapter.getCount() && sameWindow() &&
                        adapter.isEnabled(motionPosition)) {
                    final View view = getChildAt(motionPosition - mFirstPosition);
                    // If there is no view, something bad happened (the view scrolled off the
                    // screen, etc.) and we should cancel the click
                    if (view != null) {
                        performItemClick(view, motionPosition, adapter.getItemId(motionPosition));
                    }
                }
            }
    
    • 可以看到该方法最终调用的是AdapterView中的performItemClick()方法,贴出其代码:
        public boolean performItemClick(View view, int position, long id) {
            final boolean result;
            if (mOnItemClickListener != null) {
                playSoundEffect(SoundEffectConstants.CLICK);
                mOnItemClickListener.onItemClick(this, view, position, id);
                result = true;
            } else {
                result = false;
            }
    
            if (view != null) {
                view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
            }
            return result;
        }
    
    • 终于,在第5行见到了它的庐山真面目,在这里调用的我们设置的mOnItemClickListener()方法。
  2. RecyclerView的点击事件应该如何设计其回调接口呢,我归纳了2种方式,当然思想都是一样的,那就是配置回调接口然后在对应的时机实现该方法。

    1. 第一种方式 按照我在开头贴的代码,在Activity中就定义需要用的回调接口

          //定义点击事件的接口
          public interface OnItemClickListener {
              void onItemClick(View view, int position);
          }
      

      然后在定义Adapter时,声明变量中加入对应变量与构造方法,并且在onCreateViewHolder中绑定该方法

            private OnItemClickListener mOnItemClickListener;
      
            public void setOnItemClickListener(OnItemClickListener mOnItemClickListener) {
                  this.mOnItemClickListener = mOnItemClickListener;
              }
      
              public ViewHolder onCreateViewHolder(@NonNull @NotNull ViewGroup viewGroup, int i) {
                  View view = LayoutInflater.from(testForRecyclerViewActivity.this).inflate(R.layout.test_rv_item, viewGroup, false);
                  ViewHolder viewHolder = ViewHolder.create(view);
                  viewHolder.getItemView(R.id.title);
                  viewHolder.getItemView(R.id.date);
                  
                  if (mOnItemClickListener != null) {
                      viewHolder.getmConvertView().setOnClickListener(new View.OnClickListener() {
                          @Override
                          public void onClick(View view) {
                              mOnItemClickListener.onItemClick(view, i);
                          }
                      });
                  }
                  return viewHolder;
              }
      

      最后在onCreate中实现该接口并重写该点击方法,怎么样,是不是很简单,条理清晰井井有条。

            LogAdapter mLogAdapter = new LogAdapter();
              mLogAdapter.setOnItemClickListener(new OnItemClickListener() {
                  @Override
                  public void onItemClick(View view, int position) {
                      String webUrl = "https://www.baidu.com/";
                      Intent intent = new Intent(testForRecyclerViewActivity.this, newDetailsActivity.class);
                      intent.putExtra("ARGS_KEY_URL", webUrl );
                      intent.putExtra("ARGS_KEY_TITLE", "标题");
                      startActivity(intent);
                  }
              });
      
    2. 第二种方式 ,在定义持有者时实现OnClickListener接口,重写其onClick方法,根据点击对象的不同而配置不同的方法。组件多的时候,可以通过getId来使用switch,case来分id处理事件。

      public static class ViewHolder extends RecyclerView.ViewHolder implements OnClickListener {
      
          public TextView txtViewTitle;
          public ImageView imgViewIcon;
          public IMyViewHolderClicks mListener;
      
          public ViewHolder(View itemLayoutView, IMyViewHolderClicks listener) {
              super(itemLayoutView);
              mListener = listener;
              txtViewTitle = (TextView) itemLayoutView.findViewById(R.id.item_title);
              imgViewIcon = (ImageView) itemLayoutView.findViewById(R.id.item_icon);
              imgViewIcon.setOnClickListener(this);
              itemLayoutView.setOnClickListener(this);
          }
      
          @Override
          public void onClick(View v) {
              if (v instanceof ImageView){
                 mListener.onTomato((ImageView)v);
              } else {
                 mListener.onPotato(v);
              }
          }
      
          public static interface IMyViewHolderClicks {
              public void onPotato(View caller);
              public void onTomato(ImageView callerImage);
          }
      
      }
      

      在编写好ViewHolder的代码后,在适配器中我们只需要重写其IMyViewHolderClicks方法即可。

      public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
      
         @Override
         public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
             View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.my_layout, parent, false);
      
             MyAdapter.ViewHolder vh = new ViewHolder(v, new MyAdapter.ViewHolder.IMyViewHolderClicks() {
                 public void onPotato(View caller) { Log.d("VEGETABLES","Poh-tah-tos"); };
                 public void onTomato(ImageView callerImage) {&nbsp;Log.d("VEGETABLES","To-m8-tohs"); }
              });
              return vh;
          }
      
        ...
      

与ListView的区别

  1. RecyclerView与ListView最大的区别就是它的布局方式十分丰富:线性布局(横向或者纵向)、表格布局、瀑布流布局。而ListView只有一个纵向布局的效果,若需要不同的呈现方式还得自己去定义。面对现在更多样的需求,无论是从美观还是效率上说,RecyclerView都是首选;
  2. RecyclerView编写的规范化,从上面可以得知RecyclerView的组件都是定义好的,在什么阶段定义什么得到什么。而ListView需要重写getView,布局的载入、持有、资源设置全部在里面完成。
  3. 缓存方法的优势,RecyclerView比ListView多两级缓存,开发有缓存池,支持多个RecyclerView共同使用;RV缓存的是ViewHolder,支持屏幕外的列表项进入屏幕时无须bindView就可以快速重用。

RecyclerView布局文件(test_rv.xml)

<?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"
    android:background="@color/xxxx">
    <include layout="@layout/xxxx" />
    <ProgressBar
        android:id="@+id/我是进度条"
        style="@android:style/xxx"
        android:layout_width="match_parent"
        android:layout_height="2dip"
        android:layout_below="@+id/title_bar"
        android:gravity="center_vertical"
        android:max="100"
        android:progressDrawable="@drawable/play_progress"
        android:indeterminateDrawable="@null" />

    <TextView
        android:id="@+id/activity_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/margin_5dp"
        android:layout_marginStart="25dp"
        android:layout_marginBottom="23dp"
        android:textColor="@color/x"
        android:textSize="@dimen/x"
        android:text="xxx"
        />

    <androidx.recyclerview.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical"
        android:id="@+id/xxx"/>

</LinearLayout>

布局内item组件(test_rv_item.xml)

<?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="@dimen/xxx">

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentEnd="true"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:layout_marginEnd="@dimen/xxx"
            android:layout_marginRight="@dimen/xxx"
            android:importantForAccessibility="no"
            android:src="@drawable/我是右侧的图标" />

        <LinearLayout
            android:id="@+id/xxxxxx"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentStart="true"
            android:layout_alignParentLeft="true"
            android:layout_centerVertical="true"
            android:layout_marginStart="24dp"
            android:layout_marginEnd="70dp"
            android:layout_marginRight="70dp"
            android:layout_toStartOf="@id/xxx"
            android:layout_toLeftOf="@id/xxx"
            android:orientation="vertical">

            <TextView
                android:id="@+id/title"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="@color/xxx"
                android:text=""
                android:textSize="@dimen/xxx" />
            <TextView
                android:id="@+id/date"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text=""
                android:textColor="@color/xxx"
                android:textSize="@dimen/xxx" />

        </LinearLayout>

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

推荐阅读更多精彩内容