基本UI组件的封装库(一)——basicUI

查看在线pdf文档:
http://note.youdao.com/s/EM20Cggm

以下是我的基本UI组件该系列的文章,欢迎大家转载和分享:
基本UI组件的封装库(一)——basicUI
基本UI组件的封装库(二)——basicUI
基本UI组件的封装库(三)——basicUI
基本UI组件的封装库(四)——basicUI

初衷

  • 我们在项目开发的时候,通常会遇到很多不同UI要求,可能每次都要写很多次的布局。
  • BasicUI设计初衷就是希望可以提高我们开发的效率和节省时间。
  • 如果大家觉得有点帮助,希望可以抬抬你的贵手,送我一个星星,谢谢。如果有什么问题,也欢迎你在下方留言或者在BasicUI中提

Gradle依赖

  • Step 1. Add the JitPack repository to your build file
    Add it in your root build.gradle at the end of repositories:
    allprojects {
        repositories {
            ...
            maven { url 'https://jitpack.io' }
        }
    }
  • Step 2. Add the dependency
    dependencies {
             implementation 'com.github.Peakmain:BasicUI:1.1.0-androidx'
    }

使用

一、启动器优化

原理

原理图.png

有向无环图的设计


image.png

使用

  • 1、并发运行,只需要继承Task
public class UtilsTask extends Task {
    @Override
    public void run() {
        //处理数据
    }
}
  • 2、在某个Task之后执行,比如极光需要在获取设备id之后执行
    如:获取设备id的Task
public class DeviceIdTask extends Task {
    private String mDeviceId;

    @Override
    public void run() {
        // 真正自己的代码
        TelephonyManager tManager = (TelephonyManager) mContext.getSystemService(
                Context.TELEPHONY_SERVICE);
        mDeviceId = tManager.getDeviceId();
        App app = (App) mContext;
        app.setDeviceId(mDeviceId);
    }
}

极光的Task需要实现dependsOn方法
如下代码

public class JPushTask extends Task {
    @Override
    public List<Class<? extends Task>> dependsOn() {
        List<Class<? extends Task>> tasks = new ArrayList<>();
        tasks.add(DeviceIdTask.class);
        return tasks;
    }

    @Override
    public void run() {
        //模拟极光推送
        LogUtils.e("极光推送开始");
        App app = (App) mContext;
        LogUtils.e("极光推送获取id:",app.getDeviceId());
    }
}
  • 3、运行在主线程只需要继承MainTask即可
public class WeexTask extends MainTask {
    @Override
    public void run() {
        InitConfig config = new InitConfig.Builder().build();
        WXSDKEngine.initialize((Application) mContext, config);
    }
}
  • 4、想空闲的时候处理数据,只需要在Application中使用DelayInitDispatcher添加相关Task
    DelayInitDispatcher delayInitDispatcher=new DelayInitDispatcher();
    delayInitDispatcher.addTask(任务即可).start();
  • 5、最后只需要在Application中进行初始化和添加运行就可以了
  TaskDispatcher.init(this);
        TaskDispatcher dispatcher = TaskDispatcher.createInstance();
        dispatcher.addTask(new AMapTask())
                .addTask(new UtilsTask())
                .addTask(new JPushTask())
                .addTask(new DeviceIdTask())
                .addTask(new WeexTask())
                .start();

二、工具类封装

image.png
OkHttp的使用
  • get请求方式
     HttpUtils.with(OkHttpActivity.this)
                            .url("http://i.jandan.net/")
                            .addParams("oxwlxojflwblxbsapi", "jandan.get_pic_comments")
                            .addParams("page", "1")
                            .execture(new EngineCallBack() {
                                @Override
                                public void onError(Exception e) {
                                    LogUtils.e(e.getMessage());
                                }

                                @Override
                                public void onSuccess(String result) {
                                    mTvResult.setText(result);
                                }

                            });
  • post请求方式
                    HttpUtils.with(OkHttpActivity.this)
                            .url("https://www.wanandroid.com/user/login")
                            .addParams("username", "peakmain")
                            .addParams("password", "123456")
                            .post()
                            .execture(new EngineCallBack() {
                                @Override
                                public void onError(Exception e) {
                                    LogUtils.e(e.getMessage());
                                }

                                @Override
                                public void onSuccess(String result) {
                                    mTvResult.setText(result);
                                }
                            });
  • 单线程下载
                    File file = new File(Environment.getExternalStorageDirectory(), "test.apk");
                    if (file.exists()) {
                        file.delete();
                    }
                    HttpUtils.with(OkHttpActivity.this)
                            .url("http://imtt.dd.qq.com/16891/apk/87B3504EE9CE9DC51E9F295976F29724.apk")
                            .downloadSingle()
                            .file(file)
                            .exectureDownload(new DownloadCallback() {
                                @Override
                                public void onFailure(Exception e) {
                                    LogUtils.e(e.getMessage());
                                }

                                @Override
                                public void onSucceed(File file) {
                                    ToastUtils.showShort("file下载完成");
                                    LogUtils.e("文件保存的位置:" + file.getAbsolutePath());
                                    mProgressBar.setVisibility(View.GONE);
                                    mProgressBar.setProgress(0);
                                }

                                @Override
                                public void onProgress(int progress) {
                                    LogUtils.e("单线程下载apk的进度:" + progress);
                                    mProgressBar.setProgress(progress);
                                    mProgressBar.setVisibility(View.VISIBLE);
                                }
                            });
  • 多线程下载
                    file = new File(Environment.getExternalStorageDirectory(), "test.apk");
                    if (file.exists()) {
                        file.delete();
                    }
                    HttpUtils.with(OkHttpActivity.this)
                            .url("http://imtt.dd.qq.com/16891/apk/87B3504EE9CE9DC51E9F295976F29724.apk")
                            .downloadMutil()
                            .file(file)
                            .exectureDownload(new DownloadCallback() {
                                @Override
                                public void onFailure(Exception e) {
                                    LogUtils.e(e.getMessage());
                                }

                                @Override
                                public void onSucceed(File file) {
                                    LogUtils.e(file.getAbsolutePath() + "," + file.getName());
                                    Toast.makeText(OkHttpActivity.this, "下载完成", Toast.LENGTH_LONG).show();
                                    mProgressBar.setVisibility(View.GONE);
                                    mProgressBar.setProgress(0);
                                }

                                @Override
                                public void onProgress(int progress) {
                                    LogUtils.e(progress + "%");
                                    mProgressBar.setVisibility(View.VISIBLE);
                                    mProgressBar.setProgress(progress);
                                }
                            });
  • 切换引擎的方法
    默认是okHttpEngine
  HttpUtils.with(OkHttpActivity.this)
   .exchangeEngine(切换的引擎)

或者在application中直接初始化
HttpUtils.init(初始化的引擎);

2、 gilde图片选择库切换
image.png
  • 简单使用
 ImageLoader.getInstance().displayImage(this, data.get(0).getUrl(), mImageView);
  • 占位图的使用
 ImageLoader.getInstance().displayImage(this, data.get(1).getUrl(), mImageView, R.mipmap.ic_default_portrait);
  • 圆角图片
 ImageLoader.getInstance().displayImageRound(this, data.get(2).getUrl(), mImageView,50 ,0);
  • 指定图片的大小
ImageLoader.getInstance().displayImage(this,data.get(4).getUrl(),mImageView,800,800,0);
  • 切换图片加载库
ImageLoader.getInstance().exchangeImageLoader(切换的库)
3、Sqlitedatabase数据库封装的使用
  • 1、需要实体类实现空的构造方法

-2、获得IDaoSupport实体类

IDaoSupport<Person> dao = DaoSupportFactory.getInstance().getDao(Person.class);
  • 3、插入数据
  dao.insert(persons);
  • 4、查询数据
    查询所有数据
 List<Person> list = dao.querySupport().queryAll();

查询指定数据:如指定指定名字的范围

dao.querySupport().selection("name like ?").selectionArgs(new String[]{"新%"}).query();
  • 5、删除数据
    需要传入两个参数
    1、条件 2、条件参数
    源码
int delete(String whereClause, String... whereArgs);

如下删除年龄109的数据,返回的是删除数据所在位置的索引

dao.delete("age = ?", new String[]{"109"});
  • 6、更新数据
    需要传入三个参数,1、新数据的实体类,2、条件 3、条件参数
    源码
    int update(T obj, String whereClause, String... whereArgs);

如下更新年龄是108的数据

 dao.update(new Person("peakmain", 18), "age =?", new String[]{"108"});

四、关于Recyclerview的使用

  • 单布局继承于 CommonRecyclerAdapter<T>,其中需要的方法是
public CommonRecyclerAdapter(Context context, List<T> data, int layoutId) {}

1、关于设置文本,第一种我们可以直接holder.setText方法

holder.setText(int viewId,CharSequence text)

第二种就是首先getView然后设置文字

TextView tv=holder.getView(view viewId)
tv.setText("")

2、除了设置文本之外,里面还提供了设置文字的颜色,文字的大小,view的点击事件和长按事件,view是否可见,某一条的点击事件

3、关于设置图片,我提供了一个默认Glide加载图片

  holder.setImageByUrl(R.id.iv_meizhi, new GlideImageLoader(item.getUrl()));

如果大家不想使用Glide或者想用自己的Glide,大家可以新建一个类去继承ViewHolder.HolderImageLoade即可

for example:

public class PagePoupAdapter extends CommonRecyclerAdapter<String>{
    private int mSelectPosition=0;
    public PagePoupAdapter(Context context, List<String> data) {
        super(context, data, R.layout.item_popup_window);
    }

    @Override
    public void convert(ViewHolder holder, String item) {
        TextView tvName=holder.getView(R.id.tv_name);
        tvName.setText(item);
       tvName.setTextColor(mContext.getResources().getColor(R.color.colorAccent));
    }
}
  • 关于多布局
    也是继承extends CommonRecyclerAdapter<T>.但是实现的方法是
 public CommonRecyclerAdapter(Context context, List<T> data, MultiTypeSupport<T> multiTypeSupport) {}

for example:

    public SearchCityAdapter(Context context, List<SearchCityBean> data) {
        super(context, data, new MultiTypeSupport<SearchCityBean>() {
            @Override
            public int getLayoutId(SearchCityBean item, int position) {
                return R.layout.default_city_recycler_item;
            }
        });
    }
    @Override
    public void convert(ViewHolder holder, SearchCityBean item) {
        int itemViewType = getItemViewType(holder.getAdapterPosition());
        if (itemViewType == R.layout.default_city_recycler_item) {
            holder.setText(R.id.tv_city_name, item.getName());
        }
    }
  • 我们有时候会使用到recycleview的悬浮列表,这里我也提供了一个基本的悬浮列表BaseSuspenisonItemDecoration,使用很简单,大家只需要继承BaseSuspenisonItemDecoration即可
    因为有时候我们需要自定背景颜色和文字颜色,文字的大小,悬浮之间的距离等。这里我提供了Builder方法,大家继承即可

for example:

public class SectionItemDecoration extends BaseItemDecoration<SearchCityBean> {

    public SectionItemDecoration(Builder builder) {
        super(builder);
    }
    @Override
    public String getTopText(List<SearchCityBean> data, int position) {
        return data.get(position).getSection();
    }
    public static class Builder extends BaseItemDecoration.Builder<SectionItemDecoration.Builder,SearchCityBean>{

        public Builder(Context context, List<SearchCityBean> data) {
            super(context, data);
        }
        @Override
        public SectionItemDecoration create() {
            return new SectionItemDecoration(this);
        }
    }
}

使用非常简单

   SectionItemDecoration decoration = new SectionItemDecoration.Builder(this, mAllCities)
                .setBgColor(ContextCompat.getColor(this,R.color.colorAccent))
                .setTextColor(ContextCompat.getColor(this,R.color.color_black))
                .setSectionHeight(SizeUtils.dp2px(this,30))
                .create();

recyclerview实现下拉刷新、加载更多和多状态布局

  • 封装库类中我封装了一个默认的下拉刷新和加载更多
  • 1、布局
    <com.peakmain.ui.recyclerview.view.LoadRefreshRecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
  • 2、下拉刷新
        mRecyclerView.addRefreshViewCreator(new DefaultRefreshViewCreator());
        mRecyclerView.setOnRefreshListener(this);
  • 3、重写onRefresh的方法,数据获取完之后在其中调用onStopRefresh方法停止下拉刷新
    比如示例中的代码
    public void onRefresh() {
        new Handler().postDelayed(() -> {
            List<String> data = getData();
            mAdapter.setData(data);
            mRecyclerView.onStopRefresh();
        }, 2000);
    }
  • 4、加载更多
  mRecyclerView.addLoadViewCreator(new DefalutLoadViewCreator());
        mRecyclerView.setOnLoadMoreListener(this);
  • 5、重写onLoad方法,数据获取完之后在其中调用onStopLoad方法停止加载更多
    比如示例中的代码
   public void onLoad() {
        new Handler().postDelayed(() -> {
            List<String> moreData = getMoreData();
            mAdapter.addData(moreData);
            mRecyclerView.onStopLoad();
        }, 2000);

    }
  • 6、大家还可以自定以下拉刷新和加载更多
    下拉刷新继承RefreshViewCreator实现相关功能即可
    上图下拉刷新效果2示例代码
public class BestMissRefreshCreator extends RefreshViewCreator {
    // 加载数据的ImageView
    private ImageView mRefreshIv;
    @Override
    public View getRefreshView(Context context, ViewGroup parent) {
        View refreshView = LayoutInflater.from(context).inflate(R.layout.layout__bestmiss_refresh_header_view, parent, false);
        mRefreshIv = refreshView.findViewById(R.id.img_progress);
        return refreshView;
    }

    @Override
    public void onPull(int currentDragHeight, int refreshViewHeight, int currentRefreshStatus) {
        if (currentRefreshStatus == LoadRefreshRecyclerView.LOAD_STATUS_PULL_DOWN_REFRESH) {
            mRefreshIv.setImageResource(R.drawable.list_view_pull);
        }
        if (currentRefreshStatus == LoadRefreshRecyclerView.LOAD_STATUS_LOOSEN_LOADING) {
            mRefreshIv.setImageResource(R.drawable.list_view_release);
        }
    }

    @Override
    public void onRefreshing() {
        mRefreshIv.setImageResource(R.drawable.load_more_anim);
        ((AnimationDrawable) mRefreshIv.getBackground()).start();
    }
    @Override
    public void onStopRefresh() {
        // 停止加载的时候清除动画
        mRefreshIv.setRotation(0);
        ((AnimationDrawable) mRefreshIv.getBackground()).stop();
        mRefreshIv.clearAnimation();
    }
}

加载更多继承LoadViewCreator即可
上图下拉刷新2示例代码

public class LoadMoreCreator extends LoadViewCreator {
    // 加载数据的ImageView
    private TextView mLoadTv;
    private View mRefreshIv;

    @Override
    public View getLoadView(Context context, ViewGroup parent) {
        View refreshView = LayoutInflater.from(context).inflate(R.layout.layout_load_footer_view, parent, false);
        mLoadTv = (TextView) refreshView.findViewById(R.id.load_tv);
        mRefreshIv = refreshView.findViewById(R.id.refresh_iv);
        return refreshView;
    }

    @Override
    public void onPull(int currentDragHeight, int refreshViewHeight, int currentRefreshStatus) {
        if (currentRefreshStatus == LoadRefreshRecyclerView.LOAD_STATUS_PULL_DOWN_REFRESH) {
            mLoadTv.setText("上拉加载更多");
        }
        if (currentRefreshStatus == LoadRefreshRecyclerView.LOAD_STATUS_LOOSEN_LOADING) {
            mLoadTv.setText("松开加载更多");
        }
    }

    @Override
    public void onLoading() {
        mLoadTv.setVisibility(View.INVISIBLE);
        mRefreshIv.setVisibility(View.VISIBLE);

        // 加载的时候不断旋转
        RotateAnimation animation = new RotateAnimation(0, 720,
                Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        animation.setRepeatCount(-1);
        animation.setDuration(1000);
        mRefreshIv.startAnimation(animation);
    }

    @Override
    public void onStopLoad() {
        // 停止加载的时候清除动画
        mRefreshIv.setRotation(0);
        mRefreshIv.clearAnimation();
        mLoadTv.setText("上拉加载更多");
        mLoadTv.setVisibility(View.VISIBLE);
        mRefreshIv.setVisibility(View.INVISIBLE);
    }


    @Override
    public void onFinishLoadData() {
       mLoadTv.setText("无更多数据");
    }
}

RecycleView实现多状态布局

多状态布局.gif

使用
使用WarpRecyclerView(只能添加头部或尾部)、RefreshRecyclerView(下拉刷新)或者LoadRefreshRecyclerView(加载更多或者下拉刷新)

  • 1、可以自定义状态布局,一共四个属性
    <declare-styleable name="MultipleStatusView">
        <attr name="loadingView" format="reference" />
        <attr name="errorView" format="reference" />
        <attr name="emptyView" format="reference" />
        <attr name="noNetworkView" format="reference" />
    </declare-styleable>

所以使用示例如下

    <com.peakmain.ui.recyclerview.view.LoadRefreshRecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:emptyView="@layout/layout_empty_view"
        app:errorView="@layout/layout_error_view"
        app:noNetworkView="@layout/layout_network_view"
        app:loadingView="@layout/layout_loading_view"/>

注意:这里的emptyView、errorView、noNetworkView布局的根布局需要设置id,对应的id分别是:empty_retry_view、error_retry_view、no_network_retry_view

  • 2、basicUI提供了默认的状态布局,可不设置自定义属性

  • 3、没有网络显示

 mRecyclerView.showNoNetwork();
  • 4、空布局
    这里大家放在什么位置都可以,源码中会自动根据是否有数据进行显示或隐藏
mRecyclerView.showEmptyView();
  • 5、显示loading和隐藏loading
  mRecyclerView.showLoading();
mRecyclerView.hideLoading();
  • 6、显示错误
 mRecyclerView.showError();
  • 7、显示内容
mRecyclerView.showContentView();
  • 8、设置emptyView、errorView、noNetworkView可以设置点击事件
 mRecyclerView.setOnRetryClickListener(v -> ToastUtils.showShort("正在重新请求接口..."));

五、关于NavigationBar的使用

我们每次在项目添加头部的时候,一般做法都是说定义一个公用的布局,但是这其实并不友好,而且都需要findVIewById,我这里用了Builder设计模式,可以动态添加头部

    mDefaultNavigationBar = new DefaultNavigationBar
                  .Builder(this, (ViewGroup) findViewById(android.R.id.content))
                  //Whether to display the return button
                  .setDisplayHomeAsUpEnabled(true)
                  //Set left click event
                  .setLeftClickListener(v -> {
  
                  })
                  //Whether to display the title that comes with the toolbar by default
                  .setDisplayShowTitleEnabled(true)
                  //Hide right view
                  .hideRightView()
                  //Set the click event of the return button
                  .setNavigationOnClickListener(v -> finish())
                  //set left text color
                  .setLeftTextColor(ContextCompat.getColor(this,R.color.color_272A3D))
                  //set title
                  .setTitleText("")
                  //set toolbar background color
                  .setToolbarBackgroundColor(0)
                  .create();
image.png

六、关于AlertDialog

支持从底部弹出,支持宽度全屏,支持设置动画

        AlertDialog dialog = new AlertDialog.Builder(ImagePreviewActivity.this)
                .setContentView(R.layout.dialog_show_image_deal)
                .fromButtom(true)
                // Set click events for view
                .setOnClickListener(R.id.bt_logout, new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                    }
                })
                //set animation
                .setAnimation(R.style.dialog_from_bottom_anim)
                //Eject from bottom
                .fromButtom(true)
                //set width  MATCH_PARENT
                .setFullWidth()
                .show();
image.png

七、关于PopupWindow的封装

       new CustomPopupWindow.PopupWindowBuilder(this)
                .setView(R.layout.popup_window_view)
                .enableBackgroundDark(true)
                .setAnimationStyle(R.style.PopupWindowAnimation)
                .setBgDarkAlpha(0.7f)
                .create();

八、TextView的封装

这个支持设置背景颜色,背景的圆角,线条的颜色,线条的宽度,支持文字上下左右图片资源两者居中,减少布局嵌套,使用方法还是android:drawableLeft=""

    <com.peakmain.ui.widget.ShapeTextView
        android:id="@+id/shape_text_view"
        android:layout_width="@dimen/space_100"
        android:layout_height="@dimen/space_100"
        android:layout_centerInParent="true"
        android:gravity="center"
        android:textColor="@color/color_white"
        android:textSize="28sp"
        tools:text="A"
        app:shapeTvRadius="@dimen/space_6"
        app:shapeTvBackgroundColor="#333333" />
image.png

说明:一共有四个属性:shapeTvStrokeWidth线的宽度、shapeTvStrokeColor线的颜色、shapeTvRadius圆角的半径、shapeTvBackgroundColor背景颜色

九、EditText的封装

自带清除按钮的图标,并且可对删除按钮进行填色,或者对删除按钮图标进行替换

    <com.peakmain.ui.widget.AutoDeleteEditText
                    android:id="@+id/edt_address"
                    android:layout_width="0dp"
                    android:layout_height="match_parent"
                    android:layout_weight="3"
                    android:paddingBottom="@dimen/spacing_5"
                    app:ad_HintColor="@color/color_9B9B9B"
                    app:ad_TextColor="@color/color_4A4A4A"
                    app:ad_TextSize="@dimen/font_16"
                    app:ad_hint="@string/input_details_address"
                    app:ad_paddingTop="@dimen/spacing_15"
                    app:ad_top="true" />

说明:一共有11个属性:字体大小adet_text_size、字体颜色adet_text_color、未输入文字时字体颜色adet_hint_color、内容是否居上adet_isTop、内容内边距离顶部的距离adet_padding_top、hint的资源adet_hint、是否单行adet_isSingle、输入类型android:inputType、输入长度adet_max_length、删除的图片的颜色adet_tint_color、删除图片的资源adet_delete_src

结语

如果大家感兴趣想知道更多的使用,大家可以看我实战项目wanandorid

我的项目BasicUI的Github地址:https://github.com/Peakmain/BasicUI

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