Android——RecyclerView入门学习之DiffUtil(四)

学习资料:

十分感谢 :)

DiffUtilandroid.support:design:24.2.0中推出的一个RecyclerView的辅助工具类

作用:
Adapter中的数据发生变化时,用来比较Adpater中变化前和后两个数据集合,实现差异化更新。而两个集合的整个比较过程,以及针对数据变化该调用哪些notifyData方法,内部会自动进行处理


1.简单使用 <p>

使用的步骤:

  1. 创建一个DiffUtilCallback继承DiffUtil.Callback,重写里面的抽象方法
  2. 使用DiffUtil.calculateDiff(callback),计算差异结果,需要DiffUtilCallback,返回对象为DiffUtil.DiffResult
  3. 使用DiffResult对象,通过diffResult.dispatchUpdatesTo(adapter)方法,将DiffUtilAdapter关联起来

大致的思路就是这个样子,基本也是死套路


效果:

改变全部学科,并在加入一条

点击RecyclerView的一个item,将学科全部改变,并在postion2处,加入一个item


1.1 创建DiffUtilCallback

直接继承DiffUtil.Callback,之后Anddroid Studio便会自动提示必须要重写4个的方法

class DiffUtilCallback extends DiffUtil.Callback {

    private List<ResourceBean> oldList = new ArrayList<>();
    private List<ResourceBean> newList = new ArrayList<>();

    public void setNewList(List<ResourceBean> newList) {
        if (null != newList) {
            this.newList.clear();
            this.newList.addAll(newList);
        }
    }

    public void setOldList(List<ResourceBean> oldList) {
        if (null != oldList) {
            this.oldList.clear();
            this.oldList.addAll(oldList);
        }
    }

    /**
     * 数据变化之前,旧的数据集合size
     */
    @Override
    public int getOldListSize() {
        Log.e("oldList.size", "---->" + oldList.size());
        return oldList.size();
    }

    /**
     * 数据变化之后,新的数据集合size
     */
    @Override
    public int getNewListSize() {
        Log.e("newList.size", "---->" + newList.size());
        return newList.size();
    }

    /**
     * 根据position来对新旧集合中的Object进行判断
     */
    @Override
    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
        ResourceBean oldBean = oldList.get(oldItemPosition);
        ResourceBean newBean = newList.get(newItemPosition);
        Log.e("areItemsTheSame", oldItemPosition + "--" + newItemPosition + "---->" + oldBean.getSubject().equals(newBean.getSubject()));
        return oldBean.getSubject().equals(newBean.getSubject());//比较科目
    }

    /**
     * 根据position来对新旧集合中的Object的内容进行判断
     */
    @Override
    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
        ResourceBean oldBean = oldList.get(oldItemPosition);
        ResourceBean newBean = newList.get(newItemPosition);
        Log.e("areContentsTheSame", oldItemPosition + "--" + newItemPosition + "---->" + oldBean.getName().equals(newBean.getName()));
        return oldBean.getName().equals(newBean.getName());//比较学科
    }

    /**
     * 返回的特定的差异化结果
     */
    @Nullable
    @Override
    public Object getChangePayload(int oldItemPosition, int newItemPosition) {
        ResourceBean oldBean = oldList.get(oldItemPosition);
        ResourceBean newBean = newList.get(newItemPosition);
        Bundle diffBundle = new Bundle();
        if (!oldBean.getName().equals(newBean.getName())) {
            diffBundle.putSerializable("newBean",newBean);
        }
        return diffBundle;
    }
}

ResourceBean内只有两个String类型的subject,nameset,get方法,不再给出


1.2 Adapter代码

适配器就是一个一般写法的适配,代码比较简单

class PayloadAdapter extends RecyclerView.Adapter<PayloadAdapter.RecyclerHolder> {
    private Context mContext;
    private List<ResourceBean> dataList = new ArrayList<>();
    private onRecyclerItemClickerListener mListener;

    public PayloadAdapter(RecyclerView recyclerView) {
        this.mContext = recyclerView.getContext();
    }


    /**
     * 增加点击监听
     */
    public void setItemListener(onRecyclerItemClickerListener mListener) {
        this.mListener = mListener;
    }

    /**
     * 设置数据源
     */
    public void setData(List<ResourceBean> dataList) {
        if (null != dataList) {
            this.dataList.clear();
            this.dataList.addAll(dataList);
        }
    }

    public List<ResourceBean> getDataList() {
        return dataList;
    }

    @Override
    public RecyclerHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(mContext).inflate(R.layout.id_rv_item_layout, parent, false);
        return new RecyclerHolder(view);
    }

    @Override
    public void onBindViewHolder(RecyclerHolder holder, int position) {
        ResourceBean resourceBean = dataList.get(position);
        holder.textView.setText(resourceBean.getSubject() + "--->" + resourceBean.getName());
        holder.textView.setOnClickListener(getOnClickListener(position));
    }

    @Override
    public void onBindViewHolder(RecyclerHolder holder, int position, List<Object> payloads) {
        super.onBindViewHolder(holder, position, payloads);
        if (payloads.isEmpty()) {
            Log.e("isEmpty", "---->" + payloads.isEmpty());
            onBindViewHolder(holder, position);
        }else {
            Log.e("isEmpty", "---->no null");
            Bundle bundle = (Bundle) payloads.get(0);
            ResourceBean resourceBean = (ResourceBean) bundle.get("newBean");
            if (null == resourceBean) return;
            // Log.e("change", "---->" + change);
            holder.textView.setText(resourceBean.getSubject() + "--->" + resourceBean.getName());
        }
    }


    private View.OnClickListener getOnClickListener(final int position) {
        return new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (null != mListener && null != v) {
                    mListener.onRecyclerItemClick(v, dataList.get(position), position);
                }
            }
        };
    }

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

    class RecyclerHolder extends RecyclerView.ViewHolder {
        TextView textView;

        private RecyclerHolder(View itemView) {
            super(itemView);
            textView = (TextView) itemView.findViewById(R.id.tv__id_item_layout);
        }
    }

    /**
     * 点击监听回调接口
     */
    public interface onRecyclerItemClickerListener {
        void onRecyclerItemClick(View view, Object data, int position);
    }
}

加入了一个onBindViewHolder(RecyclerHolder holder, int position, List<Object> payloads)方法


1.3 与Adapter进行关联 <p>

在创建Adapter的Activity中,完整代码:

 class DiffUtil2Activity extends AppCompatActivity {
    private RecyclerView rv;
    private PayloadAdapter mAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_diff_util2);
        init();
    }

    private void init() {
        rv = (RecyclerView) findViewById(R.id.rv_diff2_util_activity);
        //布局管理器
        LinearLayoutManager manager = new LinearLayoutManager(this);
        manager.setOrientation(LinearLayoutManager.VERTICAL);
        rv.setLayoutManager(manager);
        //设置ItemDecoration
        rv.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL));
        //适配器
        mAdapter = new PayloadAdapter(rv);
        rv.setAdapter(mAdapter);
        //添加数据
        addData();
    }

    private void addData() {
        DiffUtilCallback callback = new DiffUtilCallback();
        callback.setOldList(mAdapter.getDataList());
        List<ResourceBean> oldList = new ArrayList<>();
        for (int i = 'A'; i < 'D'; i++) {
            for (int j = 0; j < 15; j++) {
                ResourceBean resourceBean = new ResourceBean();
                resourceBean.setSubject("学科" + (char) i);
                resourceBean.setName("资料" + j);
                oldList.add(resourceBean);
            }
        }
        callback.setNewList(oldList);
        DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(callback);//子线程计算差异
        diffResult.dispatchUpdatesTo(mAdapter);
        //记得将新数据替换adapter中旧的集合数据
        mAdapter.setData(oldList);
        //设置点击事件
        mAdapter.setItemListener(new PayloadAdapter.onRecyclerItemClickerListener() {
            @Override
            public void onRecyclerItemClick(View view, Object data, int position) {
                changeData();
            }
        });
    }

    private void changeData() {
        DiffUtilCallback callback = new DiffUtilCallback();
        callback.setOldList(mAdapter.getDataList());
        List<ResourceBean> newList = new ArrayList<>();
        for (int i = 'A'; i < 'D'; i++) {
            for (int j = 0; j < 15; j++) {
                ResourceBean resourceBean = new ResourceBean();
                resourceBean.setSubject("学科" + (char) i);
                resourceBean.setName("吃饭课" + j);
                newList.add(resourceBean);
            }
        }
        ResourceBean r = new ResourceBean();
        r.setSubject("体育课");
        r.setName("---->666666");
        newList.add(4, r);
        callback.setNewList(newList);
        //这是主线程。下面有案例放在了子线程中
        DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(callback);//子线程计算差异 
        diffResult.dispatchUpdatesTo(mAdapter);
        mAdapter.setData(newList);
    }
     @Override
    protected void onDestroy() {
        super.onDestroy();
        if (null != rv) rv.setAdapter(null);
    }
}

而关联操作关键的代码也就2

DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(callback);
diffResult.dispatchUpdatesTo(adapter);//与Adapter进行关联

这时计算两个List的过程是放在UI主线程到了实际开发中,数据量往往会比较大,计算差异化这一步,最好做些处理放在一个子线程中

在上一篇RecyclerView.Adapter学习的1.5小节一系列的notifyData方法中,移除或者增加同类型对象的item时,说会感觉卡顿,使用DiffUtil感觉好多了,可能心理作用。。。

如果只是想做个,感觉没必要使用DiffUtilRecycelrView.Adapter提供的局部刷新感觉蛮好用的了。有一个特别适合使用的场景便是下拉刷新,不仅有动画,效率也有提高,尤其是下拉刷新操作后,Adapter内集合数据并没有发生改变,不需要进行重新绘制RecyclerView


2. DiffUtil <p>

DiffUtil.DiffResultDiffUtil.CallbackDiffUtil的两个内部类。DiffUtil对外提供的public方法只有2

方法1:

 /**
  * Calculates the list of update operations that can covert one list into the other one.
  *
  * @param cb The callback that acts as a gateway to the backing list data
  *
  * @return A DiffResult that contains the information about the edit sequence to convert the old list into the new list.
  */
public static DiffResult calculateDiff(Callback cb) {
    return calculateDiff(cb, true);
}

这个方法用于计算newListoldList的差异,在内部计算出后的操作便是将两个列表转换成为另一个新的集合,但转换过程并不用再操心的

在方法内,调用了两个参数的calculateDiff(cb, true),便是方法2


方法2:

/**
 * Calculates the list of update operations that can covert one list into the other one.
 * <p>
 * If your old and new lists are sorted by the same constraint and items never move (swap positions), you can disable move detection which takes <code>O(N^2)</code> time where N is the number of added, moved, removed items.
 *
 * @param cb The callback that acts as a gateway to the backing list data
 * @param detectMoves True if DiffUtil should try to detect moved items, false otherwise.
 *
 * @return A DiffResult that contains the information about the edit sequence to convert the old list into the new list.
 */
public static DiffResult calculateDiff(Callback cb, boolean detectMoves) {
   ...

   //做过修改的Eugene W. Myers’s difference算法
   final Snake snake = diffPartial(cb, range.oldListStart, range.oldListEnd, range.newListStart, range.newListEnd, forward, backward, max);
   
   ...
   
   return new DiffResult(cb, snakes, forward, backward, detectMoves);
  • boolean detectMoves:是否检测item是否移动,使用方法1默认为ture,这样相对fasle也会耗时一些

具体哪种场景下设置为false,目前还不了解,有知道的同学请留言告诉一声

在这个方法中,calculateDiff()算是整个DiffUtil的核心操作,计算差异化操作,里面涉及到的算法以后再进行学习

最后返回了一个DiffResult对象,这个对象内部封装着进行了差异化比较操作后的转换的数据集合。这个对象内部涉及到的转换操作,暂时也不打算进行学习,等对于DiffUtil的使用相对熟练一些后,再进行学习


2.1 DiffUtil.Callback <p>

在这个抽象类中,一共有5个抽象方法:

方法 作用
public abstract int getOldListSize() 新集合的size
public abstract int getNewListSize() 旧集合的size
public abstract boolean areItemsTheSame() 判断两个集合中Object是否相同
public abstract boolean areContentsTheSame() 检查两个item的对象内容是否相同
public Object getChangePayload() @Nullable,非必需重写的方法,得到封装新旧集合中两个item负载对象,默认返回null

前两个方法很好理解,后面3个方法需要根据源码中的注释来学习


2.1.1 areItemsTheSame()判断Item中Objecct是否相同 <p>

源码:

/**
 * Called by the DiffUtil to decide whether two object represent the same Item.
 * 进行比较新旧集合中在oldItemPosition与newItemPosition两个对象是否相同
 *
 * For example, if your items have unique ids, this method should check their id equality.
 *例如,当集合中的对象有唯一的标记ids时,就比较两个对象的ids
 *
 * @param oldItemPosition The position of the item in the old list
 * 对象在旧的集合中的position
 * @param newItemPosition The position of the item in the new list
 * 对象在新的集合中的position
 * @return True if the two items represent the same object or false if they are different.
 * 返回比较结果
 */
 public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);

这个方法的返回结果会对areContentsTheSame()方法有影响


2.1.2 areContentsTheSame()判断Objecct中的内容是否相同<p>

源码:

 /**
  * Called by the DiffUtil when it wants to check whether two items have the same data.
  * 比较新旧集合对象中的内容是否一样
  * DiffUtil uses this information to detect if the contents of an item has changed.
  * DiffUtil利用这个方法的返回值来检测一个对象是否发生变化
  * 
  * DiffUtil uses this method to check equality instead of {@link Object#equals(Object)}
  * DiffUtil使用这个方法代替equals()来检测是否相等
  * so that you can change its behavior depending on your UI.
  * 可以根据UI需求来改变返回值
  *
  * For example, if you are using DiffUtil with a {@link android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}, you should return whether the items' visual representations are the same.
  * 例如,在配合RecyclerView.Adapter使用时,需要返回RecycelrView的item视觉效果是否一致
  *这里视觉效果应该指的就是动画吧
  *
  * This method is called only if {@link #areItemsTheSame(int, int)} returns{@code true} for these items.
  *这个方法只有在areItemsTheSame()方法返回ture时才会被调用
  * @param oldItemPosition The position of the item in the old list
  * @param newItemPosition The position of the item in the new list which replaces the oldItem
  * @return True if the contents of the items are the same or false if they are different.
  */
public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);

只有当areContentsTheSame()方法返回true时,这个方法才会被掉y用。不难理解,只有听一个类型的对象才可以比较的意义


2.1.3 getChangePayload()得到封装差异化信息的对象 <p>

源码:

 /**
  * When {@link #areItemsTheSame(int, int)} returns {@code true} for two items and {@link #areContentsTheSame(int, int)} returns false for them, DiffUtil calls this method to get a payload about the change.
  * 当areItemsTheSame()返回true并且areContentsTheSame返回false,DiffUtil便会调用这个方法将两个item的差异封装在一个负载对象中
  *
  * <p>
  * For example, if you are using DiffUtil with {@link RecyclerView}, you can return the particular field that changed in the item and your{@link android.support.v7.widget.RecyclerView.ItemAnimator ItemAnimator} can use that information to run the correct animation.
  * 例如,当配合RecyclerView使用DiffUtil时,可以将新的item改变的特定属性返回,还可以使用返回的差异属性利用RecyclerView.ItemAnimator使用改变的动画
  *
  * <p>
  * Default implementation returns {@code null}.
  *默认返回为null
  * @param oldItemPosition The position of the item in the old list
  * @param newItemPosition The position of the item in the new list
  *
  * @return A payload object that represents the change between the two items.
  */
  @Nullable
  public Object getChangePayload(int oldItemPosition, int newItemPosition) {
        return null;
  }

使用这个方法,有特定的条件:
areItemsTheSame()返回true,areContentsTheSame返回false

这个方法还要重写RecyclerView.AdapteronBindViewHolder(RecyclerHolder holder, int position, List<Object> payloads)

DiffUtil将返回的封装差异化的对象存储在一个List集合中,集合payloads对象不会为null,但可能会为empty,也就是只有集合对象,而集合中却是空的。在onBindViewHolder()需要加判断payloads.isEmpty(),若为empty就调用两个参数的onBindViewHolder()


简单使用:

DiffUtilCallback中对应:

    /**
     * 返回的特定的差异化结果
     */
    @Nullable
    @Override
    public Object getChangePayload(int oldItemPosition, int newItemPosition) {
        ResourceBean oldBean = oldList.get(oldItemPosition);
        ResourceBean newBean = newList.get(newItemPosition);
        Bundle diffBundle = new Bundle();
        if (!oldBean.getName().equals(newBean.getName())) {
            diffBundle.putSerializable("newBean",newBean);
        }
        return diffBundle;
    }```

**在Adapter中对应:**
```java
    @Override
    public void onBindViewHolder(RecyclerHolder holder, int position, List<Object> payloads) {
        super.onBindViewHolder(holder, position, payloads);
        if (payloads.isEmpty()) {
            Log.e("isEmpty", "---->" + payloads.isEmpty());
            onBindViewHolder(holder, position);
        }else {
            Log.e("isEmpty", "---->no null");
            Bundle bundle = (Bundle) payloads.get(0);
            ResourceBean resourceBean = (ResourceBean) bundle.get("newBean");
            if (null == resourceBean) return;
            // Log.e("change", "---->" + change);
            holder.textView.setText(resourceBean.getSubject() + "--->" + resourceBean.getName());
        }
    }

注意要先判断payloads中是否含有差异化数据


子线程模拟网络请求,完整的Activity代码:

public class DiffUtilActivity extends AppCompatActivity {

    private RecyclerView rv;
    private PayloadAdapter mAdapter;
    private StaticHandler mHandler = null;
    private static final int ADD_DATA = 2 << 5;
    private static final int CHANGE_DATA = 2 << 6;
    private List<ResourceBean> oldList;
    private List<ResourceBean> newList;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_diff_util);
        init();
    }

    private void init() {
        mHandler = new StaticHandler(DiffUtilActivity.this);
        rv = (RecyclerView) findViewById(R.id.rv_diff_util_activity);
        //布局管理器
        LinearLayoutManager manager = new LinearLayoutManager(this);
        manager.setOrientation(LinearLayoutManager.VERTICAL);
        rv.setLayoutManager(manager);
        //设置ItemDecoration
        rv.addItemDecoration(new DividerItemDecoration(this, LinearLayoutManager.VERTICAL));
        //适配器
        mAdapter = new PayloadAdapter(rv);
        rv.setAdapter(mAdapter);
        //设置点击事件
        mAdapter.setItemListener(new PayloadAdapter.onRecyclerItemClickerListener() {
            @Override
            public void onRecyclerItemClick(View view, Object data, int position) {
                changeData();
            }
        });
        //添加数据
        addData();
    }

    public static class StaticHandler extends Handler {
        private final WeakReference<DiffUtilActivity> mWeakReference;

        private StaticHandler(DiffUtilActivity activity) {
            mWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            DiffUtilActivity activity = mWeakReference.get();
            if (null != activity) {
                PayloadAdapter adapter = activity.mAdapter;
                if (msg.what == ADD_DATA) {
                    List<ResourceBean> list = activity.oldList;
                    DiffUtil.DiffResult diffResult = (DiffUtil.DiffResult) msg.obj;
                    diffResult.dispatchUpdatesTo(adapter);
                    adapter.setData(list);
//                 adapter.notifyDataSetChanged(); //由上面代替
                } else if (msg.what == CHANGE_DATA) {
                    List<ResourceBean> newData = activity.newList;
                    DiffUtil.DiffResult diffResult = (DiffUtil.DiffResult) msg.obj;
                    diffResult.dispatchUpdatesTo(adapter);
                    adapter.setData(newData);
                }
            }
        }
    }


    /**
     * 点击之后,更改数据
     */
    private void changeData() {

        new Thread(new Runnable() {
            @Override
            public void run() {
                Message message = mHandler.obtainMessage();
                DiffUtilCallback callback = new DiffUtilCallback();
                callback.setOldList(mAdapter.getDataList());
                newList = new ArrayList<>();
                //模拟下拉刷新数据没有变化
//                for (int i = 'A'; i < 'D'; i++) {
//                    for (int j = 0; j < 15; j++) {
//                        ResourceBean resourceBean = new ResourceBean();
//                        resourceBean.setSubject("学科" + (char) i);
//                        resourceBean.setName("资料" + j);
//                        newList.add(resourceBean);
//                    }
//                }
                for (int i = 'A'; i < 'D'; i++) {
                    for (int j = 0; j < 15; j++) {
                        ResourceBean resourceBean = new ResourceBean();
                        resourceBean.setSubject("学科" + (char) i);
                        resourceBean.setName("吃饭课" + j);
                        newList.add(resourceBean);
                    }
                }
                ResourceBean r = new ResourceBean();
                r.setSubject("体育课");
                r.setName("---->666666");
                newList.add(4, r);
                callback.setNewList(newList);
                DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(callback);//子线程计算差异
                message.what = CHANGE_DATA;
                message.obj = diffResult;
                try {
                    TimeUnit.MILLISECONDS.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                mHandler.sendMessage(message);
            }
        }).start();
    }

    /**
     * 第一次加入数据
     */
    private void addData() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Message message = mHandler.obtainMessage();
                DiffUtilCallback callback = new DiffUtilCallback();
                callback.setOldList(mAdapter.getDataList());
                oldList = new ArrayList<>();
                for (int i = 'A'; i < 'D'; i++) {
                    for (int j = 0; j < 15; j++) {
                        ResourceBean resourceBean = new ResourceBean();
                        resourceBean.setSubject("学科" + (char) i);
                        resourceBean.setName("资料" + j);
                        oldList.add(resourceBean);
                    }
                }
                try {
                    TimeUnit.MILLISECONDS.sleep(500);//模拟网络耗时
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                callback.setNewList(oldList);
                DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(callback);//子线程计算差异
                message.what = ADD_DATA;
                message.obj = diffResult;
                mHandler.sendMessage(message);
            }
        }).start();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (null != rv) rv.setAdapter(null);
    }
}

补充:

2016 12月8号 00:16 发现之前写的有严重错误,已经修改。改代码一直到1:02,越看越感觉代码烂,困了,先睡觉,以后对DiffUtil再熟悉一点再来修改


3.最后 <p>

这篇学习记录得稀里糊涂的,希望不会坑到看博客的同学

本人很菜,有错误请指出

共勉 :)

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

推荐阅读更多精彩内容