我遇到了这个奇怪ViewPager
的问题,setCurrentItem(position, false)
工作得很好,然后我切换到另一个活动,在我回到第一个活动后,ViewPager
总是以第一个项目结束。即使我添加setCurrentItem
了onResume
方法,它仍然会忽略它。当我尝试将项目设置为越界索引时,它甚至没有抛出任何异常。虽然稍后当我调用此方法时,当点击“下一步”按钮时,它会像预期的那样工作。检查我的代码 10 次是否有任何可能的调用setCurrentItem(0)
或其他东西,但它根本不存在。
18 回答
我无法真正回答为什么会发生这种情况,但是如果您将 setCurrentItem 调用延迟几毫秒,它应该可以工作。我的猜测是,因为在此期间onResume
还没有渲染通道,而 ViewPager 需要一个或类似的东西。
private ViewPager viewPager;
@Override
public void onResume() {
final int pos = 3;
viewPager.postDelayed(new Runnable() {
@Override
public void run() {
viewPager.setCurrentItem(pos);
}
}, 100);
}
更新:故事时间
所以今天我遇到了 viewpager 忽略了我的 setCurrentItem 操作的问题,我在 stackoverflow 中搜索了解决方案。我发现有人有同样的问题并解决了;我实施了修复,但没有奏效。哇!回到stackoverflow以否决那个虚假修复提供者,然后......
那是我。我实施了自己的错误非修复,这是我第一次偶然发现问题时提出的(后来被遗忘了)。我现在不得不对自己提供不良信息投反对票。
我最初的“修复”工作的原因不是因为“渲染通行证”;问题是寻呼机的内容是由微调器控制的。微调器和寻呼机状态都恢复了 onResume,因此在下一个事件传播周期期间调用了微调器 onItemSelected 侦听器,这确实重新填充了 viewpager - 这次使用不同的默认值。
在初始状态恢复期间删除和重置监听器解决了这个问题。
上面的修复第一次起作用,因为它在 onItemSelected 事件触发后设置了寻呼机的当前位置。后来,由于某种原因它停止工作(可能应用程序变得太慢 - 在我的实现中我没有使用 100 毫秒,而是 10 毫秒)。然后我在清理周期中删除了 postDelayed,因为它没有改变已经有问题的行为。
更新 2:我不能对自己的帖子投反对票。我想,尊贵的切腹是唯一的选择。
我在我的活动的 OnCreate 中遇到了类似的问题。适配器设置了正确的计数,我在将适配器设置为 ViewPager 后应用了 setCurrentItem,但是会返回超出范围的索引。我认为 ViewPager 在我设置当前项目时还没有加载我的所有片段。通过在 ViewPager 上发布一个可运行文件,我能够解决这个问题。这是一个带有一点上下文的示例。
// Locate the viewpager in activity_main.xml
final ViewPager viewPager = (ViewPager) findViewById(R.id.pager);
// Set the ViewPagerAdapter into ViewPager
viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager()));
viewPager.setOffscreenPageLimit(2);
viewPager.post(new Runnable() {
@Override
public void run() {
viewPager.setCurrentItem(ViewPagerAdapter.CENTER_PAGE);
}
});
我为此找到了一个非常简单的解决方法:
if (mViewPager.getAdapter() != null)
mViewPager.setAdapter(null);
mViewPager.setAdapter(mPagerAdapter);
mViewPager.setCurrentItem(desiredPos);
而且,如果这不起作用,您可以将其放入处理程序中,但不需要定时延迟:
new Handler().post(new Runnable() {
@Override
public void run() {
mViewPager.setCurrentItem(desiredPos);
}
});
我在代码中有类似的错误,问题是我在更改数据之前设置了位置。
解决方案是简单地在之后设置位置并通知数据更改
notifyDataSetChanged()
setCurrentItem()
ViewTreeObserver 可用于避免静态延迟。
科特林:
随意使用 Kotlin 扩展作为一个简洁的选项。
view_pager.doOnPreDraw {
view_pager.currentItem = 1
}
请确保您具有 gradle 依赖项: implementation 'androidx.core:core-ktx:1.3.2' 或更高版本
爪哇
OneShotPreDrawListener.add(view_pager, () -> view_pager.currentItem = 1);
Fragment
or中的一种现代方法是在以下上下文中Activity
调用协程中的函数:ViewPager.setcurrentItem(Int)
Dispatchers.Main
lifecycleScope.launch(Dispatchers.Main) {
val index = 1
viewPager.setCurrentItem(index)
}
我有同样的问题,我编辑
@Override
public int getCount() { return NUM_PAGES; }
我NUM_PAGES
只将错误设置为 1。
我已经使用了这里描述的 post() 方法,并且确实在某些情况下它工作得很好,但是因为我的数据来自服务器,所以它不是圣杯。
我的问题是我想拥有
notifyDataSetChanged
在任意时间调用,然后在我的 viewPager 上切换选项卡。所以在通知电话之后我有这个
ViewUtilities.waitForLayout(myViewPager, new Runnable() {
@Override
public void run() {
myViewPager.setCurrentItem(tabIndex , false);
}
});
和
public final class ViewUtilities {
public static void waitForLayout(final View view, final Runnable runnable) {
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
//noinspection deprecation
view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
runnable.run();
}
});
}
}
有趣的事实://noinspection deprecation
最后是因为在 API 16 之后修复的 API 中存在拼写错误,所以应该阅读
removeOnGlobalLayoutListener
^^
ON Global
代替
removeGlobalOnLayoutListener
^^
ON Layout
这似乎涵盖了我的所有情况。
有人在这里的论坛上写过。https://code.i-harness.com/en/q/126bff9为我工作
if (mViewPager.getAdapter() != null)
mViewPager.setAdapter(null);
mViewPager.setAdapter(mPagerAdapter);
mViewPager.setCurrentItem(desiredPos);
解决方案(在带有 ViewModel 等的 Kotlin 中)对于那些试图在没有 hacky “解决方案”onCreate
的情况下设置当前项目的人:Activity
Runnable
class MyActivity : AppCompatActivity() {
lateinit var mAdapter: MyAdapter
lateinit var mPager: ViewPager
// ...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.fragment_pager)
// ...
mainViewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
mAdapter = MyAdapter(supportFragmentManager)
mPager = findViewById(R.id.pager)
mainViewModel.someData.observe(this, Observer { items ->
items?.let {
// first give the data to the adapter
// this is where the notifyDataSetChanged() happens
mAdapter.setItems(it)
mPager.adapter = mAdapter // assign adapter to pager
mPager.currentItem = idx // finally set the current page
}
})
这显然会执行正确的操作顺序,而不会出现任何黑客攻击Runnable
或延迟。
为了完整起见,您通常像这样实现setItems()
适配器(在本例中FragmentStatePagerAdapter
):
internal fun setItems(items: List<Item>) {
this.items = items
notifyDataSetChanged()
}
我在这个问题上工作了一个星期,我意识到这个问题的发生是因为我在视图寻呼机片段中使用家庭活动上下文,我们只能在片段附加到活动后在片段中使用上下文。
当创建视图寻呼机时,活动仅附加到第一 (0) 和第二 (1) 页。当您打开第二页时,会附加第三页,依此类推!当您使用setCurrentItem()
方法并且参数大于1时,它想在附加之前打开该页面,因此该页面片段中的上下文将为空并且应用程序崩溃!这就是为什么当你延迟时setCurrentItem()
,它会起作用!一开始它会被附加,然后它会打开页面......
正如这里的几位海报所指出的,这是一个生命周期问题。但是,我发现发布 a 的解决方案Runnable
是不可预测的,并且可能容易出错。这似乎是一种通过将问题发布到未来来忽略问题的方法。
我并不是说这是最好的解决方案,但它绝对可以在不使用Runnable
. 我在Fragment
具有ViewPager
. 这个整数将保存我们想要onResume
在下一次调用时设置为当前页面的页面。整数的值可以在任何时候设置,因此可以在活动之前FragmentTransaction
或恢复活动时设置。另请注意,所有成员都设置在 中onResume()
,而不是 中onCreateView()
。
public class MyFragment extends Fragment
{
private ViewPager mViewPager;
private MyPagerAdapter mAdapter;
private TabLayout mTabLayout;
private int mCurrentItem = 0; // Used to keep the page we want to set in onResume().
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.my_layout, container, false);
mViewPager = (ViewPager) view.findViewById(R.id.my_viewpager);
mTabLayout = (TabLayout) view.findViewById(R.id.my_tablayout);
return view;
}
@Override
public void onResume()
{
super.onResume();
MyActivity myActivity = (MyActivity) getActivity();
myActivity.getSupportActionBar().setTitle(getString(R.string.my_title));
mAdapter = new MyPagerAdapter(getChildFragmentManager(), myActivity);
mViewPager.setAdapter(mAdapter);
mViewPager.setOffscreenPageLimit(PagerConstants.OFFSCREEN_PAGE_LIMIT);
mViewPager.setCurrentItem(mCurrentItem); // <-- Note the use of mCurrentItem here!
mTabLayout.setupWithViewPager(mViewPager);
}
/**
* Call this at any point before needed, for example before performing a FragmentTransaction.
*/
public void setCurrentItem(int currentItem)
{
mCurrentItem = currentItem;
// This should be called in cases where onResume() is not called later,
// for example if you only want to change the page in the ViewPager
// when clicking a Button or whatever. Just omit if not needed.
mViewPager.setCurrentItem(mCurrentItem);
}
}
对我来说,设置适配器后设置当前项目有效
viewPager.setAdapter(new MyPagerAdapter(getSupportFragmentManager()));
viewPager.setCurrentItem(idx);
pagerSlidingTabStrip.setViewPager(viewPager);// assign viewpager to tabs
我这样做是为了恢复当前项目:
@Override
protected void onSaveInstanceState(Bundle outState) {
if (mViewPager != null) {
outState.putInt(STATE_PAGE_NO, mViewPager.getCurrentItem());
}
super.onSaveInstanceState(outState);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
if (savedInstanceState != null) {
mCurrentPage = savedInstanceState.getInt(STATE_PAGE_NO, 0);
}
super.onRestoreInstanceState(savedInstanceState);
}
@Override
protected void onRestart() {
mViewPager.setCurrentItem(mCurrentPage);
super.onRestart();
}
到我打电话setCurrentItem()
的时候,视图即将被重新创建。所以实际上我setCurrentItem()
为 viewpager 调用,然后系统调用onCreateView()
,因此创建了一个新的 viewpager。
这就是我看不到任何变化的原因。这就是为什么 apostDelayed()
可能会有所帮助的原因。
理论解决方案:推迟setCurrentItem()
调用,直到重新创建视图。
实际解决方案:我不知道一个稳定而简单的解决方案。我们应该能够检查该类是否要重新创建它的视图,如果是这种情况,则将调用推迟setCurrentItem()
到onCreateView()
我使用dsalaj代码作为参考。如有必要,我会与完整的解决方案共享代码。
我也强烈推荐使用ViewPager2
解决方案
两种情况都必须在Observer {}
:
第一种情况:只有在我们拥有第一个数据集而不是之前才初始化适配器,因为这会在分页中产生不一致。对于第一个数据集,我们必须将其作为 Adapter 的参数传递。
第二种情况:从 observable 的第一次更改开始,我们必须从第二个数据集开始,只有当我们已经使用第一个数据集初始化适配器时,才必须通过公共方法将其传递给适配器。
总帐
我对 onActivityCreated() 被不相关的标签调用感到困惑 @Mahdi Arabpour 让我大开眼界:)
对我来说,问题是当我单击第二个选项卡等时,第三页(如上面@Mahdi Arabpour 所述)正在重建,并且它丢失了它的数据适配器,在 onActivityCreted 中再次设置它可以解决我的问题:
if (myXXRecyclerAdapter != null) {
myXXRecyclerAdapter = new MyXXRecyclerAdapter(myStoredData);
mRecyclerView.setAdapter(myXXRecyclerAdapter );
return;
}
您需要在 pager.setAdapter(buildAdapter()) 之后立即调用 pager.setCurrentItem(activePage)
@Override
public void onResume() {
if (pager.getAdapter() != null) {
activePage=pager.getCurrentItem();
Log.w(getClass().getSimpleName(), "pager.getAdapter()!=null");
pager.setAdapter(null);
}
pager.setAdapter(buildAdapter());
pager.setCurrentItem(activePage);
}