I'm using the following example to impliment my viewPager:
http://code.google.com/p/viewpagerexample/issues/list
The problem with this example is that I can't figure out how to set my starting position, the default starting position is 0. Basically I wan't to be able to control if there is an available view on its left or the right.
Is there any way to control center's View current position? is there a better way to do it?
is it possible to make it circular?
I've found a way to set it's position, which is done outside of the class:
awesomePager = (ViewPager) findViewById(R.id.awesomepager);
awesomePager.setAdapter(awesomeAdapter);
awesomePager.setCurrentItem(CurrentPosition);
and it can be limited by calculating the amount of items I want to fit in to it
I have noticed that if you recreate Activity (orientation change) with ViewPager having FragmentStatePagerAdapter, then the Adapter will reuse it's Fragments. The way to stop it is:
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
if (viewPager != null) {
// before screen rotation it's better to detach pagerAdapter from the ViewPager, so
// pagerAdapter can remove all old fragments, so they're not reused after rotation.
viewPager.setAdapter(null);
}
super.onSaveInstanceState(savedInstanceState);
}
but then after Activity recreation ViewPager alwayes opens page 0 first and setCurrentItem(CurrentPosition); doesn't work. Fix for that is changing page after delay:
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
viewPager.setCurrentItem(newPosition);
}
}, 100);
To start with the last fragment I did this:
PagerAdapter pagerAdapter = new PagerAdapter();
viewPager.setAdapter(pagerAdapter);
viewPager.setCurrentItem(pagerAdapter.getCount() - 1);
I came across a problem whereby if I set the current item before I set the adapter, the first item I get back will always be the one at position 0.
Make sure you do:
awesomePager.setAdapter(awesomeAdapter);
awesomePager.setCurrentItem(CurrentPosition);
and not:
awesomePager.setCurrentItem(CurrentPosition);
awesomePager.setAdapter(awesomeAdapter);
I have an array of size more than 1000 and in dymanic viewpager I was facing leftswipe stuck on firstload. The below code solved this and resulted in smooth scroll:
#Override
onResume(){
super.onResume();
viewPager.setCurrentItem(newPosition);
}
I'm using 3 fragments and on starting my app, the second (middle) fragment will be shown by default. Just I'm using the onResume function and all works great.
#Override
protected void onResume() {
super.onResume();
m_viewPager.setCurrentItem(1);
}
Using viewPager.setCurrentItem(newPosition); shows to the user the transition from the starting page to the newPosition, to prevent that from happening and show the newPosition directly as if it was the starting point, I added false to the second parameter something like this:
int newPosition = pages.size()-1; // Get last page position
viewPager.setCurrentItem(newPosition, false); // 2nd parameter (false) stands for "smoothScroll"
I encountered same problem.
When I initialize ViewPager, the indicator position was 0.
This may depends on amount of calculating for Pager contents. I use ViewTreeObserver like below.
mGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
mViewPager.setCurrentItem(newPosition);
removeOnGlobalLayoutListener(mSlidingTabLayout.getViewTreeObserver(), mGlobalLayoutListener);
}
};
mSlidingLayout.getViewTreeObserver().addOnGlobalLayoutListener(mGlobalLayoutListener);
and,
private void removeOnGlobalLayoutListener(ViewTreeObserver observer, ViewTreeObserver.OnGlobalLayoutListener listener) {
if (observer == null) return;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
observer.removeGlobalOnLayoutListener(listener);
} else {
observer.removeOnGlobalLayoutListener(listener);
}
}
In this way, also never to bother with time setting of delay.
#Override
public Object instantiateItem(ViewGroup container, int position) {
View currentPage = null;
switch(position){
case 0:
currentPage = LayoutInflater.from(context).inflate(R.layout.page0, null)
break;
case 1:
currentPage = LayoutInflater.from(context).inflate(R.layout.page1, null)
///////////// This page will be default ////////////////////
((ViewPager)container).setCurrentItem(position);
////////////////////////////////////////////////////////////
break;
case 2:
currentPage = LayoutInflater.from(context).inflate(R.layout.page2, null)
break;
return currentPage;
}
Related
i update recycleView inside viewpager fragments and call pagerAdapter.notifyDataSetChanged(); to update them. It updates all recycleviews and the app takes long time. Now to overcome this problem i want to update only the current visible fragment and on ViewPage change update other fragments with notifyDataSetChanged(); is there a way to tell which fragment to update?
ViewPagerAdapter
private class MyPageAdapter extends FragmentStatePagerAdapter {
private List<Fragment> fragments;
private int[] mResources;
public MyPageAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.fragments = fragments;
}
#Override
public Fragment getItem(int position) {
return this.fragments.get(position);
}
#Override
public int getCount() {
return this.fragments.size();
}
#Override
public int getItemPosition(Object object) {
MyFragment f = (MyFragment) object;
if (f != null) {
f.update();
}
return super.getItemPosition(object);
}
}
this is fragment
public static class MyFragment extends Fragment implements CustomAdapterOdds.OnGameClickListener, Updateable {
public static final String COLUMN_IN_DISPLAY = "column_in_display";
RecyclerView recyclerView, recyclerView2;
HashMap<String, List<OddsFeed>> oddsList1;
HashMap<String, List<OddsFeed>> oddsList2;
int oddsColDisp;
CustomAdapterOdds adapterOdds1, adapterOdds2;
boolean isvisible = false;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
oddsList1 = new HashMap<>();
oddsList2 = new HashMap<>();
Bundle b = this.getArguments();
oddsColDisp = b.getInt(COLUMN_IN_DISPLAY);
List<GameFeed> leftGames = getArguments().getParcelableArrayList("list");
if (b.getSerializable("hashmap1") != null && b.getSerializable("hashmap2") != null) {
oddsList1 = (HashMap<String, List<OddsFeed>>) b.getSerializable("hashmap1");
oddsList2 = (HashMap<String, List<OddsFeed>>) b.getSerializable("hashmap2");
}
View v = inflater.inflate(R.layout.view_table_viewpager_fragment_layout, container, false);
ArrayList<GameFeed> gameFeedsCol0 = getArguments().getParcelableArrayList("list");
//recycleview1
recyclerView = v.findViewById(R.id.table_view_recycle_view_odds1);
adapterOdds1 = new CustomAdapterOdds(getContext(), leftGames, this, oddsList1, bestSitesList, oddsColDisp);
configRecyclerViewOdds(getContext(), recyclerView, adapterOdds1);
//recycleview2
recyclerView2 = v.findViewById(R.id.table_view_recycle_view_odds2);
adapterOdds2 = new CustomAdapterOdds(getContext(), leftGames, this, oddsList2, bestSitesList, oddsColDisp + 1, true);
configRecyclerViewOdds(getContext(), recyclerView2, adapterOdds2);
return v;
}
#Override
public void setMenuVisibility(final boolean visible) {
super.setMenuVisibility(visible);
if (visible) {
isvisible = true;
}
}
#Override
public void onResume() {
super.onResume();
Toast.makeText(getContext(), "onresume", Toast.LENGTH_SHORT).show();
adapterOdds1.updateListOdds(leftGames, oddsList1, oddsColDisp);
adapterOdds2.updateListOdds(leftGames, oddsList2, oddsColDisp + 1);
}
#Override
public void OnGameClickListener(View view, int position) {
}
#Override
public void update() {
Toast.makeText(getContext(), "update", Toast.LENGTH_SHORT).show();
if (isVisible()) {
adapterOdds1.updateListOdds(leftGames, oddsList1, oddsColDisp);
adapterOdds2.updateListOdds(leftGames, oddsList2, oddsColDisp + 1);
updateViewpager++;
int size = bestSitesList.size() / 2;
adapterOdds1.updateListOdds(leftGames, oddsList1, oddsColDisp);
adapterOdds2.updateListOdds(leftGames, oddsList2, oddsColDisp + 1);
if (updateViewpager == size - 1) {
progressBar.setVisibility(View.INVISIBLE);
updateViewpager = 0;
}
}
}
}
I'm sorry this is a bit of rambling answer but there was not enough code example given to make a functioning answer, so more really trying to explain a concept. Really need details of when and how the data is updated BUT...
As you are using androidx, you might want to consider moving to viewpager2 or with viewpager you can change how the fragment lifecycle states are managed.
If you changed to using BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT https://developer.android.com/reference/androidx/fragment/app/FragmentStatePagerAdapter#BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT when you constructed the Adapter https://developer.android.com/reference/androidx/fragment/app/FragmentStatePagerAdapter#FragmentStatePagerAdapter(androidx.fragment.app.FragmentManager,%20int)
Then all the Fragments will only be brought up to "Started" when created or re-created and only the current one on screen with "Resumed" and then "Paused" when moved off screen.
The exact mechanics depends on how the Fragments recyclerviews get their data and what triggers the update but then general idea is for the Fragments onCreateView to create a lightweight "shell" of a layout, I basically have the static buttons/text and create the recyclerview with an empty dataset.
Then in the onResume method of the Fragment which only gets called for the Fragment currently on Screen it calls your update method to replace the two recyclerview's empty datasets with the actual dataset and does a notifyDataSetChanged() on the recyclerviews.
Therefore when the viewpager is initially created X number of Fragments gets created and there static content is laid out plus 1 Fragment (the current one on screen) gets the recyclerview populated with actual data.
You might also then want to put in some optimisation checks in onResume of the Fragment to check the recyclerview views data has actually changed (a simple size check or using Recyclerview's DiffUtils) otherwise as you move between the Fragment's in the viewpager each Fragment will be paused/resumed.
This really only delays the cost of the two recyclerviews in the Fragment until it is really needed (when it is about to be displayed), it's a form of "Lazy loading"
With moving the "dynamic" data to this "lazy loading" is could be possible to remove the need to notifyDataSetChanged on the Fragments BUT the code snippets don't show enough about how and why the recyclerviews content changes.
With this method when the data is drawn is changed and you might not like how it looks.
This is really at a high level an inversion of the Fragments update logic, instead of saying "The data has changed redraw the data in all the Fragments" it is "This fragment is been shown, redraw the data IF it has been changed since the last time I drew it"
What I want:
I have been trying to implement two directional Endless viewpager in Android, Left to Right & Right to Left
What I did:
I have implemented Endless viewpager adapter, it works fine for right to left direction, I have set current item position by viewPager.setCurrentItem(Integer.MAX_VALUE/2);.
Reference:
Help would be appreciate.
Try to check below FragmentPagerAdapter to get endless viewpager adapter :
public static class MyAdapter extends FragmentPagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return Integer.MAX_VALUE;
}
#Override
public Fragment getItem(int position) {
return getFragmentBasedOnPosition(position);
}
private Fragment getFragmentBasedOnPosition(int position) {
int fragmentPos = position % 3; // Assuming you have 3 fragments
switch(fragmentPos) {
case 0:
return Fragment1.newInstance();
case 1:
return Fragment2.newInstance();
case 2:
return Fragment3.newInstance();
}
}
}
I found solution here.
I hope its helps you.
I have made my own solution. I created a ViewPager that supports infinite looping effect, smart auto-scroll, compatible with any indicators and easy to use. It especially uses it as banners of application with a simple item page.
My custom ViewPager can:
Plug and play, easy to use
Infinite Looping items
Auto-scroll items, allow config, auto-resume/pause when activity/fragment resume/pause
Won't scroll or loop if it has only 1 item
Compatible with many indicators
Github link: https://github.com/kenilt/LoopingViewPager
Hope it helps!
One simple way to achieve this for ViewPager2 is with 3 basic ideas:
Add the first and last items of your data model collection to the end and start, respectively, of that same collection. E.g. listOf(1, 2, 3, 4, 5) should become listOf(5, 1, 2, 3, 4, 5, 1).
When setting up the pager, set it to start with index 1.
When the user scrolls to index 0, have the pager scroll instantly to the penultimate index. When the user scrolls to the last index, have the pager scroll instantly to index 1.
Some sample code to do this is as follows:
1.
private fun <T> List<T>.prepareForTwoWayPaging(): List<T> {
val first = first()
val last = last()
return toMutableList().apply {
add(0, last)
add(first)
}
}
pager.setCurrentItem(1, false)
pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
// We're only interested when the pager offset is exactly centered. This
// will help create a convincing illusion of two-way paging.
if (positionOffsetPixels != 0) {
return
}
when (position) {
0 -> pager.setCurrentItem(adapter.itemCount - 2, false)
adapter.itemCount - 1 -> pager.setCurrentItem(1, false)
}
}
})
Caveat: this code does not reconcile any TabLayout or an empty data model collection.
The proposed solutions are correct but to achieve the result you need to set the initial value of your viewpager to Integer.MAX_VALUE/2.
Anyway, I don't really like this solution, setting getCount to return Integer.MAX_VALUE can have huge impact on application performance.
I figured out a solution in order to avoid this problem using the:
onPageScrollStateChanged Listener
I simply reorder the fragment list, update the viewPager and move to the new page without animation, the result is an endless loop in both directions:
mainViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener()
{
Boolean first = false;
Boolean last = false;
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
{}
#Override
public void onPageSelected(int position)
{
if (position == 0)
{
first = true;
last = false;
}
else if (position == mainFragmentList.size() -1)
{
first = false;
last = true;
}
else
{
first = false;
last = false;
}
}
#Override
public void onPageScrollStateChanged(int state)
{
if (first && state == ViewPager.SCROLL_STATE_IDLE)
{
// Jump without animation
Fragment fragment = mainFragmentList.get(mainFragmentList.size() -1);
mainFragmentList.remove(mainFragmentList.size() -1 );
mainFragmentList.add(0,fragment);
mainPagerAdapter.setData(mainFragmentList);
mainPagerAdapter.notifyDataSetChanged();
Log.e(TAG,mainFragmentList.toString());
mainViewPager.setCurrentItem(1,false);
}
if(last && state == ViewPager.SCROLL_STATE_IDLE)
{
// Jump without animation
Fragment fragment = mainFragmentList.get(0);
mainFragmentList.remove(0);
mainFragmentList.add(fragment);
mainPagerAdapter.setData(mainFragmentList);
mainPagerAdapter.notifyDataSetChanged();
Log.e(TAG,mainFragmentList.toString());
mainViewPager.setCurrentItem(mainFragmentList.size()-2,false);
}
}
});
This is what happens here:
in this example, we have 4 fragments A-B-C-D
if the user is on fragment A (first), the new List will become: D-A-B-C
[remove the last and push as first]
I update the ViewPager and move (without animation) again to fragment A so index 1.
Now the user can continue to scroll left and will find fragment D.
Same thing with the last fragment:
starting again with A-B-C-D
if the user is on fragment D (last), the new List will become: B-C-D-A
[remove the first and push as last]
I update the ViewPager and move (without animation) again to fragment D so index mainFragmentList.size()-2.
Now the user can continue to scroll right and will find fragment A.
Remember to implement FragmentStatePagerAdapter NOT FragmentPagerAdapter
Background
FragmentList contains a view pager with an underlying FragmentStatePagerAdapter to show pages of FragmentDetail
FragmentDetail contains a scroll view
What i want to do
As you swipe, i want the previous FragmentDetail scroll position reset to the top. At the moment, when you swipe back to it, the scroll position goes back to where you left off.
E.g. I am on page 1 of view pager, i scroll to bottom of the current detail fragment. I then go to next page. Finally i go back to the first page, i want the scroll to be at the top and not where i left it
I tried the following
in OnPause of fragment detail, i tried the following code
#Override
public void onPause() {
super.onPause();
mScrollView.scrollTo(0,0);
}
I also tried the following
#Override
public void onPause() {
super.onPause();
mScrollView.post(new Runnable() {
#Override
public void run() {
mScrollView.fullScroll(View.FOCUS_UP);
}
});
}
Also put the scrollTo code in a runnable is well.
Does not scroll to the top
Forget about onPause(); you will drive yourself insane.
The best way I have found to do this sort of thing is override setPrimaryItem() in the FragmentPagerAdapter subclass. setPrimaryItem() is called whenever a fragment is displayed by the ViewPager.
Let's say you have an interface that looks like this:
public interface ResettableFragment {
public void reset();
}
and so your fragment implementation is:
#Override
public void reset() {
mScrollView.scrollTo(0,0);
}
In your FragmentPagerAdapter subclass, create a member
private ResettableFragment mLast;
and override setPrimaryItem() like this:
#Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
if (mLast != null) {
mLast.reset();
mLast = null;
}
if (object instanceof ResettableFragment) {
mLast = (ResettableFragment) object;
}
}
Some people prefer to register a ViewPager.OnPageChangListener which overrides onPageSelected() to do the reset logic instead of overriding setPrimaryItem() in the adapter.
That might look something like this:
viewPager.addOnPageChangeListener(new SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
if (position == POSITION_OF_PAGE_TO_RESET + 1 ||
position == POSITION_OF_PAGE_TO_RESET - 1) {
FragmentPagerAdapter adapter = (FragmentPagerAdapter) viewPager.getAdapter();
ResettableFragment fragment = (ResettableFragment) adapter.getItem(position);
fragment.reset();
}
}
});
I have created 30 scrollable tabs using tablayout.
So first three tabs are visible on screen and rest of them are invisible which can be scroll using swipe gesture.
The problem is when I am selecting last tab programmatically but it is not get visible (tab layout not get scrolled to last tab).
How can I make tablayout to scroll to last tab?
I found the solution.
First I had found the width of tablayout and scroll it's x position to width and than called the select() method of last tab.
And it works fine.
Below is the code.
mTabLayout.setScrollX(mTabLayout.getWidth());
mTabLayout.getTabAt(lastTabIndex).select();
Updated:
If above is not working you can use the below code as well, it is also working fine.
new Handler().postDelayed(
new Runnable() {
#Override public void run() {
mTabLayout.getTabAt(TAB_NUMBER).select();
}
}, 100);
write this method in your custom tablayout (Your own layout which extends tablayout). So, in future you can use this method whenever you need instad of code duplication
public void selectTabAt(int tabIndex) {
if (tabIndex >= 0 && tabIndex < getTabCount() && getSelectedTabPosition() != tabIndex) {
final Tab currentTab = getTabAt(tabIndex);
if (currentTab != null) {
this.post(new Runnable() {
#Override
public void run() {
currentTab.select();
}
});
}
}
}
If you don't want yo use CustomLayout. you can just do this
final Tab currentTab = mTabLayout.getTabAt(tabIndex);
if(currentTab != null){
mTabLayout.post(new Runnable() {
#Override
public void run() {
currentTab.select();
}
});
}
I found this solution for me:
TabLayout tabLayout = activity.getTabLayout();
tabLayout.setSmoothScrollingEnabled(true);
tabLayout.setScrollPosition(targetChannelPosition, 0f, true);
Also, if you receive this error: "Only the original thread that created a view hierarchy can touch its views.", you can use this code, in order to run on Ui thread:
// find a way to get the activity containing the tab layout
TabLayout tabLayout = activity.getTabLayout();
activity.runOnUiThread(new Runnable()
{
#Override
public void run()
{
TabLayout.Tab tab = tabLayout.getTabAt(targetChannelPosition);
tab.select();
}
});
Are you calling tab.select() before the TabLayout and its children have actually been measured and drawn? If so, your TabLayout won't animate to the selection with tab.select() (or Kayvan N's suggestion of scrollTo()). Using a Handler will probably work, but that's not an ideal solution.
Provided the layout hasn't been laid out yet, a ViewTreeObserver will allow you to move to your selected tab after the layout process is finished.
private void scrollToTabAfterLayout(final int tabIndex) {
if (getView() != null) {
final ViewTreeObserver observer = mTabLayout.getViewTreeObserver();
if (observer.isAlive()) {
observer.dispatchOnGlobalLayout(); // In case a previous call is waiting when this call is made
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
observer.removeOnGlobalLayoutListener(this);
} else {
//noinspection deprecation
observer.removeGlobalOnLayoutListener(this);
}
mTabLayout.getTabAt(tabIndex).select();
}
});
}
}
}
Please comment if you have any suggestions.
The above answer wouldn't work because first As agirardello mentioned you should not use mTabLayout.getWidth() since it doesn't return what we need (which is the position of the child you want to scroll to) and the updated solution doesn't always work because of a bug in TabLayout (reported here) but a work around is simple.
The tabs on the tabLayout are not direct children of the TabLayout so we need to go one level deeper using
((ViewGroup) mTabLayout.getChildAt(0)).getChildAt(YOUR_DESIRED_TAB_INDEX).getRight()
the only child of tabLayout is a TabLayout.SlidingTabStrip which is also a ViewGroup and getRight() will give us the right most position of our desired tab view. Thus scrolling to that position will give us what we desire. Here is a complete code:
int right = ((ViewGroup) mTabLayout.getChildAt(0)).getChildAt(4).getRight();
mTabLayout.scrollTo(right,0);
mTabLayout.getTabAt(4).select();
NOTE: Make sure you are calling these methods after the layout has been drown (like onResume and not onCreate)
Hope this helps.
new Handler().postDelayed(
new Runnable() {
#Override public void run() {
mTabLayout.getTabAt(TAB_NUMBER).select();
}
}, 100);
The code snippet below works for me
class TriggerOnceListener(private val v: View, private val block: () -> Unit) : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
block()
v.viewTreeObserver.removeOnPreDrawListener(this)
return true
}
}
fun onCreate() {
val position = ***The tab position you want to scroll to, 29 for your case here***
tabLayout.let { it.viewTreeObserver.addOnPreDrawListener(TriggerOnceListener(it)
{ it.setScrollPosition(position, 0f, true) } ) }
}
I dived into Tab.select(), and found Android uses Tablayout.setScrollPosition() to do this scrolling. And in onCreate() the widgets have not been measured, you need to postpone the call until layout is complete.
To select the last tab, use
tabLayout.getTabAt(X).select(); where X is the last tab index
If your TabLayout is used in conjunction with a ViewPager, which is common, simply add the following in the onCreate() method in your Activity:
tabLayout.addOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(viewPager);
viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout);
That some of your tabs are not being shown indicates the tabMode attribute is set to app:tabMode="scrollable".
viewpager.setItem(position) should also set the position of the tab
This solution worked for me. My situation is a little bit different though; in my case, I am using the TabLayout with a ViewPager and adding more views and calling notifyDataSetChange().
The solution is to set a callback on the observer of TabLayout and scroll when the children are actually added to the TabLayout. Here is my example:
/**
Keep in mind this is how I set my TabLayout up...
PagerAdapter pagerAdapter = new PagerAdapter(...);
ViewPager pager = (ViewPager)findViewById(...);
pager.setAdapter(pagerAdapter);
TabLayout tabLayout = (TabLayout)findViewById(...);
tabLayout.setupWithViewPager(pager);
*/
public void loadTabs(String[] topics) {
animateTabsOpen(); // Irrelevant to solution
// Removes fragments from ViewPager
pagerAdapter.clear();
// Adds new fragments to ViewPager
for (String t : topics)
pagerAdapter.append(t, new TestFragment());
// Since we need observer callback to still animate tabs when we
// scroll, it is essential to keep track of the state. Declare this
// as a global variable
scrollToFirst = true;
// Alerts ViewPager data has been changed
pagerAdapter.notifyOnDataSetChanged();
// Scroll to the beginning (or any position you need) in TabLayout
// using its observer callbacks
tabs.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
/**
We use onGlobalLayout() callback because anytime a tab
is added or removed the TabLayout triggers this; therefore,
we use it to scroll to the desired position we want. In my
case I wanted to scroll to the beginning position, but this
can easily be modified to scroll to any position.
*/
if (scrollToFirst) {
tabs.getTabAt(0).select();
tabs.scrollTo(0, 0);
scrollToFirst = false;
}
}
});
}
Here is my code for the PagerAdapter if you need it too lol:
public class PagerAdapter extends FragmentStatePagerAdapter {
private List<Fragment> fragments;
private List<String> titles;
public PagerAdapter(FragmentManager fm) {
super(fm);
this.fragments = new ArrayList<>();
this.titles = new ArrayList<>();
}
/**
* Adds an adapter item (title and fragment) and
* doesn't notify that data has changed.
*
* NOTE: Remember to call notifyDataSetChanged()!
* #param title Fragment title
* #param frag Fragment
* #return This
*/
public PagerAdapter append(String title, Fragment frag) {
this.titles.add(title);
this.fragments.add(frag);
return this;
}
/**
* Clears all adapter items and doesn't notify that data
* has changed.
*
* NOTE: Rememeber to call notifyDataSetChanged()!
* #return This
*/
public PagerAdapter clear() {
this.titles.clear();
this.fragments.clear();
return this;
}
#Override
public Fragment getItem(int position) {
return fragments.get(position);
}
#Override
public CharSequence getPageTitle(int position) {
return titles.get(position);
}
#Override
public int getCount() {
return fragments.size();
}
#Override
public int getItemPosition(Object object) {
int position = fragments.indexOf(object);
return (position >= 0) ? position : POSITION_NONE;
}
}
I wonder if this is answer will be relevant since its coming very late. i actually achieved it in C# using Xamarin.
tabs.GetChildAt(0).Selected = true;
viewPager.SetCurrentItem(0, true);
tab = tabLayout.getSelectedTabPosition();
tab++;
TabLayout.Tab tabs = tabLayout.getTabAt(tab);
if (tabs != null) {
tabs.select();
}
else {
tabLayout.getTabAt(0).select();
}
if you want next tab on click event then use this code its work perfactly
I've got this strange issue, ViewPager's setCurrentItem(position, false) works perfectly fine, then im switching to another activity, and after I'm back to the first activity, the ViewPager always ends up on the first item. Even though I've added setCurrentItem to onResume method it still ignores it. It's not even throwing any exception when I'm trying to set item to out of bounds index.
Though later on when I call this method, when the button "next" is tapped, it works like expected.
Checked my code 10 times for any possible calls to setCurrentItem(0) or something but it's just not there at all.
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.
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);
}
});
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);
}
});
ViewTreeObserver can be used to avoid a static delay.
Kotlin:
Feel free to use Kotlin extension as a concise option.
view_pager.doOnPreDraw {
view_pager.currentItem = 1
}
Please, make sure you have a gradle dependency: implementation 'androidx.core:core-ktx:1.3.2' or above
Java
OneShotPreDrawListener.add(view_pager, () -> view_pager.currentItem = 1);
A modern approach in a Fragment or Activity is to call ViewPager.setcurrentItem(Int) function in a coroutine in the context of Dispatchers.Main :
lifecycleScope.launch(Dispatchers.Main) {
val index = 1
viewPager.setCurrentItem(index)
}
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()
I have the same problem and I edit
#Override
public int getCount() { return NUM_PAGES; }
I set NUM_PAGES be mistake to 1 only.
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);
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()
}
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
^^
ON Global
instead of
removeGlobalOnLayoutListener
^^
ON Layout
This seems to be covering all cases for me.
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 the second page, the third page gets attached and so on! When you use setCurrentItem() 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 setCurrentItem(), it works! At first it gets attached and then it'll open the page...
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);
}
}
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
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();
}
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()
I use the dsalaj code as a reference. If necessary I share the code with the complete solution.
I also strongly recommend using ViewPager2
Solution
Both cases have to go within the Observer {}:
First case: Initialize the adapter only when we have the first data set and not before, since this would generate inconsistencies in the paging. To the first data set we have to pass it as the argument of the Adapter.
Second case: From the first change in the observable we would have from the second data sets onwards which have to be passed to the Adapter through a public method only if we have already initialized the adapter with a first data set.
GL
I was confused with the onActivityCreated() getting invoked for unrelated tab #Mahdi Arabpour was an eye opener for me :)
For me the problem was the third page (as elaborated by #Mahdi Arabpour above) was getting reconstructed when I click the second tab, etc and it was losing its data adapter, setting it again in onActivityCreted solves my problems:
if (myXXRecyclerAdapter != null) {
myXXRecyclerAdapter = new MyXXRecyclerAdapter(myStoredData);
mRecyclerView.setAdapter(myXXRecyclerAdapter );
return;
}
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);
}