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
有几种方法可以实现这一目标.
第一种选择更容易,但效率更低.
像getItemPosition
你PagerAdapter
这样覆盖:
public int getItemPosition(Object object) {
return POSITION_NONE;
}
Run Code Online (Sandbox Code Playgroud)
这样,当您调用时notifyDataSetChanged()
,视图寻呼机将删除所有视图并重新加载它们.因此,获得了重载效果.
Alvaro Luis Bustamante(以前的alvarolb)建议的第二个选项是setTag()
在instantiateItem()
实例化新视图时使用方法.然后notifyDataSetChanged()
,您可以使用findViewWithTag()
查找要更新的视图而不是使用它.
第二种方法非常灵活,性能高.感谢alvarolb的原创研究.
Alv*_*nte 473
我认为没有任何类型的错误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
秒,你只想定期更新一个值.使用前面介绍的方法,这意味着您要TextView
在每次更新时删除并实例化100 秒.它没有任何意义...
小智 79
更改FragmentPagerAdapter
到FragmentStatePagerAdapter
.
覆盖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
刷新视图.在我的代码中,我使用完全相同的方法.
小智 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
是存储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;
}
}
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
通过删除所有以前的用新的数据数据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;
}
Run Code Online (Sandbox Code Playgroud)
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();
Run Code Online (Sandbox Code Playgroud)
我希望它有所帮助.
我有同样的问题,我的解决方案是压倒一切的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
每页都需要.通常情况下,它应该是唯一的,但我建议,对于这种情况,它应该与同一页面的先前值不同.因此,可以在适配器或随机整数(具有广泛分布)中使用连续计数器.
我认为这是更一致的方式而不是使用在本主题中提到的视图标签.但可能不适用于所有情况.
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)
希望这可以帮助!
小智 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在此方面的优先级并不是特别高,所以我找到了一个解决方法,而不是找到修复方法.对我来说最大的突破是发现什么问题的真正原因(见接受的答案这一职位).一旦显然问题是任何活动页面都没有正确刷新,我的解决方法显而易见:
在我的片段(页面):
在我的Activity中,我在哪里加载页面:
在此之后,当您重新加载第二组页面时,该错误仍将导致一些显示旧数据.但是,它们现在将被刷新,您将看到新数据 - 您的用户将不会知道该页面是不正确的,因为这种刷新将在他们看到页面之前发生.
希望这有助于某人!
小智 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 "
归档时间: |
|
查看次数: |
352444 次 |
最近记录: |