RecyclerView的使用,一篇文章就夠了。

RecyclerView是support v7中提供的一個控件,可以說是listView和GridView的增強版,提供了一種插拔式的體驗。提供了三個設置方法來供我們快速做出一些我們想要的效果效果。

  • LayoutManager:控制的是RecyclerView的顯示方式。
  • ItemDecoration:繪制Item之間的間隔,并在特定時間繪制一些我們想要的東西。
  • ItemAnimator:控制Item增刪的動畫。

今天我們來學習一下RecyclerView的基本使用方法。我們想要使用它,首先我們要在app/build.gradle文件中添加依賴。

    implementation 'com.android.support:recyclerview-v7:27.1.1'
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rcl_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>

添加好布局之后,和我們的listView一樣,要創建Adapter和ViewHolder。和listView不同Recycler提供了內部抽象類為我們來繼承,RecyclerView.Adapter<ViewHolder> 和 RecyclerView.ViewHolder。我們先來看一個例子。

    class MyAdapter  extends RecyclerView.Adapter<MyAdapter.ViewHolder>{
        private List<User> userList;

        public MyAdapter(List<User> userList) {
            this.userList = userList;
        }

        @NonNull
        @Override
        public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            View view  = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_main,parent,false);
            ViewHolder viewHolder = new ViewHolder(view);
            return viewHolder;
        }

        @Override
        public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
            holder.tvName.setText("Name:"+userList.get(position).getName());         holder.tvAge.setText("Age:"+userList.get(position).getAge());
        }
        @Override
        public int getItemCount() {
            return userList.size();
        }
        class ViewHolder extends RecyclerView.ViewHolder{
            private TextView tvName ;
            private TextView tvAge;

            public ViewHolder(View itemView) {
                super(itemView);
                tvName = itemView.findViewById(R.id.tv_name);
                tvAge = itemView.findViewById(R.id.tv_age);
            }
        }
    }

我們可以看到 我們的MyAdapter繼承自RecyclerView.Adapter,重寫了三個方法。

  • onCreateViewHolder():用來創建ViewHolder實例,我們在這個方法中將item布局加載進來,并創建一個ViewHolder實例,將我們的item布局傳入到ViewHolder的構造方法中,并將ViewHolder返回。
  • onBindViewHolder():通過ViewHolder拿到的item布局中的View進行賦值,寫過listView的你對這一步一定不會陌生。
  • getItemCount():;返回數據集的個數,也就是我們RecyclerView的item的個數。

ViewHolder的實現,我們繼承RecyclerView.ViewHolder,重寫了構造方法。

  • 在構造方法中我們拿到Item的View。進行findViewById查找到我們索要用的控件。方便我們進行使用。

接下來我們在Activity中找到我們到RecyclerView進行使用。

//創建一個存儲User數據的集合
List<User> userList = new ArrayList<>();
//在布局中找到我們的RecyclerView
rclMain = findViewById(R.id.rcl_main);
//增加一些User假數據,放在我們的集合當中
for (int i=0 ;i<=30 ;i++){
     userList.add(new User("張"+i,20+i));
}
//為我們的RecyclerView設置Adapter
rclMain.setAdapter(new MyAdapter(userList));
//設置我們的LayoutManager為線性布局豎直方向的
rclMain.setLayoutManager(new LinearLayoutManager(this));
//設置我們的Item之間的間隔為DividerItemDecoration為豎直方向。如需修改其默認樣式,
//可以修改 <!-- Application theme. -->中名字為android:listDivider的屬性。
rclMain.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL));

以上的效果和listView完全相同,并且Item之間的間隔是一個白色的一條線。大家可以看一下1-1的圖。


1-1

LayoutManager

RecyclerView 中設置LayoutManager方法的參數是RecyclerView.LayoutManager吧,這是一個抽象類,好在系統提供了3個實現類:

  • LinearLayoutManager 現行管理器,支持橫向、縱向。(默認)
  • GridLayoutManager 網格布局管理器。
  • StaggeredGridLayoutManager 瀑布就式布局管理器。
    如果我們想做一個橫向listView的效果也非常簡單,稍微修改一下Item布局,將我們設置的LayoutManager設置成橫向的LinearLayoutManager就可以了
 rclMain.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL,false));
1-2

GridLayoutManager 實現縱向的網格布局。

//設置列數為3的縱向網格布局,注意 一下網格中間的間隔我這里設置的是item的margin實現的。我們上邊Demo中使用的DividerItemDecoration,在這里是無法使用的,因為網格布局的間隔是縱向與橫向都要的,而DividerItemDecoration只能設置縱向或者橫向。無法同時設置
 rclMain.setLayoutManager(new GridLayoutManager(this,3,GridLayoutManager.VERTICAL,false));
1-3

GridLayoutManager 實現橫向的網格布局。

//設置列數為3的縱向網格布局,注意 一下網格中間的間隔我這里設置的是item的margin實現的。我們上邊Demo中使用的DividerItemDecoration,在這里是無法使用的,因為網格布局的間隔是縱向與橫向都要的,而DividerItemDecoration只能設置縱向或者橫向。無法同時設置
 rclMain.setLayoutManager(new GridLayoutManager(this,3,GridLayoutManager.HORIZONTAL,false));
1-4

StaggeredGridLayoutManager實現瀑布流,已經看過上面的實現網格布局和線性list布局,我相信實現網格布局對你來說已經非常之簡單了。如何隨機設置item的高度保證item每個都不一樣高。這里就不貼出來了。相信一定難不倒你的。

//設置數量和方向,我們設置的是3列縱向布局。
 rclMain.setLayoutManager(new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL));
1-5

只要修改一行代碼就能實現三種不同的布局方式,Recycler的強大你是不是已經體會到了。反正我早已經被他震撼到了。

ItemDecoration

我們可以使用addItemDecoration(Recycler.ItemDecoration)添加item之間要繪制的東西。RecyclerView.ItemDecoration是抽象類,Android只提供了DividerItemDecoration一個實現類用來給使用LinearLayoutManager的RecyclerView繪制Item之間的間隙。我們可以修改APPTheme 中的android:listDivider來修改我們想要的間隙樣式

values/styles.xml
    <!-- Base application theme. -->
    <style name="AppBaseTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>
    <style name="AppTheme" parent="AppBaseTheme">
        <item name="android:listDivider">@drawable/divider_bg</item>
    </style>

divider_bg.xml
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="line" >
    <stroke android:color="@android:color/white"/>
    <size android:height="4dp" />
</shape>

而Android提供的這個不能滿足我們所有的需求,比如我們想給我們的網格布局添加item之間的間隔就不能使用DividerItemDecoration,我們只能自己來實現了。繼承RecyclerView.ItemDecoration。首先我們來了解一下RecyclerView.ItemDecoration中的幾個方法的含義

  • getItemOffests:可以通過outRect.set(l,t,r,b)設置指定itemview的paddingLeft,paddingTop, paddingRight, paddingBottom
  • onDraw:可以通過一系列c.drawXXX()方法在繪制itemView之前繪制我們需要的內容。可以理解為在Item下層 像BackGround
  • onDrawOver:與onDraw類似,只不過是在繪制itemView之后繪制,具體表現形式,就是繪制的內容在itemview上層。

我們只設置item間隔的話 是不需要進行onDraw和onDrawOver操作的,除非你想在你要設置的這個間隔中繪制一些想要的東西。而想要設置Item之間的間隔,只要通過計算我們就可以通過getItemOffests設置item的padding就能夠實現我們的效果。首先我們來看一下我們具體的實現思路。

1、首先我們要初步確定我們沒個item條目的padding計算規則。為了讓我們的網格布局平分寬高所以我們每個item的padding值是不一樣的,我們來看下面的一張演示圖。
2-1無邊距

2-2有邊距

當orientation為vertical時,我們需要在getItemOffsets方法中計算每個Item的PaddingLeft,以及PaddingRight,保證每個Item的paddingLeft+paddingRight相等,這樣才能達到均分的目的。
這里我們可以用數字的帶入來計算我們每個item的padding規律,從而推導出我們要的公式。這里我也是查看的大神的博客,如果想知道具體規則可在本章底部找到答案。

@Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        RecyclerView.LayoutManager manager = parent.getLayoutManager();
        int childPosition = parent.getChildAdapterPosition(view);
        int itemCount = parent.getAdapter().getItemCount();
        if (manager != null) {
            if (manager instanceof GridLayoutManager) {
                // manager為GridLayoutManager時
                setGridOffset(((GridLayoutManager) manager).getOrientation(), ((GridLayoutManager) manager).getSpanCount(), outRect, childPosition, itemCount);
            }
        }
    }

/**
     * 設置GridLayoutManager 類型的 offest
     *
     * @param orientation   方向
     * @param spanCount     個數
     * @param outRect       padding
     * @param childPosition 在 list 中的 postion
     * @param itemCount     list size
     */
    private void setGridOffset(int orientation, int spanCount, Rect outRect, int childPosition, int itemCount) {
        float totalSpace = mSpace * (spanCount - 1) + mEdgeSpace * 2; // 總共的padding值
        float eachSpace = totalSpace / spanCount; // 分配給每個item的padding值
        int column = childPosition % spanCount; // 列數
        int row = childPosition / spanCount;// 行數
        float left;
        float right;
        float top;
        float bottom;
        if (orientation == GridLayoutManager.VERTICAL) {
            top = 0; // 默認 top為0
            bottom = mSpace; // 默認bottom為間距值
            if (mEdgeSpace == 0) {
                left = column * eachSpace / (spanCount - 1);
                right = eachSpace - left;
                // 無邊距的話  只有最后一行bottom為0
                if (itemCount / spanCount == row) {
                    bottom = 0;
                }
            } else {
                if (childPosition < spanCount) {
                    // 有邊距的話 第一行top為邊距值
                    top = mEdgeSpace;
                } else if (itemCount / spanCount == row) {
                    // 有邊距的話 最后一行bottom為邊距值
                    bottom = mEdgeSpace;
                }
                left = column * (eachSpace - mEdgeSpace - mEdgeSpace) / (spanCount - 1) + mEdgeSpace;
                right = eachSpace - left;
            }
        } else {
            // orientation == GridLayoutManager.HORIZONTAL 跟上面的大同小異, 將top,bottom替換為left,right即可
            left = 0;
            right = mSpace;
            if (mEdgeSpace == 0) {
                top = column * eachSpace / (spanCount - 1);
                bottom = eachSpace - top;
                if (itemCount / spanCount == row) {
                    right = 0;
                }
            } else {
                if (childPosition < spanCount) {
                    left = mEdgeSpace;
                } else if (itemCount / spanCount == row) {
                    right = mEdgeSpace;
                }
                top = column * (eachSpace - mEdgeSpace - mEdgeSpace) / (spanCount - 1) + mEdgeSpace;
                bottom = eachSpace - top;
            }
        }
        outRect.set((int) left, (int) top, (int) right, (int) bottom);
    }

接下來是onDraw和onDrawOver的用法。這里我制作簡單的介紹,如果你想要做出一些不一樣的內容。文章末尾我會給出幾個比較好的博客,供大家欣賞。
首先我們要知道onDraw和onDrawOver的執行時間,一下是RecyclerView繪制流程的源碼

/**
     * RecyclerView的draw方法
     * @param c
     */
    @Override
    public void draw(Canvas c) {
        // 調用父類也就是View的draw方法
        super.draw(c);
        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            // 執行ItemDecorations的onDrawOver方法
            mItemDecorations.get(i).onDrawOver(c, this, mState);
        }
    }

    /**
     *  View的draw方法
     * @param canvas
     */
    @CallSuper
    public void draw(Canvas canvas) {
        ....
        // View會繼續調用onDraw
        if (!dirtyOpaque) onDraw(canvas);
        ....
    }

    /**
     * RecyclerView的onDraw方法
     * @param c
     */
    @Override
    public void onDraw(Canvas c) {
        super.onDraw(c);
        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            // 執行ItemDecorations的onDraw方法
            mItemDecorations.get(i).onDraw(c, this, mState);
        }
    }

View先會調用draw方法,在draw中又會調用onDraw方法。 而在RecyclerView的draw方法中會先通過super.draw() 調用父類也就是View的draw方法,進而繼續調用RecyclerView的OnDraw方法,ItemDecorations的onDraw方法就在此時會被調用,RecyclerView執行完super.draw()之后,ItemDecorations的onDrawOver方法也被調用,這也就解釋了為什么說onDraw會繪制在itemview之前,表現形式是在最底層(抽象的說法,最底層應該是background),onDrawOver是在itemview繪制之后,表現形式在最上層。知道了onDraw和onDrawOver的具體執行時間與含義。我們這里再來看Android提供給我們的DividerItemDecoration實現我們listView的Divider效果是如何做到的

   @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        if (parent.getLayoutManager() == null || mDivider == null) {
            return;
        }
        if (mOrientation == VERTICAL) {
            drawVertical(c, parent);
        } else {
            drawHorizontal(c, parent);
        }
    }

   private void drawVertical(Canvas canvas, RecyclerView parent) {
        canvas.save();
        final int left;
        final int right;
        //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
        if (parent.getClipToPadding()) {
            left = parent.getPaddingLeft();
            right = parent.getWidth() - parent.getPaddingRight();
            canvas.clipRect(left, parent.getPaddingTop(), right,
                    parent.getHeight() - parent.getPaddingBottom());
        } else {
            left = 0;
            right = parent.getWidth();
        }

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            parent.getDecoratedBoundsWithMargins(child, mBounds);
            final int bottom = mBounds.bottom + Math.round(child.getTranslationY());
            final int top = bottom - mDivider.getIntrinsicHeight();
            mDivider.setBounds(left, top, right, bottom);
            mDivider.draw(canvas);
        }
        canvas.restore();
    }

    private void drawHorizontal(Canvas canvas, RecyclerView parent) {
        canvas.save();
        final int top;
        final int bottom;
        //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
        if (parent.getClipToPadding()) {
            top = parent.getPaddingTop();
            bottom = parent.getHeight() - parent.getPaddingBottom();
            canvas.clipRect(parent.getPaddingLeft(), top,
                    parent.getWidth() - parent.getPaddingRight(), bottom);
        } else {
            top = 0;
            bottom = parent.getHeight();
        }

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

原理很簡單,總結起來就是通過getItemOffsets方法來設置我們item的paddingBottom或paddingRight,然后在我們onDraw方法中遍歷我們的item并在我們需要的區域內繪制我們想要的繪制的內容。

setItemAnimator

設置我們的Item動畫效果。Android提供了一個默認的實現。DefaultItemAnimator,添加以下代碼我們就可以了。

rclMain.setItemAnimator(new DefaultItemAnimator());

添加了這行代碼我們再運行我們的代碼發現,完全木有效果啊,什么鬼。
這里我忘記和大家說了。如果我們想要看到效果,就在我們的數據集合中添加刪除數據。并執行notifyItemInserted(position)或notifyItemRemoved(position) ,就可以看到效果啦。當然這一種效果怎么可能滿足的了我們產品的需求呢,幸好我們有大神。大神們已經開源了很多有關的代碼。這就要大家自己去挖掘了。

點擊事件

因為RecyclerView沒有給我們提供onItemClickListener的方法。所以這個只能我們自己來實現,具體實現比較簡單,和我們ListView,Item中設置點擊按鈕的實現方式是一樣的。我們只需要自己在RecyclerView中寫一個onclickListener監聽器,并提供出去,在BindViewHolder的時候設置ItemView的點擊事件,調用我們監聽器的方法回調給我們的使用者就可以了。

class HomeAdapter extends RecyclerView.Adapter<HomeAdapter.MyViewHolder>
{

//...
    public interface OnItemClickLitener
    {
        void onItemClick(View view, int position);
        void onItemLongClick(View view , int position);
    }

    private OnItemClickLitener mOnItemClickLitener;

    public void setOnItemClickLitener(OnItemClickLitener mOnItemClickLitener)
    {
        this.mOnItemClickLitener = mOnItemClickLitener;
    }

    @Override
    public void onBindViewHolder(final MyViewHolder holder, final int position)
    {
        holder.tv.setText(mDatas.get(position));

        // 如果設置了回調,則設置點擊事件
        if (mOnItemClickLitener != null)
        {
            holder.itemView.setOnClickListener(new OnClickListener()
            {
                @Override
                public void onClick(View v)
                {
                    int pos = holder.getLayoutPosition();
                    mOnItemClickLitener.onItemClick(holder.itemView, pos);
                }
            });

            holder.itemView.setOnLongClickListener(new OnLongClickListener()
            {
                @Override
                public boolean onLongClick(View v)
                {
                    int pos = holder.getLayoutPosition();
                    mOnItemClickLitener.onItemLongClick(holder.itemView, pos);
                    return false;
                }
            });
        }
    }
//...
}

知道了如何設置Item的點擊事件,我相信設置item中單獨View的點擊事件也一定難不住你了。

不同類型的Item

我們可以根據RecyclerView.Adapter提供的getItemViewType();方法根據Position位置返回我們不同的Type類型。
在onCreateViewHolder中根據不同的type創建不同的布局和ViewHolder并返回。在onBindViewHolder中根據ViewHodler instanceof方法獲取他數據哪個ViewHolder來進行賦值操作,例子如下:

lass HomeAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private static final int TYPE_HEADER = 1;
    private static final int TYPE_ITEM = 2;

    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_HEADER) {
            //...
            return new HeaderViewHolder(v);
        } else {
            //...
            return new MyViewHolder(v);
        }
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof HeaderViewHolder) {
            //...
        } else if (holder instanceof MyViewHolder) {
            //...
        }
    }

    @Override
    public int getItemCount() {
        return mDatas.size() + 1; //增加頭部ItemView
    }

    @Override
    public int getItemViewType(int position) {
        if (position == 0) {
            return TYPE_HEADER;
        } else {
            return TYPE_ITEM;
        } 
    }

    class HeaderViewHolder extends RecyclerView.ViewHolder {
        //...
    }

    class MyViewHolder extends RecyclerView.ViewHolder {
        //...
    }
}

參考文章:http://www.lxweimin.com/p/bb09d3ddc64f
https://blog.csdn.net/lmj623565791/article/details/45059587
http://www.lxweimin.com/p/e742df6f59e2
http://www.lxweimin.com/p/6a093bcc6b83
http://www.lxweimin.com/p/3221b5c8fc38

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,208評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,746評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,477評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,960評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,200評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,726評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,617評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,807評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,049評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,432評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,769評論 2 372