问题
Ive got this strange issue, ViewPager's setCurrentItem(position, false) works perfectly fine, then im switching to another activity, and after im back to the first activity, the ViewPager always ends up on the first item. Even though ive added setCurrentItem to onResume method it still ignores it. Its not even throwing any exception when im trying to set item to out of bounds index. Though later on when i call this method, when the button "next" is clicked, it works like expected. Checked my code 10 times for any possible calls to setCurrentItem(0) or smth but its just not there at all.
回答1:
i can't really answer WHY exactly this happens, but if you delay the setCurrentItem call for a few milliseconds it should work. My guess is that because during onResume
there hasn't been a rendering pass yet, and the ViewPager needs one or something like that.
private ViewPager viewPager;
@Override
public void onResume() {
final int pos = 3;
viewPager.postDelayed(new Runnable() {
@Override
public void run() {
viewPager.setCurrentItem(pos);
}
}, 100);
}
UPDATE: story time
so today i had the problem that the viewpager ignored my setCurrentItem action, and i searched stackoverflow for a solution. i found someone with the same problem and a fix; i implemented the fix and it didn't work. whoa! back to stackoverflow to downvote that faux-fix-provider, and ...
it was me. i implemented my own faulty non-fix, which i came up with the first time i stumbled over the problem (and which was later forgotten). i'll now have to downvote myself for providing bad information.
the reason my initial "fix" worked was not because of of a "rendering pass"; the problem was that the pager's content was controlled by a spinner. both the spinners and the pagers state were restored onResume, and because of this the spinners onItemSelected listener was called during the next event propagation cycle, which did repopulate the viewpager - this time using a different default value.
removing and resetting the listener during the initial state restoration fixed the issue.
the fix above kind-of worked the first time, because it set the pagers current position after the onItemSelected event fired. later, it ceased to work for some reason (probably the app became too slow - in my implementation i didn't use 100ms, but 10ms). i then removed the postDelayed in a cleanup cycle, because it didn't change the already faulty behaviour.
update 2: i can't downvote my own post. i assume, honorable seppuku is the only option left.
回答2:
I had a similar issue in the OnCreate of my Activity. The adapter was set up with the correct count and I applied setCurrentItem after setting the adapter to the ViewPager however is would return index out of bounds. I think the ViewPager had not loaded all my Fragments at the point i set the current item. By posting a runnable on the ViewPager i was able to work around this. Here is an example with a little bit of context.
// 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);
}
});
回答3:
I found a very simple workaround for this:
if (mViewPager.getAdapter() != null)
mViewPager.setAdapter(null);
mViewPager.setAdapter(mPagerAdapter);
mViewPager.setCurrentItem(desiredPos);
And, if that doesn't work, you can put it in a handler, but there's no need for a timed delay:
new Handler().post(new Runnable() {
@Override
public void run() {
mViewPager.setCurrentItem(desiredPos);
}
});
回答4:
I have the same problem and I edit
@Override
public int getCount() { return NUM_PAGES; }
I set NUM_PAGES
be mistake to 1 only.
回答5:
I had similar bug in the code, the problem was that I was setting the position before changing the data.
The solution was simply to set the position afterwards and notify the data changed
notifyDataSetChanged()
setCurrentItem()
回答6:
I've used the post() method described here and sure enough it was working great under some scenarios but because my data comes from the server, it was not the holy grail.
My problem was that i want to have
notifyDataSetChanged
called at an arbitrary time and then switch tabs on my viewPager. So right after the notify call i have this
ViewUtilities.waitForLayout(myViewPager, new Runnable() {
@Override
public void run() {
myViewPager.setCurrentItem(tabIndex , false);
}
});
and
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();
}
});
}
}
Fun fact: the //noinspection deprecation at the end is because there is a spelling mistake in the API that was fixed after API 16, so that should read removeOnGlobalLayoutListener instead of removeGlobalOnLayoutListener
This seems to be covering all cases for me.
回答7:
Solution (in Kotlin with ViewModel etc.) for those trying to set the current item in the onCreate
of Activity
without the hacky Runnable
"solutions":
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
}
})
This will obviously do the correct order of operations without any hacks with Runnable
or delays.
For the completeness, you usually implement the setItems()
of the adapter (in this case FragmentStatePagerAdapter
) like this:
internal fun setItems(items: List<Item>) {
this.items = items
notifyDataSetChanged()
}
回答8:
For me this worked setting current item after setting adapter
viewPager.setAdapter(new MyPagerAdapter(getSupportFragmentManager()));
viewPager.setCurrentItem(idx);
pagerSlidingTabStrip.setViewPager(viewPager);// assign viewpager to tabs
回答9:
some guy wrote on forums here. https://code.i-harness.com/en/q/126bff9 worked for me
if (mViewPager.getAdapter() != null)
mViewPager.setAdapter(null);
mViewPager.setAdapter(mPagerAdapter);
mViewPager.setCurrentItem(desiredPos);
回答10:
I've done it this way to restore the current item:
@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();
}
回答11:
This is a lifecycle issue, as pointed out by several posters here. However, I find the solutions with posting a Runnable
to be unpredictable and probably error prone. It seems like a way to ignore the problem by posting it into the future.
I am not saying that this is the best solution, but it definitely works without using Runnable
. I keep a separate integer inside the Fragment
that has the ViewPager
. This integer will hold the page we want to set as the current page when onResume
is called next. The integer's value can be set at any point and can thus be set before a FragmentTransaction
or when resuming an activity. Also note that all the members are set up in onResume()
, not in 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);
}
}
回答12:
By the time I call setCurrentItem()
the view is about to be recreated. So in fact I invoke setCurrentItem()
for the viewpager and afterwards the system calls onCreateView()
and hence creates a new viewpager.
This is the reason for me why I do not see any changes. And this is the reason why a postDelayed()
may help.
Theoretical solution: Postpone the setCurrentItem()
invocation until the view has been recreated.
Practical solution: I have no clue for a stable and simple solution. We should be able to check if the class is about to recreate it's view and if that is the case postpone the invocation of setCurrentItem()
to the end of onCreateView()
回答13:
I was working on this problem for one week and I realized that this problem happens because I was using home activity context in view pager fragments and we can only use context in fragment after it gets attached to activity..
When a view pager gets created, activity only attach to the first (0) and second (1) page. When you open second page, the third page gets attached and so on! When you use setCorrentItem()
method and the argument is greater than 1, it wants to open that page before it is attached, so the context in fragment of that page will be null and the application gets crashed! That's why when you delay setCorrentItem()
, it works! At first it gets attached and then it'll open the page...
回答14:
CLEAN AND SIMPLE No need to add post method just setCurrentItem after calling notifyDataSetChanged().
回答15:
You need to call pager.setCurrentItem(activePage) right after pager.setAdapter(buildAdapter())
@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);
}
来源:https://stackoverflow.com/questions/19316729/android-viewpager-setcurrentitem-not-working-after-onresume