Android_MVP小封装

MVP是个好东西,可是最近项目一直用的是mvc模式,先马克下之前鼓捣过的mvp框架,过年回家再用它重构下代码。

先上依赖库####

  compile 'io.reactivex:rxandroid:1.2.1'  
  compile 'com.squareup.okhttp3:okhttp:3.3.1'  
  compile 'io.reactivex:rxandroid:1.1.0'  
  compile 'io.reactivex:rxjava:1.1.0'  
  compile 'com.squareup.retrofit:retrofit:2.0.0-beta2'  
  compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2'  
  compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2'  
  compile 'com.android.support:design:24.2.1'  
  compile 'com.android.support:recyclerview-v7:24.2.1'  
  compile 'com.android.support:cardview-v7:24.2.1'  
  compile 'com.jakewharton:butterknife:7.0.1'  
  compile 'com.github.bumptech.glide:glide:3.7.0'  
  compile 'com.github.chrisbanes.photoview:library:1.2.3'  

建议写死依赖的版本号,而不要使用“+”,避免版本升级带了一些奇葩的问题。

依赖retrolambda####

在app.build依赖

    apply plugin: 'me.tatarka.retrolambda'  

再加上

    compileOptions {  
    sourceCompatibility JavaVersion.VERSION_1_8  
     targetCompatibility JavaVersion.VERSION_1_8  
   }  

然后在整个项目的build文件上面加入:

      classpath 'me.tatarka:gradle-retrolambda:3.2.5' 

OK,到这里我们的环境搭建就完成了


首先我们看下项目的目录结构:

base文件

BaseContract(基本的文件类,我们可以在里面写上view层,model层,Presenter层的interface)

  /**
   * Created by Ly on 2016/11/2.
   */

  public class BaseContract  {
}

BaseModel(基本的model层,所有耗时操作应该写在model层中)

  /**
   * Created by Ly on 2016/11/2.
   */

  public interface BaseModel {
  }

BaseView层(写入跟用户交互的方法集合类,比如showTosast,showDialog)

    /**
   * Created by Ly on 2016/11/2.
     */

  public interface BaseView {
      void TsShow(String msg);
  }

看下我们的基本baseActivity.java:

package com.Ly.BaseJustTalk.base;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.AppBarLayout;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.MenuItem;
import android.widget.Toast;

import com.Ly.BaseJustTalk.R;

import butterknife.ButterKnife;

/**
 * Created by Ly on 2017/1/12.
 */
public abstract class BaseActivity<V, T extends BasePresenter<V>> extends AppCompatActivity {

    protected T mPresenter;
    private SwipeRefreshLayout mRefreshLayout;
    private AppBarLayout mAppBar;
    private Toolbar mToolbar;
    private ProgressDialog mProgressBar;
    protected Context mContext;
    private boolean mIsRequestDataRefresh = false;

    private static final String EXTRA_KEY = "extra";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        dogetExtra();
        mContext = this;
        // 允许为空,不是所有的页面都要实现这个模式
        if (createPresenter() != null) {
            mPresenter = createPresenter();
            mPresenter.attachView((V) this);
        }
        setContentView(provideContentViewId());
        ButterKnife.bind(this);

        mAppBar = findViewById(R.id.app_bar_layout);
        mToolbar = (Toolbar) findViewById(R.id.toolbar);
        if (mToolbar != null && mAppBar != null) {
            setSupportActionBar(mToolbar); //把Toolbar当做ActionBar给设置

            if (canBack()) {
                ActionBar actionBar = getSupportActionBar();
                if (null != actionBar) {
                    //设置ActionBar一个返回箭头,主界面没有,次级界面有
                    actionBar.setDisplayHomeAsUpEnabled(true);
                }
                if (Build.VERSION.SDK_INT >= 21) {
                    //Z轴浮动
                    mAppBar.setElevation(10.6F);
                }
            }
        }
        if (isSetRefresh()) {
            setupSwipeRefresh();
        }
    }


    public static void doStartActivity(Context context, Bundle bundle, Class<?> clz) {
        Intent intent = new Intent(context, clz);
        if (bundle != null) {
            intent.putExtra(EXTRA_KEY, bundle);
        }
        context.startActivity(intent);
    }


    protected abstract void dogetExtra();

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // 此时android.R.id.home即为返回箭头
        if (item.getItemId() == android.R.id.home) {
            onBackPressed();
            finish();
            return true;
        } else {
            return super.onOptionsItemSelected(item);
        }
    }

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

    /**
     * 生成下拉刷新
     */
    private void setupSwipeRefresh() {
        mRefreshLayout = findViewById(R.id.swipe_refresh);
        if (null != mRefreshLayout) {
            mRefreshLayout.setColorSchemeResources(R.color.colorAccent, R.color.colorPrimary);
            mRefreshLayout.setProgressViewOffset(true,
                    0,
                    (int) TypedValue.applyDimension
                            (TypedValue.COMPLEX_UNIT_DIP, 24, getResources()
                                    .getDisplayMetrics()));
        }
    }


    /**
     * 设置刷新
     *
     * @param requestDataRefresh
     */
    public void setRefresh(boolean requestDataRefresh) {
        if (mRefreshLayout == null) {
            return;
        }
        if (!requestDataRefresh) {
            mIsRequestDataRefresh = false;
            mRefreshLayout.postDelayed(() -> {
                if (mRefreshLayout != null) {
                    mRefreshLayout.setRefreshing(false);
                }
            }, 1000);
        } else {
            mRefreshLayout.setRefreshing(true);
        }
    }


    /**
     * 数据刷新
     */
    public void requestDataRefresh() {
        mIsRequestDataRefresh = true;
    }

    /**
     * 判断当前页面是否可以返回,
     * 主界面不可以返回  子界面可以放回
     *
     * @return
     */
    public boolean canBack() {
        return false;
    }

    /**
     * 判断子Activity是否需要上下拉刷新功能
     *
     * @return
     */
    public Boolean isSetRefresh() {
        return false;
    }

    /**
     * 创建P
     *
     * @return
     */
    protected abstract T createPresenter();

    /**
     * 引入布局文件
     *
     * @return
     */
    protected abstract int provideContentViewId();

    protected void ShowTs(String msg) {
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }


    protected void ShowDialog() {
        ShowDialog(null);
    }

    protected void ShowDialog(String msg) {
        if (TextUtils.isEmpty(msg)) {
            msg = getString(R.string.loading);
        }
        mProgressBar = ProgressDialog.show(this, null, msg);
    }

    protected void DissDialog() {
        if (mProgressBar != null && mProgressBar.isShowing()) {
            mProgressBar.dismiss();
        }
    }
}

相对应的BaseFragment.java 代码如下:

package com.Ly.BaseJustTalk.base;

import android.content.Context;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.Ly.BaseJustTalk.R;

import butterknife.ButterKnife;

/**
* Created by Ly on 2011/1/12.
*/

public abstract class BaseFragment<V, T extends BasePresenter<V>> extends Fragment {
   protected Context mContext;
   protected T mPresenter;

   private boolean mIsRequestDataRefresh = false;
   private SwipeRefreshLayout mRefreshLayout;

   @Override
   public void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       mContext = getActivity();
       mPresenter = createPresenter();
       mPresenter.attachView((V) this);
   }

   @Override
   public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
       View rootView = inflater.inflate(createViewLayoutId(), container, false);
       ButterKnife.bind(this, rootView);
       initView(rootView);
       if (isSetRefresh()) {
           setupSwipeRefresh(rootView);
       }
       return rootView;
   }

   @Override
   public void onDestroy() {
       super.onDestroy();
       mPresenter.detachView();
   }

   private void setupSwipeRefresh(View view) {
       mRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.swipe_refresh);
       if (mRefreshLayout != null) {
           mRefreshLayout.setColorSchemeResources(R.color.refresh_progress_1,
                   R.color.refresh_progress_2, R.color.refresh_progress_3);
           mRefreshLayout.setProgressViewOffset(true, 0, (int) TypedValue
                   .applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, getResources().getDisplayMetrics()));
           mRefreshLayout.setOnRefreshListener(this::requestDataRefresh);
       }
   }

   public void requestDataRefresh() {
       mIsRequestDataRefresh = true;
   }


   public void setRefresh(boolean requestDataRefresh) {
       if (mRefreshLayout == null) {
           return;
       }
       if (!requestDataRefresh) {
           mIsRequestDataRefresh = false;
           mRefreshLayout.postDelayed(() -> {
               if (mRefreshLayout != null) {
                   mRefreshLayout.setRefreshing(false);
               }
           }, 1000);
       } else {
           mRefreshLayout.setRefreshing(true);
       }
   }

   protected abstract T createPresenter();

   protected abstract int createViewLayoutId();

   protected void initView(View rootView) {
   }

   public Boolean isSetRefresh() {
       return true;
   }
}

Ok,直接看实例代码来理解,就用登录界面的吧。

登录界面的包目录

其中LoginActiviy为用户能看到的界面;
Contract是mvp的协议层;Model为数据处理层;Presenter可以理解为桥梁,负责沟通连接M层和V层。

在我的理解里的MVP是这个意思:我们可以看到,这个类持有了View和Model两个模块,在方法体里面,我们调用了model的方法去做耗时,在结果方法体里面我们调用了view的方法去修改UI,同时presenter这个模块又被view持有了,view可以在声明周期里面去调用特定的方法,view和presenter相互沟通,view和model完全隔离,presenter调控model,presenter沟通全局。

看下我们的登录界面:
activity_login.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1.5"
        android:gravity="center"
        android:text="@string/text_welcome"
        android:textSize="25sp" />


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_marginLeft="@dimen/margin_left"
        android:layout_marginRight="@dimen/margin_right"
        android:layout_weight="3"
        android:orientation="vertical">

        <android.support.design.widget.TextInputLayout
            android:id="@+id/til_username"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <EditText
                android:id="@+id/et_username"
                style="@style/edit_style"
                android:inputType="text"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="@string/text_username" />
        </android.support.design.widget.TextInputLayout>

        <android.support.design.widget.TextInputLayout
            android:id="@+id/til_password"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/margin6">

            <EditText
                android:id="@+id/et_password"
                style="@style/edit_style"
                android:inputType="textPassword"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="@string/text_password" />
        </android.support.design.widget.TextInputLayout>

        <Button
            android:id="@+id/bt_login"
            style="@style/button_style"
            android:layout_width="match_parent"
            android:layout_height="@dimen/button_height"
            android:layout_marginTop="@dimen/margin20"
            android:text="@string/text_login_reg" />
    </LinearLayout>
</LinearLayout>

LoginContract.java

package com.Ly.BaseJustTalk.ui.activity.Login;

import com.Ly.BaseJustTalk.base.BaseModel;
import com.Ly.BaseJustTalk.base.BaseView;

/**
 * Created by Ly on 2017/1/16.
 */
public class LoginContract {


    interface LoginView extends BaseView {
        String getUserName();

        String getUserPass();

        //            userName错误后进行提示
        void setUserNameErr(String errMsg);

        //            密码错误后进行提示
        void setPassErr(String errMsg);

        void doJustalkLogin();

        void doLoginFial();
    }


    interface LoginModel extends BaseModel {

        /**
         * @param userName
         * @param Pass
         * @return 0 合法账户密码 给予执行下一步
         * -1 用户名为空
         * -2 用户密码为空
         * -3 用户名不合法
         * -4 用户密码不合法
         */
        int isRightUserNamePass(String userName, String Pass);

        /**
         * 进行justalk的登录
         *
         * @param userName
         * @param pass
         * @return
         */
        boolean doJustalkLogin(String userName, String pass);
    }


    interface LoginPresenter {
        //        验证用户输入的信息
        void doVerificationInfo();

        //          验证正确后进行登录
        void doJustalkLogin();
    }
}

LoginModel.java

package com.Ly.BaseJustTalk.ui.activity.Login;

import android.text.TextUtils;
import android.util.Log;

import com.Ly.BaseJustTalk.R;
import com.Ly.BaseJustTalk.application.LyApplication;
import com.justalk.cloud.juslogin.LoginDelegate;

/**
 * Created by Administrator on 2017/1/16.
 */
public class LoginModel implements LoginContract.LoginModel {


    /**
     * 验证账号合法性
     *
     * @param userName
     * @param Pass
     * @return 0 合法账户密码 给予执行下一步
     * -1 用户名为空
     * -2 用户密码为空
     * -3 用户名不合法
     * -4 用户密码不合法
     */
    @Override
    public int isRightUserNamePass(String userName, String Pass) {
        if (TextUtils.isEmpty(userName)) {
            return -1;
        } else if (TextUtils.isEmpty(Pass)) {
            return -2;
        } else if (userName.length() < 4 || userName.length() > 12) {
            return -3;
        } else if (Pass.length() < 6 || Pass.length() > 12) {
            return -4;
        } else
            return 0;
    }

    @Override
    public boolean doJustalkLogin( String userName, String pass) {
        if (LoginDelegate.getInitState() == LoginDelegate.InitStat.MTC_INIT_FAIL) {
            return false;
        }
        String server = LyApplication.getInstance().getString(R.string.JusTalkCloud_network_address);
        String network = null;
        if (!server.startsWith("sudp:")) {
            network = server;
            server = "sarc:arc@AccessEntry:99";
            Log.e("testtest", server);
        }
        if (LoginDelegate.login(userName, pass, server, network)) {
            return true;
        }
        return false;
    }
}

LoginPresenter.java

package com.Ly.BaseJustTalk.ui.activity.Login;

import com.Ly.BaseJustTalk.base.BasePresenter;

/**
 * Created by Ly on 2017/1/16.
 */
public class LoginPresenter extends BasePresenter<LoginContract.LoginView> implements
        LoginContract.LoginPresenter {
    private LoginContract.LoginView loginView;
    private LoginContract.LoginModel loginModel;

    public LoginPresenter(LoginContract.LoginView loginView) {
        this.loginView = loginView;
        loginModel = new LoginModel();
    }

    @Override
    public void doVerificationInfo() {
        int resultcode = this.loginModel.isRightUserNamePass(this.loginView.getUserName(), this.loginView.getUserPass());
        switch (resultcode) {
            case -1:
                this.loginView.setUserNameErr("请输入用户名");
                break;
            case -2:
                this.loginView.setPassErr("请输入用户密码");
                break;
            case -3:
                this.loginView.setUserNameErr("请检查用户名格式或长度");
                break;
            case -4:
                this.loginView.setPassErr("请检查密码格式或长度");
                break;
            case 0:
                this.loginView.setUserNameErr(null);
                this.loginView.setPassErr(null);
                this.loginView.doJustalkLogin();
                break;
            default:
                this.loginView.TsShow("未知错误");
                break;
        }
    }

    @Override
    public void doJustalkLogin() {
        boolean isLoginSuccess = this.loginModel.doJustalkLogin(this.loginView.getUserName(), this.loginView.getUserPass());
        if (!isLoginSuccess) {
            this.loginView.doLoginFial();
        }
    }
}

LoginActivity.java

package com.Ly.BaseJustTalk.ui.activity.Login;

import android.os.Bundle;
import android.support.design.widget.TextInputLayout;
import android.util.Log;
import android.widget.Button;
import android.widget.EditText;

import com.Ly.BaseJustTalk.R;
import com.Ly.BaseJustTalk.base.BaseActivity;
import com.Ly.BaseJustTalk.ui.activity.MainActivity;
import com.Ly.BaseJustTalk.utils.Signer;
import com.justalk.cloud.juslogin.LoginDelegate;
import com.justalk.cloud.lemon.MtcCli;
import com.justalk.cloud.lemon.MtcCliConstants;

import butterknife.Bind;
import butterknife.OnClick;

/**
 * Created by Ly on 2017/1/16.
 */
public class LoginActivity extends BaseActivity<LoginContract.LoginView, LoginPresenter>
        implements LoginContract.LoginView, LoginDelegate.Callback {
    @Bind(R.id.et_username)
    EditText etUsername;
    @Bind(R.id.til_username)
    TextInputLayout tilUsername;
    @Bind(R.id.et_password)
    EditText etPassword;
    @Bind(R.id.til_password)
    TextInputLayout tilPassword;
    @Bind(R.id.bt_login)
    Button btLogin;


    private LoginContract.LoginPresenter loginPresenter = new LoginPresenter(this);

    @OnClick(R.id.bt_login)
    void toLogin() {
        loginPresenter.doVerificationInfo();
    }

    @Override
    protected LoginPresenter createPresenter() {
        return new LoginPresenter(this);
    }

    @Override
    protected int provideContentViewId() {
        return R.layout.activity_login;
    }

    @Override
    public void TsShow(String msg) {
        ShowTs(msg);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LoginDelegate.setCallback(this);
    }


    @Override
    protected void dogetExtra() {

    }


    @Override
    public String getUserName() {
        return etUsername.getText().toString().trim();
    }

    @Override
    public String getUserPass() {
        return etPassword.getText().toString().trim();
    }

    @Override
    public void setUserNameErr(String errMsg) {
        tilUsername.setError(errMsg);
    }

    @Override
    public void setPassErr(String errMsg) {
        tilPassword.setError(errMsg);
    }

    @Override
    public void doJustalkLogin() {
        ShowDialog();
        if (MtcCli.Mtc_CliGetState() != MtcCliConstants.EN_MTC_CLI_STATE_LOGINED) {
            this.loginPresenter.doJustalkLogin();
        } else {
            MainActivity.doStartActivity(mContext, null, MainActivity.class);
        }
    }

    @Override
    public void doLoginFial() {
        ShowTs(getString(R.string.tips_login_fail));
    }

    @Override
    public void mtcLoginOk() {
        MainActivity.doStartActivity(mContext, null, MainActivity.class);
    }

    @Override
    public void mtcLoginDidFail() {
        ShowTs(getString(R.string.tips_login_fail_justalk));
    }

    @Override
    public void mtcLogoutOk() {

    }

    @Override
    public void mtcLogouted() {

    }

    @Override
    public void mtcAuthRequire(String s, String s1) {
        Log.e("LHT", "mtcAuthRequire: " + s + "----" + s1);
        String key =
                "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +
                        "tRPBKBjUTEQwaKEjPy8YnX1bUODPB+7hto8KeGJbnCdCcnJdLxPFE7ld1skKIyPi" +
                        "YkyHj73JqA41ntHML2LNqw5Mhs1pewE4QLCu6icIUNtH8+bL53EhVnfdzwIDAQAB" +
                        "AoGAA6i6c5xHEGfKzoDHQJgiC5X9ZFAtES5AG3IMJmtF9flQyeoeDzRit+/FwNXi" +
                        "M1CKohnvLAJTvPs/8TBp5us4rabQ5Hnp+ylr7I2IbYIP2LV6TKkiTq/fBOJBxZiw" +
                        "qs0tjXxRZnC2IWqoCt/ciE4DXQIYVV3gYMRcKae5KZ3F2LECQQDqL4Sd2xyG0FsW" +
                        "cKwrlFRQ1cfFrSF/jmm2onkCZgDfq0R5aIGewpbTciLj8rf/Zq0XgAmCa3qQYo6M" +
                        "7G0OgIXTAkEAxbIC2xJocvPfEFbUd+hEWDFl/3LtQWZSHVLx9SXLXWSRY4/3dyRM" +
                        "6L78eQ2yWIVF4pxJrIHTbJqhxItlVM/elQJBAJ3jRZ0L8hKufQsHEf0btzD8wQB0" +
                        "doZCZOF+bumADgy+sp7MJ7/636dVZ1KZ/RWTixWx/DdS8UJRQFygtfI2EoMCQHky" +
                        "4tFPfb1LiStJMES6nnu6/R8YZB++DQVxPmjeXMjKyN9S+ZGPLZ9axwmnvfjK68c7" +
                        "rWcWyHlCa35FP0A5l+kCQB5cEu5Au1RkY9XfUodKmFhlCvdY8Ig0JgZ8DC6m+A31" +
                        "o4xrCoGHiPldKdCo0I7gQ4WMvoVNHCQyNv5qcw9t7uk=";
        String code = Signer.signWithKey(key, s, s1, 3600);
        LoginDelegate.promptAuthCode(code);
    }


    @Override
    protected void onDestroy() {
        super.onDestroy();
        LoginDelegate.logout();
    }
}

更新 这个框架里面的循环嵌套的方法


 final String account = "111_111_1111", password = "1234556";
        HttpRequest.getApiService().doRegister(account, password).concatMap(new Function<RegisterBean, Flowable<LoginUserBean>>() {
            @Override
            public Flowable<LoginUserBean> apply(RegisterBean registerBean) throws Exception {
                if (registerBean.getStatus().equals(ErrCodeMessage.statusSuc)) {
                    XLog.e(registerBean.getMessage());
                    return HttpRequest.getApiService().doLogin(account, password);
                } else {
                    return Flowable.empty();
                }
            }
        }).compose(XApi.<LoginUserBean>getScheduler()).compose(XApi.<LoginUserBean>getApiTransformer())
                .compose(this.<LoginUserBean>bindToLifecycle()).subscribe(new ApiSubscriber<LoginUserBean>() {
            @Override
            protected void onFail(NetError error) {
                XLog.e("xxxxxxxxxxxx" + error.getMessage());
            }

            @Override
            public void onNext(LoginUserBean loginUserBean) {
                XLog.e("xxxxxxxxxxxx" + loginUserBean.toString());
            }
        });

如果注册失败的时候 返回 return Flowable.empty(); 一个空的

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

推荐阅读更多精彩内容