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를 매우 시원하게 만드는 뷰 간 부드러운 스크롤을 제공합니다.
원하는 효과를 얻으려면 몇 가지 작업을 수행해야합니다.
-
FragmentPagerAdapter를 FragmentStatePagerAdapter로 변경하십시오. 그 이유는 FragmentPagerAdapter가 메모리에로드하는 모든 뷰를 영원히 메모리에 유지하기 때문입니다. FragmentStatePagerAdapter가 현재 및 통과 가능한보기를 벗어나는보기를 처리하는 위치입니다.
-
어댑터 메소드 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에서 동적 조각을 제거하는 예를 들어.
귀하의 정보를 위해 :
조항:
샘플 저장소 : https://github.com/googlesamples/android-viewpager2