ViewPager의 일부로 ListFragment의 데이터 업데이트


142

Android에서 v4 호환성 ViewPager를 사용하고 있습니다. 내 FragmentActivity에는 ViewPager의 다른 페이지에 다른 방식으로 표시되는 많은 데이터가 있습니다. 지금까지는 동일한 ListFragment의 인스턴스가 3 개 있지만 앞으로는 다른 ListFragments의 인스턴스가 3 개 있습니다. ViewPager는 수직 전화기 화면에 있으며 목록은 나란히 없습니다.

이제 ListFragment의 버튼은 FragmentActivity를 통해 별도의 전체 페이지 활동을 시작합니다. FragmentActivity는 데이터를 반환하고 FragmentActivity가 데이터를 수정하고 저장 한 다음 ViewPager의 모든보기를 업데이트하려고 시도합니다. 내가 붙어있는 곳입니다.

public class ProgressMainActivity extends FragmentActivity
{
    MyAdapter mAdapter;
    ViewPager mPager;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
    ...
        mAdapter = new MyAdapter(getSupportFragmentManager());

        mPager = (ViewPager) findViewById(R.id.viewpager);
        mPager.setAdapter(mAdapter);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data)
    {
        ...
        updateFragments();
        ...
    }
    public void updateFragments()
    {
        //Attempt 1:
        //mAdapter.notifyDataSetChanged();
        //mPager.setAdapter(mAdapter);

        //Attempt 2:
        //HomeListFragment fragment = (HomeListFragment) getSupportFragmentManager().findFragmentById(mAdapter.fragId[0]);
        //fragment.updateDisplay();
    }

    public static class MyAdapter extends FragmentPagerAdapter implements
         TitleProvider
    {
      int[] fragId = {0,0,0,0,0};
      public MyAdapter(FragmentManager fm)
      {
         super(fm);
      }
      @Override
      public String getTitle(int position){
         return titles[position];
      }
      @Override
      public int getCount(){
         return titles.length;
      }

      @Override
      public Fragment getItem(int position)
      {

         Fragment frag = HomeListFragment.newInstance(position);
         //Attempt 2:
         //fragId[position] = frag.getId();
         return frag;
      }

      @Override
      public int getItemPosition(Object object) {
         return POSITION_NONE; //To make notifyDataSetChanged() do something
     }
   }

    public class HomeListFragment extends ListFragment
    {
    ...
        public static HomeListFragment newInstance(int num)
        {
            HomeListFragment f = new HomeListFragment();
            ...
            return f;
        }
   ...

보시다시피, 첫 번째 시도는 전체 FragmentPagerAdapter에 대해 DataSetChanged를 알리는 것이 었으며 때때로 데이터를 업데이트하는 것으로 나타 났지만 다른 경우에는 IllegalStateException이 발생했습니다. onSaveInstanceState 후에이 작업을 수행 할 수 없습니다.

두 번째 시도는 내 ListFragment에서 업데이트 함수를 호출하려고 시도했지만 getItem의 getId는 0을 반환했습니다.

findFragmentById () 또는 findFragmentByTag ()를 사용하여 FragmentManager에서 Fragment에 대한 참조 확보

하지만 내 조각의 태그 또는 ID를 모르겠습니다! ListFragment 레이아웃에서 ViewPager 용 android : id = "@ + id / viewpager"및 ListView 용 android : id = "@ android : id / list"가 있지만 유용하다고 생각하지 않습니다.

그래서, 어떻게 할 수 있습니까? a) 전체 ViewPager를 한 번에 안전하게 업데이트하십시오 (이상적으로 사용자를 이전에 있던 페이지로 되돌아가는 것)-사용자가보기 변경 사항을 볼 수 있습니다. 또는 b) 영향을받는 각 ListFragment에서 함수를 호출하여 ListView를 수동으로 업데이트하십시오.

도움을 주시면 감사하겠습니다.

답변:


58

Fragement가 인스턴스화 될 때마다 태그를 기록하십시오.

public class MPagerAdapter extends FragmentPagerAdapter {
    private Map<Integer, String> mFragmentTags;
    private FragmentManager mFragmentManager;

    public MPagerAdapter(FragmentManager fm) {
        super(fm);
        mFragmentManager = fm;
        mFragmentTags = new HashMap<Integer, String>();
    }

    @Override
    public int getCount() {
        return 10;
    }

    @Override
    public Fragment getItem(int position) {
        return Fragment.instantiate(mContext, AFragment.class.getName(), null);
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Object obj = super.instantiateItem(container, position);
        if (obj instanceof Fragment) {
            // record the fragment tag here.
            Fragment f = (Fragment) obj;
            String tag = f.getTag();
            mFragmentTags.put(position, tag);
        }
        return obj;
    }

    public Fragment getFragment(int position) {
        String tag = mFragmentTags.get(position);
        if (tag == null)
            return null;
        return mFragmentManager.findFragmentByTag(tag);
    }
}

이것은 괜찮아 보이지만 내 수업에서는 작동하지 않습니다. 당신과 함께 작동합니까? 나는 여전히 null조각을 얻는다 .
sandalone

1
그것은 나를 위해 작동합니다, 나는 여러 프로젝트에서 사용했습니다. FragmentPagerAdapter의 소스 코드를 읽고 일부 로그를 인쇄하여 디버그 할 수 있습니다.
faylon

화면을 회전 한 후에도 감사처럼 작동합니다.이 솔루션을 사용하여 FragmentB의 MyActivity를 사용하여 FragmentA의 내용을 변경할 수있었습니다.
Mehdi Fanai

5
그것은 작동 FragmentPagerAdaper하지만 실패 FragmentStatePagerAdaper( Fragment.getTag()항상 돌아갑니다 null.
엔진 바이

1
이 솔루션을 시도하고 조각 데이터를 업데이트하는 방법을 만들었습니다. 그러나 지금 나는 조각의 견해를 볼 수 없습니다. 데이터가 조각화되는 것을 볼 수 있지만 뷰는 보이지 않습니다. 어떤 도움?
Arun Badole

256

Barkside의 답변은 작동 FragmentPagerAdapter하지만 작동 하지 않습니다 . FragmentStatePagerAdapter전달되는 조각에 태그를 설정하지 않기 때문 FragmentManager입니다.

FragmentStatePagerAdapter보인다 우리는 그것의 사용에 의해 얻을 수있는 instantiateItem(ViewGroup container, int position)전화를. position의 조각에 대한 참조를 반환합니다 position. FragmentStatePagerAdapter문제의 조각에 대한 참조를 이미 보유하고 있다면 해당 조각에 대한 instantiateItem참조를 반환 getItem()하고 다시 인스턴스화하기 위해 호출하지 않습니다 .

따라서 현재 조각 # 50을보고 조각 # 49에 액세스하려고한다고 가정합니다. 그들이 가까이 있기 때문에 # 49가 이미 인스턴스화 될 가능성이 큽니다. 그래서,

ViewPager pager = findViewById(R.id.viewpager);
FragmentStatePagerAdapter a = (FragmentStatePagerAdapter) pager.getAdapter();
MyFragment f49 = (MyFragment) a.instantiateItem(pager, 49)

5
"a"를 PagerAdapter (PagerAdapter a = pager.getAdapter ();)로 만들어서 단순화합니다. 또는 심지어 MyFragment f49 = (MyFragment) pager.getAdapter (). instantiateItem (pager, 49); 당신이 instantiateItem ()를 호출하는 데 캐스트 할 필요가 없습니다 instantiateItem ()는 PagerAdapter에서입니다
Dandre 앨리슨

12
... 누군가 궁금해하는 경우를 대비하여 ViewPager는 위의 예제에서 49 인 표시된 조각 (ViewPager.getCurrentItem ())의 색인을 추적합니다 ...하지만 나는 ViewPager API가 실제 프래그먼트에 대한 참조를 직접 반환하지 않는다는 것이 놀랍습니다.
mblackwell8

6
FragmentStatePagerAdapter와 위의 코드 사용하여 a.instantiateItem(viewPager, viewPager.getCurrentItem());완벽하게 작동 @ mblackwell8 제안한 (49 제거하기 위해).
Cedric Simon

1
잠깐, 조각의 뷰에 작업을 수행 할 수 있도록 ViewPager를 포함하는 모든 조각에 전달해야합니까? 현재 현재 페이지에서 onCreate에서 수행하는 모든 작업은 다음 페이지에서 발생합니다. 누출이 심하게 그늘진 것처럼 들립니다.
G_V

호출 instantiateItemstartUpdate/ finishUpdate호출 로 둘러 싸야한다는 점을 언급하는 것이 중요 합니다. 세부 사항은 비슷한 질문에 대한 내 대답에 있습니다 : stackoverflow.com/questions/14035090/…
morgwai

154

좋아, 나는 내 자신의 질문에 요청을 수행하는 방법을 찾았다 고 생각한다. 그래서 나는 다른 사람들의 이익을 위해 나눌 것이다. ViewPager 내부 단편의 태그 형태이고 "android:switcher:VIEWPAGER_ID:INDEX", VIEWPAGER_ID는 IS R.id.viewpagerXML 레이아웃 및 INDEX는 viewpager의 위치이다. 따라서 위치가 알려진 경우 (예 : 0) updateFragments()다음을 수행 할 수 있습니다 .

      HomeListFragment fragment = 
          (HomeListFragment) getSupportFragmentManager().findFragmentByTag(
                       "android:switcher:"+R.id.viewpager+":0");
      if(fragment != null)  // could be null if not instantiated yet
      {
         if(fragment.getView() != null) 
         {
            // no need to call if fragment's onDestroyView() 
            //has since been called.
            fragment.updateDisplay(); // do what updates are required
         }
      }

이것이 유효한 방법인지는 모르겠지만 더 나은 것이 제안 될 때까지 할 것입니다.


4
귀하의 답변과 질문을 +1하기 위해 로그인했습니다.
SuitUp

5
이것은 공식 문서에 없기 때문에 사용하지 않을 것입니다. 향후 릴리스에서 태그를 변경하면 어떻게됩니까?
Thierry-Dimitri Roy

4
FragmentPagerAdapter에는 makeFragmentName태그를 생성하는 데 사용되는 정적 메소드가 포함되어 있으며,이를 통해 약간 덜 HomeListFragment fragment = (HomeListFragment) getSupportFragmentManager().findFragmentByTag(FragmentPagerAdapter.makeFragmentName(R.id.viewpager, 0));
해킹

8
@dgmltn makeFragmentNameprivate static메소드이므로 액세스 할 수 없습니다.
StenaviN 2016 년

1
예수님의 달콤한 어머니가 작동합니다! 그냥 손가락을 교차 시켜서 사용하겠습니다.
Bogdan Alexandru

35

당신이 저에게 묻는다면, 모든 "활성"조각 페이지를 추적하는 아래 페이지의 두 번째 해결책이 더 좋습니다. http://tamsler.blogspot.nl/2011/11/android-viewpager-and-fragments-part -ii.html

barkside의 대답은 너무 해키입니다.

모든 "활성"조각 페이지를 추적합니다. 이 경우 ViewPager에서 사용되는 FragmentStatePagerAdapter에서 조각 페이지를 추적합니다.

private final SparseArray<Fragment> mPageReferences = new SparseArray<Fragment>();

public Fragment getItem(int index) {
    Fragment myFragment = MyFragment.newInstance();
    mPageReferences.put(index, myFragment);
    return myFragment;
}

"비활성"프래그먼트 페이지에 대한 참조를 유지하지 않으려면 FragmentStatePagerAdapter의 destroyItem (...) 메소드를 구현해야합니다.

public void destroyItem(View container, int position, Object object) {
    super.destroyItem(container, position, object);
    mPageReferences.remove(position);
}

... 현재 표시된 페이지에 액세스해야하는 경우 다음을 호출하십시오.

int index = mViewPager.getCurrentItem();
MyAdapter adapter = ((MyAdapter)mViewPager.getAdapter());
MyFragment fragment = adapter.getFragment(index);

... MyAdapter의 getFragment (int) 메소드는 다음과 같습니다.

public MyFragment getFragment(int key) {
    return mPageReferences.get(key);
}

"


링크의 @Frank 두 번째 솔루션은 간단합니다 ...! 감사합니다 .. :)
TheFlash

3
맵 대신
SparseArray


내 대답을 업데이트하여 SparseArray를 사용합니다. <11을 대상으로하는 경우 버전 11에서 업데이트 된 클래스 때문에 SparseArrayCompat를 대신 사용하십시오.
Frank

2
수명주기 이벤트가 중단됩니다. 참조 stackoverflow.com/a/24662049/236743
도리

13

좋아, 위의 @barkside에 의해 메소드를 테스트 한 후 내 응용 프로그램에서 작동하지 못했습니다. 그런 다음 IOSched2012 앱도 사용 한다는 것을 기억 viewpager했으며 그 곳에서 솔루션을 찾았습니다. viewpager쉽게 액세스 할 수있는 방식으로 저장되지 않으므로 조각 ID 또는 태그를 사용하지 않습니다 .

다음 은 IOSched 앱인 HomeActivity 의 중요한 부분 입니다. 주석에 특히주의 하십시오.

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    // Since the pager fragments don't have known tags or IDs, the only way to persist the
    // reference is to use putFragment/getFragment. Remember, we're not persisting the exact
    // Fragment instance. This mechanism simply gives us a way to persist access to the
    // 'current' fragment instance for the given fragment (which changes across orientation
    // changes).
    //
    // The outcome of all this is that the "Refresh" menu button refreshes the stream across
    // orientation changes.
    if (mSocialStreamFragment != null) {
        getSupportFragmentManager().putFragment(outState, "stream_fragment",
                mSocialStreamFragment);
    }
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    if (mSocialStreamFragment == null) {
        mSocialStreamFragment = (SocialStreamFragment) getSupportFragmentManager()
                .getFragment(savedInstanceState, "stream_fragment");
    }
}

그리고 당신의 인스턴스를 다음 과 같이 저장 Fragments하십시오 FragmentPagerAdapter:

    private class HomePagerAdapter extends FragmentPagerAdapter {
    public HomePagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        switch (position) {
            case 0:
                return (mMyScheduleFragment = new MyScheduleFragment());

            case 1:
                return (mExploreFragment = new ExploreFragment());

            case 2:
                return (mSocialStreamFragment = new SocialStreamFragment());
        }
        return null;
    }

또한 다음 Fragment과 같이 전화 를 지키십시오 .

    if (mSocialStreamFragment != null) {
        mSocialStreamFragment.refresh();
    }

Ryan, 이것은 ViewPager가 프래그먼트 인스턴스를 '유지'하고 액세스 가능한 공용 변수로 설정하는 데 사용하는 기능을 무시하고 있다고 말하는 것입니까? 복원 인스턴스에서 if (mSocialStreamFragment == null) {?
토마스 Clowes

1
이것은 좋은 답변이며 io12 앱의 경우 +1입니다 getItem(). 라이프 사이클 변경으로 인해 부모가 다시 작성된 후에 호출되지 않기 때문에 라이프 사이클 변경에서 작동하는지 확실 하지 않습니다. stackoverflow.com/a/24662049/236743을 참조하십시오 . 이 예에서 이것은 하나의 프래그먼트에 대해 부분적으로 보호되지만 다른 프래그먼트에서는 그렇지 않습니다
Dori

2

FragmentPagerAdapter소스 코드를 복사 및 수정하고 getTag()메소드를 추가 할 수 있습니다

예를 들어

public abstract class AppFragmentPagerAdapter extends PagerAdapter {
private static final String TAG = "FragmentPagerAdapter";
private static final boolean DEBUG = false;

private final FragmentManager mFragmentManager;
private FragmentTransaction mCurTransaction = null;
private Fragment mCurrentPrimaryItem = null;

public AppFragmentPagerAdapter(FragmentManager fm) {
    mFragmentManager = fm;
}


public abstract Fragment getItem(int position);

@Override
public void startUpdate(ViewGroup container) {
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }

    final long itemId = getItemId(position);


    String name = getTag(position);
    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,
                getTag(position));
    }
    if (fragment != mCurrentPrimaryItem) {
        fragment.setMenuVisibility(false);
        fragment.setUserVisibleHint(false);
    }

    return fragment;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
            + " v=" + ((Fragment) object).getView());
    mCurTransaction.detach((Fragment) object);
}

@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
    Fragment fragment = (Fragment) object;
    if (fragment != mCurrentPrimaryItem) {
        if (mCurrentPrimaryItem != null) {
            mCurrentPrimaryItem.setMenuVisibility(false);
            mCurrentPrimaryItem.setUserVisibleHint(false);
        }
        if (fragment != null) {
            fragment.setMenuVisibility(true);
            fragment.setUserVisibleHint(true);
        }
        mCurrentPrimaryItem = fragment;
    }
}

@Override
public void finishUpdate(ViewGroup container) {
    if (mCurTransaction != null) {
        mCurTransaction.commitAllowingStateLoss();
        mCurTransaction = null;
        mFragmentManager.executePendingTransactions();
    }
}

@Override
public boolean isViewFromObject(View view, Object object) {
    return ((Fragment) object).getView() == view;
}

@Override
public Parcelable saveState() {
    return null;
}

@Override
public void restoreState(Parcelable state, ClassLoader loader) {
}


public long getItemId(int position) {
    return position;
}

private static String makeFragmentName(int viewId, long id) {
    return "android:switcher:" + viewId + ":" + id;
}

protected abstract String getTag(int position);
}

그런 다음 확장 하고이 추상적 인 방법을 재정의하십시오 .Android 그룹 변경을 두려워 할 필요가 없습니다.

FragmentPageAdapter 미래의 소스 코드

 class TimeLinePagerAdapter extends AppFragmentPagerAdapter {


    List<Fragment> list = new ArrayList<Fragment>();


    public TimeLinePagerAdapter(FragmentManager fm) {
        super(fm);
        list.add(new FriendsTimeLineFragment());
        list.add(new MentionsTimeLineFragment());
        list.add(new CommentsTimeLineFragment());
    }


    public Fragment getItem(int position) {
        return list.get(position);
    }

    @Override
    protected String getTag(int position) {
        List<String> tagList = new ArrayList<String>();
        tagList.add(FriendsTimeLineFragment.class.getName());
        tagList.add(MentionsTimeLineFragment.class.getName());
        tagList.add(CommentsTimeLineFragment.class.getName());
        return tagList.get(position);
    }


    @Override
    public int getCount() {
        return list.size();
    }


}

1

문제없이 작동합니다.

페이지 조각의 레이아웃 어딘가 :

<FrameLayout android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" android:id="@+id/fragment_reference">
     <View android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone"/>
</FrameLayout>

조각의 onCreateView ()에서 :

...
View root = inflater.inflate(R.layout.fragment_page, container, false);
ViewGroup ref = (ViewGroup)root.findViewById(R.id.fragment_reference);
ref.setTag(this);
ref.getChildAt(0).setTag("fragment:" + pageIndex);
return root;

존재하는 경우 ViewPager에서 Fragment를 반환하는 메소드 :

public Fragment getFragment(int pageIndex) {        
        View w = mViewPager.findViewWithTag("fragment:" + pageIndex);
        if (w == null) return null;
        View r = (View) w.getParent();
        return (Fragment) r.getTag();
}

1

또는 다음 과 같이 setPrimaryItem메소드를 대체 할 수 있습니다 FragmentPagerAdapter.

public void setPrimaryItem(ViewGroup container, int position, Object object) { 
    if (mCurrentFragment != object) {
        mCurrentFragment = (Fragment) object; //Keep reference to object
        ((MyInterface)mCurrentFragment).viewDidAppear();//Or call a method on the fragment
    }

    super.setPrimaryItem(container, position, object);
}

public Fragment getCurrentFragment(){
    return mCurrentFragment;
}

0

다른 사람을 도울 수있는 경우를 대비하여 접근 방식을 제공하고 싶습니다.

이것은 내 호출기 어댑터입니다.

 public class CustomPagerAdapter extends FragmentStatePagerAdapter{
    private Fragment[] fragments;

    public CustomPagerAdapter(FragmentManager fm) {
        super(fm);
        fragments = new Fragment[]{
                new FragmentA(),
                new FragmentB()
        };
    }

    @Override
    public Fragment getItem(int arg0) {
        return fragments[arg0];
    }

    @Override
    public int getCount() {
        return fragments.length;
    }

}

내 활동에는 다음이 있습니다.

public class MainActivity {
        private ViewPager view_pager;
        private CustomPagerAdapter adapter;


        @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        adapter = new CustomPagerAdapter(getSupportFragmentManager());
        view_pager = (ViewPager) findViewById(R.id.pager);
        view_pager.setAdapter(adapter);
        view_pager.setOnPageChangeListener(this);
          ...
         }

}

그런 다음 현재 작업을 수행하는 방법은 다음과 같습니다.

int index = view_pager.getCurrentItem();
Fragment currentFragment = adapter.getItem(index);

1
이것은 활동 / 프래그먼트 라이프 사이클 방법 후에 깨질 것입니다
Dori

0

이것은 내 탭을 추적 할 필요가 없으며 어쨌든 새로 고쳐야하기 때문에 내 솔루션입니다.

    Bundle b = new Bundle();
    b.putInt(Constants.SharedPreferenceKeys.NUM_QUERY_DAYS,numQueryDays);
    for(android.support.v4.app.Fragment f:getSupportFragmentManager().getFragments()){
        if(f instanceof HomeTermFragment){
            ((HomeTermFragment) f).restartLoader(b);
        }
    }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.