RecyclerView完全解析(四)——与SwipeRefreshLayout实现下拉刷新与上拉加载

特别声明:

一、前言

  • 话说RecyclerView已经面市很久,也在很多应用中得到广泛的使用,在整个开发者圈子里面也拥有很不错的口碑,那说明RecyclerView拥有比ListView,GridView之类控件有很多的优点,例如:数据绑定,Item View创建,View的回收以及重用等机制。前三三篇文章已经贡呢更新了以下三个部分:

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

  • 本来这个专题不打算更新,不过前两天看到各位童鞋还是挺积极的评论到,希望可以更新RecyclerView加入下拉刷新和上拉加载更多的功能。正好昨天周末,所以我这边也就实现了这样的功能,今天更新一下。具体代码已经上传到下面的项目中,欢迎各位去star和fork一下:

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

  • RecyclerView实现的列表,默认情况下面是不带下拉刷新和上拉记载更多效果的,但是我在我们的实际项目当中,为了提高用户体验,这种效果一般都需要实现,在我以前的博客中已经重写了ListView(Android 列表下拉刷新组件PullToRefreshListView使用)实现上拉刷新和上拉加载更多效果。现在我们已经学会了ListView,GridView的替代品RecyclerView的基本使用方法,那么必不可少的也需要实现上拉刷新和上拉加载更多的效果了。今天我们会通过两种方式来实现,具体会采用Android的另一控件SwipeRefreshLayout。

二、SwipeRefreshLayout介绍

  • SwipeRefrshLayout是Google官方更新的一个Widget,可以实现下拉刷新的效果。该控件集成自ViewGroup在support-v4兼容包下,不过我们需要升级supportlibrary的版本到19.1以上。基本使用的方法如下:
  • setOnRefreshListener(OnRefreshListener):添加下拉刷新监听器
  • setRefreshing(boolean):显示或者隐藏刷新进度条

  • isRefreshing():检查是否处于刷新状态

  • setColorSchemeResources():设置进度条的颜色主题,最多设置四种,以前的setColorScheme()方法已经弃用了。

  • 具体使用效果下面我们会看到。

三、RecyclerView+SwpieRefreshLayout实现下拉刷新效果

1、SwipeRefreshLayout本身自带下拉刷新的效果,那么我们可以选择在RecyclerView布局外部嵌套一层SwipeRefreshLayout布局即可

  • 具体布局文件如下:
<?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">  
    
    <android.support.v4.widget.SwipeRefreshLayout  
       android:id="@+id/demo_swiperefreshlayout"  
       android:layout_width="fill_parent"  
       android:layout_height="fill_parent"  
       android:scrollbars="vertical"  
        >  
       <android.support.v7.widget.RecyclerView  
           android:id="@+id/demo_recycler"  
           android:layout_width="fill_parent"  
           android:layout_height="fill_parent"  
           ></android.support.v7.widget.RecyclerView>  
   </android.support.v4.widget.SwipeRefreshLayout>  
</LinearLayout>  

2、接着在Activity中获取SwipeRefreshLayout控件并且设置OnRefreshListener监听器,同时实现里边的onRefresh()方法,在该方法中进行网络请求最新数据,然后刷新RecyclerView列表同时设置SwipeRefreshLayout的进度Bar的隐藏或者显示效果。

  • 具体代码如下:
demo_swiperefreshlayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {  
            @Override  
            public void onRefresh() {  
                Log.d("zttjiangqq","invoke onRefresh...");  
                new Handler().postDelayed(newRunnable() {  
                    @Override  
                    public void run() {  
                        List<String> newDatas = new ArrayList<String>();  
                        for (int i = 0; i <5; i++) {  
                            int index = i + 1;  
                           newDatas.add("new item" + index);  
                        }  
                       adapter.addItem(newDatas);  
                       demo_swiperefreshlayout.setRefreshing(false);  
                       Toast.makeText(RecyclerRefreshActivity.this, "更新了五条数据...", Toast.LENGTH_SHORT).show();  
                    }  
                }, 5000);  
            }  
   });  

3、除此之外我们也来看一下Adapter和Activity中的其他代码,也方便各位童鞋查看。

RecyclerRefreshActivity.java

public class RecyclerRefreshActivity extends BaseActivity {  
    private SwipeRefreshLayout demo_swiperefreshlayout;  
    private RecyclerView demo_recycler;  
    private RefreshRecyclerAdapter adapter;  
    private LinearLayoutManager linearLayoutManager;  
    private int lastVisibleItem;  
    @Override  
    protected void onCreate(BundlesavedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.recycler_refresh_layout);  
        
        demo_swiperefreshlayout=(SwipeRefreshLayout)this.findViewById(R.id.demo_swiperefreshlayout);  
        demo_recycler=(RecyclerView)this.findViewById(R.id.demo_recycler);  
        //设置刷新时动画的颜色,可以设置4个  
        demo_swiperefreshlayout.setProgressBackgroundColorSchemeResource(android.R.color.white);  
        demo_swiperefreshlayout.setColorSchemeResources(android.R.color.holo_blue_light,  
                android.R.color.holo_red_light,android.R.color.holo_orange_light,  
               android.R.color.holo_green_light);  
        demo_swiperefreshlayout.setProgressViewOffset(false, 0, (int) TypedValue  
               .applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, getResources()  
                        .getDisplayMetrics()));  

        linearLayoutManager=new LinearLayoutManager(this);  
        linearLayoutManager.setOrientation(OrientationHelper.VERTICAL);  
        demo_recycler.setLayoutManager(linearLayoutManager);  
        //添加分隔线  
        demo_recycler.addItemDecoration(new AdvanceDecoration(this, OrientationHelper.VERTICAL));  
        demo_recycler.setAdapter(adapter = new RefreshRecyclerAdapter(this));  
        //设置下拉刷新监听
        demo_swiperefreshlayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {  
            @Override  
            public void onRefresh() {  
                Log.d("zttjiangqq","invoke onRefresh...");  
                new Handler().postDelayed(newRunnable() {  
                    @Override  
                    public void run() {  
                        List<String> newDatas = new ArrayList<String>();  
                        for (int i = 0; i <5; i++) {  
                            int index = i + 1;  
                           newDatas.add("new item" + index);  
                        }  
                       adapter.addItem(newDatas);  
                       demo_swiperefreshlayout.setRefreshing(false);  
                       Toast.makeText(RecyclerRefreshActivity.this, "更新了五条数据...", Toast.LENGTH_SHORT).show();  
                    }  
                }, 5000);  
            }  
        });  
          
}  

***RefreshRecyclerAdapter.java ***

public class RefreshRecyclerAdapter extends RecyclerView.Adapter<RefreshRecyclerAdapter.ViewHolder>{  
    private LayoutInflater mInflater;  
    private List<String> mTitles=null;  
    public RefreshRecyclerAdapter(Context context){  
       this.mInflater=LayoutInflater.from(context);  
        this.mTitles=new ArrayList<String>();  
        for (int i=0;i<20;i++){  
            int index=i+1;  
           mTitles.add("item"+index);  
        }  
    }  
    /** 
     * item显示类型 
     * @param parent 
     * @param viewType 
     * @return 
     */  
    @Override  
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {  
        final Viewview=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.get(position));  
        holder.itemView.setTag(position);  
    }  
    @Override  
    public int getItemCount() {  
        return mTitles.size();  
    }  
   
    //自定义的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);  
        }  
    }  
   
    //添加数据  
    public void addItem(List<String> newDatas) {  
        //mTitles.add(position, data);  
        //notifyItemInserted(position);  
        newDatas.addAll(mTitles);  
        mTitles.removeAll(mTitles);  
        mTitles.addAll(newDatas);  
        notifyDataSetChanged();  
    }  
   
    public void addMoreItem(List<String> newDatas) {  
        mTitles.addAll(newDatas);  
        notifyDataSetChanged();  
    }  
}  
  • 以上重要地方的注释已经加上去了。

4、运行效果大致如下:

.gif

如无法显示图片:点击此链接查看

四、RecyclerView设置滚动事件加入上拉加载更多功能

  • 下面我们再来看RecyclerView和相关类的一些特性:

LayoutManger给我们提供了以下几个方法来让开发者方便的获取到屏幕上面的顶部item和顶部item相关的信息:

  • findFirstVisibleItemPosition()
  • findFirstCompletlyVisibleItemPosition()
  • findLastVisibleItemPosition()
  • findLastCompletlyVisibleItemPosition()

同时通过Recycler.Adapter的getItemCount()方法可以轻松获取到RecyclerView列表中Item View的个数。

  • 那么下面我们通过监听滑动(滚动)事件,然后在里边判断是否已经滑动到最底部来加载更多的数据,使用方法如下:
       //RecyclerView滑动监听  
       demo_recycler.setOnScrollListener(new RecyclerView.OnScrollListener() {  
           @Override  
           public void onScrollStateChanged(RecyclerView recyclerView, int newState) {  
              super.onScrollStateChanged(recyclerView, newState);  
               if (newState ==RecyclerView.SCROLL_STATE_IDLE && lastVisibleItem + 1 ==adapter.getItemCount()) {  
                   new Handler().postDelayed(new Runnable() {  
                       @Override  
                       public void run() {  
                           List<String> newDatas = new ArrayList<String>();  
                           for (int i = 0; i< 5; i++) {  
                               int index = i +1;  
                              newDatas.add("more item" + index);  
                           }  
                          adapter.addMoreItem(newDatas);  
                       }  
                   },1000);  
               }  
           }  
           @Override  
           public void onScrolled(RecyclerView recyclerView, int dx, int dy) {  
               super.onScrolled(recyclerView,dx, dy);  
               lastVisibleItem =linearLayoutManager.findLastVisibleItemPosition();  
           }  
       });  
  • 运行效果如下:


    .gif

如不能显示效果图:点击此链接查看

五、升级RecyclerView加入FootView实现上拉加载

  • 上面我们虽然已经实现了上拉加载更多的效果,但是还比较丑陋,最起码要让用户知道确实在上拉加载的过程吧,例如加载一个底部的进度布局。这样一想,那么我们就按照ListView方式addFootView()呗,不过很可惜的是RecyclerView没有给我们提供addFootView()方法,那该怎么样办呢?

我们来看RecyclerView.Apapter类:

  • 我们要实现一个自定义Adapter一定需要实现onCreateViewHolder(ViewGroup paren,int viewType)方法,注意看方法中的第二个参数viewType,是不是想到布局类型了,也就是说该也支持多套布局显示的,那么查看基类中的所有方法如下:


上面有一个方法getItemType(),这个就和ListView的Adapter的实现差不多了,那么我们这边可以使用多套布局给RecyclerView加入一个FootView布局即可。

RefreshFootAdapter.Java具体实现流程如下:

1、加入布局状态标志-用来判断此时加载是普通Item还是foot view:

 private static final int TYPE_ITEM =0;  //普通Item View
 private static final intTYPE_FOOTER = 1;  //顶部FootView

2、重写getItemCount()方法,返回的Item数量在数据的基础上面+1,增加一项FootView布局项:

//返回的总条目数 +1
public intgetItemCount() {  
     return mTitles.size()+1;  
 }  

3、重写getItemViewType方法来判断返回加载的布局的类型:

public int getItemViewType(int position) {  
    // 最后一个item设置为footerView  
    if (position + 1 == getItemCount()) {  
        return TYPE_FOOTER;  
    } else {  
        return TYPE_ITEM;  
    }  
 }  

4、接着onCreateViewHolder(ViewGroup parent,int viewType)加载布局的时候根据viewType的类型来选择指定的布局创建,返回即可:

public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {  
        //进行判断显示类型,来创建返回不同的View  
        if(viewType==TYPE_ITEM){  
            Viewview=mInflater.inflate(R.layout.item_recycler_layout,parent,false);  
            //这边可以做一些属性设置,甚至事件监听绑定  
            //view.setBackgroundColor(Color.RED);  
            ItemViewHolder itemViewHolder=new ItemViewHolder(view);  
            return itemViewHolder;  
        }else if(viewType==TYPE_FOOTER){  
            Viewfoot_view=mInflater.inflate(R.layout.recycler_load_more_layout,parent,false);  
            //这边可以做一些属性设置,甚至事件监听绑定  
            //view.setBackgroundColor(Color.RED);  
            FootViewHolder footViewHolder=new FootViewHolder(foot_view);  
            return footViewHolder;  
        }  
       return null;  
}  

5、最后进行判断数据的时候(onBindViewHolder),判断holder的类型来进行判定数据即可:

public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {  
       if(holder instanceof ItemViewHolder) {  
          ((ItemViewHolder)holder).item_tv.setText(mTitles.get(position));  
           holder.itemView.setTag(position);  
       }else if(holder instanceof FootViewHolder){  
           FootViewHolderfootViewHolder=(FootViewHolder)holder;  
           switch (load_more_status){  
               case PULLUP_LOAD_MORE:  
                  footViewHolder.foot_view_item_tv.setText("上拉加载更多...");  
                   break;  
               case LOADING_MORE:  
                  footViewHolder.foot_view_item_tv.setText("正在加载更多数据...");  
                   break;  
           }  
       }  
} 

6、整个RefreshFootAdapter完整代码如下:

  • 同时该Adaper中我们还可以定义一个changeMoreStatus()方法和两个字符串常量可以来进行修改FootView中字符串提醒文本的。
public class RefreshFootAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{  
    //上拉加载更多  
    public static final int  PULLUP_LOAD_MORE=0;  
    //正在加载中  
    public static final int  LOADING_MORE=1;  
    //上拉加载更多状态-默认为0  
    private int load_more_status=0;  
    private LayoutInflater mInflater;  
    private List<String> mTitles=null;  
    private static final intTYPE_ITEM = 0;  //普通Item View  
    private static final intTYPE_FOOTER = 1;  //顶部FootView  
    public RefreshFootAdapter(Context context){  
       this.mInflater=LayoutInflater.from(context);  
        this.mTitles=new ArrayList<String>();  
        for (int i=0;i<20;i++){  
            int index=i+1;  
           mTitles.add("item"+index);  
        }  
    }  
    /** 
     * item显示类型 
     * @param parent 
     * @param viewType 
     * @return 
     */  
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {  
        //进行判断显示类型,来创建返回不同的View  
        if(viewType==TYPE_ITEM){  
            Viewview=mInflater.inflate(R.layout.item_recycler_layout,parent,false);  
            //这边可以做一些属性设置,甚至事件监听绑定  
           //view.setBackgroundColor(Color.RED);  
            ItemViewHolder itemViewHolder=new ItemViewHolder(view);  
            return itemViewHolder;  
        }else if(viewType==TYPE_FOOTER){  
            Viewfoot_view=mInflater.inflate(R.layout.recycler_load_more_layout,parent,false);  
            //这边可以做一些属性设置,甚至事件监听绑定  
           //view.setBackgroundColor(Color.RED);  
            FootViewHolder footViewHolder=new FootViewHolder(foot_view);  
            return footViewHolder;  
        }  
       return null;  
    }  
   
    /** 
     * 数据的绑定显示 
     * @param holder 
     * @param position 
     */  
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {  
        if(holder instanceof ItemViewHolder) {  
           ((ItemViewHolder)holder).item_tv.setText(mTitles.get(position));  
            holder.itemView.setTag(position);  
        }else if(holder instanceof FootViewHolder){  
            FootViewHolder footViewHolder=(FootViewHolder)holder;  
            switch (load_more_status){  
                case PULLUP_LOAD_MORE:  
                   footViewHolder.foot_view_item_tv.setText("上拉加载更多...");  
                    break;  
                case LOADING_MORE:  
                   footViewHolder.foot_view_item_tv.setText("正在加载更多数据...");  
                    break;  
            }  
        }  
    }  
   
    /** 
     * 进行判断是普通Item视图还是FootView视图 
     * @param position 
     * @return 
     */  
    @Override  
    public int getItemViewType(int position) {  
    // 最后一个item设置为footerView  
    if (position + 1 == getItemCount()) {  
                return TYPE_FOOTER;  
            } else {  
                return TYPE_ITEM;  
            }  
        }  
    @Override  
    public int getItemCount() {  
        return mTitles.size()+1;  
    }  
    //自定义的ViewHolder,持有每个Item的的所有界面元素  
    public static class ItemViewHolder extends RecyclerView.ViewHolder {  
        public TextView item_tv;  
        public ItemViewHolder(View view){  
            super(view);  
            item_tv = (TextView)view.findViewById(R.id.item_tv);  
        }  
    }  
    /** 
     * 底部FootView布局 
     */  
    public static class FootViewHolder extends  RecyclerView.ViewHolder{  
        private TextView foot_view_item_tv;  
        public FootViewHolder(View view) {  
            super(view);  
           foot_view_item_tv=(TextView)view.findViewById(R.id.foot_view_item_tv);  
        }  
    }  
   
    //添加数据  
    public void addItem(List<String> newDatas) {  
        //mTitles.add(position, data);  
        //notifyItemInserted(position);  
        newDatas.addAll(mTitles);  
        mTitles.removeAll(mTitles);  
        mTitles.addAll(newDatas);  
        notifyDataSetChanged();  
    }  
   
    public void addMoreItem(List<String> newDatas) {  
        mTitles.addAll(newDatas);  
        notifyDataSetChanged();  
    }  
   
    /** 
     * //上拉加载更多 
     * PULLUP_LOAD_MORE=0; 
     * //正在加载中 
     * LOADING_MORE=1; 
     * //加载完成已经没有更多数据了 
     * NO_MORE_DATA=2; 
     * @param status 
     */  
    public void changeMoreStatus(int status){  
        load_more_status=status;  
        notifyDataSetChanged();  
    }  
}  

7、整体Activity中还是设置监听RecyclerView的滚动事件.代码和第一个例子差不多,不过RecyclerView需要设置这边的Adapter了,demo_recycler.setAdapter(adapter= new RefreshFootAdapter(this)):

//设置滑动监听
demo_recycler.setOnScrollListener(new RecyclerView.OnScrollListener() {  
            @Override  
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {  
               super.onScrollStateChanged(recyclerView, newState);  
                if (newState ==RecyclerView.SCROLL_STATE_IDLE && lastVisibleItem + 1 ==adapter.getItemCount()) {  
                    adapter.changeMoreStatus(RefreshFootAdapter.LOADING_MORE);  
                    newHandler().postDelayed(new Runnable() {  
                        @Override  
                        public void run() {  
                            List<String> newDatas = new ArrayList<String>();  
                            for (int i = 0; i< 5; i++) {  
                                int index = i +1;  
                               newDatas.add("more item" + index);  
                            }  
                           adapter.addMoreItem(newDatas);  
                           adapter.changeMoreStatus(RefreshFootAdapter.PULLUP_LOAD_MORE);  
                        }  
                    }, 2500);  
                }  
            }  
            @Override  
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {  
                super.onScrolled(recyclerView,dx, dy);  
                lastVisibleItem =linearLayoutManager.findLastVisibleItemPosition();  
            }  
 });  

8、运行效果大致如下:

六、最后总结

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

推荐阅读更多精彩内容