it-roy-ru.com

ViewPager PagerAdapter не обновляет представление

Я использую ViewPager из библиотеки совместимости. У меня, к счастью, есть несколько просмотров, которые я могу пролистать.

Тем не менее, мне трудно понять, как обновить ViewPager с новым набором представлений.

Я пробовал всевозможные вещи, такие как вызов mAdapter.notifyDataSetChanged(), mViewPager.invalidate(), даже создавая новый адаптер каждый раз, когда я хочу использовать новый список данных.

Ничего не помогло, текстовые представления остаются неизменными по сравнению с исходными данными.

Обновление: Я сделал небольшой тестовый проект, и я почти смог обновить представления. Я вставлю класс ниже.

Что не обновляет, тем не менее, это 2-й вид, «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) {
        }
    }
}
549
C0deAttack

Есть несколько способов добиться этого.

Первый вариант проще, но немного неэффективен.

Переопределите getItemPosition в вашем PagerAdapter следующим образом:

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

Таким образом, когда вы вызываете notifyDataSetChanged(), пейджер представлений удалит все представления и перезагрузит их все. Как таковой эффект перезагрузки получается.

Второй вариант, предложенный Альваро Луисом Бустаманте (ранее alvarolb) , - это метод setTag() в instantiateItem() при создании нового представления. Затем вместо notifyDataSetChanged() вы можете использовать findViewWithTag(), чтобы найти представление, которое вы хотите обновить. 

Второй подход очень гибкий и высокопроизводительный. Престижность alvarolb для оригинального исследования.

780
rui.araujo

Я не думаю, что есть какая-то ошибка в 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 TextViews, и вы хотите периодически обновлять только одно значение. При описанных выше подходах это означает, что вы удаляете и создаете 100 экземпляров TextViews при каждом обновлении. Это не имеет смысла...

460
Alvaro Luis Bustamante

Измените FragmentPagerAdapter на FragmentStatePagerAdapter.

Переопределите метод getItemPosition() и верните POSITION_NONE.

В конце концов, он прослушает пейджер notifyDataSetChanged() on.

76
Kit

Ответ данный 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();
}

На самом деле вы можете использовать похожий код в instantiateItem и notifyDataSetChanged, чтобы обновить ваш вид. В моем коде я использую точно такой же метод.

40
Grimmace

Была такая же проблема. Для меня это работало над расширением FragmentStatePagerAdapter и переопределением следующих методов:

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

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

}
24
Mario Mosca

После нескольких часов разочарования, когда вы пытались решить все вышеперечисленные решения, чтобы преодолеть эту проблему, а также пытались найти множество решений по другим подобным вопросам, таким как это , это и это которые все не смогли со мной решить эту проблему и заставить 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) Заставить конструктор ViewPager взять мой экземпляр FragmentManager, чтобы сохранить его в моем class следующим образом:

private FragmentManager mFragmentManager;
private ArrayList<PagerItem> mPagerItems;

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

4) Создайте метод для переустановки данных adapter с новыми данными, удалив все предыдущие fragment из самого fragmentManager напрямую, чтобы сделать adapter для установки нового fragment из нового списка снова, как показано ниже:

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

Я надеюсь, что это помогает.

20
Sami Eltamawy

У меня была та же проблема, и мое решение переопределяет функцию ViewPagerAdapter#getItemId(int position):

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

По умолчанию этот метод возвращает позицию элемента. Я предполагаю, что ViewPager проверяет, был ли itemId изменен, и воссоздает страницу, только если она была. Но не переопределенная версия возвращает ту же позицию, что и itemId, даже если страница на самом деле отличается, и ViewPager не определяет, что страница заменена, и ее необходимо создать заново.

Чтобы использовать это, long id необходим для каждой страницы. Обычно ожидается, что он будет уникальным, но для этого случая я предлагаю, чтобы он просто отличался от предыдущего значения для той же страницы. Таким образом, здесь можно использовать непрерывный счетчик в адаптере или случайные целые числа (с широким распределением).

Я думаю, что это более последовательный способ, а не использование тегов зрения, упомянутых в качестве решения в этой теме. Но, вероятно, не для всех случаев.

8
atlascoder

Спасибо rui.araujo и Альваро Луису Бустаманте. Сначала я пытаюсь использовать способ rui.araujo, потому что это легко. Это работает, но когда данные меняются, страница будет перерисовываться, очевидно. Это плохо, поэтому я стараюсь использовать путь Альваро Луиса Бустаманте. Это идеально. Вот код:

@Override
protected void onStart() {
    super.onStart();
}

private class TabPagerAdapter extends PagerAdapter {
    @Override
    public int getCount() {
        return 4;
    }

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

    @Override
    public void destroyItem(final View container, final int position, final Object object) {
        ((ViewPager) container).removeView((View) object);
    }

    @Override
    public Object instantiateItem(final ViewGroup container, final int position) {
        final View view = LayoutInflater.from(
                getBaseContext()).inflate(R.layout.activity_approval, null, false);
        container.addView(view);
        ListView listView = (ListView) view.findViewById(R.id.list_view);
        view.setTag(position);
        new ShowContentListTask(listView, position).execute();
        return view;
    }
}

И когда данные меняются:

for (int i = 0; i < 4; i++) {
    View view = contentViewPager.findViewWithTag(i);
    if (view != null) {
        ListView listView = (ListView) view.findViewById(R.id.list_view);
        new ShowContentListTask(listView, i).execute();
    }
}
5
evan.z

Через два с половиной года после того, как ФП поставил свой вопрос, эта проблема все еще остается проблемой. Очевидно, что приоритет Google в этом не очень высок, поэтому вместо того, чтобы найти решение, я нашел обходной путь. Большим прорывом для меня стало выяснение истинной причины проблемы (см. Принятый ответ в этом посте ). Как только стало очевидно, что проблема заключалась в том, что все активные страницы не обновлялись должным образом, мой обходной путь стал очевиден:

В моем фрагменте (страницы):

  • Я взял весь код, который заполняет форму, из onCreateView и поместил его в функцию с именем PopulateForm, которую можно вызывать из любого места, а не из фреймворка. Эта функция пытается получить текущий View, используя getView, и если он имеет значение null, он просто возвращает. Важно, чтобы PopulateForm содержал только отображаемый код - весь другой код, который создает прослушиватели FocusChange и т.п., все еще находится в OnCreate. 
  • Создайте логическое значение, которое можно использовать как флаг, указывающий, что форма должна быть перезагружена. Мой mbReloadForm
  • Переопределите OnResume () для вызова PopulateForm (), если установлена ​​функция mbReloadForm.

В моей деятельности, где я делаю загрузку страниц: 

  • Перейдите на страницу 0, прежде чем что-либо менять. Я использую FragmentStatePagerAdapter, поэтому я знаю, что затронуты максимум две или три страницы. Переход на страницу 0 гарантирует, что у меня возникнут проблемы только на страницах 0, 1 и 2.
  • Прежде чем очистить старый список, возьмите его размер (). Таким образом, вы узнаете, сколько страниц затронуто этой ошибкой. Если> 3, уменьшите его до 3 - если вы используете другой PagerAdapter, вам нужно будет увидеть, сколько страниц вам приходится иметь дело (может быть, все?)
  • Перезагрузите данные и вызовите страницу Adapter.notifyDataSetChanged ()
  • Теперь для каждой из затронутых страниц посмотрите, активна ли эта страница, используя pager.getChildAt (i) - это говорит вам, есть ли у вас представление. Если это так, вызовите pager.PopulateView (). Если нет, установите флаг ReloadForm.

После этого, когда вы перезагрузите второй набор страниц, ошибка все равно заставит некоторых отображать старые данные. Однако теперь они будут обновлены, и вы увидите новые данные - ваши пользователи не будут знать, что страница когда-либо была неправильной, потому что это обновление произойдет до того, как они увидят страницу.

Надеюсь, это поможет кому-то!

5
RichardP

Гораздо более простой способ: используйте переменную FragmentPagerAdapter и оберните свои постраничные представления фрагментами. Они обновляются

5
alfongj

Я нашел очень интересное решение этой проблемы . Вместо использования FragmentPagerAdapter , которые сохраняют в памяти все фрагменты, мы можем использовать FragmentStatePagerAdapter (Android.support.v4.app.FragmentStatePagerAdapter), который перезагружает фрагмент каждый раз, когда мы его выбираем.

Реализации обоих адаптеров идентичны. Таким образом, нам нужно просто изменить " расширить FragmentPagerAdapter " на " расширить FragmentStatePagerAdapter "

5
Рома Богдан

На всякий случай, если кто-то использует адаптер FragmentStatePagerAdapter (который позволит ViewPager создавать минимум страниц, необходимых для отображения, максимум 2 для моего случая), ответ @ rui.araujo о перезаписи getItemPosition в вашем адаптере не приведет к значительным потерям , но это еще можно улучшить.

В псевдокоде:

public int getItemPosition(Object object) {
    YourFragment f = (YourFragment) object;
    YourData d = f.data;
    logger.info("validate item position on page index: " + d.pageNo);

    int dataObjIdx = this.dataPages.indexOf(d);

    if (dataObjIdx < 0 || dataObjIdx != d.pageNo) {
        logger.info("data changed, discard this fragment.");
        return POSITION_NONE;
    }

    return POSITION_UNCHANGED;
}
4
Jerry Tian

Все эти решения не помогли мне. таким образом, я нашел рабочее решение: Вы можете setAdapter каждый раз, но этого недостаточно . Вы должны сделать это перед сменой адаптера:

FragmentManager fragmentManager = slideShowPagerAdapter.getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
List<Fragment> fragments = fragmentManager.getFragments();
for (Fragment f : fragments) {
    transaction.remove(f);
}
transaction.commit();

и после этого:

viewPager.setAdapter(adapter);
4
Mahdi Perfect

У меня была похожая проблема, в которой у меня было четыре страницы, и одна из страниц обновляла просмотры остальных трех. Мне удалось обновить виджеты (SeekBars, TextViews и т.д.) На странице рядом с текущей страницей. Последние две страницы будут иметь неинициализированные виджеты при вызове mTabsAdapter.getItem(position)

Чтобы решить мою проблему, я использовал setSelectedPage(index) перед вызовом getItem(position). Это создаст экземпляр страницы, что позволит мне изменять значения и виджеты на каждой странице. 

После всех обновлений я бы использовал setSelectedPage(position), а затем notifyDataSetChanged().

Вы можете увидеть небольшое мерцание в ListView на главной странице обновления, но ничего заметного. Я не проверил это полностью, но это решает мою непосредственную проблему. 

3
enKoder

После долгих поисков этой проблемы я нашел действительно хорошее решение, которое я считаю правильным. По сути, instantiateItem вызывается только тогда, когда создается экземпляр представления, и никогда больше, если только представление не уничтожено (это то, что происходит, когда вы переопределяете функцию getItemPosition, чтобы вернуть POSITION_NONE). Вместо этого вы хотите сохранить / созданные представления и либо обновить их в адаптере, сгенерировать функцию get, чтобы кто-то другой мог ее обновить, либо функцию set, которая обновляет адаптер (мой любимый).

Итак, в вашем MyViewPagerAdapter добавьте переменную вроде:

private View updatableView;

в вашем instantiateItem:

 public Object instantiateItem(View collection, int position) {
        updatableView = new TextView(ctx); //My change is here
        view.setText(data.get(position));
        ((ViewPager)collection).addView(view);
        return view;
    }

таким образом, вы можете создать функцию, которая будет обновлять ваш вид:

public updateText(String txt)
{
    ((TextView)updatableView).setText(txt);
}

Надеюсь это поможет!

3
German

Я просто публикую этот ответ на тот случай, если кто-нибудь еще посчитает его полезным. Чтобы сделать то же самое, я просто взял исходный код ViewPager и PagerAdapter из библиотеки совместимости и скомпилировал его в своем коде (вам нужно разобраться со всеми ошибками и импортировать самостоятельно, но это определенно можно сделать).

Затем в CustomViewPager создайте метод с именем updateViewAt (int position). Само представление может быть получено из элементов ArrayList, определенных в классе ViewPager (необходимо установить Id для представлений в экземпляре элемента и сравнить этот идентификатор с позицией в методе updateViewAt ()). Затем вы можете обновить представление по мере необходимости.

3
Naveen LP

Вы можете динамически обновлять все фрагменты, вы можете увидеть в три этапа.

В вашем адаптере:

public class MyPagerAdapter extends FragmentPagerAdapter {
private static int NUM_ITEMS = 3;
private Map<Integer, String> mFragmentTags;
private FragmentManager mFragmentManager;

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

// Returns total number of pages
@Override
public int getCount() {
    return NUM_ITEMS;
}

// Returns the fragment to display for that page
@Override
public Fragment getItem(int position) {
    switch (position) {
        case 0:
            return FirstFragment.newInstance();
        case 1:
            return SecondFragment.newInstance();
        case 2:
            return ThirdFragment.newInstance();
        default:
            return null;
    }
}

// Returns the page title for the top indicator
@Override
public CharSequence getPageTitle(int position) {
    return "Page " + position;
}

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

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

Сейчас в вашей деятельности:

public class MainActivity extends AppCompatActivity implements ViewPager.OnPageChangeListener{

MyPagerAdapter mAdapterViewPager;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ViewPager viewPager = (ViewPager) findViewById(R.id.vpPager);
    mAdapterViewPager = new MyPagerAdapter(getSupportFragmentManager());
    viewPager.setAdapter(mAdapterViewPager);
    viewPager.addOnPageChangeListener(this);
}

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

}

@Override
public void onPageSelected(int position) {

    Fragment fragment = mAdapterViewPager.getFragment(position);
    if (fragment != null) {
        fragment.onResume();
    }
}

@Override
public void onPageScrollStateChanged(int state) {

}}

Наконец в вашем фрагменте, что-то вроде этого:

public class YourFragment extends Fragment {

// newInstance constructor for creating fragment with arguments
public static YourFragment newInstance() {

    return new YourFragment();
}

// Store instance variables based on arguments passed
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
}

// Inflate the view for the fragment based on layout XML
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    return inflater.inflate(R.layout.fragment, container, false);
}


@Override
public void onResume() {
    super.onResume();

    //to refresh your view
    refresh();

}}

Вы можете увидеть полный код здесь .

Спасибо Альваро Луису Бустаманте.

1
Cabezas

Вы можете добавить преобразование пейджера на Viewpager, как это 

myPager.setPageTransformer(true, new MapPagerTransform());

В приведенном ниже коде я изменил цвет моего представления во время выполнения при прокрутке пейджера

public class MapPagerTransform implements ViewPager.PageTransformer {


    public void transformPage(View view, float position) {
     LinearLayout  showSelectionLL=(LinearLayout)view.findViewById(R.id.showSelectionLL);

     if (position < 0) {

                showSelectionLL.setBackgroundColor(Color.WHITE);
     }else if (position >0){

                showSelectionLL.setBackgroundColor(Color.WHITE);
     }else {
                showSelectionLL.setBackgroundColor(Color.RED);
     }
   }
}
1
Akshay dashore

Всегда возвращать POSITION_NONE просто, но немного неэффективно, потому что это вызывает создание экземпляров всех страниц, которые уже были созданы.

Я создал библиотеку ArrayPagerAdapter для динамического изменения элементов в PagerAdapter.

Внутри адаптеры этой библиотеки возвращают POSITION_NONE в getItemPosiition() только при необходимости.

С помощью этой библиотеки вы можете динамически изменять элементы, как описано ниже.

@Override
protected void onCreate(Bundle savedInstanceState) {
        /** ... **/
    adapter = new MyStatePagerAdapter(getSupportFragmentManager()
                            , new String[]{"1", "2", "3"});
    ((ViewPager)findViewById(R.id.view_pager)).setAdapter(adapter);
     adapter.add("4");
     adapter.remove(0);
}

class MyPagerAdapter extends ArrayViewPagerAdapter<String> {

    public MyPagerAdapter(String[] data) {
        super(data);
    }

    @Override
    public View getView(LayoutInflater inflater, ViewGroup container, String item, int position) {
        View v = inflater.inflate(R.layout.item_page, container, false);
        ((TextView) v.findViewById(R.id.item_txt)).setText(item);
        return v;
    }
}

Библиотека Thils также поддерживает страницы, созданные Fragments.

1
T.Nakama

Это для всех таких, как я, которым необходимо обновить Viewpager из службы (или другого фонового потока), и ни одно из предложений не сработало: После небольшой проверки логов я понял, что метод notifyDataSetChanged () никогда не возвращается. getItemPosition (Object object) называется все концами там без дальнейшей обработки. Затем я нашел в документах родительского класса PagerAdapter (не в документах подклассов): «Изменения в наборе данных должны происходить в главном потоке и должны заканчиваться вызовом notifyDataSetChanged ()» . Итак, рабочим решением в этом случае было (с использованием FragmentStatePagerAdapter и getItemPosition (Object object) установлено возвращать POSITION_NONE):

а затем вызов notifyDataSetChanged ():

runOnUiThread(new Runnable() {
         @Override
         public void run() {
             pager.getAdapter().notifyDataSetChanged();
         }
     });
1
Helmwag

Я думаю, у меня есть логика ViewPager. 

Если мне нужно обновить набор страниц и отобразить их на основе нового набора данных, я вызываю notifyDataSetChanged () . Затем ViewPager делает несколько вызовов getItemPosition () , передавая Fragment как объект. Этот фрагмент может быть либо из старого набора данных (который я хочу удалить), либо из нового (который я хочу отобразить). Итак, я переопределяю getItemPosition () и там я должен каким-то образом определить, является ли мой фрагмент из старого набора данных или из нового. 

В моем случае у меня есть двухпанельный макет со списком верхних элементов на левой панели и представлением пролистывания (ViewPager) справа. Таким образом, я сохраняю ссылку на мой текущий верхний элемент внутри моего PagerAdapter, а также внутри каждого экземпляра страницы, для которого создается экземпляр Когда выбранный верхний элемент в списке изменяется, я сохраняю новый верхний элемент в PagerAdapter и вызываю notifyDataSetChanged ( ) . И в переопределенном getItemPosition () я сравниваю верхний элемент из моего адаптера с верхним элементом из моего фрагмента. И только если они не равны, я возвращаю POSITION_NONE. Затем PagerAdapter восстанавливает все фрагменты, которые вернули POSITION_NONE.

НОТА. Лучше было бы сохранить идентификатор верхнего элемента вместо ссылки.

Фрагмент кода ниже немного схематичный, но я адаптировал его из реально работающего кода.

public class SomeFragment extends Fragment {
  private TopItem topItem;
}

public class SomePagerAdapter extends FragmentStatePagerAdapter {
  private TopItem topItem;

  public void changeTopItem(TopItem newTopItem) {
    topItem = newTopItem;
    notifyDataSetChanged();
  }

  @Override
  public int getItemPosition(Object object) {
    if (((SomeFragment) object).getTopItemId() != topItem.getId()) {
      return POSITION_NONE;
    }
    return super.getItemPosition(object);
  }
}

Спасибо всем предыдущим исследователям!

1
Stanislav Karakhanov

Код ниже работал для меня.

Создайте класс, который расширяет класс FragmentPagerAdapter, как показано ниже.

public class Adapter extends FragmentPagerAdapter {

private int tabCount;
private Activity mActivity;
private Map<Integer, String> mFragmentTags;
private FragmentManager mFragmentManager;
private int container_id;
private ViewGroup container;
private List<Object> object;

public Adapter(FragmentManager fm) {
    super(fm);
}

public Adapter(FragmentManager fm, int numberOfTabs , Activity mA) {
    super(fm);
    mActivity = mA;
    mFragmentManager = fm;
    object = new ArrayList<>();
    mFragmentTags = new HashMap<Integer, String>();
    this.tabCount = numberOfTabs;
}

@Override
public Fragment getItem(int position) {
    switch (position) {
        case 0:
            return Fragment0.newInstance(mActivity);
        case 1:
            return Fragment1.newInstance(mActivity);
        case 2:
            return Fragment2.newInstance(mActivity);
        default:
            return null;
    }}


@Override
public Object instantiateItem(ViewGroup container, int position) {
    Object object = super.instantiateItem(container, position);
    if (object instanceof Fragment) {
        Log.e("Already defined","Yes");
        Fragment fragment = (Fragment) object;
        String tag = fragment.getTag();
        Log.e("Fragment Tag","" + position + ", " + tag);
        mFragmentTags.put(position, tag);
    }else{
        Log.e("Already defined","No");
    }
    container_id = container.getId();
    this.container = container;
    if(position == 0){
        this.object.add(0,object);
    }else if(position == 1){
        this.object.add(1,object);
    }else if(position == 2){
        this.object.add(2,object);
    }
    return object;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    super.destroyItem(container, position, object);
    if (object instanceof Fragment) {
        Log.e("Removed" , String.valueOf(position));
    }
}

@Override
public int getItemPosition (Object object)
{   int index = 0;
    if(this.object.get(0) == object){
        index = 0;
    }else if(this.object.get(1) == object){
        index = 1;
    }else if(this.object.get(2) == object){
        index = 2;
    }else{
        index = -1;
    }
    Log.e("Index" , "..................." + String.valueOf(index));
    if (index == -1)
        return POSITION_NONE;
    else
        return index;
}

public String getFragmentTag(int pos){
    return "Android:switcher:"+R.id.pager+":"+pos;
}

public void NotifyDataChange(){
    this.notifyDataSetChanged();
}

public int getcontainerId(){
    return container_id;
}

public ViewGroup getContainer(){
    return this.container;
}

public List<Object> getObject(){
    return this.object;
}

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

Затем внутри каждого созданного вами фрагмента создайте метод updateFragment. В этом методе вы изменяете вещи, которые нужно изменить во фрагменте. Например, в моем случае Fragment0 содержал GLSurfaceView, который отображает трехмерный объект на основе пути к файлу .ply, поэтому внутри моего метода updateFragment я изменяю путь к этому файлу ply.

затем создайте экземпляр ViewPager,

viewPager = (ViewPager) findViewById(R.id.pager);

и экземпляр Adpater,

adapter = new Adapter(getSupportFragmentManager(), 3, this);

тогда сделай это,

viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(1);

Затем внутри класса вы инициализировали класс Adapter выше и создали viewPager, каждый раз, когда вы хотите обновить один из ваших фрагментов (в нашем случае Fragment0), используйте следующее:

adapter.NotifyDataChange();

adapter.destroyItem(adapter.getContainer(), 0, adapter.getObject().get(0)); // destroys page 0 in the viewPager.

fragment0 = (Fragment0) getSupportFragmentManager().findFragmentByTag(adapter.getFragmentTag(0)); // Gets fragment instance used on page 0.

fragment0.updateFragment() method which include the updates on this fragment

adapter.instantiateItem(adapter.getContainer(), 0); // re-initialize page 0.

Это решение было основано на методике, предложенной Альваро Луисом Бустаманте. 

1
Rabee

то, что работало для меня, собиралось viewPager.getAdapter().notifyDataSetChanged();

и в адаптере положить ваш код для обновления представления внутри getItemPosition, как это

@Override
public int getItemPosition(Object object) {

    if (object instanceof YourViewInViewPagerClass) { 
        YourViewInViewPagerClass view = (YourViewInViewPagerClass)object;
        view.setData(data);
    }

    return super.getItemPosition(object);
}

может быть, не самый правильный способ сделать это, но это сработало (трюк return POSITION_NONE вызвал сбой для меня, поэтому не было выбора)

1
Fonix

В моем случае каждый раз, когда Fragment входил в @override onCreateView(...), я перезапускал значения, поэтому обрабатывал нулевые или пустые значения, такие как:

if(var == null){
    var = new Something();
}

if(list.isEmpty()){
    list = new ArrayList<MyItem>();
}

if(myAdapter == null){
    myAdapter = new CustomAdapter();
    recyclerView.setAdapter(myAdapter);
}

// etc...

Класс фрагмента может входить в onCreateView каждый раз, когда мы меняемся, и возвращаться к представлению из пользовательского интерфейса во время выполнения.

0
Ninja Coding

1. Прежде всего, вам нужно установить метод getItemposition в вашем классе Pageradapter 2. Вам нужно прочитать Точную позицию вашего View Pager 3. Затем отправить эту позицию как местоположение данных вашего нового 4 .Написать кнопку обновления на слушателе щелчка внутри слушателя setonPageChange

этот программный код немного изменен, чтобы установить только конкретный элемент позиции 

public class MyActivity extends Activity {

private ViewPager myViewPager;
private List<String> data;
public int location=0;
public Button updateButton;
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

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

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

      updateButton = (Button) findViewById(R.id.update);

    myViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int i, float v, int i2) {
             //Toast.makeText(MyActivity.this, i+"  Is Selected  "+data.size(), Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onPageSelected( int i) {
          // here you will get the position of selected page
            final int k = i;
             updateViewPager(k);

        }

        @Override
        public void onPageScrollStateChanged(int i) {

        }
    });
}

private void updateViewPager(final int i) {  
    updateButton.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            Toast.makeText(MyActivity.this, i+"  Is Selected  "+data.size(), Toast.LENGTH_SHORT).show();
            data.set(i, "Replaced "+i);         
            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 int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    @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) {
    }
}
}
0
Naveen Kumar

На самом деле я использую notifyDataSetChanged() для ViewPager и CirclePageIndicator, и после этого я вызываю destroyDrawingCache() для ViewPager, и это работает. Ни одно из других решений не помогло мне.

0
czaku

Я использую Tablayout с ViewPagerAdapter. Для передачи данных между фрагментами или для связи между фрагментами используйте приведенный ниже код, который прекрасно работает и обновляет фрагмент, когда он появляется. Нажатием внутренней кнопки второго фрагмента напишите код ниже.

b.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            String text=e1.getText().toString(); // get the text from EditText

            // move from one fragment to another fragment on button click
            TabLayout tablayout = (TabLayout) getActivity().findViewById(R.id.tab_layout); // here tab_layout is the id of TabLayout which is there in parent Activity/Fragment
            if (tablayout.getTabAt(1).isSelected()) { // here 1 is the index number of second fragment i-e current Fragment

                LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getContext());
                Intent i = new Intent("EDIT_TAG_REFRESH");
                i.putExtra("MyTextValue",text);
                lbm.sendBroadcast(i);

            }
            tablayout.getTabAt(0).select(); // here 0 is the index number of first fragment i-e to which fragment it has to moeve

        }
    });

ниже приведен код, который должен быть записан в первом фрагменте (в моем случае) i-e при получении фрагмента.

MyReceiver r;
Context context;
String newValue;
public void refresh() {
    //your code in refresh.
    Log.i("Refresh", "YES");
}
public void onPause() {
    super.onPause();

    LocalBroadcastManager.getInstance(context).unregisterReceiver(r);
}
public void onResume() {
    super.onResume();
    r = new MyReceiver();
    LocalBroadcastManager.getInstance(getActivity()).registerReceiver(r,
            new IntentFilter("EDIT_TAG_REFRESH"));
} // this code has to be written before onCreateview()


 // below code can be written any where in the fragment
 private class MyReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
    PostRequestFragment.this.refresh();
        String action = intent.getAction();
        newValue=intent.getStringExtra("MyTextValue");
        t1.setText(newValue); // upon Referesh set the text
    }
}
0
Tara

Вместо того, чтобы возвращать POSITION_NONE и снова создавать все фрагменты, вы можете сделать, как я предложил здесь: Динамически обновлять ViewPager?

0
M-WaJeEh

Что-то, что сработало для меня, было переопределить метод finishUpdate, который вызывается в конце перехода, и выполнить там функцию notifyDataSetChanged():

public class MyPagerAdapter extends FragmentPagerAdapter {
    ...
    @Override
    public void finishUpdate(ViewGroup container) {
        super.finishUpdate(container);
        this.notifyDataSetChanged();
    }
    ...
}
0
horro

Правильный способ переопределить getItemPosition ():

@Override
public int getItemPosition(@NonNull Object object) {
    if (!(object instanceof MyPageView)) {
        // Should never happen
        return super.getItemPosition(object);
    }
    MyDataObject dataObject = ((MyPageView) object).getData();
    if (!(dataObject instanceof MyDataObject)) {
        // Should never happen
        return super.getItemPosition(object);
    }
    if (lstItems.contains(dataObject)) {
        return POSITION_UNCHANGED;
    }
    return POSITION_NONE;
}

Короче говоря, вам нужно прикрепить/сохранить данные, которые вы используете для рисования вашего представления. Оттуда, когда вызывается getItemPosition (), ViewPager может определить, присутствует ли в данный момент отображаемое представление на адаптере или нет, и действовать соответствующим образом.

0
Reaper

Вам просто нужен этот простой код:

Код:

private void updateAdapter(){
    if(adapterViewPager != null){
        int from = vpMyViewPager.getCurrentItem() - vpMyViewPager.getOffscreenPageLimit();
        int to   = vpMyViewPager.getCurrentItem() + vpMyViewPager.getOffscreenPageLimit();
        vpMyViewPager.removeAllViews();
        for(int i = from; i <= to; i++){
            if(i < 0){
                continue;
            }
            adapterViewPager.instantiateItem(vpMyViewPager, i);
        }
    }
}

Объяснение:

Если вы не изменили offscreenPageLimitViewPager, у него всегда есть 3-4 ребенка в зависимости от того, в каком направлении вы собираетесь. И чтобы показать правильное содержимое в своих дочерних элементах, он использует адаптер для получения нужного содержимого .. Теперь, когда вы вызываете removeAllViews() для своей ViewPager, только 3–4 Views фактически удаляются из иерархии Window, и вызывая instantiateItem(ViewGroup viewPager, int index), вы только воссоздаете от 3 до 4 Views. Затем все возвращается к нормальному состоянию, вы проводите и прокручиваете, ViewPager показывает содержимое с помощью своего адаптера. Число его дочерних элементов не одинаково на всех устройствах во всех ситуациях, например, если вы установите offscreenPageLimit равным 5, у него, вероятно, будет около 11-12 дочерних элементов, но это все, это не так много. это быстро.

0
M D P

Я думаю, что я сделал простой способ уведомить об изменениях набора данных:

Сначала немного изменим способ работы функции instantiateItem:

    @Override
    public Object instantiateItem(final ViewGroup container, final int position) {
        final View rootView = mInflater.inflate(...,container, false);
        rootView.setTag(position);
        updateView(rootView, position);
        container.addView(rootView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        mViewPager.setObjectForPosition(rootView, position);
        return rootView;
    }

для "updateView" заполните представление всеми данными, которые вы хотите заполнить (setText, setBitmapImage, ...).

убедитесь, что destroyView работает так:

    @Override
    public void destroyItem(final ViewGroup container, final int position, final Object obj) {
        final View viewToRemove = (View) obj;
        mViewPager.removeView(viewToRemove);
    }

Теперь предположим, что вам нужно изменить данные, сделать это, а затем вызвать следующую функцию в PagerAdapter:

    public void notifyDataSetChanged(final ViewPager viewPager, final NotifyLocation fromPos,
            final NotifyLocation toPos) {
        final int offscreenPageLimit = viewPager.getOffscreenPageLimit();
        final int fromPosInt = fromPos == NotifyLocation.CENTER ? mSelectedPhotoIndex
                : fromPos == NotifyLocation.MOST_LEFT ? mSelectedPhotoIndex - offscreenPageLimit
                        : mSelectedPhotoIndex + offscreenPageLimit;
        final int toPosInt = toPos == NotifyLocation.CENTER ? mSelectedPhotoIndex
                : toPos == NotifyLocation.MOST_LEFT ? mSelectedPhotoIndex - offscreenPageLimit
                        : mSelectedPhotoIndex + offscreenPageLimit;
        if (fromPosInt <= toPosInt) {
            notifyDataSetChanged();
            for (int i = fromPosInt; i <= toPosInt; ++i) {
                final View pageView = viewPager.findViewWithTag(i);
                mPagerAdapter.updateView(pageView, i);
            }
        }
    }

public enum NotifyLocation {
    MOST_LEFT, CENTER, MOST_RIGHT
}

Например, если вы хотите уведомить все представления, отображаемые viewPager, о том, что что-то изменилось, вы можете вызвать:

notifyDataSetChanged(mViewPager,NotifyLocation.MOST_LEFT,NotifyLocation.MOST_RIGHT);

Вот и все.

0
android developer

Лучшее решение из моего опыта: https://stackoverflow.com/a/44177688/3118950 , которое переопределяет long getItemId() и возвращает уникальный идентификатор вместо позиции по умолчанию. В дополнение к этому ответ импортируется, чтобы заметить, что старый фрагмент будет сохранен в диспетчере фрагментов в случае, если общая сумма меньше предела страницы, и onDetach()/onDestory() не будет вызываться при замене фрагмента. 

0
Eliyahu Shwartz

Что бы это ни стоило, на KitKat + кажется, что adapter.notifyDataSetChanged() достаточно для отображения новых представлений, при условии, что вы setOffscreenPageLimit достаточно высоки. Я могу получить желаемое поведение, выполнив viewPager.setOffscreenPageLimit(2).

0
mszaro

Это ужасная проблема, и я рад представить отличное решение; просто, эффективно и эффективно!

См. Ниже, код показывает использование флага для указания когда для возврата POSITION_NONE

public class ViewPagerAdapter extends PagerAdapter
{
    // Members
    private boolean mForceReinstantiateItem = false;

    // This is used to overcome terrible bug that Google isn't fixing
    // We know that getItemPosition() is called right after notifyDataSetChanged()
    // Therefore, the fix is to return POSITION_NONE right after the notifyDataSetChanged() was called - but only once
    @Override
    public int getItemPosition(Object object)
    {
        if (mForceReinstantiateItem)
        {
            mForceReinstantiateItem = false;
            return POSITION_NONE;
        }
        else
        {
            return super.getItemPosition(object);
        }
    }

    public void setData(ArrayList<DisplayContent> newContent)
    {
        mDisplayContent = newContent;
        mForceReinstantiateItem = true;
        notifyDataSetChanged();
    }

}
0
Someone Somewhere

Я оставляю здесь свое собственное решение, которое является обходным путем, потому что, похоже, проблема в том, что FragmentPagerAdapter не очищает предыдущую fragments, вы можете быть добавлены к ViewPager, в Менеджере фрагментов. Итак, я создаю метод для выполнения перед добавлением FragmentPagerAdapter:

(В моем случае я никогда не добавляю более 3 фрагментов, но вы можете использовать, например, getFragmentManager().getBackStackEntryCount() и проверить все fragments.

/**
 * this method is solving a bug in FragmentPagerAdapter which don't delete in the fragment manager any previous fragments in a ViewPager.
 *
 * @param containerId
 */
public void cleanBackStack(long containerId) {
    FragmentTransaction transaction = getFragmentManager().beginTransaction();
    for (int i = 0; i < 3; ++i) {
        String tag = "Android:switcher:" + containerId + ":" + i;
        Fragment f = getFragmentManager().findFragmentByTag(tag);
        if (f != null) {
            transaction.remove(f);
        }
    }
    transaction.commit();
}

Я знаю, что это обходной путь, потому что он перестанет работать, если изменится способ создания тегов каркасом. 

(в настоящее время "Android:switcher:" + containerId + ":" + i)

Тогда способ использовать его после получения контейнера:

ViewPager viewPager = (ViewPager) view.findViewById(R.id.view_pager);
cleanBackStack(viewPager.getId());
0
lm2a