안드로이드의 ViewPager에서 조각 페이지 제거 public String getTextForPosition(int

ViewPager에서 Fragments를 동적으로 추가하고 제거하려고하는데 아무런 문제없이 작품을 추가하지만 제거가 예상대로 작동하지 않습니다.

현재 항목을 제거하려고 할 때마다 마지막 항목이 제거됩니다.

또한 FragmentStatePagerAdapter를 사용하거나 어댑터의 getItemPosition 메소드에서 POSITION_NONE을 리턴하려고 시도했습니다.

내가 뭘 잘못하고 있죠?

기본 예는 다음과 같습니다.

MainActivity.java

public class MainActivity extends FragmentActivity implements TextProvider {

    private Button mAdd;
    private Button mRemove;
    private ViewPager mPager;

    private MyPagerAdapter mAdapter;

    private ArrayList<String> mEntries = new ArrayList<String>();

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

        mEntries.add("pos 1");
        mEntries.add("pos 2");
        mEntries.add("pos 3");
        mEntries.add("pos 4");
        mEntries.add("pos 5");

        mAdd = (Button) findViewById(R.id.add);
        mRemove = (Button) findViewById(R.id.remove);
        mPager = (ViewPager) findViewById(R.id.pager);

        mAdd.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                addNewItem();
            }
        });

        mRemove.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                removeCurrentItem();
            }
        });

        mAdapter = new MyPagerAdapter(this.getSupportFragmentManager(), this);

        mPager.setAdapter(mAdapter);

    }

    private void addNewItem() {
        mEntries.add("new item");
        mAdapter.notifyDataSetChanged();
    }

    private void removeCurrentItem() {
        int position = mPager.getCurrentItem();
        mEntries.remove(position);
        mAdapter.notifyDataSetChanged();
    }

    @Override
    public String getTextForPosition(int position) {
        return mEntries.get(position);
    }
    @Override
    public int getCount() {
        return mEntries.size();
    }


    private class MyPagerAdapter extends FragmentPagerAdapter {

        private TextProvider mProvider;

        public MyPagerAdapter(FragmentManager fm, TextProvider provider) {
            super(fm);
            this.mProvider = provider;
        }

        @Override
        public Fragment getItem(int position) {
            return MyFragment.newInstance(mProvider.getTextForPosition(position));
        }

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

    }

}

TextProvider.java

public interface TextProvider {
    public String getTextForPosition(int position);
    public int getCount();
}

MyFragment.java

public class MyFragment extends Fragment {

    private String mText;

    public static MyFragment newInstance(String text) {
        MyFragment f = new MyFragment(text);
        return f;
    }

    public MyFragment() {
    }

    public MyFragment(String text) {
        this.mText = text;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {

        View root = inflater.inflate(R.layout.fragment, container, false);

        ((TextView) root.findViewById(R.id.position)).setText(mText);

        return root;
    }

}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/add"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="add new item" />

    <Button
        android:id="@+id/remove"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="remove current item" />

    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="0dip"
        android:layout_weight="1" />

</LinearLayout>

fragment.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/position"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:textSize="35sp" />

</LinearLayout>


답변

Louth의 솔루션은 기존 조각이 파괴되지 않았기 때문에 나를 위해 일하기에 충분하지 않았습니다. 이 답변에 동기를 부여 하여 솔루션 의 단편 위치가 변경 될 때마다 새로운 고유 ID를 제공하는 getItemId(int position)방법 을 재정의하는 것이 해결책이라는 것을 알았습니다 FragmentPagerAdapter.

소스 코드:

private class MyPagerAdapter extends FragmentPagerAdapter {

    private TextProvider mProvider;
    private long baseId = 0;

    public MyPagerAdapter(FragmentManager fm, TextProvider provider) {
        super(fm);
        this.mProvider = provider;
    }

    @Override
    public Fragment getItem(int position) {
        return MyFragment.newInstance(mProvider.getTextForPosition(position));
    }

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


    //this is called when notifyDataSetChanged() is called
    @Override
    public int getItemPosition(Object object) {
        // refresh all fragments when data set changed
        return PagerAdapter.POSITION_NONE;
    }


    @Override
    public long getItemId(int position) {
        // give an ID different from position when position has been changed
        return baseId + position;
    }

    /**
     * Notify that the position of a fragment has been changed.
     * Create a new ID for each position to force recreation of the fragment
     * @param n number of items which have been changed
     */
    public void notifyChangeInPosition(int n) {
        // shift the ID returned by getItemId outside the range of all previous fragments
        baseId += getCount() + n;
    }
}

예를 들어 단일 탭을 삭제하거나 주문을 변경 한 경우 전화 notifyChangeInPosition(1)하기 전에 전화 해야합니다.notifyDataSetChanged() 모든 조각을 다시 작성해야합니다.

이 솔루션이 작동하는 이유

getItemPosition () 재정의 :

경우 notifyDataSetChanged()라고, 어댑터는 호출 notifyChanged()의 방법 ViewPager이 부착되어 있습니다. 그런 ViewPager다음 getItemPosition()각 항목에 대해 어댑터가 반환 한 값을 확인하여 반환되는 항목을 제거하고 POSITION_NONE( 소스 코드 참조 ) 다시 채 웁니다.

getItemId () 재정의 :

이것은 ViewPager다시 채워질 때 어댑터가 이전 조각을 다시로드하지 못하도록하기 위해 필요합니다 . 에서 instantiateItem () 의 소스 코드 를 보면 이것이 왜 작동하는지 쉽게 이해할 수 있습니다 FragmentPagerAdapter.

    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));
    }

보다시피 getItem(), 프래그먼트 관리자가 동일한 ID를 가진 기존 프래그먼트를 찾지 못한 경우에만 메소드가 호출됩니다. 나에게 그것은 오래된 조각이 여전히 notifyDataSetChanged()호출 된 후에도 첨부 된 버그처럼 보이지만에 대한 설명서 ViewPager에는 다음과 같이 명시되어 있습니다.

이 클래스는 현재 초기 설계 및 개발 단계에 있습니다. API는 이후 버전의 호환성 라이브러리에서 변경 될 수 있으며, 새로운 버전에 대해 컴파일 될 때 앱의 소스 코드를 변경해야합니다.

따라서 향후 버전의 지원 라이브러리에서는 여기에 제공된 해결 방법이 필요하지 않기를 바랍니다.


답변

ViewPager는 위의 코드를 사용하여 프래그먼트를 제거하지 않습니다. 여러 뷰 (또는 귀하의 경우 프래그먼트)를 메모리에로드하기 때문입니다. 가시적 뷰 외에도 가시적 뷰의 양쪽에 뷰를로드합니다. 이것은 ViewPager를 매우 시원하게 만드는 뷰 간 부드러운 스크롤을 제공합니다.

원하는 효과를 얻으려면 몇 가지 작업을 수행해야합니다.

  1. FragmentPagerAdapter를 FragmentStatePagerAdapter로 변경하십시오. 그 이유는 FragmentPagerAdapter가 메모리에로드하는 모든 뷰를 영원히 메모리에 유지하기 때문입니다. FragmentStatePagerAdapter가 현재 및 통과 가능한보기를 벗어나는보기를 처리하는 위치입니다.

  2. 어댑터 메소드 getItemPosition을 대체하십시오 (아래 참조). mAdapter.notifyDataSetChanged();ViewPager 를 호출 하면 어댑터에 질문하여 위치 지정 측면에서 변경된 사항을 확인합니다. 이 방법을 사용하면 모든 것이 변경되었다고 말하고 모든 뷰 위치를 다시 처리하십시오.

그리고 여기 코드가 있습니다 …

private class MyPagerAdapter extends FragmentStatePagerAdapter {

    //... your existing code

    @Override
    public int getItemPosition(Object object){
        return PagerAdapter.POSITION_NONE;
    }

}

답변

뷰 호출기에서 조각 페이지를 제거하는 작업 솔루션

public class MyFragmentAdapter extends FragmentStatePagerAdapter {

    private ArrayList<ItemFragment> pages;

    public MyFragmentAdapter(FragmentManager fragmentManager, ArrayList<ItemFragment> pages) {
        super(fragmentManager);
        this.pages = pages;
    }

    @Override
    public Fragment getItem(int index) {
        return pages.get(index);
    }

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

    @Override
    public int getItemPosition(Object object) {
        int index = pages.indexOf (object);

        if (index == -1)
            return POSITION_NONE;
        else
            return index;
    }
}

색인으로 일부 페이지를 제거해야 할 때이 작업을 수행합니다.

pages.remove(position); // ArrayList<ItemFragment>
adapter.notifyDataSetChanged(); // MyFragmentAdapter

여기 내 어댑터 초기화입니다

MyFragmentAdapter adapter = new MyFragmentAdapter(getSupportFragmentManager(), pages);
viewPager.setAdapter(adapter);

답변

조각이 이미 제거되었지만 문제는 viewpager 저장 상태였습니다.

시험

myViewPager.setSaveFromParentEnabled(false);

아무것도 효과가 없었지만 문제가 해결되었습니다!

건배!


답변

소스 코드를에서 android.support.v4.app.FragmentPagerAdpater이라는 사용자 정의 클래스로
복사하는 아이디어를 얻었습니다 CustumFragmentPagerAdapter. 이렇게 instantiateItem(...)하면 호출 할 때마다 getItem()메소드 에서받은 새로운 조각을 추가하기 전에 현재 첨부 된 조각을 제거 / 파괴 할 수 있습니다 .

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);

    // remove / destroy current fragment
    if (fragment != null) {
        mCurTransaction.remove(fragment);
    }

    // get new fragment and add it
    fragment = getItem(position);
    mCurTransaction.add(container.getId(), fragment,    makeFragmentName(container.getId(), itemId));

    if (fragment != mCurrentPrimaryItem) {
        fragment.setMenuVisibility(false);
        fragment.setUserVisibleHint(false);
    }

    return fragment;
}

답변

더 나은 두 가지를 결합 할 수 있습니다.

private class MyPagerAdapter extends FragmentStatePagerAdapter {

    //... your existing code

    @Override
    public int getItemPosition(Object object){

      if(Any_Reason_You_WantTo_Update_Positions) //this includes deleting or adding pages
 return PagerAdapter.POSITION_NONE;
    }
else
return PagerAdapter.POSITION_UNCHANGED; //this ensures high performance in other operations such as editing list items.

}

답변

미래의 독자들을 위해!

이제 ViewPager2 를 사용 하여 viewpager에서 조각을 동적으로 추가하고 제거 할 수 있습니다 .

인용 양식 API 참조

ViewPager2는 ViewPager를 대체하여 오른쪽에서 왼쪽으로 레이아웃 지원, 수직 방향, 수정 가능한 조각 모음 등을 포함하여 이전 버전의 대부분의 어려움을 해결합니다.

봐 가지고 MutableCollectionFragmentActivity.kt에서 googlesample / 안드로이드 – viewpager2을 , 부가 viewpager에서 동적 조각을 제거하는 예를 들어.


귀하의 정보를 위해 :

조항:

API 참조

릴리즈 노트

샘플 저장소 : https://github.com/googlesamples/android-viewpager2