RecyclerView完全解析(一)——基本使用

特别声明:

一、前言

  • 话说RecyclerView已经面市很久,也在很多应用中得到广泛的使用,在整个开发者圈子里面也拥有很不错的口碑,那说明RecyclerView拥有比ListView,GridView之类控件有很多的优点,例如:数据绑定,Item View创建,View的回收以及重用等机制。那么今天开始我们来重点学习一下RecyclerView控件,本系列文章会包括到以下三个部分:

1. RecyclerView控件的基本使用,包括基础,进阶,高级部分,动画之类
2. RecyclerView控件的实战实例
3. RecyclerView控件集合AA(Android Annotations)注入框架实例

  • 那么今天我们首先来看第一部分:RecyclerView控件的基本使用,进阶,动画相关知识点。本次讲解所有用的Demo例子已经全部更新到下面的项目中了,欢迎大家star和fork。

FastDev4Android框架项目地址:https://github.com/jiangqqlmj/FastDev4Android

二、RecyclerView基本介绍:

  • 通过使用RecyclerView控件,我们可以在APP中创建带有Material Design风格的复杂列表。RecyclerView控件和ListView的原理有很多相似的地方,都是维护少量的View来进行显示大量的数据,不过RecyclerView控件比ListView更加高级并且更加灵活。当我们的数据因为用户事件或者网络事件发生改变的时候也能很好的进行显示。
  • 和ListView不同的是,RecyclerView不用在负责Item的显示相关的功能,在这边所有有关布局,绘制,数据绑定等都被分拆成不同的类进行管理,下面我这边会一个个的进行讲解。同时RecyclerView控件提供了以下两种方法来进行简化和处理大数量集合:

1. 采用LayoutManager来处理Item的布局
2. 提供Item操作的默认动画,例如在增加或者删除item的时候

  • 你也可以自定义LayoutManager或者设置添加/删除的动画,整体的RecyclerView结构图如下:


  • 为了使用RecyclerView控件,我们需要创建一个Adapter和一个LayoutManager:

Adapter:继承自RecyclerView.Adapetr类,主要用来将数据和布局item进行绑定。
LayoutManager:布局管理器,设置每一项view在RecyclerView中的位置布局以及控件item view的显示或者隐藏。当View重用或者回收的时候,LayoutManger都会向Adapter来请求新的数据来进行替换原来数据的内容。这种回收重用的机制可以提供性能,避免创建很多的view或者是频繁的调用findViewById方法。这种机制和ListView还是很相似的。

  • RecyclerView提供了三种内置的LayoutManager:

1. LinearLayoutManager:线性布局,横向或者纵向滑动列表
2. GridLayoutManager:表格布局
3. StaggeredGridLayoutManager:流式布局,例如瀑布流效果

  • 当然除了上面的三种内部布局之外,我们还可以继承RecyclerView.LayoutManager来实现一个自定义的LayoutManager。

  • Animations(动画)效果:

RecyclerView对于Item的添加和删除是默认开启动画的。我们当然也可以通过RecyclerView.ItemAnimator类定制动画,然后通过RecyclerView.setItemAnimator()方法来进行使用。

  • RecyclerView相关类:
类名 说明
RecyclerView.Adapter 可以托管数据集合,为每一项Item创建视图并且绑定数据
RecyclerView.ViewHolder 承载Item视图的子布局
RecyclerView.LayoutManager 负责Item视图的布局的显示管理
RecyclerView.ItemDecoration 给每一项Item视图添加子View,例如可以进行画分隔线之类的
RecyclerView.ItemAnimator 负责处理数据添加或者删除时候的动画效果

三、RecyclerView基本实现:

  • 我这边实例采用Android Studio 1.3.2。
1、添加库依赖:
dependencies {  
   …….  
    compile'com.android.support:recyclerview-v7:23.1.1'  
}    
2、新建布局,引入RecyclerView控件:
<?xmlversionxmlversion="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:androidLinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"  
    android:orientation="vertical"android:layout_width="match_parent"  
   android:layout_height="match_parent">  
    <includelayoutincludelayout="@layout/common_top_bar_layout"/>  
   
    <android.support.v7.widget.RecyclerView  
       android:id="@+id/recyclerView_one"  
       android:layout_width="match_parent"  
       android:layout_height="match_parent"  
        android:scrollbars="vertical"  
       ></android.support.v7.widget.RecyclerView>  
</LinearLayout>  
3、在Activity中获取RecyclerView控件然后进行设置LayoutManger以及Adapter即可,和ListView的写法有点类似:
public class RecyclerViewTestActivity extends AppCompatActivity {
    
    private RecyclerView recyclerView_one;
    private RecyclerView.Adapter mAdapter;
    private LinearLayoutManager mLayoutManager;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        //开始设置RecyclerView
        recyclerView_one=(RecyclerView)this.findViewById(R.id.recyclerView);
        //设置固定大小
        recyclerView_one.setHasFixedSize(true);
        //创建线性布局
        mLayoutManager = new LinearLayoutManager(this);
        //垂直方向
        mLayoutManager.setOrientation(OrientationHelper.VERTICAL);
        //给RecyclerView设置布局管理器
        recyclerView_one.setLayoutManager(mLayoutManager);
        //创建适配器,并且设置
        mAdapter = new TestRecyclerAdapter(this);
        recyclerView_one.setAdapter(mAdapter);
    }
    
}
4、自定义一个适配器来进行创建item view以及绑定数据
public class TestRecyclerAdapter extends RecyclerView.Adapter<TestRecyclerAdapter.ViewHolder>{

    private LayoutInflater mInflater;
    private String[] mTitles=null;

    public TestRecyclerAdapter(Context context){
        this.mInflater=LayoutInflater.from(context);
        this.mTitles=new String[20];
        for (int i=0;i<20;i++){
            int index=i+1;
            mTitles[i]="item"+index;
        }
    }
    /**
     * item显示类型
     * @param parent
     * @param viewType
     * @return
     */
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view=mInflater.inflate(R.layout.item_recycler_layout,parent,false);
        //view.setBackgroundColor(Color.RED);
        ViewHolder viewHolder=new ViewHolder(view);
        return viewHolder;
    }
    /**
     * 数据的绑定显示
     * @param holder
     * @param position
     */
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.item_tv.setText(mTitles[position]);
    }

    @Override
    public int getItemCount() {
        return mTitles.length;
    }

    //自定义的ViewHolder,持有每个Item的的所有界面元素
    public static class ViewHolder extends RecyclerView.ViewHolder {
        public TextView item_tv;
        public ViewHolder(View view){
            super(view);
            item_tv = (TextView)view.findViewById(R.id.item_tv);
        }
    }
}

这个自定义Adapter和我们在使用Listview时候的Adapter相比还是有点不太一样的,首先这边我们需要继承RecyclerView.Adaper类,然后实现两个重要的方法onBindViewHodler()以及onCreateViewHolder(),这边我们看出来区别,使用RecyclerView控件我们就可以把Item View视图创建和数据绑定这两步进行分来进行管理,用法就更加方便而且灵活了,并且我们可以定制打造千变万化的布局。同时这边我们还需要创建一个ViewHolder类,该类必须继承自RecyclerView.ViewHolder类,现在Google也要求我们必须要实现ViewHolder来承载Item的视图。

  • 该Demo运行效果如下:


  • 上面的例子我们这边比较简单使用LinearLayoutManager来实现了,其中布局是采用垂直布局的,当然我们还可以设置线性布局的方向为横向,只要如下设置即可:

mLayoutManager.setOrientation(OrientationHelper.HORIZONTAL);  

运行效果如下:


那么另外两种内置的布局如下:
  • 1、GridLayoutManger:使用如下设置:
GridLayoutManager girdLayoutManager=new GridLayoutManager(this,4);  
 recyclerView_one.setLayoutManager(girdLayoutManager);  

运行效果如下:


  • 2、StaggeredGridLayoutManager :使用如下设置:
StaggeredGridLayoutManager staggeredGridLayoutManager=new   StaggeredGridLayoutManager(2,OrientationHelper.VERTICAL);  
  recyclerView_one.setLayoutManager(staggeredGridLayoutManager);  

实现的是瀑布流的效果

四、RecyclerView分隔线实现(ItemDecoration):

  • 大家肯定观察到上面的显示效果还是比较丑,例如就没有分隔线这个效果,下面我们一起来实现以下分隔线的效果。还记得前面的一个表格中有写关于RecyclerView的相关类:
RecyclerView.ItemDecoration 给每一项Item视图添加子View,可以进行画分隔线之类的东西
  • 我们可以创建一个继承RecyclerView.ItemDecoration类来绘制分隔线,通过ItemDecoration可以让我们每一个Item从视觉上面相互分开来,例如ListView的divider非常相似的效果。当然像我们上面的例子ItemDecoration我们没有设置也没有报错哦,那说明ItemDecoration我们并不是强制需要使用,作为我们开发者可以设置或者不设置Decoration的。

  • 实现一个ItemDecoration,系统提供的ItemDecoration是一个抽象类,内部除去已经废弃的方法以外,我们主要实现以下三个方法:

public static abstract class ItemDecoration {  
        public void onDraw(Canvas c,RecyclerView parent, State state) {  
            onDraw(c, parent);  
        }  
        public void onDrawOver(Canvas c,RecyclerView parent, State state) {  
            onDrawOver(c, parent);  
        }  
        public void getItemOffsets(RectoutRect, View view, RecyclerView parent, State state) {  
            getItemOffsets(outRect,((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),  
                    parent);  
        }  
    }  
  • 又因为当我们RecyclerView在进行绘制的时候会进行绘制Decoration,那么会去调用onDraw和onDrawOver方法,那么这边我们其实只要去重写onDraw和getItemOffsets这两个方法就可以实现啦。然后LayoutManager会进行Item布局的时候,回去调用getItemOffset方法来计算每个Item的Decoration合适的尺寸

  • 下面我们来具体实现一个Decoration:
    TestDecoration.java

public class TestDecoration extends RecyclerView.ItemDecoration {  
    //采用系统内置的风格的分割线  
    private static final int[] attrs=newint[]{android.R.attr.listDivider};  
    private Drawable mDivider;  
   
    public TestDecoration(Context context) {  
        TypedArray typedArray=context.obtainStyledAttributes(attrs);  
        mDivider=typedArray.getDrawable(0);  
    }  
   
    /** 
     * 进行自定义绘制 
     * @param c 
     * @param parent 
     * @param state 
     */  
    @Override  
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {  
        int top=parent.getPaddingTop();  
        intbottom=parent.getHeight()-parent.getPaddingBottom();  
        int childCount=parent.getChildCount();  
        for(int i=0;i<childCount;i++){  
            View child=parent.getChildAt(i);  
            RecyclerView.LayoutParams layoutParams=(RecyclerView.LayoutParams)child.getLayoutParams();  
            intleft=child.getRight()+layoutParams.rightMargin;  
            intright=left+mDivider.getIntrinsicWidth();  
            mDivider.setBounds(left,top,right,bottom);  
            mDivider.draw(c);  
        }  
    }  
   
    @Override  
    public void getItemOffsets(Rect outRect,View view, RecyclerView parent, RecyclerView.State state) {  
       outRect.set(0,0,mDivider.getIntrinsicWidth(),0);  
    }  
}  
  • 我这边实例中采用系统主题(android.R.attr.listDivider)来设置成分隔线的,然后来获取尺寸,位置进行setBound(),绘制,接着通过outRect.set()来设置绘制整个区域范围,最后不要忘记往RecyclerView中设置该自定义的分割线:
//添加分割线
recyclerView_one.addItemDecoration(newTestDecoration(this));  
  • 运行效果大致如下:


  • 上面的分割线效果只是垂直画了分割线,但是我们水平方向也要进行画分割线,那么我们下面对于自定义的Decoration进行改进:
    AdvanceDecoration.java

/** 
 * 当前类注释:改进之后的自定义Decoration分割线 
 */  
public class AdvanceDecoration extends RecyclerView.ItemDecoration{  
    //采用系统内置的风格的分割线  
    private static final int[] attrs=newint[]{android.R.attr.listDivider};  
    private Drawable mDivider;  
    private int orientation;  
    public AdvanceDecoration(Contextcontext,int orientation) {  
        TypedArray typedArray=context.obtainStyledAttributes(attrs);  
        mDivider=typedArray.getDrawable(0);  
        typedArray.recycle();  
        this.orientation=orientation;  
    }  
    @Override  
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {  
                 drawHDeraction(c,parent);  
                 drawVDeraction(c,parent);  
    }  
    /** 
     * 绘制水平方向的分割线 
     * @param c 
     * @param parent 
     */  
    private void drawHDeraction(Canvas c,RecyclerView parent){  
        int left=parent.getPaddingLeft();  
        intright=parent.getWidth()-parent.getPaddingRight();  
        int childCount=parent.getChildCount();  
        for(int i=0;i<childCount;i++){  
            View child=parent.getChildAt(i);  
            RecyclerView.LayoutParams layoutParams=(RecyclerView.LayoutParams)child.getLayoutParams();  
            inttop=child.getBottom()+layoutParams.bottomMargin;  
            intbottom=top+mDivider.getIntrinsicHeight();  
           mDivider.setBounds(left,top,right,bottom);  
            mDivider.draw(c);  
        }  
    }  
    /** 
     * 绘制垂直方向的分割线 
     * @param c 
     * @param parent 
     */  
    private void drawVDeraction(Canvas c,RecyclerView parent){  
        int top=parent.getPaddingTop();  
        intbottom=parent.getHeight()-parent.getPaddingBottom();  
        int childCount=parent.getChildCount();  
        for(int i=0;i<childCount;i++){  
            View child=parent.getChildAt(i);  
            RecyclerView.LayoutParamsla youtParams=(RecyclerView.LayoutParams)child.getLayoutParams();  
            intleft=child.getRight()+layoutParams.rightMargin;  
            intright=left+mDivider.getIntrinsicWidth();  
           mDivider.setBounds(left,top,right,bottom);  
            mDivider.draw(c);  
        }  
    }  
    @Override  
    public void getItemOffsets(Rect outRect,View view, RecyclerView parent, RecyclerView.State state) {  
       if(OrientationHelper.HORIZONTAL==orientation){  
            outRect.set(0, 0,mDivider.getIntrinsicWidth(), 0);  
        }else {  
            outRect.set(0, 0, 0,mDivider.getIntrinsicHeight());  
        }  
    }  
}  
  • 改良之后的自定义分割器的构造函数中新增一个int参数,用来表示横向还是纵向布局,这样我们可以分别来绘制分割线。
  • 具体使用方法:
recyclerView_one.addItemDecoration(new AdvanceDecoration(this,OrientationHelper.VERTICAL));
  • 运行比较效果如下:
    横向效果

    纵向效果

五、RecyclerView高级用户(监听事件处理)

  • 我们知道在ListView使用的时候,该控件给我们提供一个onItemClickListener监听器,这样当我们的item发生触发事件的时候,会回调相关的方法,以便我们方便处理Item点击事件。

  • 对于RecyclerView来讲,非常可惜的时候,该控件没有给我们提供这样的内置监听器方法,不过我们可以进行改造实现。我们先来看一下之前我们写得TestRecyclerAdapter中的onCreateViewHolder()方法中的代码:

public ViewHolder onCreateViewHolder(ViewGroupparent, int viewType) {  
       Viewview=mInflater.inflate(R.layout.item_recycler_layout,parent,false);  
       //这边可以做一些属性设置,甚至事件监听绑定  
       //view.setBackgroundColor(Color.RED);  
       ViewHolder viewHolder=newViewHolder(view);  
       return viewHolder;  
   }  
  • 该方法创建一个ViewHolder,其中承载的就是每一项Item View视图,那么我们可以在view创建出来之后给它进行添加相应的属性或者监听方法,例如:背景颜色,大小,以及点击事件。既然可以这样解决,OK,我们给View添加一个onClickListener监听器,然后点击的时候回调onClick()方法。同时我们需要自定义一个类似于onItemClickListener()的监听器来处理。
    注意:这个监听就在该adapter里定义
   /** 
    * 自定义RecyclerView 中item view点击回调方法 
    */  
    interface OnRecyclerItemClickListener{  
        /** 
         * item view 回调方法 
         * @param view  被点击的view 
         * @param position 点击索引 
         */  
        void onItemClick(View view, intposition);  
    }  
  • 然后声明以及Adapter初始化的时候传入进去:
public  TestRecyclerAdapter(Contextcontext,OnRecyclerItemClickListener onRecyclerItemClickListener){  
        ……
       this.onRecyclerItemClickListener=onRecyclerItemClickListener;  
    }  
  • 然后我们在onClick回调方法中调用OnRecyclerItemClickListener接口的方法:
view.setOnClickListener(new View.OnClickListener() {  
         @Override  
         public void onClick(View v) {  
            if(onRecyclerItemClickListener!=null){  
                onRecyclerItemClickListener.onItemClick(view, (int)view.getTag());  
             }  
         }  
   });  

注意:这里的view就是onCreateViewHolder方法里返回的view,完整逻辑大致就是如下的样子:

public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        final View view=mInflater.inflate(R.layout.item_recycler_layout,parent,false);
        //这边可以做一些属性设置,甚至事件监听绑定
        //view.setBackgroundColor(Color.RED);
        ViewHolder viewHolder=new ViewHolder(view);
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if(onRecyclerItemClickListener!=null){
                    onRecyclerItemClickListener.onItemClick(view, (int)view.getTag());
                }
            }
        });
        return viewHolder;
    }
  • 上面的onItemClick中第二个参数的position,采用view.getTag的方法获取,那么我们就需要在onBindViewHolder()方法中设置一个tag了:
public voidonBindViewHolder(ViewHolder holder, int position) {  
       holder.item_tv.setText(mTitles[position]);  
       holder.itemView.setTag(position);  
    }  
  • 最后我们在外部使用一下接口:
mAdapter = new TestRecyclerAdapter(this, new TestRecyclerAdapter.OnRecyclerItemClickListener() {  
            @Override  
            public void onItemClick(View view,int position) {  
               Toast.makeText(RecyclerViewTestActivity.this, "点击了第"+position+"项", Toast.LENGTH_SHORT).show();  
            }  
  });  
  • 运行结果大致如下:


六、RecyclerView数据添加删除处理

  • 讲了以上RecyclerView中各种用户,处理之后,现在我们来看一下当数据发生变化之后的处理。例如我们在使用ListView的时候,当数据发生变化的时候可以通过notifyDatasetChange()来刷新界面。

  • 对于RecyclerView控件来讲,给我们提供更加高级的使用方法notifyItemInserted(position)和notifyItemRemoved(position)

  • 我们可以在TestRecyclerAdapter中添加数据新增和数据删除的方法如下:

//添加数据  
 public void addItem(String data, int  position) {  
     mTitles.add(position, data);  
     notifyItemInserted(position);  
 }  
 //删除数据  
 public void removeItem(String data) {  
     int position = mTitles.indexOf(data);  
     mTitles.remove(position);  
     notifyItemRemoved(position);  
 }  
  • 然后我们在Activity中进行调用即可:
//添加数据  
mAdapter.addItem("additem",5);  
//删除数据  
mAdapter.removeItem("item4");  
  • 在运行之前我们不要忘记RecyclerView给提供了动画设置,我这边就直接采用了默认动画,设置方法如下:
//添加默认的动画效果  
recyclerView_one.setItemAnimator(new DefaultItemAnimator());  
  • 最终运行效果大致如下图:


七、RecyclerView总结

  • 到此为止就完成我们RecyclerView控件使用的第一讲内容,其中包括控件的基本介绍,基本使用,高级用法(自定义间隔符,加入点击监听事件以及Item添加删除动画处理)。总体来讲RecyclerView控件是非常不错,尤其在布局以及数据绑定,动画方面,除了系统内置的三种布局方式之外,我们还可以定制出我们自己的布局管理器。同时当item数据发生变化的时候还给我们提供非常炫的效果。相信大家在今天这一讲之后,会越来越爱上RecyclerView控件的使用,从此可以抛弃ListView和GridView啦.

  • 本次具体实例注释过的全部代码已经上传到FastDev4Android项目中了。同时欢迎大家去Github站点进行clone或者下载浏览:
    https://github.com/jiangqqlmj/FastDev4Android 同时欢迎大家star和fork整个开源快速开发框架项目~下一讲我们会通过一个具体实例来自定义实现一个广告条控件实例。

  • 本人录制AA(Android Annotations)注入框架的视频教程已经上线了,欢迎大家前往观看。http://www.cniao5.com/course/10074

  • 再次声明:本文转载自:【江清清的博客】http://blog.csdn.net/developer_jiangqq/article/details/49927631**

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

推荐阅读更多精彩内容