MaterialDesign--(7)TabLayout的使用及其源碼分析

簡介

TabLayout繼承自 HorizontalScrollView


TabLayout 提供了一個水平布局來顯示標簽妙色。
所有的 Tab 選項卡實例化都是通過 TabLayout.Tab 完成的。你可以通過 TabLayout.newTab()來創(chuàng)建 Tab 對象盆顾。你可以通過更改Tab 的setText()诬像、setIcon()分別設(shè)置選項卡的文字和 Icon屋群。要顯示選項卡 Tab,你必須通過一個方法 addTab(tab)方法將其添加到布局。例如:

TabLayout tabLayout = ...;
tabLayout.addTab(tabLayout.newTab().setText("Tab 1"));
tabLayout.addTab(tabLayout.newTab().setText("Tab 2"));
tabLayout.addTab(tabLayout.newTab().setText("Tab 3"));

你可以設(shè)一個監(jiān)聽setOnTabSelectedListener(OnTabSelectedListener),當任何表情的選擇狀態(tài)改變的時候回調(diào)坏挠。你也可以在 xml 布局中使用TabItem添加 tab 到TabLayout 里面谓晌,例如

<android.support.design.widget.TabLayout
     android:layout_height="wrap_content"
     android:layout_width="match_parent">

 <android.support.design.widget.TabItem
         android:text="@string/tab_text"/>

 <android.support.design.widget.TabItem
         android:icon="@drawable/ic_android"/>
</android.support.design.widget.TabLayout>

結(jié)合ViewPager

如果你的 ViewPager 和這個布局用在一起,你可以調(diào)用 setupWithVIewPager(ViewPager)兩個鏈接在一起癞揉,這種布局將會自動填充 PagerAdapter 的頁面標題

你也可以把這種用法當成 ViewPager 的裝飾纸肉,并且可以這樣寫布局資源直接添加到 ViewPager 當中:

<android.support.v4.view.ViewPager
 android:layout_width="match_parent"
 android:layout_height="match_parent">

  <android.support.design.widget.TabLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_gravity="top" />

</android.support.v4.view.ViewPager>

哈哈哈哈哈,有木有感覺讀起來很尷尬喊熟,上面這段文字翻譯自Google 官方文檔,是用我三級沒過的英語翻譯的哀九,官方文檔真的講的很清楚罐脊,大家真的不要恐懼看官方文檔。but,上文中的兩種 xml寫法要出,我用了這么久 TabLayout盐欺,真的是第一次知道。

XML attributes

<declare-styleable name="TabLayout">
 <attr format="color" name="tabIndicatorColor"/>
 <attr format="dimension" name="tabIndicatorHeight"/>
 <attr format="dimension" name="tabContentStart"/>
 <attr format="reference" name="tabBackground"/>
 <attr name="tabMode">
        <enum name="scrollable" value="0"/>
        <enum name="fixed" value="1"/>
    </attr>
    <attr name="tabGravity">
        <enum name="fill" value="0"/>
        <enum name="center" value="1"/>
    </attr>
    <attr format="dimension" name="tabMinWidth"/>
    <attr format="dimension" name="tabMaxWidth"/>
    <attr format="reference" name="tabTextAppearance"/>
    <attr format="color" name="tabTextColor"/>
    <attr format="color" name="tabSelectedTextColor"/>
    <attr format="dimension" name="tabPaddingStart"/>
    <attr format="dimension" name="tabPaddingTop"/>
    <attr format="dimension" name="tabPaddingEnd"/>
    <attr format="dimension" name="tabPaddingBottom"/>
    <attr format="dimension" name="tabPadding"/>
</declare-styleable>
  • tabIndicatorColor 下標顏色
  • tabIndicatorHeight 下標高度
  • tabContentStart 設(shè)置左邊的 padding
  • tabBackground 背景顏色
  • tabGravity fill:tabs 平均填充整個寬度 center:tabs 居中顯示
  • tabMode scrollable:可滑動;fixed:不能滑動柏靶,平分tabLayout寬度
  • tabMinWidth tab 的最新寬度
  • tabMaxWidth tab 的最大寬度
  • tabTextAppearance tab 的文字style
  • tabTextColor tab 文字顏色
  • tabSelectedTextColor tab 選中文字顏色
  • tabPadding***** tab 的 padding 值

Public methods

方法名 作用
addOnTabSelectedListener() 添加 tab 選中監(jiān)聽
addTab() 添加一個 tab
addView 添加一個 View。注意只能是TabItem溃论,然后最終轉(zhuǎn)換成 tab
clearOnTabSelectedListener() 移除條目選中監(jiān)聽
generateLayoutparams() 獲取 layoutParams
getSelectedTabPositing() 獲取當前所選標簽 position
getTabAt(int index) 獲取指定索引的 Tab
gettabCount() 獲取 tab 數(shù)
getTabGravity() 獲取tabGravity
getTabMode() 獲取 TabMode
getTabTextColors() 獲取選項卡中不同狀態(tài)顏色
newTab() 創(chuàng)建并返回一個新的 TabLayout.Tab
removeAllTabs() 刪除所有選項卡
removeOnTabSelectedListener() 刪除所有 OnTabSelectedListener
removeTab(Tab) 移除指定 tab
removetabAt(position) 移除指定 position 的 tab
setOnTabSelectedListener() 等同 addOnTabSelectedListener()
setScrollPosition() 設(shè)置選項卡滾動位置
setSelectedTabIndicatorColor() 設(shè)置選中下標顏色
setTabGravity() 設(shè)置 TabGravity
setTabMode() 設(shè)置 TabMode
setTabTextCloros() 設(shè)置 tab 的文字顏色
setTabsFromPagerAdapter() 已過期,使用 setupWithViewPager()
setupViewPager() 綁定 ViewPager
shouldDelayChildPressedState() 如果tab可以滾動屎蜓,只延遲按下狀態(tài)

其實沒什么好寫的,基本上看到方法名就能知道是干嘛的钥勋,初入 android 開發(fā)的同學切記不要死記硬背這些 api炬转,有個大概的印象就行。老司機權(quán)當查漏補缺吧算灸。

一張圖看懂 TabLayout 的類層次

View層次.jpg

可能有些同學沒看懂SlidingTabStrip是什么扼劈。
TabLayout繼承自 HorizontalScrollView,ScrollView 只能添加一個子 View菲驴,所以 SlidingTabStrip 就是那個用來添加子View 的HorizontalLinearLayout荐吵。

花式玩法

1.tab 之間分割線

Ui 說要在 tab 條目之間添加分割線,很操蛋有木有赊瞬。拿到需求之后研究了一遍 Api捍靠,然而發(fā)現(xiàn)并沒有提供添加分割線的方法,然后自己去手擼一個 TabLayout森逮。

其實不用手擼,沒有Api 我們可以曲線救國磁携。魯迅當年覺得學醫(yī)救不了中國人褒侧,不也選擇了 DJ 來曲線救國么。


用過 ScrollView 的童鞋都知道谊迄,ScrollView 只能有一個子 View闷供,因此,ScrollView 都會有一個 LinearLayout统诺。然后 LinearLayout 有個方法setShowDividers()可以設(shè)置分割線歪脏,而 TabLayout 就算繼承自HorizontalScrollView,那么我們是不是可以去找一下粮呢,TabLayout 里面的 ScrollView婿失。通過閱讀源碼,我們找到了這樣幾行代碼

mTabStrip = new SlidingTabStrip(context);
super.addView(mTabStrip, 0, new HorizontalScrollView.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
            ...       
 
private class SlidingTabStrip extends LinearLayout {

這里的mTabStrip就是我們要找的LinearLayout啄寡。but豪硅,mTabStrip是一個 private 變量。

當然挺物,獲取一個 private 變量攔不到我們牛逼的 java 程序員懒浮,tabLayout.getClass()...分分鐘獲取到mTabStrip對象。

stop识藤,我們這里有個優(yōu)雅的方法獲取mTabStrip對象砚著。我們都知道TabLayout 繼承自HorizontalScrollView次伶,HorizontalScrollView只能有一個子類!;隆冠王!tabLayout.getChildAt(0)是不是就獲取到了 mTabStrip對象。
然后調(diào)用以下方法給 LinearLayout 設(shè)置分割線即可

 LinearLayout linearLayout = (LinearLayout) toolbar_tab.getChildAt(0);
linearLayout.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE);
linearLayout.setDividerDrawable(ContextCompat.getDrawable(this,R.drawable.divider)); //設(shè)置分割線的樣式linearLayout.setDividerPadding(20); //設(shè)置分割線間隔

這里貼上 R.drawable.divider 的代碼
<?xml version="1.0" encoding="utf-8"?>
 <shape xmlns:android="http://schemas.android.com/apk/res/android">
   <solid android:color="#c0c0c0" />
   <size android:width="1px"/>
 </shape>

綁定 ViewPager

哈哈秧骑,其實這個只是基本功能版确。兩個步驟
1.如果你的 TabLayout 的節(jié)點不是在 ViewPager 節(jié)點內(nèi)部,需要把TabLayout 和 ViewPager 綁定起來乎折。否則可以跳過這一步

 mTabLayout.setupWithViewPager(mViewPager);

2.重寫 PagerAdapter 的 getPageTitle()方法绒疗。

自定義指示器的長度

UI 說,指示器的長度不要充滿屏幕~~~~~
這里有個辦法通過反射的方式修改指示器長度骂澄,如果需要指示器寬度等于文字寬度需要自己微調(diào)吓蘑。原理就是通過反射的方式獲取 TabLayout 的字段 mTabStrip,然后再去遍歷修改每一個子 View 的 padding 值。代碼如下:

 /**
 * 通過反射設(shè)置TabLayout每一個的長度
 * @param left 左邊 Padding 單位 dp
 * @param right 右邊 Padding 單位 dp
 */
public void setIndicator(TabLayout tabLayout, int left, int right) {
    Class<?> tabLayoutClass = tabLayout.getClass();
    Field tabStrip = null;
    try {
        tabStrip = tabLayoutClass.getDeclaredField("mTabStrip");
        tabStrip.setAccessible(true);
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    }

    LinearLayout llTab = null;
    try {
        llTab = (LinearLayout) tabStrip.get(tabLayout);
    } catch (Exception e) {
        e.printStackTrace();
    }

    int l = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, left, Resources.getSystem().getDisplayMetrics());
    int r = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, right, Resources.getSystem().getDisplayMetrics());

    if (llTab != null) {
        for (int i = 0; i < llTab.getChildCount(); i++) {
            View child = llTab.getChildAt(i);
            child.setPadding(0, 0, 0, 0);
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1);
            params.leftMargin = l;
            params.rightMargin = r;
            child.setLayoutParams(params);
            child.invalidate();
        }
    }
}

自定義 Tab

可能有些童鞋不滿足于TabLayout 當前的定制坟冲,想要完全自定義磨镶。可以的~很有想法
大家對這種方式添加一個 Tab 條目肯定不陌生吧

 tabLayout.addTab(tabLayout.newTab());  

tabLayout.newTab()的返回值是一個TabLayout.Tab健提。既然 tabLayout.addTab(Tab)就能添加一個條目琳猫,那么可以大膽的斷定 Tab 就是代表一個條目,然后我們通過查看源碼可以知道 tab.getCustomView()可以獲得這個 View私痹,這時就很簡單了脐嫂,你可以直接設(shè)置這個 mCustomView ,然后自己處理 mCustomView 的顯示。

能干什么紊遵?比如說首頁底部導(dǎo)航账千,選中條目放大等等。暗膜。匀奏。。自由發(fā)揮

源碼分析

這個源碼好像比較簡單学搜,我們先從構(gòu)造方法開始

 public TabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
   //檢查當前主題是否是 AppCompat 系列的娃善,否則報錯,里面代碼就只有幾行
    ThemeUtils.checkAppCompatTheme(context);

    // Disable the Scroll Bar 禁用滾動條
    setHorizontalScrollBarEnabled(false);

    // Add the TabStrip 創(chuàng)建SlidingTabStrip瑞佩,
    // 以后 tabView 就是添加到這里面
    mTabStrip = new SlidingTabStrip(context);
    super.addView(mTabStrip, 0, new HorizontalScrollView.LayoutParams(
            LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));

    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabLayout,
            defStyleAttr, R.style.Widget_Design_TabLayout);

      ...讀取attributes屬性代碼会放,省略

    final Resources res = getResources();
    //設(shè)置默認文字大小12sp
    mTabTextMultiLineSize = res.getDimensionPixelSize(R.dimen.design_tab_text_size_2line);
    //設(shè)置默認最小寬度72dp
    mScrollableTabMinWidth = res.getDimensionPixelSize(R.dimen.design_tab_scrollable_min_width);

    // Now apply the tab mode and gravity
    //設(shè)置 mode 和 gravity,不明白這兩個屬性的請回頭看attributes
    applyModeAndGravity();
}

//這個應(yīng)該能看懂吧~~~ 
 private void applyModeAndGravity() {
    int paddingStart = 0;
    if (mMode == MODE_SCROLLABLE) {
        // If we're scrollable, or fixed at start, inset using padding
        paddingStart = Math.max(0, mContentInsetStart - mTabPaddingStart);
    }
    ViewCompat.setPaddingRelative(mTabStrip, paddingStart, 0, 0, 0);

    switch (mMode) {
        case MODE_FIXED:
            mTabStrip.setGravity(Gravity.CENTER_HORIZONTAL);
            break;
        case MODE_SCROLLABLE:
            mTabStrip.setGravity(GravityCompat.START);
            break;
    }

    updateTabViews(true);
}
 //遍歷所有子 View钉凌,并更新 LayoutParams
void updateTabViews(final boolean requestLayout) {
    for (int i = 0; i < mTabStrip.getChildCount(); i++) {
        View child = mTabStrip.getChildAt(i);
        child.setMinimumWidth(getTabMinWidth());
        updateTabViewLayoutParams((LinearLayout.LayoutParams) child.getLayoutParams());
        if (requestLayout) {
            child.requestLayout();
        }
    }
}
//layoutParams 屬性請參照 mode 和 gravity 的屬性看
private void updateTabViewLayoutParams(LinearLayout.LayoutParams lp) {
    if (mMode == MODE_FIXED && mTabGravity == GRAVITY_FILL) {
        lp.width = 0;
        lp.weight = 1;
    } else {
        lp.width = LinearLayout.LayoutParams.WRAP_CONTENT;
        lp.weight = 0;
    }
}
 -----------構(gòu)造方法結(jié)束-----
 -----如何添加一個 tab------
 //創(chuàng)建了一個Tab對象咧最,并持有對 TabLayout 的引用
 //這里的 sTabPool 繼承自 pool,一個可以設(shè)置最大創(chuàng)建個數(shù)的工具類,如果
 //超過最大創(chuàng)建個數(shù)則不再創(chuàng)建返回 null矢沿,這里還加了一個非空判斷滥搭,表示沒看懂
 //為什么要用Pools.SynchronizedPool來創(chuàng)建Tab
 public Tab newTab() {
    Tab tab = sTabPool.acquire();
    if (tab == null) {
        tab = new Tab();
    }
    tab.mParent = this;
    //創(chuàng)建一個 TabView,TabView 就是真正的每個條目View
    //Tab 只是一個簡單的 View Model
    tab.mView = createTabView(tab);
    return tab;
}
//創(chuàng)建 TabView
private TabView createTabView(@NonNull final Tab tab) {
    TabView tabView = mTabViewPool != null ? mTabViewPool.acquire() : null;
    if (tabView == null) {
        tabView = new TabView(getContext());
    }
    tabView.setTab(tab);
    tabView.setFocusable(true);
    tabView.setMinimumWidth(getTabMinWidth());
    return tabView;
}
//添加到
public void addTab(@NonNull Tab tab, int position, boolean setSelected) {
    if (tab.mParent != this) {
        throw new IllegalArgumentException("Tab belongs to a different TabLayout.");
    }
    //配置這個方法不重要捣鲸,就幾行代碼
    configureTab(tab, position);
    //調(diào)用方法添加 TabView 到mTabStrip里面
    addTabView(tab);
    if (setSelected) {
     //設(shè)置條目選中
        tab.select();
    }
}
//添加TabView到mTabStrip里的執(zhí)行方法
private void addTabView(Tab tab) {
    final TabView tabView = tab.mView;
    mTabStrip.addView(tabView, tab.getPosition(), createLayoutParamsForTabs());
}
//設(shè)置 Tab 選中瑟匆,并且將之前選中的 Tab 設(shè)為未選中狀態(tài)
//然后更新下標 updateIndicator
 void selectTab(Tab tab) {
    selectTab(tab, true);
}
----------------------------
--------ViewPager 綁定-------
-----------------------------
//上文說過,綁定 ViewPager 只需要一行代碼mTabLayout.setupWithViewPager(mViewPager)
//那么我們就從這個方法開始看
private void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh,
        boolean implicitSetup) {
    if (mViewPager != null) {
        // If we've already been setup with a ViewPager, remove us from it
        if (mPageChangeListener != null) {
            mViewPager.removeOnPageChangeListener(mPageChangeListener);
        }
        if (mAdapterChangeListener != null) {
            mViewPager.removeOnAdapterChangeListener(mAdapterChangeListener);
        }
    }

    if (mCurrentVpSelectedListener != null) {
        // If we already have a tab selected listener for the ViewPager, remove it
        removeOnTabSelectedListener(mCurrentVpSelectedListener);
        mCurrentVpSelectedListener = null;
    }

    if (viewPager != null) {
        mViewPager = viewPager;

        // Add our custom OnPageChangeListener to the ViewPager
        if (mPageChangeListener == null) {
            mPageChangeListener = new TabLayoutOnPageChangeListener(this);
        }
        mPageChangeListener.reset();
        viewPager.addOnPageChangeListener(mPageChangeListener);

        // Now we'll add a tab selected listener to set ViewPager's current item
        mCurrentVpSelectedListener = new ViewPagerOnTabSelectedListener(viewPager);
        addOnTabSelectedListener(mCurrentVpSelectedListener);

        final PagerAdapter adapter = viewPager.getAdapter();
        if (adapter != null) {
            // Now we'll populate ourselves from the pager adapter, adding an observer if
            // autoRefresh is enabled
            setPagerAdapter(adapter, autoRefresh);
        }

        // Add a listener so that we're notified of any adapter changes
        if (mAdapterChangeListener == null) {
            mAdapterChangeListener = new AdapterChangeListener();
        }
        mAdapterChangeListener.setAutoRefresh(autoRefresh);
        viewPager.addOnAdapterChangeListener(mAdapterChangeListener);

        // Now update the scroll position to match the ViewPager's current item
        setScrollPosition(viewPager.getCurrentItem(), 0f, true);
    } else {
        // We've been given a null ViewPager so we need to clear out the internal state,
        // listeners and observers
        mViewPager = null;
        setPagerAdapter(null, false);
    }
 //這個變量我沒看懂有什么用栽惶,private愁溜,沒有提供修改方法,
 //幾個賦值的地方都是被賦值為 false外厂。
    mSetupViewPagerImplicitly = implicitSetup;
}

好了冕象,看完了,踏馬源碼里面都有寫代碼注釋汁蝶,我三級的英語水平都看得懂渐扮,這里為了給大家原值原味的感覺,我就不再翻譯了掖棉,希望大家閱讀愉快墓律。

好了,TabLayout 分析到此結(jié)束幔亥。本來還想寫 SearchView 和 CardView 的耻讽,但是我覺得從MaterialDesign(1)開始看過來的朋友現(xiàn)在都已經(jīng)學會了自己去看源碼,所以下一篇不準備寫 View 了帕棉,沒意思针肥。
明天一起來學沉浸式設(shè)計以及沉浸式設(shè)計里面的那些坑。
加油Coder笤昨。加油Android Developer。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末握恳,一起剝皮案震驚了整個濱河市瞒窒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌乡洼,老刑警劉巖崇裁,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異束昵,居然都是意外死亡拔稳,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門锹雏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來巴比,“玉大人,你說我怎么就攤上這事∏峤剩” “怎么了采记?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長政勃。 經(jīng)常有香客問我唧龄,道長,這世上最難降的妖魔是什么奸远? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任既棺,我火速辦了婚禮,結(jié)果婚禮上懒叛,老公的妹妹穿的比我還像新娘丸冕。我一直安慰自己,他們只是感情好芍瑞,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布晨仑。 她就那樣靜靜地躺著,像睡著了一般拆檬。 火紅的嫁衣襯著肌膚如雪洪己。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天竟贯,我揣著相機與錄音答捕,去河邊找鬼。 笑死屑那,一個胖子當著我的面吹牛拱镐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播持际,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼沃琅,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蜘欲?” 一聲冷哼從身側(cè)響起益眉,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎姥份,沒想到半個月后郭脂,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡澈歉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年展鸡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片埃难。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡莹弊,死狀恐怖涤久,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情箱硕,我是刑警寧澤拴竹,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站剧罩,受9級特大地震影響栓拜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜惠昔,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一幕与、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧镇防,春花似錦啦鸣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至啦扬,卻和暖如春中狂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背扑毡。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工胃榕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瞄摊。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓勋又,卻偏偏與公主長得像,于是被迫代替她去往敵國和親换帜。 傳聞我的和親對象是個殘疾皇子楔壤,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容