RecyclerView

RecyclerView用法

gradle

compile 'com.android.support:cardview-v7:21.0.3'
compile 'com.android.support:recyclerview-v7:21.0.3'

xml

<android.support.v7.widget.RecyclerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/recycler_view"
    android:layout_centerVertical="true"
    android:layout_centerHorizontal="true"/>

Activity

 mRecyclerView.setLayoutManager(new LinearLayoutManager(this));//这里用线性显示 类似于listview
//        mRecyclerView.setLayoutManager(new GridLayoutManager(this, 2));//这里用线性宫格显示 类似于grid view
//        mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(2, OrientationHelper.VERTICAL));//这里用线性宫格显示 类似于瀑布流
        mRecyclerView.setAdapter(new NormalRecyclerViewAdapter(this));

adapter

public class NormalRecyclerViewAdapter extends RecyclerView.Adapter<NormalRecyclerViewAdapter.NormalTextViewHolder> {
    private final LayoutInflater mLayoutInflater;
    private final Context mContext;
    private String[] mTitles;

    public NormalRecyclerViewAdapter(Context context) {
        mTitles = context.getResources().getStringArray(R.array.titles);
        mContext = context;
        mLayoutInflater = LayoutInflater.from(context);
    }

    @Override
    public NormalTextViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new NormalTextViewHolder(mLayoutInflater.inflate(R.layout.item_text, parent, false));
    }

    @Override
    public void onBindViewHolder(NormalTextViewHolder holder, int position) {
        holder.mTextView.setText(mTitles[position]);
    }

    @Override
    public int getItemCount() {
        return mTitles == null ? 0 : mTitles.length;
    }

    public static class NormalTextViewHolder extends RecyclerView.ViewHolder {
        @InjectView(R.id.text_view)
        TextView mTextView;

        NormalTextViewHolder(View view) {
            super(view);
            ButterKnife.inject(this, view);
            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Log.d("NormalTextViewHolder", "onClick--> position = " + getPosition());
                }
            });
        }
    }
}

多Item布局实现(MultipleItem)

@Override
    public int getItemViewType(int position) {
        return super.getItemViewType(position);
    }

其中getItemViewType方法是用来获取当前项Item(position参数)是哪种类型的布局,这里要注意的就是这个函数onCreateViewHolder(ViewGroup parent, int viewType)这里的第二个参数就是View的类型,可以根据这个类型判断去创建不同item的ViewHolder。

public class MultipleItemAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    public static enum ITEM_TYPE {
        ITEM_TYPE_IMAGE,
        ITEM_TYPE_TEXT
    }
    
    private final LayoutInflater mLayoutInflater;
    private final Context mContext;
    private String[] mTitles;

    public MultipleItemAdapter(Context context) {
        mTitles = context.getResources().getStringArray(R.array.titles);
        mContext = context;
        mLayoutInflater = LayoutInflater.from(context);
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == ITEM_TYPE.ITEM_TYPE_IMAGE.ordinal()) {
            return new ImageViewHolder(mLayoutInflater.inflate(R.layout.item_image, parent, false));
        } else {
            return new TextViewHolder(mLayoutInflater.inflate(R.layout.item_text, parent, false));
        }
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof TextViewHolder) {
            ((TextViewHolder) holder).mTextView.setText(mTitles[position]);
        } else if (holder instanceof ImageViewHolder) {
            ((ImageViewHolder) holder).mTextView.setText(mTitles[position]);
        }
    }

    @Override
    public int getItemViewType(int position) {
        return position % 2 == 0 ? ITEM_TYPE.ITEM_TYPE_IMAGE.ordinal() : ITEM_TYPE.ITEM_TYPE_TEXT.ordinal();
    }

    @Override
    public int getItemCount() {
        return mTitles == null ? 0 : mTitles.length;
    }

    public static class TextViewHolder extends RecyclerView.ViewHolder {
        @InjectView(R.id.text_view)
        TextView mTextView;

        TextViewHolder(View view) {
            super(view);
            ButterKnife.inject(this, view);
            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Log.d("TextViewHolder", "onClick--> position = " + getPosition());
                }
            });
        }
    }

    public static class ImageViewHolder extends RecyclerView.ViewHolder {
        @InjectView(R.id.text_view)
        TextView mTextView;
        @InjectView(R.id.image_view)
        ImageView mImageView;

        ImageViewHolder(View view) {
            super(view);
            ButterKnife.inject(this, view);
            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Log.d("ImageViewHolder", "onClick--> position = " + getPosition());
                }
            });
        }
    }
    }

RecyclerView的三种方法

1、notifyItemInserted(int position)
意思就是在位置position的地方,插入一条。

mList.add(2,"插入"+count);
//我在position=2的位置,插入了数据,结果很 快插入了,不需要更新整个列表
 mAdapter.notifyItemInserted(2);
 /*从position为2位置开始的itemCount个数的item是新加来的,后面的位置position要相应的更新。 
比如我在位置2的地方添加一条数据,原来位置2的地方的的数据现在的position应该是3了,如果不加这句话,则点击3的时候,还是提示position为2。
这个应该是google的一个bug。
*/
  mAdapter.notifyItemRangeChanged(2,mList.size()-2);

2、notifyItemRangeChanged(int positionStart, int itemCount)

3、notifyItemRemoved(int position)

把position位置的那条删除

        mList.remove(2);
        mAdapter.notifyItemRemoved(2);
        mAdapter.notifyItemRangeChanged(0,mList.size()-2);

RecyclerView的头部与尾部实现

RecyclerView不像ListView拥有addHeaderView()与addFooterView()的方法简单添加头部尾部即可,而且RecyclerView也没有像ListView的列表点击监听方法(setItemOnclickListener),这里我也不明白为什么官方会取消了这些独有的属性,不过我们依然可以在onBindViewHolder方法中进行事件绑定!

具体头部与尾部实现方法,这里有个诀窍,这里先看一个方法:

public int getItemViewType(int position)

getItemViewType方法是在执行onCreateViewHolder(ViewGroup parent, int viewType)前回调用viewType,目的是为了根据viewType不同创建不同的视图。我们可以通过在onCreateViewHolder创建视图的时候,对viewType进行判断,如果添加了头部,在position = 0的时候回调头部的viewType给onCreateViewHolder,从而创建头部。尾部创建方法于此类同,直接看下代码,适配器的实现:

package cn.wsy.recyclerdemo;

import android.content.Context;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by wsy on 2016/8/4.
 */
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyHolder> {

    private RecyclerView mRecyclerView;

    private List<String> data = new ArrayList<>();
    private Context mContext;

    private View VIEW_FOOTER;
    private View VIEW_HEADER;

    //Type
    private int TYPE_NORMAL = 1000;
    private int TYPE_HEADER = 1001;
    private int TYPE_FOOTER = 1002;

    public MyAdapter(List<String> data, Context mContext) {
        this.data = data;
        this.mContext = mContext;
    }

    @Override
    public MyAdapter.MyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_FOOTER) {
            return new MyHolder(VIEW_FOOTER);
        } else if (viewType == TYPE_HEADER) {
            return new MyHolder(VIEW_HEADER);
        } else {
            return new MyHolder(getLayout(R.layout.item_list_layout));
        }
    }

    @Override
    public void onBindViewHolder(MyHolder holder, int position) {
        if (!isHeaderView(position) && !isFooterView(position)) {
            if (haveHeaderView()) position--;
            TextView content = (TextView) holder.itemView.findViewById(R.id.item_content);
            TextView time = (TextView) holder.itemView.findViewById(R.id.item_time);
            content.setText(data.get(position));
            time.setText("2016-1-1");
        }
    }

    @Override
    public int getItemCount() {
        int count = (data == null ? 0 : data.size());
        if (VIEW_FOOTER != null) {
            count++;
        }

        if (VIEW_HEADER != null) {
            count++;
        }
        return count;
    }

    @Override
    public int getItemViewType(int position) {
        if (isHeaderView(position)) {
            return TYPE_HEADER;
        } else if (isFooterView(position)) {
            return TYPE_FOOTER;
        } else {
            return TYPE_NORMAL;
        }
    }

    @Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        try {
            if (mRecyclerView == null && mRecyclerView != recyclerView) {
                mRecyclerView = recyclerView;
            }
            ifGridLayoutManager();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private View getLayout(int layoutId) {
        return LayoutInflater.from(mContext).inflate(layoutId, null);
    }

    public void addHeaderView(View headerView) {
        if (haveHeaderView()) {
            throw new IllegalStateException("hearview has already exists!");
        } else {
            //避免出现宽度自适应
            ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            headerView.setLayoutParams(params);
            VIEW_HEADER = headerView;
            ifGridLayoutManager();
            notifyItemInserted(0);
        }

    }

    public void addFooterView(View footerView) {
        if (haveFooterView()) {
            throw new IllegalStateException("footerView has already exists!");
        } else {
            ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            footerView.setLayoutParams(params);
            VIEW_FOOTER = footerView;
            ifGridLayoutManager();
            notifyItemInserted(getItemCount() - 1);
        }
    }

    private void ifGridLayoutManager() {
        if (mRecyclerView == null) return;
        final RecyclerView.LayoutManager layoutManager = mRecyclerView.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {
            final GridLayoutManager.SpanSizeLookup originalSpanSizeLookup =
                    ((GridLayoutManager) layoutManager).getSpanSizeLookup();
            ((GridLayoutManager) layoutManager).setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    return (isHeaderView(position) || isFooterView(position)) ?
                            ((GridLayoutManager) layoutManager).getSpanCount() :
                            1;
                }
            });
        }
    }

    private boolean haveHeaderView() {
        return VIEW_HEADER != null;
    }

    public boolean haveFooterView() {
        return VIEW_FOOTER != null;
    }

    private boolean isHeaderView(int position) {
        return haveHeaderView() && position == 0;
    }

    private boolean isFooterView(int position) {
        return haveFooterView() && position == getItemCount() - 1;
    }


    public static class MyHolder extends RecyclerView.ViewHolder {

        public MyHolder(View itemView) {
            super(itemView);
        }
    }

}
   private void initRecyc() {
//        mRecyclerView.setLayoutManager(new GridLayoutManager(this,2));
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));

        adapter = new MyAdapter(data, this);
        mRecyclerView.setAdapter(adapter);

        adapter.addFooterView(LayoutInflater.from(this).inflate(R.layout.item_footer_layout,null));
        adapter.addHeaderView(LayoutInflater.from(this).inflate(R.layout.item_header_layout,null));
    }

五、注意的问题

笔者在添加头部尾部的时候,发现在配置RecyclerView,如果模式是配置GridLayoutManager的时候,发现头部会跑到第一格,也就是不是自己想要独立一行的效果,这里贴上关键代码,可以解决(简单数学问题啦哈~):

分割线

RecyclerView 默认是没有分割线的,需要通过下面这个方法添加

public void addItemDecoration(ItemDecoration decor) {    
        addItemDecoration(decor, -1);
  }

ItemDecoration是 RecyclerView 的一个内部抽象类,很明显,这个东西是给我们实现的. 当我们实现 ItemDecoration 的时候,只需要关注 3 个方法

 public class ItemDivider extends RecyclerView.ItemDecoration {   
      // 构造方法,可以在这里做一些初始化,比如指定画笔颜色什么的
      public ItemDivider() {       
      }    

      /**     
       * 指定item之间的间距(就是指定分割线的宽度)   回调顺序 1     
       * @param outRect Rect to receive the output.      
       * @param view    The child view to decorate     
       * @param parent  RecyclerView this ItemDecoration is decorating     
       * @param state   The current state of RecyclerView.     
       */    
       @Override    
       public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {        
         super.getItemOffsets(outRect, view, parent, state);        
       }    

       /**     
        * 在item 绘制之前调用(就是绘制在 item 的底层)  回调顺序 2     
        * 一般分割线在这里绘制     
        * 看到canvas,对自定义控件有一定了解的话,就能想到为什么说给RecyclerView设置分割线更灵活了
        * @param c      Canvas to draw into     
        * @param parent RecyclerView this ItemDecoration is drawing into     
        * @param state  The current state of RecyclerView     
        */    
        @Override    
        public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {        
            super.onDraw(c, parent, state);             
        }    

        /**     
         * 在item 绘制之后调用(就是绘制在 item 的上层)  回调顺序 3     
         * 也可以在这里绘制分割线,和上面的方法 二选一     
         * @param c      Canvas to draw into     
         * @param parent RecyclerView this ItemDecoration is drawing into     
         * @param state  The current state of RecyclerView     
         */    
         @Override   
         public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {        
            super.onDrawOver(c, parent, state);    
         }
     }

getItemOffsets 指定item 之间的间距(默认为0),将来就是在这个间距内绘制分割线


image

getItemOffsets 中为 outRect 设置的4个方向的值,将被计算进所有 decoration 的尺寸中,而这个尺寸,被计入了 RecyclerView 每个 item view 的 padding 中 。

onDraw 在绘制 item之前执行,也就是说,在这里绘制的图形可能会被item遮盖(所以需要指定item之间的间距)

官方实现

public void drawVertical(Canvas c, RecyclerView parent) {
    final int left = parent.getPaddingLeft();
    final int right = parent.getWidth() - parent.getPaddingRight();
    final int childCount = parent.getChildCount();
    for (int i = 0; i < childCount; i++) {
        final View child = parent.getChildAt(i);
        final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                .getLayoutParams();
        final int top = child.getBottom() + params.bottomMargin +
                Math.round(ViewCompat.getTranslationY(child));
        final int bottom = top + mDivider.getIntrinsicHeight();
        mDivider.setBounds(left, top, right, bottom);
        mDivider.draw(c);
    }
}

在 onDraw 为 divider 设置绘制范围,并绘制到 canvas 上,而这个绘制范围可以超出在 getItemOffsets 中设置的范围,但由于 decoration 是绘制在 child view 的底下,所以并不可见,但是会存在 overdraw。

onDrawOver 在绘制item之后执行,在这里绘制的图形,可能会遮住item(说以如果要在这里绘制分割线的话,也要找准位置)

使用的GridItemDecoration

public class GridLayoutItemDecoration extends RecyclerView.ItemDecoration {

    private int mSpanCount;
    private int mHorizonSpan;
    private int mVerticalSpan;
    private int mOrientation;

    public GridLayoutItemDecoration(int count) {
        mSpanCount = count;
        mOrientation = GridLayoutManager.VERTICAL;
    }

    public GridLayoutItemDecoration(int count, int orientation) {
        mSpanCount = count;
        setOrientation(orientation);
    }

    public GridLayoutItemDecoration(int count, int orientation, int horizonSpan, int verticalSpan) {
        this(count);
        setDivideParams(horizonSpan, verticalSpan);
        setOrientation(orientation);
    }

    public void setOrientation(int orientation) {
        if (orientation != LinearLayoutManager.HORIZONTAL && orientation != LinearLayoutManager.VERTICAL) {
            throw new IllegalArgumentException("invalid orientation");
        }
        mOrientation = orientation;
    }


    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        //获取dapter中的位置
        final int position = parent.getChildAdapterPosition(view);
        //获取总共的数量
        final int totalCount = parent.getAdapter().getItemCount();
        //如果是第一列的话left为0,否则为定义的大小
        int left = (position % mSpanCount == 0) ? 0 : mHorizonSpan;
        //如果不是最后一列的话bottom为mVerticalSpan
        int bottom = ((position + 1) % mSpanCount == 0) ? 0 : mVerticalSpan;
        if (isVertical(parent)) {
            //如果不是最后一行的话
            if (!isLastRaw(parent, position, mSpanCount, totalCount)) {
                outRect.set(left/2, 0, mHorizonSpan/2, mVerticalSpan);
            } else {
                outRect.set(left/2, 0, mHorizonSpan/2, 0);
            }
        } else {
            if (!isLastColumn(parent, position, mSpanCount, totalCount)) {
                outRect.set(0, 0, mHorizonSpan, bottom);
            } else {
                outRect.set(0, 0, mHorizonSpan, bottom);
            }
        }
    }

    private boolean isVertical(RecyclerView parent) {
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {
            int orientation = ((GridLayoutManager) layoutManager)
                    .getOrientation();
            return orientation == StaggeredGridLayoutManager.VERTICAL;
        } else if (layoutManager instanceof StaggeredGridLayoutManager) {
            int orientation = ((StaggeredGridLayoutManager) layoutManager)
                    .getOrientation();
            return orientation == StaggeredGridLayoutManager.VERTICAL;
        }
        return false;
    }

    public void setDivideParams(int horizon, int vertical) {
        mHorizonSpan = horizon;
        mVerticalSpan = vertical;
    }

    private boolean isLastRaw(RecyclerView parent, int pos, int spanCount,
                              int childCount) {
        if (isVertical(parent)) {
            if ((pos - pos % spanCount) + spanCount >= childCount)
                return true;
        } else {
            if ((pos + 1) % spanCount == 0) {
                return true;
            }
        }
        return false;
    }


    private boolean isLastColumn(RecyclerView parent, int pos, int spanCount,
                                 int childCount) {
        if (isVertical(parent)) {
            if ((pos + 1) % spanCount == 0) {
                return true;
            }
        } else {
            childCount = childCount - childCount % spanCount;
            if (pos >= childCount)
                return true;
        }
        return false;
    }
}

StickyHeader的Decoration实现

完整代码地址https://github.com/sangx123/sangxiang/tree/master/StickyHeaderDemo-master

public class StickyHeaderDecoraton extends RecyclerView.ItemDecoration {

    public static final String TAG = "StickyHeaderDecoraton";
    private float mDividerHeight;
    private Paint mPaint;
    private Paint mTextPaint;
    private List<AppInfoBean> mDatas;

    public StickyHeaderDecoraton(int height, Context ctx) {
        this(height, Color.GRAY, ctx);
    }


    public StickyHeaderDecoraton(int height, @ColorInt int color, Context ctx) {
        this.mDividerHeight = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, height, ctx.getResources().getDisplayMetrics());
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(color);


        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setColor(Color.RED);
        mTextPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 14, ctx.getResources().getDisplayMetrics()));
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        // 如果是头部,留出顶部空间用作画Header
        if (((SectionBean) view.getTag()).isGroupStart) {
            outRect.set(0, (int) mDividerHeight, 0, 0);
        }
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {

    }

    /**
     * 画头部
     *
     * @param c
     * @param parent
     * @param view
     * @param position
     */
    private void drawHeader(Canvas c, RecyclerView parent, View view, int position) {
        RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
        final int left = parent.getPaddingLeft();
        final int right = parent.getWidth() - parent.getPaddingRight();
        int bottoom = view.getTop() - params.topMargin - Math.round(ViewCompat.getTranslationY(view));
        int top = (int) (bottoom - mDividerHeight);
        // 计算文字居中时候的基线
        Rect targetRect = new Rect(left, top, right, bottoom);
        Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt();
        int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
        c.drawRect(left, top, right, bottoom, mPaint);
        c.drawText(mDatas.get(position).tag, left, baseline, mTextPaint);
    }

    @Override
    public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            //循环获取第i个iew
            View view = parent.getChildAt(i);
            int position = parent.getChildAdapterPosition(view);
            // 如果是头,画之
            if (position != RecyclerView.NO_POSITION
                    && ((SectionBean) view.getTag()).isGroupStart) {
                drawHeader(c, parent, view, position);
            }
        }

        View view = parent.getChildAt(0);
        View view2 = parent.getChildAt(1);
        if (view != null && view2 != null) {
            SectionBean section1 = (SectionBean) view.getTag();
            SectionBean section2 = (SectionBean) view2.getTag();

            int position = parent.getChildAdapterPosition(view);
            final int left = parent.getPaddingLeft();
            final int right = parent.getWidth() - parent.getPaddingRight();
            int bottoom = (int) mDividerHeight;
            int top = 0;
            // 判断是否达到临界点
            // (第一个可见item是每组的最后一个,第二个可见tiem是下一组的第一个,并且第一个可见item的底部小于header的高度)
            // 这里直接判断item的底部位置小于header的高度有点欠妥,应该还要考虑paddingtop以及margintop,这里展示不考虑了
            if (section1.isGroupEnd && section2.isGroupStart && view.getBottom() <= mDividerHeight) {
                bottoom = view.getBottom();
                top = (int) (bottoom - mDividerHeight);
            }
            // 计算文字居中时候的基线
            Rect targetRect = new Rect(left, top, right, bottoom);
            Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt();
            int baseline = (targetRect.bottom + targetRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
            // 背景
            c.drawRect(left, top, right, bottoom, mPaint);
            // 文字
            c.drawText(mDatas.get(position).tag, left, baseline, mTextPaint);
        }
    }

    /**
     * 头部的名称
     *
     * @param mDatas
     */
    public void setDatas(List<AppInfoBean> mDatas) {
        this.mDatas = mDatas;
    }
}

git完整版代码为
compile 'com.timehop.stickyheadersrecyclerview:library:0.4.3@aar'
包含点击header的点击事件等等。

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

推荐阅读更多精彩内容