I have 3 fragments which is settled in TabLayout with ViewPager.
The Scenario is:
First fragment has one ImageView with some EditText and RadioButton with NEXT Button
Second fragment has 5 EditText with NEXT Button
Third Fragment has 4 ImageView and 4 EditText with SignUp Button
all fields are required.
Now what I have done:
Checked all validation on Button click of Fragment 1 and moved to NEXT fragment.
if (getActivity() != null) {
((RegisterActivity) getActivity()).mViewPager.setCurrentItem(1, true);
}
Same process
Same process and proceed for SignUp.
Problem:
How can I check values When I move to second fragment without clicking button (Sliding Viewpager or Clicking on TAB)
How can I update all final values (when I change it but do not click on button and move forward)
I have tried to use onAttach and onDetach for saving values but didn't worked.
Any solution for manage all data and check validation in each situation?
You can track of current selected tab by TabLayout.ViewPagerOnTabSelectedListener,
take a look here for more information docs
In general just implement this interface and then you will be notified when user swipe tabs or if you programmatically swipe you will still get notified.
Not the best solution but the workaround I have recently implemented.
Take boolean[] of total fragments.
like:
private boolean[] arrLastPageDone = new boolean[]{false, false, false};
If all values of fragment1 validated, set true on first position. Same for other fragments.
Simply check values in onPageSelected.
like:
pager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
if (position > 0 && !arrLastPageDone[position - 1]) {
pager.setCurrentItem(pager.getCurrentItem() - 1);
}
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
And it works. It dosen't let swipe to next fragment until all values validated.
And you can save those data to SingleTon Class until all process finished.
Currently I'm building an app with different tabs in it. In one of the tabs I have a camera fragment that I start and show to the user. To implement smooth swiping I want to load the tab content itself, so that the user his swiping doesn't hang. When it's loaded the camera should be started.
Is there some sort of a callback/listener/trick that can be used to see if a fragment is fully loaded?
Tab layout is bound to view pager using tablayout.setupWithViewPager(viewPager), so you can use view pager page listener to detect tab loaded event.
Official Documentation
If you're using a ViewPager together with TabLayout, you can call setupWithViewPager(ViewPager) to link the two together.
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
//tab loaded
}
#Override
public void onPageScrollStateChanged(int state) {
}
});`
It appears that many people are using setOnPageChangedListener() on the ViewPager. We ended up implementing this after noticing a race condition due to Fragments not loading synchronously due to using the FragmentManager. Check out the answer here.
Which lifecycle callback is called when a fragment pager adapter's fragment comes to screen?
I've got an App with a LinearLayout to which rectangular fragments are added below each other over time from top to bottom.
At some point the screen is full and the new fragments are "hidden" outside the view of the screen. What I'd like to do then is to make the App scroll to bottom every time a new fragment is added.
A fragment is added everytime a button in the activity is pushed or a checkbox in the above fragment is checked.
So I've tried the following in the onClickListeners and in the onCreateView of the fragments:
fm.beginTransaction().add(R.id.activity_uhf_layout, fragment).commit(); //Adding fragment
runOnUiThread(new Runnable() {
#Override
public void run() {
scrollView.scrollTo(0, scrollView.getBottom());
}});
This works sometimes, the fragment is added and the screen scrolls down, but its like 1/8 times.
I've tried other things such as:
scrollView.post(new Runnable() {
#Override
public void run() {
scrollView.scrollTo(0, scrollView.getBottom());
}
});
Which basically gives the same result and I've tried without the Runnable post, which doesn't work at all.
The result I'm getting is that the layout is only scrolled down to the bottom of the fragment which is already present and not all the way down to the end of the new fragment which has just been added by the fragmentmanager.
To me it seems that even though the fragment is added, the LayoutManager hasn't really "detected it" or something similar, but how to get around it, I'm not really sure about.
You could also try doing this.
fm.executePendingTransactions();
runOnUiThread(new Runnable() {
#Override
public void run() {
//Linearlayout is the layout the fragments are being added to.
View view = linearLayout.getChildAt(linearLayout.getChildCount()-1);
scrollView.scrollTo(0, view.getY() + view.getHeight());
}});
I don't know that the executePendingTransactions() is necessary, but if the view has been added, then that should work.
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);
}
i wrote a FragmentActivity with a ViewPager, i did not want to use the actionbar for the tabs (because i did not want the tabs to be positioned over the menu) and also not the PagerTabStrip solution, because i could not customize it as i wanted.
so i wrote an own linearlayout as tabs container:
<LinearLayout
android:id="#+id/main_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/white"
android:orientation="horizontal" >
</LinearLayout>
with an View.OnClickListener for each of each tab
OnClickListener onClickListener = new View.OnClickListener() {
#Override
public void onClick(final View view) {
mapTabs.get(lastSelectedTab).setSelected(false);
view.setSelected(true);
Runnable runnable = new Runnable() {
#Override
public void run() {
int index = Integer.parseInt(view.getTag().toString());
FragmentActivity.setTab(index);
lastSelectedTab = index;
}
};
handler.post(runnable);
}
};
}
public static void setTab(int index) {
FragmentActivity.viewPager.setCurrentItem(index, true); // true or false does not make any difference
}
this is the method i wrote to add the single tabs to the tab container (main_tabs):
private void addTab(LayoutInflater inflater, ViewGroup tabs, int imageId, View.OnClickListener onClickListener) {
inflater.inflate(R.layout.main_tab, tabs);
int index = tabs.getChildCount() - 1;
ImageView ivTab = (ImageView) tabs.getChildAt(index);
ivTab.setTag(String.valueOf(index)); // stored the tab index
ivTab.setImageResource(imageId);
ivTab.setOnClickListener(onClickListener);
mapTabs.put(index, ivTab); // add an imageview to the tab
}
i used the runnalbe so that i see the highlighting of the tab immediately, and not after the slight delay when the new page appears. i also stored the last selected tab in order to deselect it when the new one gets selected.
i store all tabs in a map in order to access them later for selection.
this is the OnPageChangeListener i wrote (to highlight the associated tab):
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
mapTabs.get(position).setSelected(true); // selected the tag asociated with the current visible page
mapTabs.get(lastSelectedTab).setSelected(false); // deselectes the last selected tab
lastSelectedTab = position;
ListFragment<?> item = FragmentActivity.fragmentAdapter.getItem(position);
setTitle(item.getTitle());
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
everything works fine, i am just not clear about the performance:
when i scroll to the next page, everything goes very fast and smooth. the next page appears immediately without any dealy; only the menu in the action bar (created in the onCreateOptionsMenu(Menu menu, MenuInflater inflater) method of each Fragment) appears with a slight delay (about 500-700 ms).
but when i click on one of the tabs, the selected page appears not immediately but with a slight delay (actually the same time the menu needs to appear as mentioned above: menu and page appear together at the same time). bzw. this happens also when i use the tabstrip solution.
i guess if its possible to achieve a delay-less page change by scrolling, it should be possible somehow to achieve the same performance for the tab clicks.
but what do i do wrong?
and is there a possibility to make the menu appear immediately just like the pages when scrolling? can the different menus not be cached just like the pages?
the solution was extremely simple. i just had to keep all my 4 tabs in the ViewPager so that they don't need to get recreated (and therefore don't cause the short delay after each tab-clicki). i just had to add following lines to the onCreate method.
viewPager = (ViewPager) findViewById(R.id.pager);
viewPager.setOffscreenPageLimit(4);
to avoid delay between tabs click in view pager should set the limit of tabs like
pager.setOffscreenPageLimit(4);
try this
myViewPager.animate().translationX(0f).setDuration(1000);
where 1000 is delay in milliseconds