ViewPager PagerAdapter не оновлює представлення даних


597

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

Однак мені важко зрозуміти, як оновити ViewPager за допомогою нового набору переглядів.

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

Ніщо не допомогло, текстові огляди залишаються незмінними від вихідних даних.

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

Що, однак, не здається оновити, це другий вигляд, "B" залишається, він повинен відображати "Y" після натискання кнопки оновлення.

public class ViewPagerBugActivity extends Activity {

    private ViewPager myViewPager;
    private List<String> data;

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

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

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

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

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

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

    private class MyViewPagerAdapter extends PagerAdapter {

        private List<String> data;
        private Context ctx;

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

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

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

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

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

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

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

        @Override
        public void startUpdate(View arg0) {
        }

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

1
Будь ласка, дивіться stackoverflow.com/questions/12510404/… щодо потенційної помилки з FragmentStatePagerAdapter.
Саміс

2
Вибачте, що викопуєте це. Ваше запитання мені дуже допомогло, тому дякую! Єдине, чого я не отримую, - це те, як посилання на дані в PagerAdapter змінюються, коли ви оновлюєте їх у розділі Діяльність
Ewanw

Зробіть ваш адаптер продовжити BaseAdapter см це: stackoverflow.com/questions/13664155 / ...

1
Мабуть, якщо ви знову встановите адаптер на пейджер перегляду, він буде скинутий.
EpicPandaForce

у мене є проблема в пейджері stackoverflow.com/questions/41217237/…
chris

Відповіді:


856

Існує кілька способів цього досягти.

Перший варіант простіше, але трохи ефективніше.

Override getItemPositionу вашій , PagerAdapterяк це:

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

Таким чином, коли ви телефонуєте notifyDataSetChanged(), пейджер перегляду видалить усі перегляди та перезавантажить їх усі. Так виходить ефект перезавантаження.

Другий варіант, запропонований Alvaro Луїс Бустаманте (раніше alvarolb) , є setTag()способом , в instantiateItem()при створенні екземпляра нового виду. Тоді замість цього notifyDataSetChanged()ви можете findViewWithTag()знайти подання, яке ви хочете оновити.

Другий підхід є дуже гнучким та високоефективним. Kudos to alvarolb для оригінальних досліджень.


4
Здається, краще, ніж моя робота, спасибі.
C0deAttack

3
У мене схожа проблема. Мій viewPager покаже нові результати пошуку, але не за замовчуванням повернеться до першого запису в курсорі. Хтось знає, як скинути позицію при оновленні набору даних?
mAndroid

1
@mAndroid вам слід подати окреме запитання з більш детальною інформацією.
rui.araujo

1
повернення POSITION_NONE, мабуть, додає багато неефективності загальній програмі, оскільки це призводить до того, що Android видаляє подання, пов'язане з позицією запиту. Після цього перегляд потрібно знову створити перед відображенням. Вирішує проблему переглядів, які не оновлюються, але, мабуть, не найкраще рішення для складних макетів.
wufoo

11
Краще відповідь (без видалення всіх елементів) -> stackoverflow.com/a/10852046/898056
yhpark

484

Я не думаю, що в програмі є якась помилка 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с, і ви хочете періодично оновлювати лише одне значення. З описаними раніше підходами це означає, що ви видаляєте та створюєте екземпляри 100 TextViews за кожне оновлення. Це не має сенсу...


11
+1 - Ваше рішення не тільки працює як шарм, я зіткнувся саме з проблемою, яку ви описали тут, коли мені довелося оновлювати сторінки, які містили вкладки зі списками, текстовими переглядами та зображеннями. (OutOfErrorExceptions тощо) Дякую!
шлінгель

3
@alvarolb: Здрастуйте, я не знаю, як ви маєте на увазі setTagметод onInstantiateItem, чи хотіли б ви оновити цю відповідь деяким кодуванням? Дякую.
Вежа

35
Було б ідеально, якби ви написали приклад.
Самі Елтамави

13
Як хтось каже: приклад можна оцінити!
Jey10

15
6 років зараз ніхто не може навести приклад.
Oussaki

80

Змініть FragmentPagerAdapterна FragmentStatePagerAdapter.

getItemPosition()Метод переосмислення та повернення POSITION_NONE.

Врешті-решт, він прослухає notifyDataSetChanged()пейджер у режимі перегляду.


Я думаю, ви повинні належним чином реалізувати весь getItemPosition І повернути POSITION_NONE, якщо фрагмент не буде знайдено.
tkhduracell

46

Відповідь, яку дає 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оновити свій погляд. У своєму коді я використовую точно такий же метод.


8
Ви можете надати повний приклад адаптера? це робиться за допомогою фрагментів чи ні?

9
<оновити перегляд новими даними> ?? що це означає, або ми мусимо додати, що він тут переглядає, або нічого?
Акарш М

Цей метод оновлює всі представлення даних. Якщо ви хочете оновити лише одне представлення даних, ви можете написати власний метод notifyDataItemChanged таким чином: public void notifyDataItemChanged (int pos) {TextView tv = (TextView) views.get (pos) .findViewById (R.id.tv); tv.setText (list.get (pos)); super.notifyDataSetChanged (); }
Кай Ван

Цей код виходить з ладу, якщо розмір нового пейджера менший від старого
Антон Дузенко

@AntonDuzenko правильно, видалення думок досить важко.
MLProgrammer-CiM

28

Була така ж проблема. Для мене він працював над тим, щоб розширити FragmentStatePagerAdapter і змінити наведені нижче методи:

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

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

}

Це єдине рішення, яке працювало і для мене. Мій випадок - це не оновлення фрагмента, а динамічне видалення або додавання фрагментів. Не розширюючи FragmentStatePagerAdapter, ViewPager повертає фрагменти в кеш фрагментів, що є неправильним положенням. Дякую!
Сіра Лам

Добре працює в бібліотеці підтримки 28.0.0
Олексій К.

Перевизначити, що робити? Як створити об'єкт стану, пов'язаний з адаптером?
Фані Рітвій

Дякую друже. Це рішення працює лише
Парфі

20

Після декількох годин розчарування при спробі всіх вищевказаних рішень , щоб подолати цю проблему , а також намагається безліч рішень на інших подібні питання , як це , це і це , який все FAILED зі мною , щоб вирішити цю проблему і зробити , ViewPagerщоб зруйнувати старе Fragmentі заповнити pagerз нові Fragmentс. Я вирішив проблему наступним чином:

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) Зробіть конструктор ViewPagertake my FragmentManagerinstance, щоб зберегти його в моєму 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();

Я сподіваюся, що це допомагає.


@Tomer, з якою проблемою ви стикалися?
Самі Елтамаві


9

У мене була та сама проблема, і моє рішення використовується FragmentPagerAdapterз переосмисленням FragmentPagerAdapter#getItemId(int position):

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

За замовчуванням цей метод повертає позицію елемента. Я припускаю, що ViewPagerперевіряє, чи itemIdбуло змінено, і відтворює сторінку лише, якщо вона була. Але неперевершена версія повертає те саме положення, що іitemId неперекрита й у тому випадку, якщо сторінка насправді інша, а ViewPager не визначає, що ця сторінка замінена такою, і її потрібно відтворити.

Щоб скористатися цим, long id потрібно для кожної сторінки. Як правило, очікується, що він буде унікальним, але я пропоную в цьому випадку, щоб воно просто мало відрізнятися від попереднього значення для тієї ж сторінки. Отже, тут можна використовувати безперервний лічильник в адаптері або випадкових цілих чисел (з широким розподілом).

Я думаю, що це більш послідовний спосіб, а не використання Тегів зору, згаданих як рішення в цій темі. Але, мабуть, не для всіх випадків.


1
Це є. Я перевірив це: просто POSITION_NONE недостатньо. Дякую за чудову відповідь! :)
Слава

Ця функція навіть не присутня в PagerAdapter. Що це за клас?
Сакет

@Saket, ти маєш рацію, класFragmentPagerAdapter
atlascoder

6

Я знайшов дуже цікаве рішення цієї проблеми. Замість використання FragmentPagerAdapter , який зберігає в пам'яті всі фрагменти, ми можемо використовувати FragmentStatePagerAdapter ( android.support.v4.app.FragmentStatePagerAdapter ), який кожного разу перезавантажує фрагмент, коли ми його вибираємо.

Реалізація обох адаптерів однакова. Отже, нам потрібно просто змінити " розширити FragmentPagerAdapter " на " розширити FragmentStatePagerAdapter "


Добрий момент, це допоможе іншим вирішити питання пам'яті viewPager, пов’язані з фрагментом
Ашу Кумар

Чудова відповідь, це мені дуже допомогло.
Серхіо Марані

5

Після багатьох пошуків цієї проблеми я знайшов дійсно вдале рішення, яке, на мою думку, є правильним шляхом для вирішення цього питання. По суті, instantiateItem викликається лише тоді, коли подання буде інстанційоване, і більше ніколи, якщо подання не буде знищено (саме це відбувається, коли ви перекриєте функцію getItemPosition, щоб повернути POSITION_NONE). Натомість те, що ви хочете зробити, - це заощадити створені представлення даних або оновити їх у адаптері, генерувати функцію отримання, щоб хтось інший міг її оновити, або набір функцій, що оновлює адаптер (мій улюблений).

Отже, у свій MyViewPagerAdapter додайте змінну на зразок:

private View updatableView;

у вашому інстанційному документі:

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

Сподіваюся, це допомагає!


Я зрозумів, що не встановлюю дані instantiateItem, тому мої погляди не оновлюються. З вашої відповіді я зрозумів це. +1
Фані Рітвій

5

Через два з половиною роки після того, як ОП поставила своє запитання, це питання все ще є, ну, досі проблемою. Очевидно, що пріоритет Google щодо цього не особливо високий, тому замість того, щоб знайти виправлення, я знайшов рішення. Великим проривом для мене було з’ясування того, що була справжньою причиною проблеми (дивіться прийняту відповідь у цій публікації ). Як тільки стало очевидним, що проблема полягає в тому, що будь-які активні сторінки не оновлюються належним чином, моє вирішення було очевидним:

У моєму фрагменті (на сторінках):

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

У своїй діяльності, де я завантажую сторінки:

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

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

Сподіваюся, це допоможе комусь!


5

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

Дякую! У моєму випадку я використовував ViewPagerвнутрішній фрагмент, тому його замінили slideShowPagerAdapter.getFragmentManager()на getChildFragmentManager(). Можливо getFragmentManager(), допоможе у вашій справі. Я звик FragmentPagerAdapter, ні FragmentStatePagerAdapter. Також дивіться stackoverflow.com/a/25994654/2914140 про хакітський спосіб.
CoolMind

5

Набагато простіший спосіб: скористайтеся FragmentPagerAdapterзнаком "А" та оберніть свої фрагменти, що базуються на фрагменти. Вони оновлюються


5

Дякую 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();
    }
}

4

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

всередині ViewPager було досить просто дізнатися минуле положення позиції, найдосконалішою реалізацією є просто повернення нової позиції, getItemPosition()коли зміниться набір даних, і ViewPager буде знати, що вони змінені чи ні. на жаль, ViewPager цього не зробив.
VinceStyling

3

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

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

Після всього оновлення, яке я б використав, setSelectedPage(position)слід за ним notifyDataSetChanged().

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


Привіт, мене цікавить ваш код, я стикаюся з подібною проблемою, я можу оновити суміжні фрагменти, але поточний фрагмент, який видно, не оновлюється. Я дуже плутаю всі відповіді тут. Як я можу оновити представлення даних у поточному фрагменті, який показує адаптер VIewPager
Pankaj Nimgade

3

Я просто публікую цю відповідь на випадок, якщо хтось вважає її корисною. Зробивши те саме, я просто взяв вихідний код ViewPager і PagerAdapter з бібліотеки сумісності і скомпілював його у своєму коді (Вам потрібно розібрати всі помилки та імпортувати самостійно, але це, безумовно, можна зробити).

Потім у CustomViewPager створіть метод, який називається updateViewAt (позиція int). Сам вигляд можна отримати з mItems ArrayList, визначених у класі ViewPager (вам потрібно встановити ідентифікатор для представлень у пункті екземпляра та порівняти цей ідентифікатор з положенням у методі updateViewAt ()). Потім ви можете оновити подання, якщо потрібно.


2

Я здогадуюсь, я знайшов логіку ViewPager.

Якщо мені потрібно оновити набір сторінок і відобразити їх на основі нового набору даних, я дзвоню notifyDataSetChanged () . Потім ViewPager робить ряд дзвінків до getItemPosition () , передаючи туди фрагмент як об'єкт. Цей фрагмент може бути або зі старого набору даних (який я хочу відкинути), або з нового (який я хочу показати). Отже, я перекриваю getItemPosition () і там я повинен якось визначити, чи є мій фрагмент зі старого набору даних або з нового.

У моєму випадку у мене є макет на 2 панелі із переліком найголовніших елементів на лівій панелі та праворучним переглядом (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);
  }
}

Дякую всім попереднім дослідникам!


2

Код нижче працював для мене.

Створіть клас, який розширює клас 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, який відображає 3d-об’єкт, заснований на шляху до файлу .ply, тому всередині мого методу updateFragment я змінюю шлях до цього файлу.

потім створіть примірник 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

1.Перше потрібно встановити метод getItemposition у вашому класі Pageradapter. 2. Вам потрібно прочитати точне положення вашого перегляду Pager. слухач

цей програмний код трохи змінений для встановлення лише конкретного елемента позиції

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

1

те, що працювало для мене, йшло 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

Ви можете динамічно оновлювати всі фрагменти, це можна побачити в три етапи.

У вашому адаптері:

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

Завжди повертатися POSITION_NONE- це простий, але трохи неефективний спосіб, оскільки це викликає інстанціювання всіх сторінок, які вже створені.

Я створив бібліотеку ArrayPagerAdapter, щоб динамічно змінювати елементи в PagerAdapters.

Внутрішньо, адаптери цієї бібліотеки повернутися 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;
    }
}

Бібліотека також підтримує сторінки, створені фрагментами.


1

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

а потім виклик сповіститиDataSetChanged ():

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

1

Ви можете додати перетворення пейджера на 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

Я знаю, що я греблю, але все-таки це може допомогти комусь. Я просто продовжую відповідь, що приймає, і я також додав коментар до неї.

добре,

сама відповідь говорить, що вона неефективна

тож, щоб змусити її оновитись лише тоді, коли потрібно, ви можете це зробити

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

1

ViewPager не був розроблений для підтримки динамічної зміни перегляду.

Я підтвердив це, шукаючи іншу помилку, пов’язану з цим https://issuetracker.google.com/isissue/36956111, зокрема https://issuetracker.google.com/isissue/36956111#comment56

Це питання трохи старе, але нещодавно 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



0

Я думаю, що я створив простий спосіб повідомити про зміни набору даних:

По-перше, трохи змініть спосіб роботи функції 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, ...).

переконайтеся, що знищенняView працює так:

    @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

Для чого це варто, на KitKat + здається, що adapter.notifyDataSetChanged()цього достатньо, щоб нові погляди з'явилися, за умови, що ви setOffscreenPageLimitдостатньо високі. Я можу отримати бажану поведінку, роблячи viewPager.setOffscreenPageLimit(2).


0

Я на насправді використовувати notifyDataSetChanged()на ViewPagerі CirclePageIndicatorі після цього я викликаю destroyDrawingCache()на ViewPagerі це працює .. Жоден з інших рішень не працює для мене.


Оригінальному питанню майже 2 роки, я впевнений, що в API Android існує багато відмінностей, що відповіді тут можуть бути невірними для всіх версій API Android.
C0deAttack
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.