2013年3月13日水曜日

ViewPager(PagerTabStrip)でFragment内のGridViewの表示が更新されない件

Google PlayでActionBarの直下にテキストとインジケーターがセットとなった
タブ(画像の赤枠内)を見た事があると思う。これはPagerTabStripという
コンポーネントで、Android Support Library v4にも含まれている。


何となく格好良かったので自アプリに適用しようとした所、Fragment内のGridViewが
更新されない問題が発生したので、その問題点と対処法を以下にメモしておく。
(問題点を簡単にするため、処理手順等を簡略化してある。)

【ViewPager配下の階層構造】
ViewPager配下の階層構造を以下に示す。

ViewPager (PagerTabStrip)
┗ FragmentPagerAdapter
  ┗ Fragment
     ┗ GridView
       ┗ ArrayAdapter

【処理手順】
(1)履歴タスクを取得してArrayListにアイコンとアプリ名が含まれるデータを格納する。
(2)FragmentPagerAdapterのgetItem()で手順(1)のArrayListを渡してFragmentを生成する。
(3)手順(2)で生成したFragmentのonCreateView()でGridViewをinflaterする。
(4)GridViewに手順(1)で取得したArrayListをArrayAdapterに格納する。
(5)GridViewのアイテムにクリックリスナーを設定し、クリックしたらアイコンの透過度を下げる(表示を変更する)。
(6)ViewPagerの全4ページに手順(2)~(5)を行う。
(7)画面1のGridViewアイテムをクリックしてアイコンの透過度を下げる。
(8)スワイプして隣の画面2に移動する。

【問題点】
処理手順(8)において、参照元が同じArrayListなのだから、各画面のGridViewの
アイテムにも適用されているはずだと思っていた。しかし隣の画面に表示を切り替えても
GridViewのアイテムが古い表示のままだった。

例えば画面1→画面2→画面3→画面4と切り替えたとする。
画面1でGridViewのアイテムをクリックすると即時表示が更新される。
スワイプして画面2に切り換えると表示が更新されず古いままとなっている。
更にスワイプして画面3に切り換えると表示が更新されている。
また更にスワイプして画面4に切り替えても表示が更新されている。
ここで一旦スワイプして画面2に戻ると何故か表示が更新されていた。

つまり「最初の画面で表示を更新した後、スワイプして隣の画面に移動しても表示が更新されていない」のである。

【対処方法】
色々とググって調べてみたら下記にヒントがあった。

■ Update data in ListFragment as part of ViewPager
http://stackoverflow.com/questions/7379165/update-data-in-listfragment-as-part-of-viewpager

スワイプした移動先の画面のFragmentをタグを指定して取得した後、
ArrayAdapterの更新を行えばいいようだ。以下はFragmentActivity内で
ViewPager#setOnPageChangeListenerを実装した例である。
ViewPager viewPager = (ViewPager)findViewById(R.id.viewpager);
viewPager.setOnPageChangeListener(new OnPageChangeListener() {
 
    @Override
    public void onPageSelected(int position) {
        // 内部でタグは以下のように定義されているらしい
        String tag = "android:switcher:"+ R.id.viewpager + ":" + String.valueOf(position);
        Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);

        if(fragment != null && fragment instanceof TestFragment){
            TestFragment testFragment = (TestFragment)fragment;

            // onDestroyView()が呼ばれていた場合はスキップ
            if(testFragment != null && testFragment.getView() != null){
                // GridViewの更新処理
                testFragment.updateView();
            }
        }
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
    }

    @Override
    public void onPageScrollStateChanged(int state) {
    }

});

以上の対策で表示が即時更新されるようになった。
今回はGridViewを例にしてみたが、ListViewや他のViewでも同様の現象が
発生すると思うので注意が必要。

2 件のコメント:

  1. getItemでArrayListをわたすところ、どのように実装していますか?

    返信削除
  2. FragmentPagerAdapter#getItemは大体こんな感じです。pageListにページ情報クラスPageDataを追加するのが前提となっています。またPageData内部には履歴タスク情報のArrayListを持っています。

    private ArrayList pageList = new ArrayList();

    public void addData(PageData pageData) {
    pageList.add(pageData);
    }

    @Override
    public Fragment getItem(int position) {
    // ページリストのpositionのページ情報取得
    PageData pageData = pageList.get(position);
    // ページ情報に格納してある履歴タスク情報のArrayListを取得
    ArrayList appList = pageData.getAppList();
    // Fragment生成時に履歴タスク情報のArrayListを引数で渡す
    Fragment fragment = TaskPageFragment.newInstance(appList);

    return fragment;
    }

    返信削除