【Android】從源碼分析PagerAdapter/FragmentPagerAdapter調用notifydataSetChanged()重新整理的原理

來源:互聯網
上載者:User

標籤:android   源碼   

相信用過viewpager的同學都會遇到調用notifydataSetChanged()後不重新整理或者不符合預期的問題,今天就來分析分析這裡的來龍去脈。這一切還得從viewpager的setAdapter說起:

    /**     * Set a PagerAdapter that will supply views for this pager as needed.     *     * @param adapter Adapter to use     */    public void setAdapter(PagerAdapter adapter) {        ...(省略若干行,下同)        ...        final PagerAdapter oldAdapter = mAdapter;        mAdapter = adapter;        mExpectedAdapterCount = 0;        if (mAdapter != null) {            if (mObserver == null) {                mObserver = new PagerObserver();            }            mAdapter.registerDataSetObserver(mObserver);            ...            ...        }        ...    }

mAdapter.registerDataSetObserver(mObserver)這裡用到了觀察者模式,mObserver是PagerObserver的一個執行個體,而PagerObserver是ViewPager的一個內部類,其聲明如下:

    private class PagerObserver extends DataSetObserver {        @Override        public void onChanged() {        //這裡調用了viewpager的dataSetChanged()方法,下同            dataSetChanged();        }        @Override        public void onInvalidated() {            dataSetChanged();        }    }

所以當mAdapter資料有變化調用notifydatasetchanged()重新整理時就會調用到PagerObserver 的onChanged方法,繼而調用到了viewpager的dataSetChanged()方法:

void dataSetChanged() {        // This method only gets called if our observer is attached, so mAdapter is non-null.        final int adapterCount = mAdapter.getCount();        mExpectedAdapterCount = adapterCount;        boolean needPopulate = mItems.size() < mOffscreenPageLimit * 2 + 1 &&                mItems.size() < adapterCount;        int newCurrItem = mCurItem;        boolean isUpdating = false;        for (int i = 0; i < mItems.size(); i++) {            final ItemInfo ii = mItems.get(i);            final int newPos = mAdapter.getItemPosition(ii.object);            if (newPos == PagerAdapter.POSITION_UNCHANGED) {                continue;            }            if (newPos == PagerAdapter.POSITION_NONE) {                mItems.remove(i);                i--;                if (!isUpdating) {                    mAdapter.startUpdate(this);                    isUpdating = true;                }                mAdapter.destroyItem(this, ii.position, ii.object);                needPopulate = true;                ...    }    ...    ...        }...        if (needPopulate) {            ...            ...            setCurrentItemInternal(newCurrItem, false, true);            requestLayout();        }    }

這裡看到,調用mAdapter.getItemPosition,如果返回的值是POSITION_UNCHANGED(PagerAdapter的預設實現),則needPopulate為false,就不會調用到setCurrentItemInternal(裡面間接調用到instantiateItem(),後面講到),所以就不會重新整理視圖。反之,如果返回值是POSITION_NONE,則needPopulate為true,就會調用到setCurrentItemInternal:

   void setCurrentItemInternal(int item, boolean smoothScroll, boolean always, int velocity) {        ...            populate(item);        ...    }

而populate(item)裡面會調用到addNewItem:

    ItemInfo addNewItem(int position, int index) {        ItemInfo ii = new ItemInfo();        ii.position = position;        ii.object = mAdapter.instantiateItem(this, position);        ii.widthFactor = mAdapter.getPageWidth(position);        if (index < 0 || index >= mItems.size()) {            mItems.add(ii);        } else {            mItems.add(index, ii);        }        return ii;    }


這裡看到調用到了mAdapter.instantiateItem(this, position);而PagerAdapter的instantiateItem裡什麼都沒做。

所以如果我們用的是PagerAdapter,我們需要複寫instantiateitem,例如我們可以這麼寫:

@Overridepublic Object instantiateItem(ViewGroup view, int position) {     view.addView(mList.get(position));     return mList.get(position);}

而對於FragmentPagerAdapter,它複寫了instantiateitem:

   @Override    public Object instantiateItem(ViewGroup container, int position) {        if (mCurTransaction == null) {            mCurTransaction = mFragmentManager.beginTransaction();        }        final long itemId = getItemId(position);        // Do we already have this fragment?        String name = makeFragmentName(container.getId(), itemId);        Fragment fragment = mFragmentManager.findFragmentByTag(name);        if (fragment != null) {            if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);            mCurTransaction.attach(fragment);        } else {            fragment = getItem(position);            if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);            mCurTransaction.add(container.getId(), fragment,                    makeFragmentName(container.getId(), itemId));        }        if (fragment != mCurrentPrimaryItem) {            fragment.setMenuVisibility(false);            fragment.setUserVisibleHint(false);        }        return fragment;    }

其中,getItemId的預設實現:

    public long getItemId(int position) {        return position;    }
也就是item對應的下標就是item的id。另外makeFragmentName的實現:

    private static String makeFragmentName(int viewId, long id) {        return "android:switcher:" + viewId + ":" + id;    }
這裡makeFragmentName相當於為這個fragment組裝成一個標識Tag,如果之前沒添加過這個fragment,也就是mFragmentManager.findFragmentByTag(name)返回null,那麼就調用 getItem(position);擷取fragment,然後把這個fragment添加進去。

反之,也就是之前已經添加過這個fragment了,則不會調用 getItem(position)了,而是直接attach上這個fragment。


參考

http://www.cnblogs.com/dancefire/archive/2013/01/02/why-notifydatasetchanged-does-not-work.html









【Android】從源碼分析PagerAdapter/FragmentPagerAdapter調用notifydataSetChanged()重新整理的原理

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.