OneAdapter: RecyclerView最簡單的萬能適配器

之前寫過一篇使用RecyclerView祭示,一句代碼就夠了断傲,介紹了一個(gè)功能較完善的RecyclerView框架的實(shí)現(xiàn)脱吱。該框架雖然代碼不多,但是仍然不夠簡潔艳悔,耦合度也比較高急凰,難以擴(kuò)展∨觯現(xiàn)將里面的核心部分 OneAdapter 抽取出來猜年,去掉不必要的泛型、類型判斷和其他方法疾忍,以實(shí)現(xiàn)最簡單乔外、通用性和擴(kuò)展性最好的Adapter。

ComplexList.png

在Github上搜索adatper一罩,選Java語言杨幼,有5K+的記錄,主要也都是RecyclerView或ListView的適配器封裝聂渊。既然已經(jīng)有這么多實(shí)現(xiàn)在先差购,這里再實(shí)現(xiàn)一遍有意義嗎?

有的汉嗽,這里的實(shí)現(xiàn)是最簡單欲逃、代碼最少的。

OneAdapter代碼如下:

/**
 * A custom adapter, supports multi-ItemViewType
 * 
 * Created by rome753 on 2018/2/1.
 */
public class OneAdapter extends RecyclerView.Adapter<OneViewHolder> {

    private final List<Object> data;
    private final List<OneListener> listeners;

    public OneAdapter(OneListener... listeners) {
        this.data = new ArrayList<>();
        this.listeners = new ArrayList<>();
        this.listeners.addAll(Arrays.asList(listeners));
    }

    public void setData(List<?> data) {
        this.data.clear();
        this.data.addAll(data);
    }

    public void addData(List<?> data) {
        this.data.addAll(data);
    }

    public List<Object> getData() {
        return data;
    }

    public List<OneListener> getListeners() {
        return listeners;
    }

    @Override
    public int getItemViewType(int position) {
        Object o = data.get(position);
        for (int i = 0; i < listeners.size(); i++) {
            OneListener listener = listeners.get(i);
            if (listener.isMyItemViewType(position, o)) {
                return i;
            }
        }
        return 0;
    }

    @Override
    public OneViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return listeners.get(viewType).getMyViewHolder(parent);
    }

    @Override
    public void onBindViewHolder(OneViewHolder holder, int position) {
        Object o = data.get(position);
        holder.bindView(position, o);
    }

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

}

OneAdatper繼承自RecyclerView.Adapter饼暑,重寫了4個(gè)方法稳析,并在其中增加了兩個(gè)List洗做,核心代碼只有幾行,在 getItemViewType(int position) 這個(gè)方法中彰居。

原因

很多同學(xué)開發(fā)時(shí)看到有列表就來一個(gè)RecyclerView诚纸,然后又實(shí)現(xiàn)一個(gè)Adapter。這兩步都是沒有必要的陈惰。

先說第一步畦徘,RecyclerView并不是有列表就使用的。Recycle的意思是回收奴潘,也就是說旧烧,只有在需要回收時(shí)才使用。什么時(shí)候需要回收呢画髓?列表數(shù)據(jù)項(xiàng)很多或者單個(gè)數(shù)據(jù)項(xiàng)占內(nèi)存很大時(shí)掘剪。其他情況下,比如類似微信的設(shè)置頁面那種簡單的列表奈虾,不需要回收夺谁,用ScrollView實(shí)現(xiàn)就可以了,代碼更簡單肉微,性能更好匾鸥。這應(yīng)該也是Google讓開發(fā)者從ListView遷移到RecyclerView的目的。

再說第二步碉纳,每個(gè)RecyclerView實(shí)現(xiàn)一個(gè)Adapter也是冗余的勿负。Adapter的本質(zhì)是控制列表中每一項(xiàng)的視圖(View)與數(shù)據(jù)(Data)的對(duì)應(yīng)關(guān)系,所以它應(yīng)該只做一件事:RecyclerView把某一項(xiàng)視圖傳過來時(shí)劳曹,Adapter把數(shù)據(jù)傳給視圖奴愉。然而現(xiàn)在Adapter中處理了數(shù)據(jù)類型和視圖類型,這導(dǎo)致它跟具體業(yè)務(wù)耦合度很高铁孵,尤其是數(shù)據(jù)類型和視圖類型多樣時(shí)锭硼。

舉個(gè)例子:

    class MyAdapter extends BaseAdapter<SkuItem, BaseHolder<BaseView>> {

        private final int TYPE_HEADER = 0;
        private final int TYPE_COINS = 1;

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

        @Override
        public BaseHolder<BaseView> onCreateViewHolder(ViewGroup parent, int viewType) {
            if (viewType == TYPE_HEADER) {
                return new BaseHolder(new CoinNumberView(parent.getContext()));
            } else {
                return new BaseHolder(new BuyCoinsView(parent.getContext(), BuyCoinActivity.this));
            }
        }

        @Override
        public void onBindViewHolder(BaseHolder<BaseView> holder, int position) {
            if (holder.itemView instanceof BuyCoinsView) {
                BuyCoinsView buyCoinsView = (BuyCoinsView) holder.bindView;
                buyCoinsView.bindDataByPosition(mData.get(position), position);
            } else if (holder.itemView instanceof CoinNumberView) {
                CoinNumberView coinNumberView = (CoinNumberView) holder.bindView;
                coinNumberView.bindData(null);
            }
        }

        @Override
        public int getItemViewType(int position) {
            if (position == 0) {
                return TYPE_HEADER;
            } else {
                return TYPE_COINS;
            }
        }
    }

這里為了給列表增加一個(gè)Header,在Adapter中增加一個(gè)類型蜕劝,然后不得不用 if...else... 或者 switch 語句判斷ItemViewType的類型檀头、ViewHolder的類型和ItemView的類型。

Adapter依賴所有類型的所有對(duì)象岖沛,畫圖來看是這樣的:

adapter.png

這里只是兩種類型暑始,如果有4,5種乃至7,8種,那么Adapter就爆炸了婴削!

原理

OneAdapter解決了Adapter的過度耦合問題廊镜,它只依賴OneListener和OneViewHolder這兩個(gè)類,只關(guān)聯(lián)List<OneListener>這一個(gè)對(duì)象馆蠕,其他所有依賴關(guān)系都被List<OneListener>轉(zhuǎn)移到外部了期升。如圖所示:

oneadapter.png

無論有多少種數(shù)據(jù)類型惊奇,都只需要在外部實(shí)現(xiàn)OneListener和OneViewHolder,給OneAdapter傳入OneListener列表即可播赁。

OneAdapter不依賴具體的數(shù)據(jù)類型颂郎,使用Object表示數(shù)據(jù)類型,而不是泛型容为。這樣做是因?yàn)榉盒鸵话汜槍?duì)一種或固定幾種不確定的類型乓序,而Adapter中不但有多種不確定的類型、而且具體有幾種也是不固定的坎背,因此無法使用泛型替劈。為了傳入數(shù)據(jù)不限制于Object類型,在OneAdapter中的 setData(List<?> data) 方法參數(shù)使用了泛型的不確定類型得滤。

OneListener代碼如下:

/**
 * A listener for: define item view type and create ViewHolder, outside of the adapter
 */
public interface OneListener{

    /**
     * Is the position or the data suits for this OneListener?
     * @param position the data's position int the list
     * @param o the data
     * @return true/false
     */
    boolean isMyItemViewType(int position, Object o);

    /**
     * Create a ViewHolder for this OneListener
     * @param parent RecyclerView
     * @return OneViewHolder
     */
    OneViewHolder getMyViewHolder(ViewGroup parent);
}

OneListener是一個(gè)接口陨献,它建立了列表中具體位置、具體數(shù)據(jù)與具體OneViewHolder的對(duì)應(yīng)關(guān)系懂更。實(shí)際上每個(gè)OneListener實(shí)例表示列表中一種條目類型眨业。它里面有兩個(gè)方法。

  • isMyItemViewType(int position, Object o) 方法讓實(shí)現(xiàn)者根據(jù)位置或者該位置的數(shù)據(jù)判斷是不是當(dāng)前OneListener對(duì)應(yīng)的條目類型沮协。
  • getMyViewHolder(ViewGroup parent) 方法讓實(shí)現(xiàn)者實(shí)現(xiàn)當(dāng)前OneListener對(duì)應(yīng)的OneViewHolder子類龄捡。

OneListener也不依賴具體的數(shù)據(jù)類型,因?yàn)榕袛鄺l目類型并不一定是根據(jù)數(shù)據(jù)類型判斷慷暂,也可能根據(jù)位置判斷聘殖。這給了調(diào)用者最大的靈活度既鞠。雖然OneViewHolder有泛型详拙,但是OneListener并不需要關(guān)心。

OneViewHolder代碼如下:

/**
 * A ViewHolder that auto cast the data, from Object to the type you define
 * @param <D> the data type you define
 */
public abstract class OneViewHolder<D> extends RecyclerView.ViewHolder {

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

    public OneViewHolder(ViewGroup parent, int layoutRes) {
        super(LayoutInflater.from(parent.getContext()).inflate(layoutRes, parent, false));
    }

    void bindView(int position, Object o){
        bindViewCasted(position, (D) o);
    }

    protected abstract void bindViewCasted(int position, D d);
}

OneViewHolder繼承自RecyclerView.ViewHolder庸推,它將具體數(shù)據(jù)與視圖綁定蘑辑。由于數(shù)據(jù)在OneAdapter中都是Object類型洋机,為了調(diào)用者方便坠宴,這里利用泛型自動(dòng)對(duì)數(shù)據(jù)進(jìn)行了強(qiáng)制類型轉(zhuǎn)換洋魂。至于綁定視圖封裝了兩個(gè)方法:

  • OneViewHolder(ViewGroup parent, int layoutRes) 方法用于直接傳入ItemView的布局資源,用于大多數(shù)情況喜鼓。
  • OneViewHolder(View itemView) 方法用于ItemView是自定義View的情況(此時(shí)要注意自定義View的LayoutParams)副砍。

到這里,主要代碼就講完了庄岖。

示例

  1. 簡單列表


    SimpleList.png
public class SimpleListActivity extends AppCompatActivity {

    OneAdapter oneAdapter;

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

        oneAdapter = new OneAdapter(new OneListener() {
            @Override
            public boolean isMyItemViewType(int position, Object o) {
                return true;
            }

            @Override
            public OneViewHolder getMyViewHolder(ViewGroup parent) {
                return new OneViewHolder<String>(parent, R.layout.item_text){

                    @Override
                    protected void bindViewCasted(int position, String s) {
                        TextView text = itemView.findViewById(R.id.text);
                        text.setText(s);
                    }
                };
            }
        });

        RecyclerView recyclerView = findViewById(R.id.recycler_view);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(oneAdapter);

        requestData();
    }

    private void requestData() {
        List<String> data = new ArrayList<>();
        for(int i = 'A'; i <= 'z'; i++) {
            data.add(" " + (char)i);
        }
        oneAdapter.setData(data);
        oneAdapter.notifyDataSetChanged();
    }
}
  1. 帶Header和Footer的列表
HeaderFooter.png
public class HeaderFooterActivity extends AppCompatActivity {
    RecyclerView recyclerView;
    OneAdapter oneAdapter;
    View footerView;

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

        oneAdapter = new OneAdapter(
                new OneListener() {
                    @Override
                    public boolean isMyItemViewType(int position, Object o) {
                        return position == 0;
                    }

                    @Override
                    public OneViewHolder getMyViewHolder(ViewGroup parent) {
                        return new OneViewHolder<Object>(parent, R.layout.item_text) {

                            @Override
                            protected void bindViewCasted(int position, Object o) {
                                TextView text = itemView.findViewById(R.id.text);
                                text.setText("This is header");
                            }
                        };
                    }
                },
                new OneListener() {
                    @Override
                    public boolean isMyItemViewType(int position, Object o) {
                        return o instanceof String;
                    }

                    @Override
                    public OneViewHolder getMyViewHolder(ViewGroup parent) {
                        return new OneViewHolder<String>(parent, android.R.layout.simple_list_item_1) {

                            @Override
                            protected void bindViewCasted(int position, String s) {
                                TextView text = itemView.findViewById(android.R.id.text1);
                                text.setText(s);
                            }
                        };
                    }
                },
                new OneListener() {
                    @Override
                    public boolean isMyItemViewType(int position, Object o) {
                        return position == oneAdapter.getItemCount() - 1;
                    }

                    @Override
                    public OneViewHolder getMyViewHolder(ViewGroup parent) {
                        return new OneViewHolder<Object>(footerView) {

                            @Override
                            protected void bindViewCasted(int position, Object o) {
                            }
                        };
                    }
                }
        );

        recyclerView = findViewById(R.id.recycler_view);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(oneAdapter);

        initFooterView();
        requestData();
    }

    private void initFooterView() {
        footerView = LayoutInflater.from(this).inflate(R.layout.item_text, recyclerView, false);
        ((TextView)footerView.findViewById(R.id.text)).setText("This is footer");
    }

    private void requestData() {
        List<Object> data = new ArrayList<>();
        data.add(null);
        for (int i = 'A'; i <= 'Z'; i++) {
            data.add(" " + (char) i);
        }
        data.add(null);
        oneAdapter.setData(data);
        oneAdapter.notifyDataSetChanged();
    }
}

擴(kuò)展:Databinding支持

Databinding是Google推薦的做法豁翎,有了它就不需要寫 findViewById() 語句了,還能直接在Layout文件中綁定數(shù)據(jù)隅忿。 使用方法也很簡單心剥,大家可以自己查一下相關(guān)教程邦尊。這里給OneAdapter添加Databinding支持。

對(duì)于OneAdapter來說优烧,Databinding主要用于具體數(shù)據(jù)與視圖綁定蝉揍,也就是OneViewHolder中所做的。OneViewHolder有兩個(gè)構(gòu)造方法畦娄,分別對(duì)應(yīng)自定義View和布局資源文件又沾。對(duì)于自定義View來說,是否使用Databinding是調(diào)用者自己控制的熙卡。因此Databinding支持是針對(duì)使用布局資源文件的情況杖刷,這里封裝了一個(gè)包裝類OneViewHolderWrapper,用它替換OneViewHolder即可驳癌。

OneViewHolderWrapper代碼:

/**
 * A wrapper of OneViewHolder, supports data binding
 * @param <D> the type of the data
 * @param <B> the type of the ViewDataBinding
 */
public abstract class OneViewHolderWrapper<D,B extends ViewDataBinding>{

    private OneViewHolder<D> oneViewHolder;

    protected B binding;

    public OneViewHolderWrapper(ViewGroup parent, int layoutRes){
        binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), layoutRes, parent, false);
        oneViewHolder = new OneViewHolder<D>(binding.getRoot()) {
            @Override
            protected void bindViewCasted(int position, D d) {
                OneViewHolderWrapper.this.bindViewCasted(position, d);
            }
        };
    }

    public OneViewHolder<D> getOneViewHolder() {
        return oneViewHolder;
    }

    protected abstract void bindViewCasted(int position, D d);
}

OneViewHolderWrapper中用D表示數(shù)據(jù)泛型滑燃,B表示ViewDataBinding泛型。binding對(duì)象用于綁定具體數(shù)據(jù)颓鲜。

實(shí)際使用代碼:

public class DataBindingActivity extends AppCompatActivity {

    OneAdapter oneAdapter;

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

        oneAdapter = new OneAdapter(new OneListener() {
            @Override
            public boolean isMyItemViewType(int position, Object o) {
                return true;
            }

            @Override
            public OneViewHolder getMyViewHolder(ViewGroup parent) {
                return new OneViewHolderWrapper<Person, ItemPersonBinding>(parent, R.layout.item_person) {
                    @Override
                    protected void bindViewCasted(int position, Person person) {
                        binding.setPerson(person);
                        binding.executePendingBindings();
                    }
                }.getOneViewHolder();
            }
        });

        RecyclerView recyclerView = findViewById(R.id.recycler_view);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(oneAdapter);

        requestData();
    }

    private void requestData() {
        List<Object> data = new ArrayList<>();
        for(int i = 0; i <= 10; i++) {
            data.add(new Person("Bill", 22));
            data.add(new Person("Chris", 10));
            data.add(new Person("David", 36));
        }
        oneAdapter.setData(data);
        oneAdapter.notifyDataSetChanged();
    }
}

需要OneViewHolder實(shí)例時(shí)不瓶,先創(chuàng)建包裝類OneViewHolderWrapper實(shí)例,然后調(diào)用 getOneViewHolder() 方法從包裝類中取得OneViewHolder實(shí)例灾杰。這樣原有的OneAdapter和OneListener都直接兼容蚊丐。

Databinding.png

擴(kuò)展:下拉刷新和加載更多

用SwipeRefreshLayout和FooterView實(shí)現(xiàn)了簡單的下拉刷新和加載更多功能,這是對(duì)OneAdapter的簡單擴(kuò)展艳吠。沒有加入EmptyView麦备,因?yàn)镋mptyView可以完全在外部控制。

public class RecyclerLayout extends SwipeRefreshLayout implements OnRefreshListener, LoadingLayout.OnLoadingListener {

    private RecyclerView recyclerView;
    private LoadingLayout loadingLayout;

    private OneAdapter oneAdapter;
    private GridLayoutManager gridLayoutManager;

    private OnRefreshListener onRefreshListener;
    private LoadingLayout.OnLoadingListener onLoadingListener;

    public RecyclerLayout(Context context) {
        this(context, null);
    }

    public RecyclerLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        setOnRefreshListener(this);

        loadingLayout = new LoadingLayout(context);

        gridLayoutManager = new GridLayoutManager(context, 1);
        recyclerView = new RecyclerView(context);
        recyclerView.setLayoutManager(gridLayoutManager);
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

            int lastVisibleItemPosition;

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                lastVisibleItemPosition = gridLayoutManager.findLastVisibleItemPosition();
            }

            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                if (newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItemPosition == oneAdapter.getItemCount() - 1 - 1) {
                    // load more
                    onLoading();
                }
            }
        });

        addView(recyclerView);
    }

    public void init(final OneAdapter oneAdapter, OnRefreshListener onRefreshListener, LoadingLayout.OnLoadingListener onLoadingListener){
        this.recyclerView.setAdapter(oneAdapter);
        this.oneAdapter = oneAdapter;
        this.oneAdapter.getListeners().add(0, new OneListener() {
            @Override
            public boolean isMyItemViewType(int position, Object o) {
                return position == oneAdapter.getItemCount() - 1;
            }

            @Override
            public OneViewHolder getMyViewHolder(ViewGroup parent) {
                return new OneViewHolder(loadingLayout) {
                    @Override
                    protected void bindViewCasted(int position, Object o) {
                        //ignore
                    }
                };
            }
        });

        if(onRefreshListener == null){
            setEnabled(false);
        }
        this.onRefreshListener = onRefreshListener;
        this.onLoadingListener = onLoadingListener;
    }

    @Override
    public void onRefresh() {
        if(onRefreshListener != null){
            onRefreshListener.onRefresh();
        }
    }

    @Override
    public void onLoading() {
        if(onLoadingListener != null && !isRefreshing() && !isLoading() && !isNoMore()){
            onLoadingListener.onLoading();
            setLoading(true, isNoMore());
        }
    }

    public void setData(List<?> data, boolean hasMore){
        data.add(null);
        oneAdapter.setData(data);
        oneAdapter.notifyDataSetChanged();

        setRefreshing(false);
        setLoading(false, !hasMore);
    }

    public void addData(List<?> data, boolean hasMore){
        List<Object> cur = oneAdapter.getData();
        if(!cur.isEmpty()){
            cur.remove(cur.size() - 1);
        }
        data.add(null);
        oneAdapter.addData(data);
        oneAdapter.notifyDataSetChanged();

        setLoading(false, !hasMore);
    }

    private boolean isNoMore(){
        return loadingLayout.isNoMore();
    }

    private boolean isLoading(){
        return loadingLayout.isLoading();
    }

    private void setLoading(boolean loading, boolean isNoMore){
        loadingLayout.setLoading(loading, isNoMore);
    }
}

實(shí)際使用如下:

public class RefreshActivity extends AppCompatActivity {

    RecyclerLayout recyclerLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        recyclerLayout = new RecyclerLayout(this);
        setContentView(recyclerLayout);

        OneAdapter oneAdapter = new OneAdapter(
                new OneListener() {

                    @Override
                    public boolean isMyItemViewType(int position, Object o) {
                        return true;
                    }

                    @Override
                    public OneViewHolder getMyViewHolder(ViewGroup parent) {

                        return new OneViewHolder<String>(parent, R.layout.item_text) {
                            @Override
                            protected void bindViewCasted(int position, String s) {
                                TextView text = itemView.findViewById(R.id.text);
                                text.setText(s);
                            }
                        };
                    }
                }
        );

        recyclerLayout.init(oneAdapter,
                new SwipeRefreshLayout.OnRefreshListener() {
                    @Override
                    public void onRefresh() {
                        requestData();
                    }
                },
                new LoadingLayout.OnLoadingListener() {
                    @Override
                    public void onLoading() {
                        requestMoreData();
                    }
                }
        );


        recyclerLayout.setRefreshing(true);
        requestData();
    }

    int page;

    private void requestData() {
        getWindow().getDecorView().postDelayed(new Runnable() {
            @Override
            public void run() {

                List<Object> data = new ArrayList<>();
                for (int i = 'A'; i <= 'Z'; i++) {
                    String s = (char) i + " " + System.nanoTime();
                    data.add(s);
                }

                page = 0;
                recyclerLayout.setData(data, page++ < 2);

            }
        }, 1000);
    }

    private void requestMoreData() {
        getWindow().getDecorView().postDelayed(new Runnable() {
            @Override
            public void run() {

                List<Object> data = new ArrayList<>();
                for (int i = 'A'; i <= 'Z'; i++) {
                    String s = (char) i + " " + System.nanoTime();
                    data.add(s);
                }

                recyclerLayout.addData(data, page++ < 2);

            }
        }, 1000);
    }

}

代碼結(jié)構(gòu)

oneadapter.png
  • 實(shí)現(xiàn)普通或多種類型的RecyclerView昭娩,使用base包中的類即可凛篙;
  • 如果需要Databinding支持,加入databinding包中的類栏渺;
  • 如果需要下拉刷新和加載更多呛梆,可以參考refresh包中的實(shí)現(xiàn)。

Github地址:https://github.com/rome753/OneAdapter
完整代碼和Demo示例都在這里磕诊,歡迎Fork和Star哦填物。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市霎终,隨后出現(xiàn)的幾起案子滞磺,更是在濱河造成了極大的恐慌,老刑警劉巖莱褒,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件击困,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡广凸,警方通過查閱死者的電腦和手機(jī)阅茶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門蛛枚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人脸哀,你說我怎么就攤上這事坤候。” “怎么了企蹭?”我有些...
    開封第一講書人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵白筹,是天一觀的道長。 經(jīng)常有香客問我谅摄,道長徒河,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任送漠,我火速辦了婚禮顽照,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘闽寡。我一直安慰自己代兵,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開白布爷狈。 她就那樣靜靜地躺著植影,像睡著了一般。 火紅的嫁衣襯著肌膚如雪涎永。 梳的紋絲不亂的頭發(fā)上思币,一...
    開封第一講書人閱讀 51,562評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音羡微,去河邊找鬼谷饿。 笑死,一個(gè)胖子當(dāng)著我的面吹牛妈倔,可吹牛的內(nèi)容都是我干的博投。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼盯蝴,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼毅哗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起结洼,我...
    開封第一講書人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤黎做,失蹤者是張志新(化名)和其女友劉穎叉跛,沒想到半個(gè)月后松忍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡筷厘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年鸣峭,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宏所。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡摊溶,死狀恐怖爬骤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情莫换,我是刑警寧澤霞玄,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站拉岁,受9級(jí)特大地震影響坷剧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜喊暖,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一惫企、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧陵叽,春花似錦狞尔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至胖替,卻和暖如春禽车,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背刊殉。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來泰國打工殉摔, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人记焊。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓逸月,卻偏偏與公主長得像,于是被迫代替她去往敵國和親遍膜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子碗硬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355