ViewPager PagerAdapter未更新视图

C0d*_*ack 581 android android-viewpager

我正在使用兼容性库中的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) {
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

rui*_*ujo 828

有几种方法可以实现这一目标.

第一种选择更容易,但效率更低.

getItemPositionPagerAdapter这样覆盖:

public int getItemPosition(Object object) {
    return POSITION_NONE;
}
Run Code Online (Sandbox Code Playgroud)

这样,当您调用时notifyDataSetChanged(),视图寻呼机将删除所有视图并重新加载它们.因此,获得了重载效果.

Alvaro Luis Bustamante(以前的alvarolb)建议的第二个选项是setTag()instantiateItem()实例化新视图时使用方法.然后notifyDataSetChanged(),您可以使用findViewWithTag()查找要更新的视图而不是使用它.

第二种方法非常灵活,性能高.感谢alvarolb的原创研究.

  • 更好的答案(不删除所有项目) - > http://stackoverflow.com/a/10852046/898056 (10认同)
  • 比我的工作更好,似乎没有任何副作用,谢谢. (4认同)
  • 我有一个类似的问题.我的viewPager将显示新的searc结果,但不会默认返回光标中的第一个条目.更新数据集时有谁知道如何重置位置? (3认同)
  • 你什么意思? (3认同)

Alv*_*nte 473

我认为没有任何类型的错误PagerAdapter.问题是了解它的工作原理有点复杂.看看这里解释的解决方案,从我的角度来看,存在误解,因此对实例化视图的使用很差.

过去几天我一直在与PagerAdapterViewPager,我发现以下几点:

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秒,你只想定期更新一个值.使用前面介绍的方法,这意味着您要TextView在每次更新时删除并实例化100 秒.它没有任何意义...

  • 如果你写一个例子,这将是完美的. (32认同)
  • 6年了,没有人可以提供一个例子. (15认同)
  • 正如有人所说:赞赏一个例子! (13认同)
  • +1 - 当我不得不更新包含listviews,textviews和图像标签的页面时,你的解决方案不仅可以像我在这里描述的问题一样运行.(OutOfErrorExceptions等......)谢谢! (10认同)
  • @alvarolb:你好,我真的不知道你对方法`onInstantiateItem`中`setTag`的意思,你想用一些编码来更新这个答案吗?谢谢. (3认同)
  • @AlvaroLuisBustamante请为此答案添加示例 (3认同)
  • 8年过去了,没有人能举出例子。 (2认同)

小智 79

更改FragmentPagerAdapterFragmentStatePagerAdapter.

覆盖getItemPosition()方法并返回POSITION_NONE.

最终,它将收听notifyDataSetChanged()视图寻呼机.


Gri*_*ace 44

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;
}
Run Code Online (Sandbox Code Playgroud)

然后通过覆盖该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();
}
Run Code Online (Sandbox Code Playgroud)

实际上,你可以在使用类似的代码instantiateItem,并notifyDataSetChanged刷新视图.在我的代码中,我使用完全相同的方法.

  • <使用新数据刷新视图> ?? 这是什么意思我们要在这里添加他的观点还是什么? (9认同)
  • 你能提供完整的适配器示例吗?是否使用碎片完成? (8认同)

小智 26

有同样的问题.对我来说,它扩展了FragmentStatePagerAdapter,并覆盖以下方法:

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

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

}
Run Code Online (Sandbox Code Playgroud)


Sam*_*awy 20

经过挫折小时,尝试所有上述解决方案来克服这个问题,也像其他类似的问题,尝试了许多解决方案,这个这个,所有与我没能解决这个问题,使ViewPager摧毁旧的Fragment,并填补了pager用新Fragment的.我已经解决了以下问题:

1)使ViewPager类扩展FragmentPagerAdapter如下:

 public class myPagerAdapter extends FragmentPagerAdapter {
Run Code Online (Sandbox Code Playgroud)

2)为创建一个项目ViewPager是存储titlefragment如下:

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

}
Run Code Online (Sandbox Code Playgroud)

3)使ViewPager我的FragmentManager实例的构造函数将它存储在我class的下面:

private FragmentManager mFragmentManager;
private ArrayList<PagerItem> mPagerItems;

public MyPagerAdapter(FragmentManager fragmentManager, ArrayList<PagerItem> pagerItems) {
    super(fragmentManager);
    mFragmentManager = fragmentManager;
    mPagerItems = pagerItems;
}
Run Code Online (Sandbox Code Playgroud)

4)创建重新设置该方法adapter通过删除所有以前的用新的数据数据fragmentfragmentManager自身直接使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;
}
Run Code Online (Sandbox Code Playgroud)

5)从容器中ActivityFragment不用新数据重新初始化适配器.使用以下新数据通过方法设置新数据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();
Run Code Online (Sandbox Code Playgroud)

我希望它有所帮助.


atl*_*der 9

我有同样的问题,我的解决方案是压倒一切的FragmentPagerAdapter:

@Override
public long getItemId(int position) {
    return mPages.get(position).getId();
}
Run Code Online (Sandbox Code Playgroud)

默认情况下,此方法返回项目的位置.我想FragmentPagerAdapter#getItemId(int position)检查是否ViewPager已更改并仅重新创建页面.但是,itemId即使页面实际上不同,not-overriden版本也返回相同的位置,并且ViewPager没有定义该页面被替换为一个并且需要重新创建.

要使用它,itemId每页都需要.通常情况下,它应该是唯一的,但我建议,对于这种情况,它应该与同一页面的先前值不同.因此,可以在适配器或随机整数(具有广泛分布)中使用连续计数器.

我认为这是更一致的方式而不是使用在本主题中提到的视图标签.但可能不适用于所有情况.


Yve*_*erm 6

ViewPager 并非旨在支持动态视图更改。

我在寻找与此相关的另一个错误时确认了这一点https://issuetracker.google.com/issues/36956111,特别是https://issuetracker.google.com/issues/36956111#comment56

这个问题有点老了,但谷歌最近用ViewPager2解决了这个问题。它将允许用标准解决方案替换手工制作(未维护且可能有问题)的解决方案。它还可以防止像某些答案那样不必要地重新创建视图。

对于 ViewPager2 示例,您可以查看https://github.com/googlesamples/android-viewpager2

如果要使用 ViewPager2,则需要在 build.gradle 文件中添加以下依赖项:

  dependencies {
     implementation 'androidx.viewpager2:viewpager2:1.0.0-beta02'
  }
Run Code Online (Sandbox Code Playgroud)

然后,您可以将 xml 文件中的 ViewPager 替换为:

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />
Run Code Online (Sandbox Code Playgroud)

之后,您需要在您的活动中用 ViewPager2 替换 ViewPager

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);
        }
    }
} 
Run Code Online (Sandbox Code Playgroud)

如果您使用的是 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();
Run Code Online (Sandbox Code Playgroud)

然后,您将能够通过修改适配器的数据并调用 notifyDataSetChanged 方法来刷新视图


小智 5

经过大量搜索此问题后,我找到了一个非常好的解决方案,我认为这是解决此问题的正确方法。本质上,instantiateItem 只在视图被实例化时被调用,除非视图被销毁(这就是当你覆盖 getItemPosition 函数返回 POSITION_NONE 时发生的情况)。相反,您想要做的是保存创建的视图并在适配器中更新它们,生成一个 get 函数以便其他人可以更新它,或者一个更新适配器的 set 函数(我最喜欢的)。

因此,在您的 MyViewPagerAdapter 添加一个变量,如:

private View updatableView;
Run Code Online (Sandbox Code Playgroud)

在您的实例化项中:

 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;
    }
Run Code Online (Sandbox Code Playgroud)

因此,通过这种方式,您可以创建一个函数来更新您的视图:

public updateText(String txt)
{
    ((TextView)updatableView).setText(txt);
}
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助!


alf*_*ngj 5

一种更简单的方法:使用a FragmentPagerAdapter,并将分页视图包装到片段上.他们确实得到了更新


小智 5

所有这些解决方案都没有帮助我。因此,我找到了一个可行的解决方案:setAdapter每次都可以,但这还不够。您应该在更改适配器之前执行以下操作:

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

之后:

viewPager.setAdapter(adapter);
Run Code Online (Sandbox Code Playgroud)


小智 5

在OP提出问题两年半之后,这个问题仍然是一个问题.显而易见,Google在此方面的优先级并不是特别高,所以我找到了一个解决方法,而不是找到修复方法.对我来说最大的突破是发现什么问题的真正原因(见接受的答案这一职位).一旦显然问题是任何活动页面都没有正确刷新,我的解决方法显而易见:

在我的片段(页面):

  • 我从onCreateView中获取了填充表单的所有代码,并将其放在一个名为PopulateForm的函数中,该函数可以从任何地方调用,而不是通过框架调用.此函数尝试使用getView获取当前View,如果为null,则返回.重要的是PopulateForm只包含显示的代码 - 创建FocusChange监听器之类的所有其他代码仍在OnCreate中
  • 创建一个布尔值,可以用作表示必须重新加载表单的标志.我的是mbReloadForm
  • 如果设置了mbReloadForm,则重写OnResume()以调用PopulateForm().

在我的Activity中,我在哪里加载页面:

  • 在更改任何内容之前转到第0页.我正在使用FragmentStatePagerAdapter,所以我知道最多会影响两到三页.更改为第0页可确保我在第0,1和2页上遇到问题.
  • 在清除旧列表之前,请选择它的大小().这样您就可以知道有多少页面受到了bug的影响.如果> 3,将其减少到3 - 如果您使用的是另一个PagerAdapter,则必须查看您需要处理的页数(可能全部?)
  • 重新加载数据并调用pageAdapter.notifyDataSetChanged()
  • 现在,对于每个受影响的页面,使用pager.getChildAt(i)查看页面是否处于活动状态 - 这会告诉您是否有视图.如果是这样,请调用pager.PopulateView().如果没有,请设置ReloadForm标志.

在此之后,当您重新加载第二组页面时,该错误仍将导致一些显示旧数据.但是,它们现在将被刷新,您将看到新数据 - 您的用户将不会知道该页面是不正确的,因为这种刷新将在他们看到页面之前发生.

希望这有助于某人!


小智 5

感谢rui.araujo和Alvaro Luis Bustamante.起初,我尝试使用rui.araujo的方式,因为它很容易.它可以工作,但是当数据发生变化时,页面会重新显示.这很糟糕所以我尝试使用Alvaro Luis Bustamante的方式.这是完美的.这是代码:

@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;
    }
}
Run Code Online (Sandbox Code Playgroud)

当数据发生变化时:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 5

我发现这个问题很有意思.我们可以使用FragmentStatePagerAdapter(android.support.v4.app.FragmentStatePagerAdapter),而不是使用FragmentPagerAdapter来保存内存中的所有片段,每当我们选择它时,重新加载片段.

两个适配器的实现是相同的.所以,我们只需要在" extend FragmentStatePagerAdapter "上更改" extend FragmentPagerAdapter "