[android] ViewPager PagerAdapter가 View를 업데이트하지 않음

호환성 라이브러리에서 ViewPager를 사용하고 있습니다. 페이지를 통과 할 수있는 여러보기를 표시하는 데 성공했습니다.

그러나 새로운 View 집합으로 ViewPager를 업데이트하는 방법을 알아내는 데 어려움을 겪고 있습니다.

나는 호출과 같은 모든 종류의 것들을 시도했습니다 mAdapter.notifyDataSetChanged(), mViewPager.invalidate()심지어 새로운 어댑터 나 데이터의 새로운 목록을 사용할 때마다 생성.

아무것도 도움이되지 않았습니다. 텍스트보기는 원본 데이터와 변경되지 않습니다.

업데이트 :
약간의 테스트 프로젝트를 만들었고 뷰를 거의 업데이트 할 수있었습니다. 아래 수업을 붙여 넣을 게요.

그러나 업데이트되지 않는 것은 두 번째보기이며 ‘B’는 남아 있으며 업데이트 버튼을 누른 후 ‘Y’가 표시되어야합니다.

public class ViewPagerBugActivity extends Activity {

    private ViewPager myViewPager;
    private List<String> data;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        data = new ArrayList<String>();
        data.add("A");
        data.add("B");
        data.add("C");

        myViewPager = (ViewPager) findViewById(R.id.my_view_pager);
        myViewPager.setAdapter(new MyViewPagerAdapter(this, data));

        Button updateButton = (Button) findViewById(R.id.update_button);
        updateButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                updateViewPager();
            }
        });
    }

    private void updateViewPager() {
        data.clear();
        data.add("X");
        data.add("Y");
        data.add("Z");
        myViewPager.getAdapter().notifyDataSetChanged();
    }

    private class MyViewPagerAdapter extends PagerAdapter {

        private List<String> data;
        private Context ctx;

        public MyViewPagerAdapter(Context ctx, List<String> data) {
            this.ctx = ctx;
            this.data = data;
        }

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

        @Override
        public Object instantiateItem(View collection, int position) {
            TextView view = new TextView(ctx);
            view.setText(data.get(position));
            ((ViewPager)collection).addView(view);
            return view;
        }

        @Override
        public void destroyItem(View collection, int position, Object view) {
             ((ViewPager) collection).removeView((View) view);
        }

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

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

        @Override
        public void restoreState(Parcelable arg0, ClassLoader arg1) {
        }

        @Override
        public void startUpdate(View arg0) {
        }

        @Override
        public void finishUpdate(View arg0) {
        }
    }
}



답변

이를 달성하는 몇 가지 방법이 있습니다.

첫 번째 옵션은 더 쉽지만 조금 더 비효율적입니다.

getItemPosition다음 PagerAdapter과 같이 재정의 하십시오 .

public int getItemPosition(Object object) {
    return POSITION_NONE;
}

이 방법 notifyDataSetChanged()으로을 호출 하면 뷰 호출기가 모든 뷰를 제거하고 다시로드합니다. 따라서 재 장전 효과가 얻어진다.

Alvaro Luis Bustamante (이전의 alvarolb)제안한 두 번째 옵션 은 새보기를 인스턴스화 할 때 사용 하는 setTag()방법입니다 instantiateItem(). 그런 다음을 사용하는 대신 업데이트 할보기를 찾는 데 notifyDataSetChanged()사용할 수 있습니다 findViewWithTag().

두 번째 방법은 매우 유연하고 성능이 뛰어납니다. 독창적 인 연구를 위해 알바 롤브에게 도움이됩니다.


답변

에 버그가 있다고 생각하지 않습니다 PagerAdapter. 문제는 작동 방식을 이해하는 것이 약간 복잡하다는 것입니다. 여기에 설명 된 솔루션을 보면 오해가있어서 내 관점에서 인스턴스화 된 뷰를 잘못 사용합니다.

지난 몇 일 내가 함께 일하고있다 PagerAdapter그리고 ViewPager, 나는 다음을 발견 :

notifyDataSetChanged()방법 은 기본 페이지가 변경되었음을 PagerAdapter알려줍니다 ViewPager. 예를 들어 페이지를 동적으로 만들거나 삭제 한 경우 (목록에서 항목 추가 또는 제거) 해당 페이지를 처리 ViewPager해야합니다. 이 경우 및 메소드를 ViewPager사용하여 새보기를 삭제하거나 인스턴스화 해야하는지 여부를 결정 한다고 생각합니다 .getItemPosition()getCount()

나는 전화를 ViewPager한 후에 notifyDataSetChanged()아이를보고 그들의 위치를 ​​확인 한다고 생각 합니다 getItemPosition(). 아동이 방법의 반환을 볼 경우 POSITION_NONE는이 ViewPager뷰가 호출, 삭제 된 것을 이해 destroyItem()하고,이 뷰를 제거.

이런 식으로 페이지의 컨텐츠 만 업데이트하려는 경우 getItemPosition()항상 리턴하도록 재정의 하는 것은 POSITION_NONE완전히 잘못된 것입니다. 이전에 작성된보기는 소멸되고 호출 할 때마다 새보기가 작성되기 때문 notifyDatasetChanged()입니다. 몇 TextView초 동안 그렇게 잘못되지 는 않았지만 데이터베이스에서 채워진 ListViews와 같은 복잡한 뷰가있는 경우 실제 문제가 될 수 있으며 리소스 낭비가 될 수 있습니다.

따라서 뷰를 다시 제거하고 인스턴스화하지 않고도 뷰의 컨텐츠를 효율적으로 변경하는 몇 가지 방법이 있습니다. 해결하려는 문제에 따라 다릅니다. 내 접근법은 setTag()메소드의 인스턴스화 된보기에 메소드 를 사용하는 것입니다 instantiateItem(). 따라서 데이터를 변경하거나 필요한 뷰를 무효화하려는 경우의 findViewWithTag()메소드를 호출 ViewPager하여 이전에 인스턴스화 된 뷰를 검색하고 원하는 때마다 새 뷰를 삭제 / 생성 할 필요없이 원하는대로 수정 / 사용할 수 있습니다. 값을 업데이트합니다.

예를 들어 100 페이지에 100 페이지가 TextView있고 주기적으로 하나의 값만 업데이트하려고 한다고 가정하십시오 . 앞에서 설명한 접근 방식을 사용하면 TextView각 업데이트에서 100 초를 제거하고 인스턴스화하고 있음을 의미합니다 . 그것은 이해가되지 않습니다…


답변

변화 FragmentPagerAdapter에를 FragmentStatePagerAdapter.

getItemPosition()메서드를 재정의하고을 반환 POSITION_NONE합니다.

결국에는 notifyDataSetChanged()온뷰 호출기를 듣게됩니다 .


답변

alvarolb 의 대답 은 확실히 최선의 방법입니다. 그의 대답을 바탕으로 이것을 구현하는 쉬운 방법은 위치별로 활성 뷰를 저장하는 것입니다.

SparseArray<View> views = new SparseArray<View>();

@Override
public Object instantiateItem(View container, int position) {
    View root = <build your view here>;
    ((ViewPager) container).addView(root);
    views.put(position, root);
    return root;
}

@Override
public void destroyItem(View collection, int position, Object o) {
    View view = (View)o;
    ((ViewPager) collection).removeView(view);
    views.remove(position);
    view = null;
}

그런 다음 notifyDataSetChanged메소드를 재정 의하여 뷰를 새로 고칠 수 있습니다 …

@Override
public void notifyDataSetChanged() {
    int key = 0;
    for(int i = 0; i < views.size(); i++) {
       key = views.keyAt(i);
       View view = views.get(key);
       <refresh view with new data>
    }
    super.notifyDataSetChanged();
}

당신은 실제로 비슷한 코드를 사용할 수 있습니다 instantiateItemnotifyDataSetChanged보기를 새로 고침 할 수 있습니다. 내 코드에서는 정확히 같은 방법을 사용합니다.


답변

같은 문제가 있었다. 나를 위해 FragmentStatePagerAdapter를 확장하고 아래 메소드를 재정의했습니다.

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

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

}


답변

좌절의 시간이 문제를 극복하기 위해 위의 모든 솔루션을 시도 또한 같은 다른 유사한 문제에 많은 솔루션을하는 동안 후 , 그리고 모두가이 문제를 해결하고 만들기 위해 나와 함께 실패하는 ViewPager기존의 파괴 Fragment와 채우기 pager로 새로운 Fragments. 다음과 같이 문제를 해결했습니다.

1) 만들기 기능 ViewPager확장에 클래스를 FragmentPagerAdapter다음과 같은 :

 public class myPagerAdapter extends FragmentPagerAdapter {

2) 에 대한 항목을 작성 ViewPager가게가 title와를 fragment다음과 같은 :

public class PagerItem {
private String mTitle;
private Fragment mFragment;


public PagerItem(String mTitle, Fragment mFragment) {
    this.mTitle = mTitle;
    this.mFragment = mFragment;
}
public String getTitle() {
    return mTitle;
}
public Fragment getFragment() {
    return mFragment;
}
public void setTitle(String mTitle) {
    this.mTitle = mTitle;
}

public void setFragment(Fragment mFragment) {
    this.mFragment = mFragment;
}

}

3) 인스턴스 생성자 를 다음과 같이 ViewPagerFragmentManager인스턴스에 저장하십시오 class.

private FragmentManager mFragmentManager;
private ArrayList<PagerItem> mPagerItems;

public MyPagerAdapter(FragmentManager fragmentManager, ArrayList<PagerItem> pagerItems) {
    super(fragmentManager);
    mFragmentManager = fragmentManager;
    mPagerItems = pagerItems;
}

4) 다음과 같이 새 목록에서 새 항목을 다시 설정 하기 위해 직접 adapter이전 항목 fragment을 모두 삭제하여 새 데이터로 데이터 를 재설정하는 방법을 작성하십시오 .fragmentManageradapterfragment

public void setPagerItems(ArrayList<PagerItem> pagerItems) {
    if (mPagerItems != null)
        for (int i = 0; i < mPagerItems.size(); i++) {
            mFragmentManager.beginTransaction().remove(mPagerItems.get(i).getFragment()).commit();
        }
    mPagerItems = pagerItems;
}

5) 컨테이너에서 Activity또는 Fragment새 데이터로 어댑터를 다시 초기화하지 마십시오. setPagerItems다음과 같이 새 데이터로 메소드 를 통해 새 데이터를 설정하십시오 .

ArrayList<PagerItem> pagerItems = new ArrayList<PagerItem>();
pagerItems.add(new PagerItem("Fragment1", new MyFragment1()));
pagerItems.add(new PagerItem("Fragment2", new MyFragment2()));

mPagerAdapter.setPagerItems(pagerItems);
mPagerAdapter.notifyDataSetChanged();

도움이 되길 바랍니다.


답변

나는 같은 문제가 있었고 내 솔루션은 다음 FragmentPagerAdapter을 재정 의하여 사용 하고 있습니다 FragmentPagerAdapter#getItemId(int position).

@Override
public long getItemId(int position) {
    return mPages.get(position).getId();
}

기본적으로이 메소드는 항목의 위치를 ​​반환합니다. 변경 ViewPager되었는지 확인하고 itemId변경된 경우에만 페이지를 다시 작성 한다고 가정합니다 . 그러나 오버라이드되지 않은 버전은itemId 페이지가 실제로 다른 경우 하며 ViewPager는 해당 페이지가 교체 된 페이지를 정의하지 않으므로 다시 작성해야합니다.

이것을 사용하려면 long id 각 페이지마다 필요합니다. 일반적으로 고유 할 것으로 예상되지만이 경우 동일한 페이지의 이전 값과 달라야한다고 제안합니다. 따라서 여기서는 연속 카운터를 어댑터 또는 임의의 정수 (넓은 분포)로 사용할 수 있습니다.

이 주제에서 솔루션으로 언급 된 관점 태그를 사용하는 것이보다 일관된 방법이라고 생각합니다. 그러나 모든 경우에 해당되는 것은 아닙니다.