ListView的HeaderView百拓、FooterView和EmptyView(源碼)

前言:顧名思義HeaderView泥彤、FooterView就是顯示在 ListView 的頭部跟尾部的一個(gè)或多個(gè) View(/ ViewGroup)衔沼,而當(dāng) ListView為空的時(shí)候蝌借,顯示的是EmptyView。但是指蚁,HeaderView和 FooterView又是怎么加進(jìn) ListView里面去的呢菩佑?用過 BaseAdapter應(yīng)該會(huì)很容易理解,就是加一個(gè)外包的adapter就行了欣舵。但是進(jìn)一步的問題是擎鸠,如果 HeaderView和 FooterView算是外包 adapter里面的一個(gè) item,那么在判斷 ListView是否為空的時(shí)候算不算進(jìn)去呢缘圈?這將會(huì)影響到 EmptyView的顯示劣光。還有袜蚕,EmptyView是不是也是外包 adapter里面的一個(gè)item呢?

一绢涡、結(jié)論

不多BB直接上結(jié)論牲剃,有興趣可以往看下面的源碼分析。 適用 Android-26

  1. HeaderView 雄可、FooterView 和 EmptyView 都只是把 View 的引用或者相關(guān)數(shù)據(jù)存在 ListView 的對象里面凿傅,等需要它們展示的時(shí)候直接顯示出來。
  2. 但是数苫,對于 headerView 和 footerView 來說聪舒,當(dāng)設(shè)置它們之后,ListView 就會(huì)把原來的 adapter 用 HeaderViewListAdapter 包裝起來虐急,然后 headerView 和 footerView 也會(huì)進(jìn)入ListView(adapterView) 的回收重復(fù)利用的機(jī)制箱残,即 Adapter#getView。所以止吁,它們也算是 ListView 的某一個(gè)item被辑,這也是為什么它們的 parent 要設(shè)置為相應(yīng)的 ListView,params 也要是 absListView#params敬惦。
  3. 而 emptyView 就只是單純的把引用存進(jìn)去盼理,當(dāng) ListView 為空的時(shí)候,ListView 的觀察者 AdapterDataSetObserver 就會(huì)去刷新狀態(tài)俄删,當(dāng) ListView 為空且 emptyView 不為空的時(shí)候宏怔, 就將 ListView 設(shè)為不可見(包括 headerView 和footerView )。
  4. 特別的對于 emptyView 有兩點(diǎn)抗蠢,第3個(gè)結(jié)論里面說的 ListView 為空举哟,是指 adapter 里面的數(shù)據(jù),需要強(qiáng)調(diào)這不是指包裝之后的
    HeaderViewListAdapter (因?yàn)?headerView迅矛、footerView 的引用也在里面,它的getCount 也是把 headerView潜叛、footerView算進(jìn)去的秽褒,因?yàn)橐WC回收機(jī)制),也就是說判斷 ListView 是否為空不會(huì)計(jì)算 headerView 和 footerView 威兜,它判斷的依據(jù)是 adapter.isEmpty() 或 HeaderViewListAdapter.isEmpty() (外包之后用它销斟,但是它的 isEmpty 做了保護(hù)),而它們兩個(gè)都不會(huì)計(jì)算 headerView 和 footerView 是否存在椒舵。
  5. 但是當(dāng) emptyView = null 時(shí)蚂踊,isEmpty = true;此時(shí) ListView 不會(huì)設(shè)置為不可見笔宿,因?yàn)?ListView 的觀察者調(diào)用的刷新方法里加了一層判斷犁钟,只有當(dāng) emptyView棱诱!= null 時(shí)才會(huì)設(shè)置 ListView 不可見,也就是說此時(shí)涝动,我們原先設(shè)置的adapter 沒有數(shù)據(jù)了迈勋,但是ListView 還是可見的,意味著如果此時(shí)有 headerView 和 footerView 醋粟,那它們還是可見靡菇。

二、EmptyView

ListView#setEmptyView 不是自己的方法 它是使用了 AdapterView#setEmptyView 米愿。
直接看代碼:

ListView extends AbsListView
class AbsListView extends AdapterView<ListAdapter>
class AdapterView<T extends Adapter> extends ViewGroup{
  /**
     * View to show if there are no items to show.
     */
    private View mEmptyView;

    public void setEmptyView(View emptyView) {
        mEmptyView = emptyView;

        // If not explicitly specified this view is important for accessibility.
        if (emptyView != null
                && emptyView.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
            emptyView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
        }

        final T adapter = getAdapter();
        final boolean empty = ((adapter == null) || adapter.isEmpty());
        updateEmptyStatus(empty);
    }

  /**
     * Update the status of the list based on the empty parameter.  If empty is true and
     * we have an empty view, display it.  In all the other cases, make sure that the listview
     * is VISIBLE and that the empty view is GONE (if it's not null).
     */
    private void updateEmptyStatus(boolean empty) {
        if (isInFilterMode()) {
            empty = false;
        }

        if (empty) {
            if (mEmptyView != null) {
                mEmptyView.setVisibility(View.VISIBLE);     // 將 emptyView 置為可見
                setVisibility(View.GONE);                   // 將 ListView 設(shè)置不可見
            } else {
                // If the caller just removed our empty view, make sure the list view is visible
                setVisibility(View.VISIBLE);                // 當(dāng)內(nèi)容為空時(shí)厦凤,即使沒有emptyView也會(huì)將ListView置為不可見
            }

            // We are now GONE, so pending layouts will not be dispatched.
            // Force one here to make sure that the state of the list matches
            // the state of the adapter.
            if (mDataChanged) {           
                this.onLayout(false, mLeft, mTop, mRight, mBottom); 
            }
        } else {
            if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
            setVisibility(View.VISIBLE);
        }
    }

   void checkFocus() {
        final T adapter = getAdapter();
        final boolean empty = adapter == null || adapter.getCount() == 0;
        final boolean focusable = !empty || isInFilterMode();
        // The order in which we set focusable in touch mode/focusable may matter
        // for the client, see View.setFocusableInTouchMode() comments for more
        // details
        super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState);
        super.setFocusable(focusable ? mDesiredFocusableState : NOT_FOCUSABLE);
        if (mEmptyView != null) {
            updateEmptyStatus((adapter == null) || adapter.isEmpty());
        }
    }
}

updateEmptyStatus() 只用以下兩個(gè)地方用到
1. public void setEmptyView(View emptyView) 
2. checkFocus()

checkFocus() 在以下情況會(huì)用到
1. ListView#setAdapter
2. AdapterView#AdapterDataSetObserver#onInvalidted
3. AdapterView#AdapterDataSetObserve@onchange
即只要數(shù)據(jù)修改就會(huì) 調(diào)一次checkFocus,但是如果 mEmptyView = null育苟,則不更新

然后總結(jié)劃個(gè)重點(diǎn):

  1. ListView 里面的 mEmptyView 只是一個(gè)引用较鼓,它并不是放在 ListView 里面,它跟 ListView 里面的內(nèi)容沒有任何關(guān)系宙搬。ListView只是存在機(jī)制去影響其可見度笨腥。
  2. 當(dāng) ListView 的數(shù)據(jù)變化的時(shí)候,就回去刷新一遍勇垛,當(dāng)ListView 的內(nèi)容為空且存在 emptyView 的時(shí)候脖母,ListView 設(shè)為不可見,emptyView 就設(shè)為可見闲孤。
  3. 特別的谆级,當(dāng)未設(shè)置 emptyView 或 emptyView = null 的時(shí)候,此時(shí)若 ListView 的內(nèi)容為空讼积,ListView 不會(huì)設(shè)置為不可見肥照。這個(gè)時(shí)候,如果 ListView 存在 headerView 和 footerView 也會(huì)可見勤众。請參見上述源碼 updateEmptyStatus() 里面的內(nèi)容舆绎。

三、HeaderView们颜、FooterView

FooterView 跟 HeaderView相同吕朵,所以一起說了

ListView 源碼里面一共有兩個(gè)方法加入HeaderView:

 /**
     * Add a fixed view to appear at the top of the list. If addHeaderView is
     * called more than once, the views will appear in the order they were
     * added. Views added using this call can take focus if they want.
     * <p>
     * Note: When first introduced, this method could only be called before
     * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with
     * {@link android.os.Build.VERSION_CODES#KITKAT}, this method may be
     * called at any time. If the ListView's adapter does not extend
     * {@link HeaderViewListAdapter}, it will be wrapped with a supporting
     * instance of {@link WrapperListAdapter}.
     *
     * @param v The view to add.
     */
public void addHeaderView(View v) {
        addHeaderView(v, null, true);
    }
/**
     * 一樣 
     * @param v The view to add.
     * @param data Data to associate with this view
     * @param isSelectable whether the item is selectable
     */
public void addHeaderView(View v, Object data, boolean isSelectable){
   // 稍后分析
}
// addFooterView 一樣

主要先看注釋,有三點(diǎn):

  1. addHeaderView(param...) 可以多次調(diào)用窥突,相當(dāng)于添加多個(gè)headerView努溃,比如調(diào)用 addHeaderView( View1 );addHeaderView(View2) 的話阻问,ListView 最開始是 View1 -> View2 -> ListView 我們加入的數(shù)據(jù) -> FooterView1 -> FooterView2梧税。
  2. android.os.Build.VERSION_CODES#KITKAT(API 19)之前的版本,listView#setAdapter 必須在 addHeaderView/ addFooterView之后,否者就會(huì)引發(fā)沖突第队。注釋里面也講清楚了哮塞,API 19 及之后的版本就沒有這個(gè)限制了,會(huì)把setAdapter() 中的 adapter用一個(gè)WrapperListAdapter來包裝一遍斥铺。WrapperListAdapter是一個(gè)非常普通的包裝 adapter彻桃,簡單到只有一個(gè)方法(ListView 中用的是HeaderViewListAdapter(extends WrapperListAdapter)附錄):
    public interface WrapperListAdapter extends ListAdapter {
    /**
     * Returns the adapter wrapped by this list adapter.
     * 返回被包裝的adapter
     *
     * @return The {@link android.widget.ListAdapter} wrapped by this adapter.
     */
    public ListAdapter getWrappedAdapter();
    }
    
  3. 設(shè)置一個(gè) HeaderView / FooterView 最多需要三個(gè)參數(shù),v晾蜘,data邻眷,isSelectable,它們的作用分別是剔交,view對象肆饶;data是關(guān)于view的數(shù)據(jù),使用 HeaderViewListAdapter#getItem() 返回岖常。isSelectable 即說明 View 是否可以被選中驯镊。它們也被封裝好在 ListView.class 中。
     // ListView.class
     /**
      * A class that represents a fixed view in a list, for example a header at the top
      * or a footer at the bottom.
      */
     public class FixedViewInfo {
         /** The view to add to the list */
         public View view;
         /** The data backing the view. This is returned from {@link ListAdapter#getItem(int)}. */
         public Object data;
         /** <code>true</code> if the fixed view should be selectable in the list */
         public boolean isSelectable;
     }
    
     // ListView.class
     ArrayList<FixedViewInfo> mHeaderViewInfos = Lists.newArrayList();
     ArrayList<FixedViewInfo> mFooterViewInfos = Lists.newArrayList();
    
     // HeaderViewListAdapter.class  完整代碼見附錄
     // These two ArrayList are assumed to NOT be null.
     // They are indeed created when declared in ListView and then shared.
     ArrayList<ListView.FixedViewInfo> mHeaderViewInfos;
     ArrayList<ListView.FixedViewInfo> mFooterViewInfos;
    
    具體這幾個(gè)變量有什么用竭鞍,稍后接著看板惑。

我們先看看addHeaderView()發(fā)生了什么。

public void addHeaderView(View v, Object data, boolean isSelectable) {
        if (v.getParent() != null && v.getParent() != this) {
            if (Log.isLoggable(TAG, Log.WARN)) {
                Log.w(TAG, "The specified child already has a parent. "
                           + "You must call removeView() on the child's parent first.");
            }
        }
        final FixedViewInfo info = new FixedViewInfo();
        info.view = v;
        info.data = data;
        info.isSelectable = isSelectable;
        mHeaderViewInfos.add(info);
        mAreAllItemsSelectable &= isSelectable;

        // Wrap the adapter if it wasn't already wrapped.
        if (mAdapter != null) {
            if (!(mAdapter instanceof HeaderViewListAdapter)) {
                wrapHeaderListAdapterInternal();
            }

            // In the case of re-adding a header view, or adding one later on,
            // we need to notify the observer.
            if (mDataSetObserver != null) {
                mDataSetObserver.onChanged();
            }
        }
    }

在這里面干了四件事:

  1. 往 ListView#mHeaderViewInfos 里面塞數(shù)據(jù)偎快。
  2. ListView#mAreAllItemsSelectable 顧名思義是判斷全部是否都可選冯乘,目前沒用過。
  3. 將 adapter 加一件“外套”晒夹。
  4. 通知 Observer 數(shù)據(jù)變化了裆馒。

我們看一下“披上外套”的方法 wrapHeaderListAdapterInternal(); 里面具體怎么干的:

 /** @hide */
    protected void wrapHeaderListAdapterInternal() {
        mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, mAdapter);
    }

 /** @hide */
    protected HeaderViewListAdapter wrapHeaderListAdapterInternal(
            ArrayList<ListView.FixedViewInfo> headerViewInfos,
            ArrayList<ListView.FixedViewInfo> footerViewInfos,
            ListAdapter adapter) {
        return new HeaderViewListAdapter(headerViewInfos, footerViewInfos, adapter);
    }

也就是說在ListView#addHeaderView(...)之后,ListView 的 mAdapter 就變成了 HeaderViewListAdapter丐怯,請注意 mAdapter 是寫在AbsListView 里面的喷好,所以你打開源碼是找不到mAdapter的,它們之間的關(guān)系有點(diǎn)繞读跷,下面用代碼簡單說一下:

ListView extends AbsListView{
    public ListAdapter getAdapter() { return mAdapter;}
}
class AbsListView extends AdapterView<ListAdapter>{
    ListAdapter mAdapter // 即mAdapter 包可見梗搅,但是我們在外部可以用 ListView#getAdapter 或 AbsListView#getAdapter獲取
}

class AdapterView{
  public abstract T getAdapter();
}
HeaderViewListAdapter implements WrapperListAdapter
interface WrapperListAdapter extends ListAdapter
interface ListAdapter extends Adapter
interface Adapter

然后,在ListView里面共三個(gè)地方用到了 void wrapHeaderListAdapterInternal():

 public void setAdapter(ListAdapter adapter) {
     //...
     if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
            mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, adapter);
        } else {
            mAdapter = adapter;
        }
      //...
}
public void addHeaderView(View v, Object data, boolean isSelectable){ // ...}
public void addFooterView(View v, Object data, boolean isSelectable){ //...}

注:這是android-26的源碼了效览,所以說已經(jīng)解決了之前 setAdapter 和 addHeaderView / addFooterView 的問題些膨,也就是當(dāng) ListView 里面存在 headerView 或 footerView 之后就會(huì)給它包一個(gè)外包。

好了钦铺,說了那么多,再看看HeaderViewListAdapter肢预,headerViews 和 footerViews 充當(dāng)什么角色矛洞。

public class HeaderViewListAdapter implements WrapperListAdapter, Filterable {
  private final boolean mIsFilterable;  // 控制 getFilter() 的,不清楚可以看一下  Filterable  源碼 和 
                                        // 用例 https://gist.github.com/DeepakRattan/26521c404ffd7071d0a4
  private final ListAdapter mAdapter;   // 原先的 adapter
  static final ArrayList<ListView.FixedViewInfo> EMPTY_INFO_LIST =
        new ArrayList<ListView.FixedViewInfo>();    // 空的list 應(yīng)該是為了預(yù)防 NPE
  boolean mAreAllFixedViewsSelectable;    //應(yīng)該和外面ListView的mAreAllItemsSelectable 一樣吧
 
  public HeaderViewListAdapter(ArrayList<ListView.FixedViewInfo> headerViewInfos,
                                 ArrayList<ListView.FixedViewInfo> footerViewInfos,
                                 ListAdapter adapter) {
        mAdapter = adapter;
        mIsFilterable = adapter instanceof Filterable;

        if (headerViewInfos == null) {
            mHeaderViewInfos = EMPTY_INFO_LIST;
        } else {
            mHeaderViewInfos = headerViewInfos;
        }

        if (footerViewInfos == null) {
            mFooterViewInfos = EMPTY_INFO_LIST;
        } else {
            mFooterViewInfos = footerViewInfos;
        }

        mAreAllFixedViewsSelectable =
                areAllListInfosSelectable(mHeaderViewInfos)
                && areAllListInfosSelectable(mFooterViewInfos);
    }

  public int getCount() {     // 可以看出這里獲取數(shù)量的時(shí)候是把 headerView 和 emptyView 算進(jìn)去的
        if (mAdapter != null) {
            return getFootersCount() + getHeadersCount() + mAdapter.getCount();
        } else {
            return getFootersCount() + getHeadersCount();
        }
    }

  public Object getItem(int position) {   // 這里就是返回對應(yīng)的數(shù)據(jù),header / footer 返回的就是我們設(shè)置data
        // Header (negative positions will throw an IndexOutOfBoundsException)
        int numHeaders = getHeadersCount();
        if (position < numHeaders) {
            return mHeaderViewInfos.get(position).data;
        }

        // Adapter
        final int adjPosition = position - numHeaders;
        int adapterCount = 0;
        if (mAdapter != null) {
            adapterCount = mAdapter.getCount();
            if (adjPosition < adapterCount) {
                return mAdapter.getItem(adjPosition);
            }
        }

        // Footer (off-limits positions will throw an IndexOutOfBoundsException)
        return mFooterViewInfos.get(adjPosition - adapterCount).data;
    }

  public View getView(int position, View convertView, ViewGroup parent) {
        // Header (negative positions will throw an IndexOutOfBoundsException)
        // 這里可以看到 header 和 footer 直接把 View 拿出來展示了沼本,data只是儲(chǔ)存一些備份的數(shù)據(jù)噩峦,不能在這里動(dòng)態(tài)變化的,當(dāng)然抽兆,它會(huì)被回收
        int numHeaders = getHeadersCount();
        if (position < numHeaders) {
            return mHeaderViewInfos.get(position).view;
        }

        // Adapter
        final int adjPosition = position - numHeaders;
        int adapterCount = 0;
        if (mAdapter != null) {
            adapterCount = mAdapter.getCount();
            if (adjPosition < adapterCount) {
                return mAdapter.getView(adjPosition, convertView, parent);
            }
        }

        // Footer (off-limits positions will throw an IndexOutOfBoundsException)
        return mFooterViewInfos.get(adjPosition - adapterCount).view;
    }
  
  public int getItemViewType(int position) {
        int numHeaders = getHeadersCount();
        if (mAdapter != null && position >= numHeaders) {
            int adjPosition = position - numHeaders;
            int adapterCount = mAdapter.getCount();
            if (adjPosition < adapterCount) {
                return mAdapter.getItemViewType(adjPosition);
            }
        }

        return AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
    }
  
  public int getViewTypeCount() {  
        if (mAdapter != null) {
            return mAdapter.getViewTypeCount();   
            // 這里我本來覺得應(yīng)該+1的识补,但是后來想了想,getViewTypeCount()
            // 一般是給開發(fā)者配合 getView 使用的辫红,那么如果把 AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER; 
            // 也給算進(jìn)去的話凭涂,可能有些開發(fā)者設(shè)計(jì)的 adapter 沒有處理headerView 反而麻煩了。
        }
        return 1;
    }

  public boolean isEmpty() { 
        // 這個(gè)是配合EmptyView 的 從這里可以看出這里計(jì)算是否為空不會(huì)把 headerView 贴妻、footerView 算進(jìn)去
        return mAdapter == null || mAdapter.isEmpty();
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末切油,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子名惩,更是在濱河造成了極大的恐慌澎胡,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件娩鹉,死亡現(xiàn)場離奇詭異攻谁,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)弯予,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進(jìn)店門戚宦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人熙涤,你說我怎么就攤上這事阁苞。” “怎么了祠挫?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵那槽,是天一觀的道長。 經(jīng)常有香客問我等舔,道長骚灸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任慌植,我火速辦了婚禮甚牲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蝶柿。我一直安慰自己丈钙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布交汤。 她就那樣靜靜地躺著雏赦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上星岗,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天填大,我揣著相機(jī)與錄音,去河邊找鬼俏橘。 笑死允华,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的寥掐。 我是一名探鬼主播靴寂,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼曹仗!你這毒婦竟也來了榨汤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤怎茫,失蹤者是張志新(化名)和其女友劉穎收壕,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體轨蛤,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蜜宪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了祥山。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片圃验。...
    茶點(diǎn)故事閱讀 38,625評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖缝呕,靈堂內(nèi)的尸體忽然破棺而出澳窑,到底是詐尸還是另有隱情,我是刑警寧澤供常,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布摊聋,位于F島的核電站,受9級(jí)特大地震影響栈暇,放射性物質(zhì)發(fā)生泄漏麻裁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一源祈、第九天 我趴在偏房一處隱蔽的房頂上張望煎源。 院中可真熱鬧,春花似錦香缺、人聲如沸手销。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽原献。三九已至馏慨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間姑隅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工倔撞, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留讲仰,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓痪蝇,卻偏偏與公主長得像鄙陡,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子躏啰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,492評論 2 348

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