閱讀ListView源碼

郭林大神的博客 http://blog.csdn.net/guolin_blog/article/details/44996879
下面這篇博客對listView的觸摸事件講的比較細(xì)
http://www.reibang.com/p/7f95297b6271
注:本文是以API 25的代碼為準(zhǔn)

使用ListView的時候,還有一個東西不得不提就是BaseAdapter,這有這兩個東西同時使用的時候,才能顯示出列表.下面我們就一起來看看ListView和BaseAdapter

BaseAdapter及其父親和爺爺:

從圖中我們可以看出BaseAdapter實現(xiàn)了ListAdapter和SpinnerAdapter,而ListAdapter和SpinnerAdapter又同時繼承了Adapter接口,下面我們就一起看看這個幾個類里面都干了些什么.

Adapter:官方文檔描述->Adapter是AdapterView和數(shù)據(jù)之間的一個橋梁,Adapter提供對數(shù)據(jù)項的訪問,也負(fù)責(zé)填充view.

Adapter中提供了getCount(),getView(),registerDataSetObserver()等最基本的方法.

ListAdapter:官方文檔描述->ListAdapter是Adapter 的擴展,ListAdapter和ListView配合可以顯示任何的數(shù)據(jù)

ListAdapter中相較于Adapter只擴展了兩個方法areAllItemsEnabled()和isEnabled(position),areAllItemsEnabled()是設(shè)置所有的items都可用(點擊或者選中)或者不可用(不可點擊或者不可選中)(由返回值來判斷),isEnabled(position)是設(shè)置指定位置的item是否可以點擊或者選中(由返回值來判斷)

SpinnerAdapter:官方文檔描述->SpinnerAdapter是Adapter的擴展,Spinner一般顯示兩種視圖,一種是Spinner本身的視圖,一種是在他按下以后顯示的列表視圖.

SpinnerAdapter中相較于Adapter中只擴展了一個方法getDropDownView(int position,View convertView,ViewGroup parent)該方法和Adapter中的getView()方法效果基本是一致的,是用于創(chuàng)建顯示在Spinner中的ui的,一般是供AbsSpinner調(diào)用. BaseAdapter中對該方法的實現(xiàn)就是直接返回getView()方法

BaseAdapter:官方文檔描述->實現(xiàn)了Adapter的普通基類,可用于listView(因為實現(xiàn)了listAdapter)和Spanner(因為實現(xiàn)了SpannerAdapter)

BaseAdapter實現(xiàn)了Adapter ,ListAdapter,Spanner中大部分的方法比如notifyDataSetChanged(),registerDataSetObserver(),unregisterDataSetObserver().BaseAdpter中只有一個成員變量就是mDataSetObservable
它是DataSetObservable的實例
private final DataSetObservable mDataSetObservable = new DataSetObservable();

其中registerDataSetObserver()(設(shè)置數(shù)據(jù)變化監(jiān)聽)就是將傳過來的DataSetObserver設(shè)置給了DataSetObserveable.

 public void registerDataSetObserver(DataSetObserver observer) {
         mDataSetObservable.registerObserver(observer);
     }

registerDataSetObserver()這個方法回到我們給listView設(shè)置adapter的時候調(diào)用

 @Override
public void setAdapter(ListAdapter adapter) {
    if (mAdapter != null && mDataSetObserver != null) {
        mAdapter.unregisterDataSetObserver(mDataSetObserver);
    }
********
     mDataSetObserver = new AdapterDataSetObserver();
     mAdapter.registerDataSetObserver(mDataSetObserver);   
     mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
 }

unregisterDataSetObserver()(取消數(shù)據(jù)變化監(jiān)聽)也是調(diào)用DataSetObserveable的取消監(jiān)聽的方法

public void unregisterDataSetObserver(DataSetObserver observer) {
    mDataSetObservable.unregisterObserver(observer);
}

notifyDataSetChanged()這是我們經(jīng)常使用的方法,是用來進(jìn)行刷新列表的他其實也是調(diào)用DataSetObserveable中的notifyChanged()方法來出發(fā)數(shù)據(jù)變化的

public void notifyDataSetChanged() {
    mDataSetObservable.notifyChanged();
}

從上面這三個方法分析得出,其實DataSetObservable才是真正監(jiān)聽數(shù)據(jù)變化的類


再說ListView之前我們先說說ListView中非常重要的緩存機制RecycleBin

RecycleBin

這里的RecycleBin代碼并不全,我只是把最主要的幾個方法提了出來。那么我們先來對這幾個方法進(jìn)行簡單解讀荠呐,這對后面分析ListView的工作原理將會有很大的幫助。

  • private View[] mActiveViews = new View[0];-->mActiveViews 中保存的是從第一個可見的item開始的連續(xù)的視圖范圍,當(dāng)布局結(jié)束時mActiveViews中的view將會轉(zhuǎn)移到mScrapViews中

  • private ArrayList<View>[] mScrapViews;-->mScrapViews中保存的是廢棄的view,比如說是移出屏幕的view

  • private ArrayList<View> mCurrentScrap;-->和mScrapViews一樣也是用來保存廢棄view的

  • fillActiveViews() 這個方法接收兩個參數(shù)捷沸,第一個參數(shù)表示要存儲的view的數(shù)量锹引,第二個參數(shù)表示ListView中第一個可見元素的position值。RecycleBin當(dāng)中使用mActiveViews這個數(shù)組來存儲View新症,調(diào)用這個方法后就會根據(jù)傳入的參數(shù)來將ListView中的指定元素存儲到mActiveViews數(shù)組當(dāng)中。

  • getActiveView() 這個方法和fillActiveViews()是對應(yīng)的响禽,用于從mActiveViews數(shù)組當(dāng)中獲取數(shù)據(jù)徒爹。該方法接收一個position參數(shù),表示元素在ListView當(dāng)中的位置芋类,方法內(nèi)部會自動將position值轉(zhuǎn)換成mActiveViews數(shù)組對應(yīng)的下標(biāo)值瀑焦。需要注意的是,mActiveViews當(dāng)中所存儲的View梗肝,一旦被獲取了之后就會從mActiveViews當(dāng)中移除榛瓮,下次獲取同樣位置的View將會返回null,也就是說mActiveViews不能被重復(fù)利用巫击。

  • addScrapView() 用于將一個廢棄的View進(jìn)行緩存禀晓,該方法接收一個View參數(shù),當(dāng)有某個View確定要廢棄掉的時候(比如滾動出了屏幕)坝锰,就應(yīng)該調(diào)用這個方法來對View進(jìn)行緩存粹懒,RecycleBin當(dāng)中使用mScrapViews和mCurrentScrap這兩個List來存儲廢棄View。

  • getScrapView 用于從廢棄緩存中取出一個View顷级,這些廢棄緩存中的View是沒有順序可言的凫乖,因此getScrapView()方法中的算法也非常簡單,就是直接從mCurrentScrap當(dāng)中獲取尾部的一個scrap view進(jìn)行返回弓颈。

  • setViewTypeCount() 我們都知道Adapter當(dāng)中可以重寫一個getViewTypeCount()來表示ListView中有幾種類型的數(shù)據(jù)項帽芽,而setViewTypeCount()方法的作用就是為每種類型的數(shù)據(jù)項都單獨啟用一個RecycleBin緩存機制。實際上翔冀,- - - getViewTypeCount()方法通常情況下使用的并不是很多导街,所以我們只要知道RecycleBin當(dāng)中有這樣一個功能就行了。

ListView

listView繼承體系.png

從上圖可以清晰的看出ListView的繼承體系下面我們就對這些類做一些大致的了解

AbsListView官方文檔描述->它是可用于實現(xiàn)虛擬化列表的基類,這個列表沒有特殊的空間定義.例如這個類的子類可以以網(wǎng)格,輪播,堆疊等方式顯示列表內(nèi)容.-->所以我們?nèi)绻远x列表就可以繼承該類
AdapterView官方文檔描述->AdapterView就是其子View由Adapter決定的View.

下面是對ListView源碼的解讀

setAdapter():這個方法的作用是綁定listView所需的數(shù)據(jù)
  1. 調(diào)用這個方法中首先是將之前給adapter設(shè)置的監(jiān)聽解除掉然后也將listView進(jìn)行重置也將之前緩存的view進(jìn)行清除
    //解除數(shù)據(jù)監(jiān)聽
    if (mAdapter != null && mDataSetObserver != null) {
    mAdapter.unregisterDataSetObserver(mDataSetObserver);
    }
    //listView進(jìn)行重置
    resetList();
    //緩沖池進(jìn)行清除
    mRecycler.clear();

     //重置成員變量
     mOldSelectedPosition = INVALID_POSITION;
     mOldSelectedRowId = INVALID_ROW_ID;
    
  2. 如果該listView中包含頭布局或者是腳布局那么就會把該adapter包裝成HeaderViewListAdapter否則不會包裝

     if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {//如果有頭布局或者腳布局則進(jìn)行包裝
         mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, adapter);
     } else {不包裝
         mAdapter = adapter;
     }
    
  3. 調(diào)用父類的setAdapter方法,也是做了一些數(shù)據(jù)還原,和初始化

     // AbsListView#setAdapter will update choice mode states.
     super.setAdapter(adapter);
    
  4. 當(dāng)mAdapter不為null的時候?qū)istView中的變量進(jìn)行初始化

       if (mAdapter != null) {
         mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
         mOldItemCount = mItemCount;
         mItemCount = mAdapter.getCount();
         checkFocus();
    
         //設(shè)置數(shù)據(jù)監(jiān)聽
         mDataSetObserver = new AdapterDataSetObserver();
         mAdapter.registerDataSetObserver(mDataSetObserver);
    
          //這里給緩存給負(fù)責(zé)緩存的對象設(shè)置一共要生成幾種緩存的view  
          mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
    
         int position;
         if (mStackFromBottom) {
             position = lookForSelectablePosition(mItemCount - 1, false);
         } else {
             position = lookForSelectablePosition(0, true);
         }
         setSelectedPositionInt(position);
         setNextSelectedPositionInt(position);
    
         if (mItemCount == 0) {
             // Nothing selected
             checkSelectionChanged();
        }
    
  5. mRecycler.setViewTypeCount(mAdapter.getViewTypeCount())這里給緩存給負(fù)責(zé)緩存的對象設(shè)置一共要生成幾種緩存的view
    public void setViewTypeCount(int viewTypeCount) {
    if (viewTypeCount < 1) {
    throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
    }
    //noinspection unchecked
    ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
    for (int i = 0; i < viewTypeCount; i++) {
    scrapViews[i] = new ArrayList<View>();
    }
    mViewTypeCount = viewTypeCount;
    mCurrentScrap = scrapViews[0];
    mScrapViews = scrapViews;
    }

  6. mAdapter為null的情況下也做了一下操作

     else {
         mAreAllItemsSelectable = true;
         checkFocus();
         // Nothing selected
         checkSelectionChanged();
     }
    
  7. 最后請求重繪

    requestLayout();
    

所以setAdapter()中主要是做數(shù)據(jù)的重置和初始化工作,在這里添加了數(shù)據(jù)監(jiān)聽,也初始化了item的緩沖池

在setAdapter()方法的最后它調(diào)用了requestLayout()方法,那么我們就來看看onLayout()中是怎么擺放item的,但是在這之前我們還是很有必要看一下onMeasure()方法,看看listView是如何測量自己的子View的

onMeasure()

listView自己實現(xiàn)了onMeasure()方法

1.在onMeasure()中的第一句代碼就是調(diào)用了父類的onMeasure()方法也就是AbsListView的onMeasure()方法,父類的onMeasure()主要是設(shè)置了listView的Padding

    // Sets up mListPadding
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

2.當(dāng)listView中有item而且他的寬或者高德MeasureSpec mode 是 UNSPECIFIED的時候

    mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
    //當(dāng)listView中有item而且他的寬或者高德MeasureSpec mode 是 UNSPECIFIED的時候
    if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED
            || heightMode == MeasureSpec.UNSPECIFIED)) {
        //去獲取第一個childView
         final View child = obtainView(0, mIsScrap);

3.其中調(diào)用了obtainView(int position, boolean[] outMetadata)這個方法去獲取childView,這個方法中的第一個if判斷是對 "transient state view"的判斷一般一般不會進(jìn)到這個if語句中 --->現(xiàn)在還不知道這個transient state view具體是什么東西0.0

    // Check whether we have a transient state view. Attempt to re-bind the
    // data and discard the view if we fail.
    final View transientView = mRecycler.getTransientStateView(position);
    if (transientView != null) {
        final LayoutParams params = (LayoutParams) transientView.getLayoutParams();

        // If the view type hasn't changed, attempt to re-bind the data.
        if (params.viewType == mAdapter.getItemViewType(position)) {
            final View updatedView = mAdapter.getView(position, transientView, this);

            // If we failed to re-bind the data, scrap the obtained view.
            if (updatedView != transientView) {
                setItemViewLayoutParams(updatedView, position);
                mRecycler.addScrapView(updatedView, position);
            }
        }

        outMetadata[0] = true;

        // Finish the temporary detach started in addScrapView().
        transientView.dispatchFinishTemporaryDetach();
        return transientView;
    }

4.下面這段代碼就是一般真正的從復(fù)用view的代碼.首先他先從recycleBin中取出scrapView然后傳給adapter的getView(),getView()也會返回一個child,如果scrapView不為null則說明緩沖池中有view,然后再進(jìn)行判斷scrapViwe和child是否相同,如果不相同就調(diào)用 mRecycler.addScrapView(scrapView, position);將scrapView再添加到緩沖池中

    final View scrapView = mRecycler.getScrapView(position);
    final View child = mAdapter.getView(position, scrapView, this);
    if (scrapView != null) {
        if (child != scrapView) {
            // Failed to re-bind the data, return scrap to the heap.
            mRecycler.addScrapView(scrapView, position);
        } else if (child.isTemporarilyDetached()) {
            outMetadata[0] = true;

            // Finish the temporary detach started in addScrapView().
            child.dispatchFinishTemporaryDetach();
        }
    }

5.到這里obtainView()就完成了產(chǎn)出一個child的任務(wù),下面的代碼就是給child進(jìn)行設(shè)置一些東西,比如"DrawingCacheBackgroundColor","LayoutParams"等

6.到最后就是直接返回這個來之不易的child

      .....
      return child;

7.obtainView()方法走完以后他返回了一個child,下面會調(diào)用measureScrapChild()這個方法測量child

        // Lay out child directly against the parent measure spec so that
        // we can obtain exected minimum width and height.
        //上面注釋的意思就是 按照父親的MeasureSpec來測量可以獲取到child的最小寬度和高度
        measureScrapChild(child, 0, widthMeasureSpec, heightSize);

8.child測量完了以后然后將child添加到緩存池中

       if (recycleOnMeasure() && mRecycler.shouldRecycleViewType(
                ((LayoutParams) child.getLayoutParams()).viewType)) {
            mRecycler.addScrapView(child, 0);
        }

9.下來的代碼就是調(diào)用setMeasureDimension(widthSize, heightSize)將listView的寬高進(jìn)行保存

ListView的onMeasure()總結(jié):

**_看完代碼以后我們知道在onMeasure()方法的時候已經(jīng)把position為0的child造出來了,也進(jìn)行了測量并加入到了緩沖池RecycleBin中.但是值得注意的是在ListView的父類的寬或者搞的mode是MeasureSpec.UNSPECIFIED這里L(fēng)istView的高只會是一個item的高 (這也就是scallView嵌套listView出現(xiàn)問題的原因)_**

    if (mItemCount > 0 && (widthMode == MeasureSpec.UNSPECIFIED
            || heightMode == MeasureSpec.UNSPECIFIED)) {
        final View child = obtainView(0, mIsScrap);
    ******
    if (heightMode == MeasureSpec.UNSPECIFIED) {
        heightSize = mListPadding.top + mListPadding.bottom + childHeight +
                getVerticalFadingEdgeLength() * 2;
    }
    ******

解決的方法也就很簡單將重寫listView的onMeasure()方法就行了,就是將父類的 mode從 MeasureSpec.UNSPECIFIED改為MeasureSpec.AT_MOST因為當(dāng)父類的mode的是MeasureSpec.AT_MOST的時候listView會循環(huán)獲取child的高并想加

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,
    MeasureSpec.AT_MOST);
    super.onMeasure(widthMeasureSpec, expandSpec);
}

onMeasure()說完了下面我們來看看onLayout()

onLayout()

ListView中沒有實現(xiàn)onLayout()方法,但是復(fù)寫了父類onLayout()中調(diào)用的一個方法layoutChildren()方法.后面我們就一起看看ListView中的layoutChildren()是怎么實現(xiàn)的,但是在看layoutChildren()之前我們先看看AbsListView中的onLayout()都干了什么.

onLayout()>AbsListView-->其實在這個方法中也沒有做什么處理就是判斷了一下當(dāng)change改變的時候的情況,主要還是在layoutChildren()方法中,下面我們就看看這個方法

layoutChildren()>ListView這個方法也是挺長的,大概有300多行.

1.layoutChildren中首先做的一個操作就是避免在短時間內(nèi)重復(fù)調(diào)用layoutChildren()該方法

    final boolean blockLayoutRequests = mBlockLayoutRequests;
    if (blockLayoutRequests) {
        return;
    }

    mBlockLayoutRequests = true;
    *****
    //最后又將mBlockLayoutRequests置為false
    finally {
     *****  
        if (!blockLayoutRequests) {
            mBlockLayoutRequests = false;
        }
    }

2.調(diào)用了父類的layoutChildren()方法,其實是一個空方法

 try {
        super.layoutChildren();
      ****

3.調(diào)用了invalidate()方法來出發(fā)draw()方法,這里不太明白為什么這里要調(diào)用invalidate(),難道是要一遍擺放一邊?

 invalidate();

4.是在對adapter為null的情況進(jìn)行了判斷,當(dāng)adapter為null的時候,會清空列表然后直接返回

 if (mAdapter == null) {
    resetList();
    invokeOnItemScrollListener();
    return;
 }

5.在不同的layoutMode情況下對第一個item,選中的item進(jìn)行緩存

        // Remember stuff we will need down below
        switch (mLayoutMode) {
        case LAYOUT_SET_SELECTION:
            index = mNextSelectedPosition - mFirstPosition;
            if (index >= 0 && index < childCount) {
                newSel = getChildAt(index);
            }
            break;
        case LAYOUT_FORCE_TOP:
        case LAYOUT_FORCE_BOTTOM:
        case LAYOUT_SPECIFIC:
        case LAYOUT_SYNC:
            break;
        case LAYOUT_MOVE_SELECTION:
        default:
            // Remember the previously selected view
            index = mSelectedPosition - mFirstPosition;
            if (index >= 0 && index < childCount) {
                oldSel = getChildAt(index);
            }

            // Remember the previous first child
            oldFirst = getChildAt(0);

            if (mNextSelectedPosition >= 0) {
                delta = mNextSelectedPosition - mSelectedPosition;
            }

            // Caution: newSel might be null
            newSel = getChildAt(index + delta);
        }

6.當(dāng)數(shù)據(jù)變化時 ,則進(jìn)行同步數(shù)據(jù)

        boolean dataChanged = mDataChanged;
        if (dataChanged) {
            handleDataChanged();
        }

7.對空item或者錯誤的item數(shù)進(jìn)行判斷和操作,當(dāng)空item的時候也會清空列表,當(dāng)錯誤的item數(shù)使會拋出異常

        if (mItemCount == 0) {
            resetList();
            invokeOnItemScrollListener();
            return;
        } else if (mItemCount != mAdapter.getCount()) {
            throw new IllegalStateException("The content of the adapter has changed but "
                    + "ListView did not receive a notification. Make sure the content of "
                    + "your adapter is not modified from a background thread, but only from "
                    + "the UI thread. Make sure your adapter calls notifyDataSetChanged() "
                    + "when its content changes. [in ListView(" + getId() + ", " + getClass()
                    + ") with Adapter(" + mAdapter.getClass() + ")]");
        }

8.同步選中的數(shù)據(jù)

       setSelectedPositionInt(mNextSelectedPosition);

9.對child是否具有輔助功能進(jìn)行判斷,如果有則進(jìn)入輔助功能的邏輯

        // Remember which child, if any, had accessibility focus. This must
        // occur before recycling any views, since that will clear
        // accessibility focus.
        final ViewRootImpl viewRootImpl = getViewRootImpl();
        if (viewRootImpl != null) {
            final View focusHost = viewRootImpl.getAccessibilityFocusedHost();
            if (focusHost != null) {
                final View focusChild = getAccessibilityFocusedChild(focusHost);
                if (focusChild != null) {
                    if (!dataChanged || isDirectChildHeaderOrFooter(focusChild)
                            || focusChild.hasTransientState() || mAdapterHasStableIds) {
                        // The views won't be changing, so try to maintain
                        // focus on the current host and virtual view.
                        accessibilityFocusLayoutRestoreView = focusHost;
                        accessibilityFocusLayoutRestoreNode = viewRootImpl
                                .getAccessibilityFocusedVirtualView();
                    }

                    // If all else fails, maintain focus at the same
                    // position.
                    accessibilityFocusPosition = getPositionForView(focusChild);
                }
            }
        }

10.對焦點進(jìn)行處理

        final View focusedChild = getFocusedChild();
        if (focusedChild != null) {
            // TODO: in some cases focusedChild.getParent() == null

            // We can remember the focused view to restore after re-layout
            // if the data hasn't changed, or if the focused position is a
            // header or footer.
            if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)
                    || focusedChild.hasTransientState() || mAdapterHasStableIds) {
                focusLayoutRestoreDirectChild = focusedChild;
                // Remember the specific view that had focus.
                focusLayoutRestoreView = findFocus();
                if (focusLayoutRestoreView != null) {
                    // Tell it we are going to mess with it.
                    focusLayoutRestoreView.dispatchStartTemporaryDetach();
                }
            }
            requestFocus();
        }

11.將所有的item都加入到緩存池中

        final int firstPosition = mFirstPosition;
        final RecycleBin recycleBin = mRecycler;
        if (dataChanged) {
            for (int i = 0; i < childCount; i++) {
                recycleBin.addScrapView(getChildAt(i), firstPosition+i);
            }
        } else {
            recycleBin.fillActiveViews(childCount, firstPosition);
        }

12.清除緩存池中舊的view

        detachAllViewsFromParent();
        recycleBin.removeSkippedScrap();

13.對mLayoutMode進(jìn)行判斷然后選擇填充listView的方法,一般的話都會執(zhí)行default.

        switch (mLayoutMode) {
        case LAYOUT_SET_SELECTION:
            if (newSel != null) {
                sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
            } else {
                sel = fillFromMiddle(childrenTop, childrenBottom);
            }
            break;
          ***********
          default:
            if (childCount == 0) {
                if (!mStackFromBottom) {
                    final int position = lookForSelectablePosition(0, true);
                    setSelectedPositionInt(position);
                    sel = fillFromTop(childrenTop);
                } else {
                    final int position = lookForSelectablePosition(mItemCount - 1, false);
                    setSelectedPositionInt(position);
                    sel = fillUp(mItemCount - 1, childrenBottom);
                }
            } else {
                if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
                    sel = fillSpecific(mSelectedPosition,
                            oldSel == null ? childrenTop : oldSel.getTop());
                } else if (mFirstPosition < mItemCount) {
                    sel = fillSpecific(mFirstPosition,
                            oldFirst == null ? childrenTop : oldFirst.getTop());
                } else {
                    sel = fillSpecific(0, childrenTop);
                }
            }
            break;
        }

14.在default中當(dāng)childCount為0的時候會執(zhí)行fillFromTop()方法,去從上倒下的填充listView

 //Fills the list from top to bottom, starting with mFirstPosition
private View fillFromTop(int nextTop) {
    mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
    mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
    if (mFirstPosition < 0) {
        mFirstPosition = 0;
    }
    return fillDown(mFirstPosition, nextTop);
}

15.在fillFromTop()方法中其實也就調(diào)用了一個方法fillDown(),這個方法中就調(diào)用了makeAndAddView()方法去填充 一屏 的listView

private View fillDown(int pos, int nextTop) {
    View selectedView = null;

    int end = (mBottom - mTop);
    if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
        end -= mListPadding.bottom;
    }

    while (nextTop < end && pos < mItemCount) {
        // is this the selected item?
        boolean selected = pos == mSelectedPosition;
        View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);

        nextTop = child.getBottom() + mDividerHeight;
        if (selected) {
            selectedView = child;
        }
        pos++;
    }

    setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
    return selectedView;
}

16.fillFromTop()中調(diào)用了makeAndAddView()方法去創(chuàng)建一個View并將這個View添加到listView中.在makeAndAddView()方法中首先回去RecycleBin中拿緩存的View如果這個view不為null,則進(jìn)行使用并返回,如果是null就會調(diào)用obtainView()去創(chuàng)建一個新的view,使用并返回

private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
        boolean selected) {
    if (!mDataChanged) {
        // Try to use an existing view for this position.
        final View activeView = mRecycler.getActiveView(position);
        if (activeView != null) {
            // Found it. We're reusing an existing child, so it just needs
            // to be positioned like a scrap view.
            setupChild(activeView, position, y, flow, childrenLeft, selected, true);
            return activeView;
        }
    }

    // Make a new view for this position, or convert an unused view if
    // possible.
    final View child = obtainView(position, mIsScrap);

    // This needs to be positioned and measured.
    setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);

    return child;
}

17.obtainView()view就將adapter和listView聯(lián)系了起來,在obtainView()中首先回去RecycleBin中獲取一個廢棄的View 然后傳到adapter中的getView()方法中,這里這個廢棄的View很有可能的null.

    final View scrapView = mRecycler.getScrapView(position);
    final View child = mAdapter.getView(position, scrapView, this);
    if (scrapView != null) {
        if (child != scrapView) {
            // Failed to re-bind the data, return scrap to the heap.
            mRecycler.addScrapView(scrapView, position);
        } else if (child.isTemporarilyDetached()) {
            outMetadata[0] = true;

            // Finish the temporary detach started in addScrapView().
            child.dispatchFinishTemporaryDetach();
        }
    }

18.調(diào)用mRecycler.getActiveView()或者obtainView()產(chǎn)生了view以后調(diào)用setupChild()方法,其中setupChild()方法中會調(diào)用addViewInLayout()或者attachViewToParent()將這個view添加到listView中attachViewToParent()比addViewInLayout()的效率高很多

19.刷新RecycleBin中mActiveViews中的緩存-->其實是將mActiveViews中的緩存挪到了mScrapViews

       // Flush any cached views that did not get reused above
        recycleBin.scrapActiveViews();

20.移除沒有用的header和footer

        // remove any header/footer that has been temp detached and not re-attached
        removeUnusedFixedViews(mHeaderViewInfos);
        removeUnusedFixedViews(mFooterViewInfos);

21.找到選中的位置然后對選中的位置進(jìn)行一系列到操作


onInterceptTouchEvent()

下面我們一起來看看listView滑動過程中的操作,那么就要從onInterceptTouchEvent()這個方法開始了.onInterceptTouchEvent()->listView中沒有重寫這個方法,那么我們就在AbsListView中看看吧.

1.MotionEvent.ACTION_DOWN中當(dāng)listView在慣性滑動或者滑動狀態(tài)是就要攔截

        if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
            mMotionCorrection = 0;
            return true;
        }

2.獲取觸摸位置對應(yīng)的item的position,這個方法在listView中實現(xiàn)了

 int motionPosition = findMotionRow(y);

3.當(dāng)listView處于fling狀態(tài)而且觸摸的位置在listView上則進(jìn)行記錄,觸摸的X坐標(biāo),觸摸的Y坐標(biāo),觸摸位置對應(yīng)的item的position,和TouchMode;

        if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
            // User clicked on an actual view (and was not stopping a fling).
            // Remember where the motion event started
            v = getChildAt(motionPosition - mFirstPosition);
            mMotionViewOriginalTop = v.getTop();//觸摸位置對應(yīng)的item的top值
            mMotionX = x;//觸摸的X坐標(biāo)
            mMotionY = y;//觸摸的Y坐標(biāo)
            mMotionPosition = motionPosition;//觸摸位置對應(yīng)的item的position
            mTouchMode = TOUCH_MODE_DOWN;//將TouchMode設(shè)置為TOUCH_MODE_DOWN
            clearScrollingCache();
        }

4.當(dāng)TouchMode為TOUCH_MODE_FLING的時候攔截

        if (touchMode == TOUCH_MODE_FLING) {
            return true;
        }

5.MotionEvent.ACTION_MOVE中判斷是否攔截,是onInterceptTouchEvent()的核心

            if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, null)) {
                return true;
            }

6.所以我們來看一下startScrollIfNeeded()這個方法

 // 這個方法在onInterceptTouchEvent的move事件中調(diào)用,在onTouchEvent()的onTouchMove()方法
  // 中開始時候也會調(diào)用
   private boolean startScrollIfNeeded(int x, int y, MotionEvent vtev) {
// Check if we have moved far enough that it looks more like a
// scroll than a tap
// 得到當(dāng)前事件的y值與down事件時候設(shè)置的值的差值
final int deltaY = y - mMotionY;
final int distance = Math.abs(deltaY);
// mScrollY!=0即overscroll為true ,核心為distance > mTouchSlop即攔截事件自己處理
// mTouchSlop在構(gòu)造函數(shù)中初始化并賦值了
final boolean overscroll = mScrollY != 0;
if ((overscroll || distance > mTouchSlop) &&
        (getNestedScrollAxes() & SCROLL_AXIS_VERTICAL) == 0) {
    createScrollingCache();
    if (overscroll) {
        mTouchMode = TOUCH_MODE_OVERSCROLL;
        mMotionCorrection = 0;
    } else {
        // 設(shè)置觸摸模式為TOUCH_MODE_SCROLL,在onTouchEvent()用到
        mTouchMode = TOUCH_MODE_SCROLL;
        mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop;
    }
    // 取消子view的長按監(jiān)聽觸發(fā)
    removeCallbacks(mPendingCheckForLongPress);
    setPressed(false);
    final View motionView = getChildAt(mMotionPosition - mFirstPosition);
    // listview攔截了事件本身處理,所以恢復(fù)可能設(shè)置子view的press狀態(tài)
    if (motionView != null) {
        motionView.setPressed(false);
    }
    // 通知ScrollState狀態(tài)變化回調(diào)
    reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
    // Time to start stealing events! Once we've stolen them, don't let anyone
    // steal from us
    final ViewParent parent = getParent();
    if (parent != null) {
        parent.requestDisallowInterceptTouchEvent(true);
    }
    // 作用如名,如果滿足條件,滾動listview
    scrollIfNeeded(x, y, vtev);
    return true;
}

return false;
}

7.MotionEvent.ACTION_CANCEL或者M(jìn)otionEvent.ACTION_UP中則是將TouchMode恢復(fù)成默認(rèn)狀態(tài),然后回收VelocityTracker相關(guān)資源

    case MotionEvent.ACTION_CANCEL:
    case MotionEvent.ACTION_UP: {
        mTouchMode = TOUCH_MODE_REST;//將TouchMode恢復(fù)成默認(rèn)狀態(tài)
        mActivePointerId = INVALID_POINTER;
        recycleVelocityTracker();//回收VelocityTracker相關(guān)資源
        reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
        stopNestedScroll();
        break;
    }

onTouchEvent()

onTouchEvent()->listView中沒有重寫onTouchEvent方法,那么我們就在AbsListView中看看吧 ,因為onTouchEvent()中的邏輯很多 所以我們就主要
看看Down事件(onTouchDown())和Move事件(onTouchMove())

onTouchDown()中首先會判斷TouchMode如果是fling狀態(tài),就讓他停下來,然后記錄手指點擊的位置和TouchMode等

    if (mTouchMode == TOUCH_MODE_OVERFLING) {
        // Stopped the fling. It is a scroll.
        mFlingRunnable.endFling();
        if (mPositionScroller != null) {
            mPositionScroller.stop();
        }
        mTouchMode = TOUCH_MODE_OVERSCROLL;
        mMotionX = (int) ev.getX();
        mMotionY = (int) ev.getY();
        mLastY = mMotionY;
        mMotionCorrection = 0;
        mDirection = 0;
    }

當(dāng)TouchMode不是fling狀態(tài)的話,首先會記錄手指的落點,然后計算到落點對應(yīng)的item的position

        final int x = (int) ev.getX();
        final int y = (int) ev.getY();
        int motionPosition = pointToPosition(x, y);

當(dāng)數(shù)據(jù)沒有變化,我們就要重新判斷TouchMode,如果是TOUCH_MODE_FLING的話就要將TouchMode改為TOUCH_MODE_SCROLL并且馬上中斷fling,然后通過findMotionRow()這個方法重新找一下手指落點對應(yīng)的item的position

          if (mTouchMode == TOUCH_MODE_FLING) {
                // Stopped a fling. It is a scroll.
                createScrollingCache();
                mTouchMode = TOUCH_MODE_SCROLL;
                mMotionCorrection = 0;
                motionPosition = findMotionRow(y);
                mFlingRunnable.flywheelTouch();//中斷fling
            }

當(dāng)點擊的item是可用的那么又要改變TouchMode

            else if ((motionPosition >= 0) && getAdapter().isEnabled(motionPosition)) {
                // User clicked on an actual view (and was not stopping a
                // fling). It might be a click or a scroll. Assume it is a
                // click until proven otherwise.
                mTouchMode = TOUCH_MODE_DOWN;

                // FIXME Debounce
                if (mPendingCheckForTap == null) {
                    mPendingCheckForTap = new CheckForTap();
                }

                mPendingCheckForTap.x = ev.getX();
                mPendingCheckForTap.y = ev.getY();
                postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
            }

各種情況下獲取到了手指落點對應(yīng)的position也就是motionPosition,這里對motionPosition進(jìn)行判斷如果大于等于零,那么就獲取到這個item,并記錄他的top值

       if (motionPosition >= 0) {
            // Remember where the motion event started
            final View v = getChildAt(motionPosition - mFirstPosition);
            mMotionViewOriginalTop = v.getTop();
        }

下來在對落點的坐標(biāo)和motionPostion進(jìn)行保存

        mMotionX = x;
        mMotionY = y;
        mMotionPosition = motionPosition;
        mLastY = Integer.MIN_VALUE;

最后判斷TouchMode是TOUCH_MODE_DOWN,而且motionPosition不是默認(rèn)值并且ListView接受這個down時間那么就取消其他的操作(后面看看mPendingCheckForTap這個runnable是干啥的)

    if (mTouchMode == TOUCH_MODE_DOWN && mMotionPosition != INVALID_POSITION
            && performButtonActionOnTouchDown(ev)) {
            removeCallbacks(mPendingCheckForTap);
    }

onTouchMove()中如果數(shù)據(jù)改變了就要重新排列item

    if (mDataChanged) {
        // Re-sync everything if data has been changed
        // since the scroll operation can query the adapter.
        layoutChildren();
    }

在mode時間中還有一個switch語句,當(dāng)TouchMode為TOUCH_MODE_DOWN,TOUCH_MODE_TAP,TOUCH_MODE_DONE_WAITING等沒有動的狀態(tài)是會去判斷是否可以滾動
,核心判斷調(diào)條件為down事件y與down事件mMotionY值的差值絕對值是否大于mTouchSlop

            if (startScrollIfNeeded((int) ev.getX(pointerIndex), y, vtev)) {
                break;
            }

下面會判斷點擊的區(qū)域是否在ListView內(nèi) 如果不是則取消操作

            // Otherwise, check containment within list bounds. If we're
            // outside bounds, cancel any active presses.
            final View motionView = getChildAt(mMotionPosition - mFirstPosition);
            final float x = ev.getX(pointerIndex);
            if (!pointInView(x, y, mTouchSlop)) {
                setPressed(false);
                if (motionView != null) {
                    motionView.setPressed(false);
                }
                removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ?
                        mPendingCheckForTap : mPendingCheckForLongPress);
                mTouchMode = TOUCH_MODE_DONE_WAITING;
                updateSelectorState();
            } else if (motionView != null) {
                // Still within bounds, update the hotspot.
                final float[] point = mTmpPoint;
                point[0] = x;
                point[1] = y;
                transformPointToViewLocal(point, motionView);
                motionView.drawableHotspotChanged(point[0], point[1]);
            }

如果已經(jīng)是TOUCH_MODE_SCROLL和TOUCH_MODE_OVERSCROLL這種滾動狀態(tài),則執(zhí)行scrollIfNeeded()方法判斷是否要進(jìn)行滾動.

        case TOUCH_MODE_SCROLL:
        case TOUCH_MODE_OVERSCROLL:
            scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev);
            break;

scrollifNeeded()中主要負(fù)責(zé)滾動的方法就是trackMotionScroll()

trackMotionScroll()這個方法接收兩個參數(shù)纤子,deltaY表示從手指按下時的位置到當(dāng)前手指位置的距離搬瑰,incrementalDeltaY則表示據(jù)上次觸發(fā)event事件手指在Y方向上位置的改變量,那么其實我們就可以通過incrementalDeltaY的正負(fù)值情況來判斷用戶是向上還是向下滑動的了.在滾動的過程中使用fillGap()方法進(jìn)行item的填充

    boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
    final int childCount = getChildCount();
    if (childCount == 0) {
        return true;
    }

    final int firstTop = getChildAt(0).getTop();
    final int lastBottom = getChildAt(childCount - 1).getBottom();

    final Rect listPadding = mListPadding;

    // "effective padding" In this case is the amount of padding that affects
    // how much space should not be filled by items. If we don't clip to padding
    // there is no effective padding.
    int effectivePaddingTop = 0;
    int effectivePaddingBottom = 0;
    if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
        effectivePaddingTop = listPadding.top;
        effectivePaddingBottom = listPadding.bottom;
    }

     // FIXME account for grid vertical spacing too?
    final int spaceAbove = effectivePaddingTop - firstTop;
    final int end = getHeight() - effectivePaddingBottom;
    final int spaceBelow = lastBottom - end;

    final int height = getHeight() - mPaddingBottom - mPaddingTop;
    if (deltaY < 0) {
        deltaY = Math.max(-(height - 1), deltaY);
    } else {
        deltaY = Math.min(height - 1, deltaY);
    }

    if (incrementalDeltaY < 0) {
        incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
    } else {
        incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
    }

    final int firstPosition = mFirstPosition;

    // Update our guesses for where the first and last views are
    if (firstPosition == 0) {
        mFirstPositionDistanceGuess = firstTop - listPadding.top;
    } else {
        mFirstPositionDistanceGuess += incrementalDeltaY;
    }
    if (firstPosition + childCount == mItemCount) {
        mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
    } else {
        mLastPositionDistanceGuess += incrementalDeltaY;
    }

    final boolean cannotScrollDown = (firstPosition == 0 &&
            firstTop >= listPadding.top && incrementalDeltaY >= 0);
    final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
            lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);

    if (cannotScrollDown || cannotScrollUp) {
        return incrementalDeltaY != 0;
    }

    //incrementalDeltaY表示據(jù)上次觸發(fā)event事件手指在Y方向上位置的改變量
    final boolean down = incrementalDeltaY < 0;//用來判斷是向上滑動還是向下滑動,true的話就是向下滑動,false就是向上滑動

    final boolean inTouchMode = isInTouchMode();
    if (inTouchMode) {
        hideSelector();
    }

    final int headerViewsCount = getHeaderViewsCount();
    final int footerViewsStart = mItemCount - getFooterViewsCount();

    int start = 0;
    int count = 0;

    if (down) {//down為true則向下滑動,false則為向上滑動
        int top = -incrementalDeltaY;
        if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
            top += listPadding.top;
        }
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);
            if (child.getBottom() >= top) {//說明child還在屏幕內(nèi)
                break;
            } else {//說明child已經(jīng)挪到了屏幕外
                count++;
                int position = firstPosition + i;
                if (position >= headerViewsCount && position < footerViewsStart) {
                    // The view will be rebound to new data, clear any
                    // system-managed transient state.
                    //將item重置
                    child.clearAccessibilityFocus();
                    //將child放進(jìn)RecycleBin的回收池內(nèi)
                    mRecycler.addScrapView(child, position);
                }
            }
        }
    } else {//邏輯和向下移動一樣只不過是向上移動
        int bottom = getHeight() - incrementalDeltaY;
        if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
            bottom -= listPadding.bottom;
        }
        for (int i = childCount - 1; i >= 0; i--) {
            final View child = getChildAt(i);
            if (child.getTop() <= bottom) {
                break;
            } else {
                start = i;
                count++;
                int position = firstPosition + i;
                if (position >= headerViewsCount && position < footerViewsStart) {
                    // The view will be rebound to new data, clear any
                    // system-managed transient state.
                    child.clearAccessibilityFocus();
                    mRecycler.addScrapView(child, position);
                }
            }
        }
    }

    mMotionViewNewTop = mMotionViewOriginalTop + deltaY;

    mBlockLayoutRequests = true;

    if (count > 0) {
        //將回收掉的item和listView切斷關(guān)系
        detachViewsFromParent(start, count);
        mRecycler.removeSkippedScrap();
    }

    // invalidate before moving the children to avoid unnecessary invalidate
    // calls to bubble up from the children all the way to the top
    if (!awakenScrollBars()) {
       invalidate();
    }
    //這個方法的作用是讓ListView中所有的子View都按照傳入的參數(shù)值進(jìn)行相應(yīng)的偏移控硼,
    //這樣就實現(xiàn)了隨著手指的拖動泽论,ListView的內(nèi)容也會隨著滾動的效果。
    offsetChildrenTopAndBottom(incrementalDeltaY);

    if (down) {
        mFirstPosition += count;
    }

    final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
    if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
        fillGap(down);
    }

    mRecycler.fullyDetachScrapViews();
    if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
        final int childIndex = mSelectedPosition - mFirstPosition;
        if (childIndex >= 0 && childIndex < getChildCount()) {
            positionSelector(mSelectedPosition, getChildAt(childIndex));
        }
    } else if (mSelectorPosition != INVALID_POSITION) {
        final int childIndex = mSelectorPosition - mFirstPosition;
        if (childIndex >= 0 && childIndex < getChildCount()) {
            positionSelector(INVALID_POSITION, getChildAt(childIndex));
        }
    } else {
        mSelectorRect.setEmpty();
    }

    mBlockLayoutRequests = false;

    invokeOnItemScrollListener();

    return false;
}

onTouchUp()中主要處理了filing狀態(tài),和狀態(tài)重置

而filing狀態(tài)的主要代碼是在FlingRunnable中我們來看看FlingRunnable的start()方法.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末卡乾,一起剝皮案震驚了整個濱河市翼悴,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌说订,老刑警劉巖抄瓦,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件潮瓶,死亡現(xiàn)場離奇詭異陶冷,居然都是意外死亡钙姊,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門埂伦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來煞额,“玉大人,你說我怎么就攤上這事沾谜〔不伲” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵基跑,是天一觀的道長婚温。 經(jīng)常有香客問我,道長媳否,這世上最難降的妖魔是什么栅螟? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮篱竭,結(jié)果婚禮上力图,老公的妹妹穿的比我還像新娘。我一直安慰自己掺逼,他們只是感情好吃媒,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著吕喘,像睡著了一般赘那。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上氯质,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天漓概,我揣著相機與錄音,去河邊找鬼病梢。 笑死胃珍,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蜓陌。 我是一名探鬼主播觅彰,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼钮热!你這毒婦竟也來了填抬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤隧期,失蹤者是張志新(化名)和其女友劉穎飒责,沒想到半個月后赘娄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡宏蛉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年遣臼,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拾并。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡揍堰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嗅义,到底是詐尸還是另有隱情屏歹,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布之碗,位于F島的核電站蝙眶,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏褪那。R本人自食惡果不足惜幽纷,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望武通。 院中可真熱鬧霹崎,春花似錦、人聲如沸冶忱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽囚枪。三九已至派诬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間链沼,已是汗流浹背默赂。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留括勺,地道東北人缆八。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像疾捍,于是被迫代替她去往敵國和親奈辰。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

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