ViewPagerデータ修正notifyDataSetChangedを使用してリフレッシュの問題がない

10323 ワード

最近viewpagerを使用しているときに問題が発生しました.viewpagerがpagerAdapterを設定した後、viewpagerのデータを変更する必要がある場合はpagerAdapterを使用します.notifyDataSetChangedメソッドは完全に有効ではないようです.たとえば、初めてviewpagerを設定したデータを2ページにして1ページに減らすと、2ページ目はまだめくれているのに2ページ目にとどまることができない現象が発生します.次に、ソースコードを読むことで原因と解決策を探します.
一、ViewPagerとPagerAdapterの概要
APIのViewPagerについては、ユーザーがページを左右にスライドさせるレイアウトマネージャで、PagerAdapterを実現することで、表示するページを生成することができると説明しています.
ではまず、APIでのPagerAdapterの紹介を見てみましょう.PagerAdapterは、複数のページをViewPagerに埋め込むために使用されますが、実際には、PagerAdapterをより具体的に実現したアダプタを使用する傾向があります.PagerAdapterを実装するには、少なくとも次の方法を書き換える必要があります.
  • instantiateItem(ViewGroup, int)
  • destroyItem(ViewGroup, int, Object)
  • getCount()
  • isViewFromObject(View, Object)

  • PagerAdapterは多くのAdapterViewのアダプタよりも一般的です.ビュー回収メカニズムを直接使用するのではなく、コールバックを使用して更新の操作手順を表示します.必要に応じて、PagerAdapterはビューの回収方法を実装したり、ページがfragmentを使用して自分のトランザクションを管理するなど、より巧みな方法でページビューを管理したりすることもできます.
    ViewPagerは、ページを直接操作するのではなく、各ページをkeyに関連付けます.このkeyは、指定されたページを追跡し、adapterとは独立して一意に識別するために使用されます.PagerAdapterを呼び出すstartUpdate(ViewGroup)メソッドは、ViewPagerのコンテンツが変化することを示します.次に、instantiateItem(ViewGroup,int)メソッドおよび/またはdestroyItem(ViewGroup,int,Object)メソッドが1回または複数回呼び出される.最後にfinishUpdate(ViewGroup)を呼び出し、このリフレッシュが完了したことを示します.finishUpdate(View Group)の実行が完了すると、instantiateItem(View Group,int)が返すkeyに関連付けられたビューが、親View Groupに追加される.一方、destroyItem(ViewGroup,int,Object)に渡されたkeyに関連付けられたビューは、親ViewGroupによって削除されます.isView FromObject(View,Object)メソッドは、ビューが指定したkeyに関連付けられているかどうかを判断するために使用されます.
    簡単なPagerAdapterでは、ページビュー自体をキーとして選択し、ビューを作成して親Viewグループに追加した後、instantiateItem(Viewグループ、int)を介して返すことができます.これに対応して、destroyItem(View Group,int,Object)のインプリメンテーションは、親View Groupからビューを削除し、isView FromObject(View,Object)はreturnview==objectとしてインプリメントされます.
    PagerAdapterはデータセットの変更をサポートします.データセットの変更はメインスレッドで行われ、BaseAdapterから継承されたAdapterViewのアダプタと同様にnotifyDataSetChanged()メソッドを呼び出して終了する必要があります.ページの追加、削除、および位置の変更に関連するデータセットの変更.Viewpagerは、アダプタでgetItemPosition(Object)メソッドを実装することで、現在のページを実行したままにします.
    二、ViewAdapterのnotifyDataSetChanged()方法
    では、なぜデータセットがnotifyDataSetChanged()メソッドを呼び出してリフレッシュを変更するのは使いにくいのでしょうか.まずはこの言葉から.
    adapter.notifyDataSetChanged();

    このメソッドは、抽象ベースクラスPagerAdapterのnotifyDataSetChanged()メソッドを呼び出します.
        public void notifyDataSetChanged() {
            this.mObservable.notifyChanged();
        }

    mObservableはDataSetObservableオブジェクトで、メソッドをフォローし、DataSetObservableクラスに入ります.DataSetObservableオブジェクトは、リスト内のすべてのDataSetObserverオブジェクトに指示を送信する責任を負います.notifyChanged()では,データセットが変化すると,各ObserverのonChange()メソッドが呼び出される.
    public void notifyChanged() {
            synchronized(mObservers) {
                for (int i = mObservers.size() - 1; i >= 0; i--) {
                    mObservers.get(i).onChanged();
                }
            }
        }

    ここで、Observableはオブザーバーモードの応用であり、Observableクラスは汎用抽象クラスであり、オブザーバーオブジェクトを表し、オブザーバー登録の取り消し、登録のクリアの3つの方法を提供する.DataSetObservableはObservableを直接継承し、DataSetObserverを使用してObservableをインスタンス化します.
    DataSetObserverは、1つのデータセットオブジェクトのオブザーバを表し、データセットの変化または失効の2つのコールバックを受信します.
    public abstract class DataSetObserver {
            public void onChanged() {
            // Do nothing
            }
            public void onInvalidated() {
            // Do nothing
            }
        }

    ViewPagerでPagerObserverを発見し,DataSetObserver抽象クラスを継承し,onChange()とonInvalidated()の2つの方法を実現した.
    private class PagerObserver extends DataSetObserver {
            private PagerObserver() {
            }
    
            public void onChanged() {
                ViewPager.this.dataSetChanged();
            }
    
            public void onInvalidated() {
                ViewPager.this.dataSetChanged();
            }
        }

    次に、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 (mCurItem == ii.position) {
                        // Keep the current item in the valid range
                        newCurrItem = Math.max(0, Math.min(mCurItem, adapterCount - 1));
                        needPopulate = true;
                    }
                    continue;
                }
    
                if (ii.position != newPos) {
                    if (ii.position == mCurItem) {
                        // Our current item changed position. Follow it.
                        newCurrItem = newPos;
                    }
    
                    ii.position = newPos;
                    needPopulate = true;
                }
            }
    
            if (isUpdating) {
                mAdapter.finishUpdate(this);
            }
    
            Collections.sort(mItems, COMPARATOR);
    
            if (needPopulate) {
                // Reset our known page widths; populate will recompute them.
                final int childCount = getChildCount();
                for (int i = 0; i < childCount; i++) {
                    final View child = getChildAt(i);
                    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                    if (!lp.isDecor) {
                        lp.widthFactor = 0.f;
                    }
                }
    
                setCurrentItemInternal(newCurrItem, false, true);
                requestLayout();
            }
        }
    

    ポイントはmAdapterです.getItemPosition(Object)の返される結果.APIでは、プライマリビューがitemの位置が変化しているかどうかを判断しようとしたときに呼び出される方法について説明する.POSITION_に戻るUNCHANGEDは、与えられたitem位置に変化がないことを示し、POSITION_に戻るNONEは与えられたitemが既に存在しないことを示す.
    APIのデフォルトのインプリメンテーションはPOSITION_を返すことですUNCHANGEDは、itemの位置が永遠に変化しないと仮定します.これは、データを削除または変更するときに、PagerAdapterがnotifyDataSetChangedだけでリフレッシュを実現できない理由を説明することができます.
     public int getItemPosition(Object object) {
            return POSITION_UNCHANGED;
        }

    三、解決
    比較的簡単な方法は、AdapterでgetItemPosition()メソッドを書き換え、viewpagerにすべてのitemを再描画させることです.
     @Override  
        public int getItemPosition(Object object) {  
            return POSITION_NONE;  
        }

    ビューが複雑な場合、このように書くとオーバーヘッドが増加する可能性があり、getItemPosition()メソッドを自分のニーズに合わせて実現することができます.例えば、特定のviewを更新するだけで、そのviewにtagを設定し、更新が必要な場合はtagを介してそのviewに位置決めして更新することができる.