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

45 ответов

Решение

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

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

Override getItemPosition в вашем PagerAdapter как это:

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

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

Второй вариант, предложенный Альваро Луисом Бустаманте (ранее 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 s, и вы хотите периодически обновлять только одно значение. С подходами, объясненными ранее, это означает, что вы удаляете и создаете TextView на каждом обновлении. Это не имеет смысла...

Изменить FragmentPagerAdapter в FragmentStatePagerAdapter,

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

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

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

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

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

}

После нескольких часов разочарования, пытаясь решить все вышеперечисленные решения, чтобы преодолеть эту проблему, а также пытаясь найти множество решений по другим подобным вопросам, таким, как этот, так и этот, которые мне НЕ удалось решить эту проблему и сделать ViewPager уничтожить старое Fragment и заполните pager с новым Fragment s. Я решил проблему следующим образом:

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

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

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

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

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

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

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

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

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

ViewPager не был разработан для поддержки динамического изменения представления.

У меня было подтверждение этого, когда я искал другую ошибку, связанную с этим https://issuetracker.google.com/issues/36956111 и, в частности, https://issuetracker.google.com/issues/36956111

Этот вопрос немного устарел, но недавно Google решил эту проблему с помощью ViewPager2. Это позволит заменить разработанные вручную (не обслуживаемые и потенциально глючные) решения на стандартные. Это также предотвращает ненужное воссоздание представлений, как это делают некоторые ответы.

Для примеров ViewPager2 вы можете проверить https://github.com/googlesamples/android-viewpager2

Если вы хотите использовать ViewPager2, вам нужно будет добавить следующую зависимость в свой файл build.gradle:

  dependencies {
     implementation 'androidx.viewpager2:viewpager2:1.0.0-beta02'
  }

Затем вы можете заменить ViewPager в своем XML-файле на:

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

После этого вам нужно будет заменить ViewPager на ViewPager2 в своей деятельности.

ViewPager2 нужен либо RecyclerView.Adapter, либо FragmentStateAdapter, в вашем случае это может быть RecyclerView.Adapter

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {

    private Context context;
    private ArrayList<String> arrayList = new ArrayList<>();

    public MyAdapter(Context context, ArrayList<String> arrayList) {
        this.context = context;
        this.arrayList = arrayList;
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false);
        return new MyViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        holder.tvName.setText(arrayList.get(position));
    }

    @Override
    public int getItemCount() {
        return arrayList.size();
    }

    public class MyViewHolder extends RecyclerView.ViewHolder {
        TextView tvName;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            tvName = itemView.findViewById(R.id.tvName);
        }
    }
} 

В случае, если вы использовали TabLayout, вы можете использовать TabLayoutMediator:

        TabLayoutMediator tabLayoutMediator = new TabLayoutMediator(tabLayout, viewPager, true, new TabLayoutMediator.OnConfigureTabCallback() {
            @Override
            public void onConfigureTab(@NotNull TabLayout.Tab tab, int position) {
                // configure your tab here
                tab.setText(tabs.get(position).getTitle());
            }
        });

        tabLayoutMediator.attach();

Затем вы сможете обновить свои представления, изменив данные вашего адаптера и вызвав метод notifyDataSetChanged.

Все эти решения не помогли мне. Таким образом, я нашел рабочее решение: вы можете 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);

Спасибо 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();
    }
}

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

Через два с половиной года после того, как ФП поставил свой вопрос, эта проблема все еще остается проблемой. Очевидно, что приоритет 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.

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

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

После долгих поисков этой проблемы я нашел действительно хорошее решение, которое, как мне кажется, является правильным способом решения этой проблемы. По сути, 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);
}

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

На всякий случай, если кто-то использует адаптер на основе 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;
}

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

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

После всего обновления я бы использовал setSelectedPage(position) с последующим notifyDataSetChanged(),

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

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

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

В ViewPager2 вы можете повторно инициализировать адаптер, чтобы обновить список пейджера новыми представлениями. viewPager2.adapter = myPagerAdapter

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

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

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

}}

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

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

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

Ну, сам ответ говорит, что это неэффективно

Итак, чтобы он обновлялся только при необходимости, вы можете сделать это

private boolean refresh;

public void refreshAdapter() {
    refresh = true;
    notifyDataSetChanged();
}

@Override
public int getItemPosition(@NonNull Object object) {
    if (refresh) {
        refresh = false;
        return POSITION_NONE;
    } else {
        return super.getItemPosition(object);
    }
}

Я думаю, у меня есть логика 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);
  }
}

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

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

Создайте класс, который расширяет класс 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. Прежде всего, вам нужно установить метод 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) {
    }
}
}

Что сработало для меня 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 трюк вызвал сбой для меня, поэтому не было выбора)

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

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

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

Вы можете добавить преобразование пейджера на 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);
     }
   }
}

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

Смотрите ниже, код показывает использование флага, чтобы указать, когда возвращать 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();
    }

}

В моем случае в моем Viewpager есть textView, при нажатии кнопки в mainActivity я хочу изменить цвет этого textView и обновить pagerAdapter. На кнопке Click я сохранил цвет в SharedPreference и обновил pagerAdapter, чтобы он мог обновлять цвет, взятый из общего предпочтения. Итак, я обновляю представление viewPager следующим образом.

btn_purple.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int color = ContextCompat.getColor(mContext, R.color.colorPrimaryDark2);
                editor.putInt("sahittoFontColor", color);
                editor.apply();
                toNotifyDatasetChanged();
            }
        });

теперь метод обновления:

 private  void  toNotifyDatasetChanged (){
        if(viewPager!=null&& pagerAdapter!=null) {
            viewPager.setAdapter(null);
            viewPager.setAdapter(pagerAdapter);
           
        }

    }

И мой pagerAdapter был:

pagerAdapter = new  Sahitto_ViewPagerAdapter (mContext, filenameParameter, 30, lineList);
                        viewPager.setAdapter(pagerAdapter);

И в instantiateItem было (в PagerAdapter):

    SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(mContext);
    int   bnfntcolor=settings.getInt("sahittoFontColor", 0);
if (bnfntcolor!=0){
    textView.setTextColor(bnfntcolor);
}

Таким образом, когда я нажимаю кнопку, цвет сразу меняется в текстовом представлении pagerAdapter.

Удачного кодирования.

Всегда возвращаюсь 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.

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

Другие вопросы по тегам