RecyclerView 从零开始一步步深入

RecyclerView作为ListView,GridView的升级版,使用起来非常灵活。并且配合动画可以实现非常赞的效果。

基本使用步骤:
mRecyclerView = findView(R.id.id_recyclerview);
//设置布局管理器
mRecyclerView.setLayoutManager(layout);
//设置adapter
mRecyclerView.setAdapter(adapter)
//设置Item增加、移除动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
//添加分割线
mRecyclerView.addItemDecoration(new DividerItemDecoration(
getActivity(), DividerItemDecoration.HORIZONTAL_LIST));

1.基础知识点

  • LayoutManager 顾名思义 负责布局的管理,通过切换布局管理器我们可以轻松实现列表,网格,瀑布流等效果。
  • Adapter 用于适配item,这里的Adapter是继承自RecyclerView.Adapter不是BaseAdapter
  • ItemDecoration 通俗点讲就是“分割线”,类似listView中的divider,但是RecyclerView中并未提供这个属性,要实现分割线,需要通过调用addItemDecoration(),系统并未提供缺省的ItemDecoration实现类。幸运的是已经有第三方实现好了的ItemDecoration,后面介绍
  • ItemAnimator 有了它可以实现各种炫酷的动画效果,系统提供了缺省的DefaultItemAnimator

2.知识点详解

⑴ LayoutManager 系统提供了LinearLayoutManager,GridLayoutManager,StaggeredGridLayoutManager,分别对应三种效果 列表网格瀑布流。示例代码:

 mNormalRecyclerView.setLayoutManager(new LinearLayoutManager(this));//设置list布局
 mNormalRecyclerView.setLayoutManager(new GridLayoutManager(this, 4));//设置网格布局
 mNormalRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL));//设置瀑布流布局

可以看到想要切换效果只需要设置下LayoutManager,对于有需求要求显示多种布局效果的时候,用RecyclerView相比listView等要省力灵活很多。
...
也许这还不够打动你,接着往下看
通常我们的listView,GirdView都是竖直方向流向的,需求来了要实现横向的listView肿么办?过去还是要花点力气去实现的吧,看RecyclerView分分钟秒杀你

      LinearLayoutManager layoutManager=new LinearLayoutManager(this);
      layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
      mNormalRecyclerView.setLayoutManager(layoutManager);//设置list横向布局

⑵ Adapter 适配item布局的东东看代码,先写个最简单的Adapter


public class AdapterNormal extends RecyclerView.Adapter<AdapterNormal.MyViewHolder> {
  private Context context;
  private List<String> list;

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

  @Override
  public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {//产生几个可复用的ViewHolder实例
      MyViewHolder viewHolder = new MyViewHolder(LayoutInflater.from(context).inflate(R.layout.adapter_item, parent, false));
      return viewHolder;
  }

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

  @Override
  public void onBindViewHolder(final MyViewHolder holder, final int position) {//为每一项View绑定数据
      holder.mTextView.setText(list.get(position));
  }


  class MyViewHolder extends RecyclerView.ViewHolder {//ViewHolder 大家都不陌生
      private TextView mTextView;

      public MyViewHolder(View itemView) {
          super(itemView);
          mTextView = (TextView) itemView.findViewById(R.id.mText);
      }
  }
}

调用它

adapterNormal = new AdapterNormal(getApplicationContext(), list);
mNormalRecyclerView.setAdapter(adapterNormal);//设置适配器

多个不同项布局,Adapter该怎么写?

  @Override
  public int getItemViewType(int position) {//在Adapter中重写该方法,根据条件返回不同的值例如100,101
      if (...) {
          return 100;
      }else if (...) {
          return 101;
      }else{
          return super.getItemViewType(position);
      }
  }

  @Override
  public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {//根据getItemViewType返回的值生成不同的ViewHolder实例
      MyViewHolder viewHolder = null;
      switch (viewType) {//示例逻辑
          case 100:
              viewHolder=...;
              break;
          case 101:
              viewHolder=...;
              break;
          default:
              viewHolder = new MyViewHolder(LayoutInflater.from(context).inflate(R.layout.adapter_item, parent, false));
              break;
      }
      return viewHolder;
  }

  @Override
  public void onBindViewHolder(final MyViewHolder holder, final int position) {//为不同的布局适配数据
      switch (holder.getItemViewType()) {
          case 100:
              ...
              break;
          case 101:
              ...
              break;
          default:
              holder.mTextView.setText(list.get(position));
              break;
      }
  }

每次都这样写Adapter是不是觉得很累?简化它

首先引入 compile 'com.zhy:base-adapter:2.0.0'

Android 万能的Adapter for ListView,RecyclerView,GridView等,支持多种Item类型的情况。

mRecyclerView.setAdapter(new CommonAdapter<String>(this, R.layout.item_list, mDatas)
{
    @Override
    public void convert(ViewHolder holder, String s)
    {
        holder.setText(R.id.id_item_list_title, s);
    }
});

是不是相当方便,在convert方法中完成数据、事件绑定即可。还有多种ItemViewType的封装等自行研究都很方便

⑶ItemDecoration "分割线" 这玩意感觉没什么好说的直接看代码

  
/**
 * ListView的分割线
 */
public class ListItemDecoration extends RecyclerView.ItemDecoration {

    private static final int[] ATTRS = new int[]{
            android.R.attr.listDivider
    };

    public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;

    public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;

    private Drawable mDivider;

    private int mOrientation;

    public ListItemDecoration(Context context, int orientation) {
        final TypedArray a = context.obtainStyledAttributes(ATTRS);
        mDivider = a.getDrawable(0);
        a.recycle();
        setOrientation(orientation);
    }

    public void setOrientation(int orientation) {
        if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
            throw new IllegalArgumentException("invalid orientation");
        }
        mOrientation = orientation;
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        if (mOrientation == VERTICAL_LIST) {
            drawVertical(c, parent);
        } else {
            drawHorizontal(c, parent);
        }

    }


    public void drawVertical(Canvas c, RecyclerView parent) {
        final int left = parent.getPaddingLeft();
        final int right = parent.getWidth() - parent.getPaddingRight();

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext());
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                    .getLayoutParams();
            final int top = child.getBottom() + params.bottomMargin;
            final int bottom = top + mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    public void drawHorizontal(Canvas c, RecyclerView parent) {
        final int top = parent.getPaddingTop();
        final int bottom = parent.getHeight() - parent.getPaddingBottom();

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                    .getLayoutParams();
            final int left = child.getRight() + params.rightMargin;
            final int right = left + mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        if (mOrientation == VERTICAL_LIST) {
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
        } else {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
        }
    }
}

/**
 * @author 鸿洋
 * GridView的分割线
 * 因为作者瀑布流的分割线在item高度不一样的情况下有bug,所以被我去掉了
 */
public class GridItemDecoration extends RecyclerView.ItemDecoration {

    private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
    private Drawable mDivider;

    public GridItemDecoration(Context context) {
        final TypedArray a = context.obtainStyledAttributes(ATTRS);
        mDivider = a.getDrawable(0);
        a.recycle();
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        drawHorizontal(c, parent);
        drawVertical(c, parent);
    }

    private int getSpanCount(RecyclerView parent) {
        // 列数
        int spanCount = -1;
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        spanCount = ((GridLayoutManager) layoutManager).getSpanCount();
        return spanCount;
    }

    public void drawHorizontal(Canvas c, RecyclerView parent) {
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                    .getLayoutParams();
            final int left = child.getLeft() - params.leftMargin;
            final int right = child.getRight() + params.rightMargin
                    + mDivider.getIntrinsicWidth();
            final int top = child.getBottom() + params.bottomMargin;
            final int bottom = top + mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    public void drawVertical(Canvas c, RecyclerView parent) {
        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                    .getLayoutParams();
            final int top = child.getTop() - params.topMargin;
            final int bottom = child.getBottom() + params.bottomMargin;
            final int left = child.getRight() + params.rightMargin;
            final int right = left + mDivider.getIntrinsicWidth();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(c);
        }
    }

    private boolean isLastColum(int pos, int spanCount) {
        if ((pos + 1) % spanCount == 0)// 如果是最后一列,则不需要绘制右边
        {
            return true;
        }
        return false;
    }

    private boolean isLastRaw(int pos, int spanCount, int childCount) {
        childCount = childCount - childCount % spanCount;
        if (pos >= childCount)// 如果是最后一行,则不需要绘制底部
            return true;
        return false;
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        int spanCount = getSpanCount(parent);
        int childCount = parent.getAdapter().getItemCount();
        if (isLastRaw(parent.getChildAdapterPosition(view), spanCount, childCount))// 如果是最后一行,则不需要绘制底部
        {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
        } else if (isLastColum(parent.getChildAdapterPosition(view), spanCount))// 如果是最后一列,则不需要绘制右边
        {
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
        } else {
            outRect.set(0, 0, mDivider.getIntrinsicWidth(),
                    mDivider.getIntrinsicHeight());
        }
    }
}

⑷ ItemAnimator 前面说过了可以产生炫酷的动画,提升逼格的神器
https://github.com/wasabeef/recyclerview-animators

demo3.gif

demo2.gif

demo.gif

⑸ “遗憾”。 是的RecyclerView并没有像listView,GridView那样提供itemClickListener和itemLongClickListener,我们需要自行实现,可以通过设置接口回调,好消息是上面提到的Android 万能的Adapter 已经为我们实现好了这些工作。

⑹ 补充。当数据发生变化时我们需要更新数据集

adapterNormal.notifyDataSetChanged(); //无动画效果
adapterNormal.notifyItemInserted(0);//有动画效果

以上就是RecyclerView的基本使用,华丽的分割线,提升。。。


自定义RecyclerView,实现下拉刷新,上拉加载更多
https://github.com/jianghejie/XRecyclerView
看效果先

default.gif

源码浅析:

public class XRecyclerView extends RecyclerView {
  @Override
  public void setAdapter(Adapter adapter) {//重写适配器方法
      mWrapAdapter = new WrapAdapter(adapter);//一个包装类
      super.setAdapter(mWrapAdapter);//设置这个包装后的适配器作为适配器
      adapter.registerAdapterDataObserver(mDataObserver);//注册自定义的数据观察者,因为适配器是包装后的适配器
      mDataObserver.onChanged();
  }

  @Override
  public void onScrollStateChanged(int state) {//通过判断最后一个可见item的位置和项数量的大小关系实现上拉加载更多
      super.onScrollStateChanged(state);

      if (state == RecyclerView.SCROLL_STATE_IDLE && mLoadingListener != null && !isLoadingData && loadingMoreEnabled) {
          LayoutManager layoutManager = getLayoutManager();
          int lastVisibleItemPosition;
          if (layoutManager instanceof GridLayoutManager) {
              lastVisibleItemPosition = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition();
          } else if (layoutManager instanceof StaggeredGridLayoutManager) {
              int[] into = new int[((StaggeredGridLayoutManager) layoutManager).getSpanCount()];
              ((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(into);
              lastVisibleItemPosition = findMax(into);
          } else {
              lastVisibleItemPosition = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
          }
          if (layoutManager.getChildCount() > 0
                  && lastVisibleItemPosition >= layoutManager.getItemCount() - 1 && layoutManager.getItemCount() > layoutManager.getChildCount() && !isNoMore && mRefreshHeader.getState() < ArrowRefreshHeader.STATE_REFRESHING) {

              View footView = mFootViews.get(0);
              isLoadingData = true;
              if (footView instanceof LoadingMoreFooter) {
                  ((LoadingMoreFooter) footView).setState(LoadingMoreFooter.STATE_LOADING);
              } else {
                  footView.setVisibility(View.VISIBLE);
              }
              mLoadingListener.onLoadMore();
          }
      }
  }

  @Override
  public boolean onTouchEvent(MotionEvent ev) {//通过重写触摸事件实现下拉刷新
      if (mLastY == -1) {
          mLastY = ev.getRawY();
      }
      switch (ev.getAction()) {
          case MotionEvent.ACTION_DOWN:
              mLastY = ev.getRawY();
              break;
          case MotionEvent.ACTION_MOVE:
              final float deltaY = ev.getRawY() - mLastY;
              mLastY = ev.getRawY();
              if (isOnTop() && pullRefreshEnabled) {
                  mRefreshHeader.onMove(deltaY / DRAG_RATE);
                  if (mRefreshHeader.getVisibleHeight() > 0 && mRefreshHeader.getState() < ArrowRefreshHeader.STATE_REFRESHING) {
                      return false;
                  }
              }
              break;
          default:
              mLastY = -1; // reset
              if (isOnTop() && pullRefreshEnabled) {
                  if (mRefreshHeader.releaseAction()) {
                      if (mLoadingListener != null) {
                          mLoadingListener.onRefresh();
                      }
                  }
              }
              break;
      }
      return super.onTouchEvent(ev);
  }
}
   private class WrapAdapter extends RecyclerView.Adapter<ViewHolder> {//定义一个包装类,将外部自定义的适配器和内部适配器逻辑关联起来

      private RecyclerView.Adapter adapter;//持有我们使用的时候自定义的适配器


      public WrapAdapter(RecyclerView.Adapter adapter) {
          this.adapter = adapter;
      }

      @Override
      public void onAttachedToRecyclerView(RecyclerView recyclerView) {//只执行一次
          super.onAttachedToRecyclerView(recyclerView);
          RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
          if (manager instanceof GridLayoutManager) {
              final GridLayoutManager gridManager = ((GridLayoutManager) manager);
              gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {//设置监听回调只需要设置一次
                  @Override
                  public int getSpanSize(int position) {//SpanSize 代表占几列
                      return (isHeader(position) || isFooter(position))//头部或者尾部占满全行或者全列
                              ? gridManager.getSpanCount() : 1;
                  }
              });
          }
      }

      @Override
      public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {//被多次调用
          super.onViewAttachedToWindow(holder);
          ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
          if (lp != null
                  && lp instanceof StaggeredGridLayoutManager.LayoutParams
                  && (isHeader(holder.getLayoutPosition()) || isFooter(holder.getLayoutPosition()))) {
              StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;
              p.setFullSpan(true);//头部或者尾部占满全行或者全列
          }
      }

      @Override
      public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
          if (viewType == TYPE_REFRESH_HEADER) {//根据返回的类型生成不同的ViewHolder实例
              mCurrentPosition++;
              return new SimpleViewHolder(mHeaderViews.get(0));
          } else if (isContentHeader(mCurrentPosition)) {
              if (viewType == sHeaderTypes.get(mCurrentPosition - 1)) {
                  mCurrentPosition++;
                  return new SimpleViewHolder(mHeaderViews.get(headerPosition++));
              }
          } else if (viewType == TYPE_FOOTER) {
              return new SimpleViewHolder(mFootViews.get(0));
          }
          return adapter.onCreateViewHolder(parent, viewType);
      }

      private int mCurrentPosition;

      @Override
      public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {//为我们自定义的item类型绑定数据
          if (isHeader(position)) {
              return;
          }
          int adjPosition = position - getHeadersCount();
          int adapterCount;
          if (adapter != null) {
              adapterCount = adapter.getItemCount();
              if (adjPosition < adapterCount) {
                  adapter.onBindViewHolder(holder, adjPosition);
                  return;
              }
          }
      }


      @Override
      public int getItemViewType(int position) {//返回刷新头类型,普通头类型,尾部类型,我们自定义的类型
          if (isRefreshHeader(position)) {
              return TYPE_REFRESH_HEADER;
          }
          if (isHeader(position)) {
              position = position - 1;
              return sHeaderTypes.get(position);
          }
          if (isFooter(position)) {
              return TYPE_FOOTER;
          }
          int adjPosition = position - getHeadersCount();
          int adapterCount;
          if (adapter != null) {
              adapterCount = adapter.getItemCount();
              if (adjPosition < adapterCount) {
                  return adapter.getItemViewType(adjPosition);
              }
          }
          return TYPE_NORMAL;
      }
 }
   private class DataObserver extends RecyclerView.AdapterDataObserver {
      @Override
      public void onChanged() {//数据改变的时候通过比较item数量显示隐藏EmptyView
          Adapter<?> adapter = getAdapter();
          if (adapter != null && mEmptyView != null) {
              int emptyCount = 0;
              if (pullRefreshEnabled) {
                  emptyCount++;
              }
              if (loadingMoreEnabled) {
                  emptyCount++;
              }
              if (adapter.getItemCount() == emptyCount) {
                  mEmptyView.setVisibility(View.VISIBLE);
                  XRecyclerView.this.setVisibility(View.GONE);
              } else {
                  mEmptyView.setVisibility(View.GONE);
                  XRecyclerView.this.setVisibility(View.VISIBLE);
              }
          }
          if (mWrapAdapter != null) {
              mWrapAdapter.notifyDataSetChanged();
          }
      }

      @Override
      public void onItemRangeInserted(int positionStart, int itemCount) {
          mWrapAdapter.notifyItemRangeInserted(positionStart, itemCount);
      }

      @Override
      public void onItemRangeChanged(int positionStart, int itemCount) {
          mWrapAdapter.notifyItemRangeChanged(positionStart, itemCount);
      }

      @Override
      public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
          mWrapAdapter.notifyItemRangeChanged(positionStart, itemCount, payload);
      }

      @Override
      public void onItemRangeRemoved(int positionStart, int itemCount) {
          mWrapAdapter.notifyItemRangeRemoved(positionStart, itemCount);
      }

      @Override
      public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
          mWrapAdapter.notifyItemMoved(fromPosition, toPosition);
      }
  };

大致思路:定义一个包装适配器,通过不同的item类型判断生成刷新头,普通头部,尾部,我们自定义的itemView并绑定item数据→监听滚动和触摸事件来显示隐藏刷新头和加载更多尾部视图并通过接口抛出刷新和加载更多抽象方法

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

推荐阅读更多精彩内容