我正在使用兼容性库中的ViewPager。我已经成功地让它显示几个视图,我可以通过页面。

但是,我很难弄清楚如何用一组新的视图更新ViewPager。

我已经尝试了各种各样的事情,比如调用mAdapter.notifyDataSetChanged(), mviewpage .invalidate(),甚至在每次我想使用新的数据列表时创建一个全新的适配器。

没有任何帮助,textviews保持不变,从原始数据。

更新: 我做了一个小测试项目,我几乎能够更新视图。我将在下面粘贴这个类。

然而,似乎没有更新的是第二个视图,“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) {
        }
    }
}

当前回答

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

在过去的几天里,我一直在使用PagerAdapter和ViewPager,我发现了以下内容:

PagerAdapter上的notifyDataSetChanged()方法只会通知ViewPager底层页面已经更改。例如,如果您动态地创建/删除页面(从列表中添加或删除项目),ViewPager应该负责这些。在这种情况下,我认为ViewPager决定是否应该使用getItemPosition()和getCount()方法删除或实例化一个新视图。

我认为ViewPager,在notifyDataSetChanged()调用后,它的子视图和检查他们的位置与getItemPosition()。如果对于子视图,该方法返回POSITION_NONE, ViewPager会认为该视图已被删除,并调用destroyItem()方法删除该视图。

通过这种方式,如果您只想更新页面的内容,覆盖getItemPosition()以总是返回POSITION_NONE是完全错误的,因为每次调用notifyDatasetChanged()时,以前创建的视图将被销毁,而新的视图将被创建。对于一些textview来说,这似乎没有什么问题,但是当你有复杂的视图时,比如从数据库中填充的listview,这可能是一个真正的问题和资源浪费。

So there are several approaches to efficiently change the content of a view without having to remove and instantiate the view again. It depends on the problem you want to solve. My approach is to use the setTag() method for any instantiated view in the instantiateItem() method. So when you want to change the data or invalidate the view that you need, you can call the findViewWithTag() method on the ViewPager to retrieve the previously instantiated view and modify/use it as you want without having to delete/create a new view each time you want to update some value.

例如,假设您有100个带有100个textview的页面,并且您只想定期更新一个值。使用前面解释的方法,这意味着您在每次更新时删除并实例化100个textview。这没有道理……

其他回答

我想,我有ViewPager的逻辑。

如果我需要刷新一组页面并基于新的数据集显示它们,我调用notifyDataSetChanged()。 然后,ViewPager对getItemPosition()进行多次调用,将Fragment作为对象传递给那里。这个片段可以来自旧数据集(我想要丢弃的数据集),也可以来自新数据集(我想要显示的数据集)。因此,我重写了getItemPosition(),在那里我必须以某种方式确定我的片段是来自旧数据集还是来自新数据集。

In my case I have a 2-pane layout with a list of top items on the left pane and a swipe view (ViewPager) on the right. So, I store a link to my current top item inside my PagerAdapter and also inside of each instantiated page Fragment. When the selected top item in the list changes, I store the new top item in PagerAdapter and call notifyDataSetChanged(). And in the overridden getItemPosition() I compare the top item from my adapter to the top item from my fragment. And only if they are not equal, I return POSITION_NONE. Then, PagerAdapter reinstantiates all the fragments that have returned POSITION_NONE.

请注意。存储顶部项id而不是引用可能是一个更好的主意。

下面的代码片段有点示意图,但我从实际工作的代码中改编了它。

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

感谢之前所有的研究人员!

有几种方法可以实现这一点。

第一种方法更简单,但效率更低。

重写PagerAdapter中的getItemPosition,如下所示:

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

这样,当您调用notifyDataSetChanged()时,视图分页器将删除所有视图并重新加载它们。这样就得到了装填效果。

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

结论

如果您有很多视图,或者希望支持修改任何特定的项和/或视图(在任何时候快速),那么第二种方法(标记)是非常灵活和高性能的,因为它可以防止重新创建所有未修改的视图。 (alvarolb的原创研究值得称赞。)

但如果你的应用只有“刷新”功能(甚至不允许单个条目的更改),或者只有很少的条目,请使用第一种方法,因为它节省了开发时间。

在我的情况下,在我的Viewpager中有一个textView,在mainActivity中单击按钮,我想改变textView的颜色并更新pagerAdapter。在按钮上单击“我在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 was(在PagerAdapter中):

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

因此,当我点击按钮,颜色立即改变在pagerAdapter的Textview。

快乐的编码。

一个更简单的方法:使用FragmentPagerAdapter,并将页面视图包装到片段上。它们确实会更新

ViewPager不是为支持动态视图更改而设计的。

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

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

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

如果您想使用ViewPager2,您需要在构建中添加以下依赖项。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" />

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

ViewPager2需要一个RecyclerView。适配器,或者FragmentStateAdapter,在你的例子中,它可以是RecyclerView。适配器

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方法来刷新视图