Android仿微信搜索,Recyclerview+關鍵字動態匹配篩選變色效果(Edittext+Recyclerview)

一、概述

??我們要實現的是模仿微信的搜索效果,通過監聽Edittext中文字的變化動態匹配Recyclerview列表中文字,刷新列表,并將關鍵字變色顯示。
??首先上圖,展示我們將要實現的效果(關鍵字是有顏色變化的,列表也有刷新。我們的gif圖表現的不是很明顯)。

1. 關鍵字全部變色效果

??然后是部分匹配——>即例如我們數據“第一天第一天”,只有第一個“一”變色。我本意是要寫上邊那個全部變色的效果的,偶然發現了只能匹配部分的問題,所以拿出來問題與解決方法與大家分享下。
2. 部分匹配效果

二、實現

??所有代碼已上傳,并且有詳細的注釋,鏈接地址在文末。大家稍后可以下載。

  1. 首先上item布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:fresco="http://schemas.android.com/apk/res-auto"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:id="@+id/ll_item"
    android:layout_marginTop="3dp"
    android:background="@drawable/shape_search"
    android:layout_marginRight="8dp"
    android:layout_marginLeft="8dp"
    android:layout_height="60dp">
    <com.facebook.drawee.view.SimpleDraweeView
        android:id="@+id/imgv_simple"
        android:layout_marginRight="5dp"
        android:layout_marginLeft="10dp"
        android:layout_gravity="center"
        fresco:backgroundImage="@mipmap/imgv_girl"
        fresco:placeholderImage="@mipmap/imgv_girl"
        fresco:roundBottomLeft="false"
        fresco:roundBottomRight="true"
        fresco:roundTopLeft="true"
        fresco:roundTopRight="false"
        fresco:roundedCornerRadius="50dp"
        android:layout_width="45dp"
        android:layout_height="45dp" />
    <TextView
        android:textColor="#7f44ff"
        android:gravity="center"
        android:text="123"
        android:id="@+id/tv_text"
        android:marqueeRepeatLimit="marquee_forever"
        android:ellipsize="marquee"
        android:focusable="true"
        android:singleLine="true"
        android:layout_gravity="center"
        android:layout_width="match_parent"
        android:layout_height="45dp" />
</LinearLayout>

??利用Fresco的圓角效果實現我們item中圖片的葉子形(姑且叫它葉子形吧)樣式,并且給整個LinearLayout布局加一個5dp圓角并且帶黑色邊框的background,如此便形成了我們效果圖中每個item的效果。

  1. 列表頁布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#f5f2f2"
    tools:context="com.example.txs.myapplication.MainActivityWhole">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:background="#6CC4B8"
        android:gravity="center"
        android:text="搜索匹配關鍵字(全部變色)"
        android:textColor="#fff" />
        <LinearLayout
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:layout_marginTop="5dp"
            android:layout_width="match_parent"
            android:layout_height="35dp"
            android:layout_gravity="center"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:background="@drawable/shape_search"
            android:orientation="horizontal">
            <ImageView
                android:layout_marginLeft="3dp"
                android:layout_width="25dp"
                android:layout_height="25dp"
                android:layout_gravity="center"
                android:scaleType="centerInside"
                android:src="@mipmap/imgv_search" />
            <EditText
                android:id="@+id/edt_search"
                android:layout_width="0dp"
                android:layout_height="28dp"
                android:layout_gravity="center"
                android:layout_weight="1"
                android:background="@null"
                android:imeOptions="actionSearch"
                android:lines="1"
                android:singleLine="true" />
            <ImageView
                android:layout_marginRight="3dp"
                android:id="@+id/imgv_delete"
                android:layout_width="25dp"
                android:layout_height="25dp"
                android:layout_gravity="center"
                android:scaleType="centerInside"
                android:src="@mipmap/imgv_delete"
                android:visibility="gone" />
        </LinearLayout>
    <android.support.v7.widget.RecyclerView
        android:id="@+id/rc_search"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>

??整個頁面很簡單,由上到下是title,搜索框,Recyclerview。我們的重點不在這里,所以界面搭建的不是很復雜,能看就好~接下來才是我們的重點。

  1. 適配器①—>部分匹配的適配器:
/**
 * @author txs
 * @date 2018/01/16
 */

public class RcAdapterPartChange extends RecyclerView.Adapter<RcAdapterPartChange.MyViewHolder> {
    private Context context;
    /**
     * adapter傳遞過來的數據集合
     */
    private List<String> list = new ArrayList<>();
    /**
     * 變色數據的其實位置 position
     */
    private int beginChangePos;
    /**
     * 需要改變顏色的text
     */
    private String text;
    /**
     * text改變的顏色
     */
    private ForegroundColorSpan span;

    /**
     * 在MainActivity中設置text和span
     */
    public void setText(String text, ForegroundColorSpan span) {
        this.text = text;
        this.span = span;
    }

    public RcAdapterPartChange(Context context, List<String> list) {
        this.context = context;
        this.list = list;
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        MyViewHolder holder = new MyViewHolder(LayoutInflater.from(context).inflate(R.layout.item_search, parent, false));
        return holder;
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, final int position) {
        /**如果沒有進行搜索操作或者搜索之后點擊了刪除按鈕 我們會在MainActivity中把text置空并傳遞過來*/
        if (text != null) {
            //獲取匹配文字的 position
            beginChangePos = list.get(position).indexOf(text);
            // 文字的builder 用來做變色操作
            SpannableStringBuilder builder = new SpannableStringBuilder(list.get(position));
            //如果沒有匹配到關鍵字的話 list.get(position).indexOf(text)會返回-1
            if (beginChangePos != -1) {
                //設置呈現的文字
                builder.setSpan(span, beginChangePos, beginChangePos + text.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                holder.mTvText.setText(builder);
            }
        } else {
            holder.mTvText.setText(list.get(position));
        }
        //點擊監聽
        holder.mLlItem.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                onItemClickListener.onClick(view, position);
            }
        });
    }

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

    public interface onItemClickListener {
        void onClick(View view, int pos);
    }

    /**
     * Recyclerview的點擊監聽接口
     */
    private onItemClickListener onItemClickListener;

    public void setOnItemClickListener(RcAdapterPartChange.onItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

    class MyViewHolder extends RecyclerView.ViewHolder {
        private LinearLayout mLlItem;
        private SimpleDraweeView mImgvSimple;
        private TextView mTvText;

        public MyViewHolder(View itemView) {
            super(itemView);
            mLlItem = (LinearLayout) itemView.findViewById(R.id.ll_item);
            mImgvSimple = (SimpleDraweeView) itemView.findViewById(R.id.imgv_simple);
            mTvText = (TextView) itemView.findViewById(R.id.tv_text);
        }
    }
}

??先說說這個適配器的瑕疵,由于.indexOf()的坑,使用這個適配器產生的最終效果如我們的第二張圖,只能匹配每個item第一條關鍵字,即比如我們的數據“第一天一天”,它只能使第一個"一"字變色(當然整個列表的刷新和匹配效果是沒問題的,它只影響了關鍵字的變色效果。僅此而已!!)。而且不論我們后續還有多少個“一”,它依舊只能變色第一個“一”字。有的人可能碰巧會需要這個效果,所以我放上來代碼和解決思路供大家參考。
首先我們適配器在創建時傳過來一個list集合,集合里面可以包含你從網絡或者數據庫或者其他方式獲取到的數據(已經經過篩選,比如我們搜索“一”字,傳過來的集合是那些包含“一”字的數據)。然后提供一個set方法void setText(String text, ForegroundColorSpan span),在刷新適配器之前用setText()將我們的關鍵字以及關鍵字要變成的顏色傳過來,像這樣:

           //設置要變色的關鍵字
            adapter.setText(text, redSpan);
           //刷新適配器
            refreshUI();

然后適配器就會重新執行到onBindViewHolder方法,刷新界面,就可以看到我們的篩選和變色效果了。下面我們來分析這段代碼:

 /**如果沒有進行搜索操作或者搜索之后點擊了刪除按鈕 我們會在MainActivity中把text置空并傳遞過來*/
        if (text != null) {
            //獲取匹配文字的 position
            beginChangePos = list.get(position).indexOf(text);
            // 文字的builder 用來做變色操作
            SpannableStringBuilder builder = new SpannableStringBuilder(list.get(position));
            //如果沒有匹配到關鍵字的話 list.get(position).indexOf(text)會返回-1
            if (beginChangePos != -1) {
                //設置呈現的文字
                builder.setSpan(span, beginChangePos, beginChangePos + text.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                holder.mTvText.setText(builder);
            }
        } else {
            holder.mTvText.setText(list.get(position));
        }

??開始我們有一個對text判空的操作,在后面的Activity代碼中你可以看到,我在刷新適配器之前,先判斷edittext中是否輸入了關鍵字,如果有關鍵字則會通過setText(text,span)把關鍵字傳遞過來,如果沒有關鍵字則會置空setText(null,null)。如果有關鍵字的話,我們用indexOf()找到它的起始位置(position),當然如果沒有匹配到關鍵字的話list.get(position).indexOf(text)會返回-1,然后我們會通過SpannableStringBuilder對關鍵字進行變色操作。下面我們再來驗證一些indexOf()的問題,上代碼:

 public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button mBtnIndexof;
    private Button mBtnMatcher;
    private String mString;
    private String mKeyword;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mBtnIndexof = (Button) findViewById(R.id.btn_indexof);
        mBtnMatcher = (Button) findViewById(R.id.btn_matcher);
        mString = "第一天第一夜第一個時辰";
        mKeyword = "一";
        setListener();
    }
    private void setListener() {
        mBtnIndexof.setOnClickListener(this);
        mBtnMatcher.setOnClickListener(this);
    }
    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_indexof:
               int pos = mString.indexOf(mKeyword);
                Log.e("test", "indexof== "+pos);
                break;
            case R.id.btn_matcher:
                //條件 keyword
                Pattern pattern = Pattern.compile(mKeyword);
                //匹配
                Matcher matcher = pattern.matcher(mString);
                while (matcher.find()) {
                    int start = matcher.start();
                    Log.e("test", "macher== "+start);
                    int end = matcher.end();
                }
                break;
            default:
                break;
        }
    }
}

結果:

01-16 22:23:06.831 1581-1581/com.example.testinndexof E/test: indexof== 1
01-16 22:23:10.139 1581-1581/com.example.testinndexof E/test: macher== 1
01-16 22:23:10.139 1581-1581/com.example.testinndexof E/test: macher== 4
01-16 22:23:10.139 1581-1581/com.example.testinndexof E/test: macher== 7

??通過控制臺輸出的結果我們可以看到,indexOf()只匹配到了第一個“一”的位置,之后沒有繼續匹配。這也是indexOf()的原理所致。所以用此方法只能匹配到首個對應字符的問題已經找到了,接下來應該怎么做讓它完全匹配呢?上述代碼已經給出了解決方法,用Matchermatcher.find()

  1. 適配器②—>全部匹配的適配器:
/**
 * @author txs
 * @date 2018/01/16
 */

public class RcAdapterWholeChange extends RecyclerView.Adapter<RcAdapterWholeChange.MyViewHolder> {
    private Context context;
    /**
     * adapter傳遞過來的數據集合
     */
    private List<String> list = new ArrayList<>();
    /**
     * 需要改變顏色的text
     */
    private String text;

    /**
     * 在MainActivity中設置text
     */
    public void setText(String text) {
        this.text = text;
    }

    public RcAdapterWholeChange(Context context, List<String> list) {
        this.context = context;
        this.list = list;
    }

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        MyViewHolder holder = new MyViewHolder(LayoutInflater.from(context).inflate(R.layout.item_search, parent, false));
        return holder;
    }

    @Override
    public void onBindViewHolder(MyViewHolder holder, final int position) {
        /**如果沒有進行搜索操作或者搜索之后點擊了刪除按鈕 我們會在MainActivity中把text置空并傳遞過來*/
        if (text != null) {
            //設置span
            SpannableString string = matcherSearchText(Color.rgb(255, 0, 0), list.get(position), text);
            holder.mTvText.setText(string);
        } else {
            holder.mTvText.setText(list.get(position));
        }
        //點擊監聽
        holder.mLlItem.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                onItemClickListener.onClick(view, position);
            }
        });
    }

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

    /**
     * Recyclerview的點擊監聽接口
     */
    public interface onItemClickListener {
        void onClick(View view, int pos);
    }

    private onItemClickListener onItemClickListener;

    public void setOnItemClickListener(RcAdapterWholeChange.onItemClickListener onItemClickListener) {
        this.onItemClickListener = onItemClickListener;
    }

    class MyViewHolder extends RecyclerView.ViewHolder {
        private LinearLayout mLlItem;
        private SimpleDraweeView mImgvSimple;
        private TextView mTvText;

        public MyViewHolder(View itemView) {
            super(itemView);
            mLlItem = (LinearLayout) itemView.findViewById(R.id.ll_item);
            mImgvSimple = (SimpleDraweeView) itemView.findViewById(R.id.imgv_simple);
            mTvText = (TextView) itemView.findViewById(R.id.tv_text);
        }
    }

    /**
     * 正則匹配 返回值是一個SpannableString 即經過變色處理的數據
     */
    private SpannableString matcherSearchText(int color, String text, String keyword) {
        SpannableString spannableString = new SpannableString(text);
        //條件 keyword
        Pattern pattern = Pattern.compile(keyword);
        //匹配
        Matcher matcher = pattern.matcher(spannableString);
        while (matcher.find()) {
            int start = matcher.start();
            int end = matcher.end();
            //ForegroundColorSpan 需要new 不然也只能是部分變色
            spannableString.setSpan(new ForegroundColorSpan(color), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
        //返回變色處理的結果
        return spannableString;
    }

}

??改動不大,重點是一個matcherSearchText方法,返回值是SpannableString,也就是經過我們經過變色處理的文字。主要使用matcher.find()方法找到所以有匹配的關鍵字,它的效果已經在上邊的代碼中展示過了(請看上邊的控制臺輸出結果)。

  1. Activity中的代碼:
public class MainActivityWhole extends AppCompatActivity {
    /**
     * 搜索框
     */
    private EditText mEdtSearch;
    /**
     * 刪除按鈕
     */
    private ImageView mImgvDelete;
    /**
     * recyclerview
     */
    private RecyclerView mRcSearch;
    /**
     * 全部匹配的適配器
     */
    private RcAdapterWholeChange adapter;
    /**
     * 所有數據 可以是聯網獲取 如果有需要可以將其儲存在數據庫中 我們用簡單的String做演示
     */
    private List<String> wholeList;
    /**
     * 此list用來保存符合我們規則的數據
     */
    private List<String> list;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_whole);
        initView();
        initData();
        refreshUI();
        setListener();
    }

    /**
     * 設置監聽
     */
    private void setListener() {
        //edittext的監聽
        mEdtSearch.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            //每次edittext內容改變時執行 控制刪除按鈕的顯示隱藏
            @Override
            public void afterTextChanged(Editable editable) {
                if (editable.length() == 0) {
                    mImgvDelete.setVisibility(View.GONE);
                } else {
                    mImgvDelete.setVisibility(View.VISIBLE);
                }
                //匹配文字 變色
                doChangeColor(editable.toString().trim());
            }
        });
        //recyclerview的點擊監聽
        adapter.setOnItemClickListener(new RcAdapterWholeChange.onItemClickListener() {
            @Override
            public void onClick(View view, int pos) {
                Toast.makeText(MainActivityWhole.this, "妹子 pos== " + pos, Toast.LENGTH_SHORT).show();
            }
        });
        //刪除按鈕的監聽
        mImgvDelete.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                mEdtSearch.setText("");
            }
        });
    }

    /**
     * 字體匹配方法
     */
    private void doChangeColor(String text) {
        //clear是必須的 不然只要改變edittext數據,list會一直add數據進來
        list.clear();
        //不需要匹配 把所有數據都傳進來 不需要變色
        if (text.equals("")) {
            list.addAll(wholeList);
            //防止匹配過文字之后點擊刪除按鈕 字體仍然變色的問題
            adapter.setText(null);
            refreshUI();
        } else {
            //如果edittext里面有數據 則根據edittext里面的數據進行匹配 用contains判斷是否包含該條數據 包含的話則加入到list中
            for (String i : wholeList) {
                if (i.contains(text)) {
                    list.add(i);
                }
            }
            //設置要變色的關鍵字
            adapter.setText(text);
            refreshUI();
        }
    }

    private void initData() {
        //假數據  實際開發中請從網絡或者數據庫獲取
        wholeList = new ArrayList<>();
        list = new ArrayList<>();
        wholeList.add("第一天一天");
        wholeList.add("第二天一天");
        wholeList.add("第三天一天");
        wholeList.add("第四天一天");
        wholeList.add("第五天五天");
        wholeList.add("第六天一天");
        wholeList.add("第七天七天");
        wholeList.add("第一天八天");
        wholeList.add("第一天九天");
        wholeList.add("第一天十天");
        wholeList.add("第一天十一天");
        //初次進入程序時 展示全部數據
        list.addAll(wholeList);
    }

    /**
     * 刷新UI
     */
    private void refreshUI() {
        if (adapter == null) {
            adapter = new RcAdapterWholeChange(this, list);
            mRcSearch.setAdapter(adapter);
        } else {
            adapter.notifyDataSetChanged();
        }
    }

    private void initView() {
        mEdtSearch = (EditText) findViewById(R.id.edt_search);
        mImgvDelete = (ImageView) findViewById(R.id.imgv_delete);
        mRcSearch = (RecyclerView) findViewById(R.id.rc_search);
        //Recyclerview的配置
        mRcSearch.setLayoutManager(new LinearLayoutManager(this));
    }
}

??這里我們的思路是首先定義兩個集合(wholeListlist),wholeList用來保存我們獲取的全部數據,list用來保存我們經過篩選后的數據。在為進行搜索操作是默認展示所有數據,所以會有list.addAll(wholeList)。之后通過對Edittext的變化監聽afterTextChanged,在里面執行刪除按鈕的顯示隱藏以及匹配文字并變色的doChangeColor()方法。

 //每次edittext內容改變時執行 控制刪除按鈕的顯示隱藏
            @Override
            public void afterTextChanged(Editable editable) {
                if (editable.length() == 0) {
                    mImgvDelete.setVisibility(View.GONE);
                } else {
                    mImgvDelete.setVisibility(View.VISIBLE);
                }
                //匹配文字 變色
                doChangeColor(editable.toString().trim());
            }

??接下來我們要講的是doChangeColor()這個方法,首先看代碼:

/**
     * 字體匹配方法
     */
    private void doChangeColor(String text) {
        //clear是必須的 不然只要改變edittext數據,list會一直add數據進來
        list.clear();
        //不需要匹配 把所有數據都傳進來 不需要變色
        if (text.equals("")) {
            list.addAll(wholeList);
            //防止匹配過文字之后點擊刪除按鈕 字體仍然變色的問題
            adapter.setText(null);
            refreshUI();
        } else {
            //如果edittext里面有數據 則根據edittext里面的數據進行匹配 用contains判斷是否包含該條數據 包含的話則加入到list中
            for (String i : wholeList) {
                if (i.contains(text)) {
                    list.add(i);
                }
            }
            //設置要變色的關鍵字
            adapter.setText(text);
            refreshUI();
        }
    }

??在執行doChangeColor()之初,我們要清空一下list,不然如果你第一次搜索了“一”,第二次搜索了“二”,那么最終的展示效果會是包含了“一”和“二”數據的并集~,接下來我們會判斷Edittext里面是否有關鍵字(搜索條件),如果沒有關鍵字,即進行展示全部數據并且不變色的操作

            list.addAll(wholeList);
            //防止匹配過文字之后點擊刪除按鈕 字體仍然變色的問題
            adapter.setText(null);

如果有關鍵字,則對wholeList進行遍歷,匹配。把符合條件(i.contains(text))的數據加入到list集合中并進行展示。

三、后記

??整個項目并不難,而且代碼中都有詳細的注釋。但是例如SpannableString的玩法以及PatternMatcher的使用沒有展開來講。最近我在考慮寫一些合集來把一些基礎知識總結一下放上來,這樣以后在寫文章的時候可以這樣寫:

重點是一個matcherSearchText方法,返回值是SpannableStringSpannableString怎么用?請見我的文章《Android 之SpannableString用法詳解》。

??有寫的不好的地方,歡迎大家指教。
github項目地址:https://github.com/tangxuesong6/editchange

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,247評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,520評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,362評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,805評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,541評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,896評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,887評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,062評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,608評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,356評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,555評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,077評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,769評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,175評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,489評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,289評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,516評論 2 379

推薦閱讀更多精彩內容