What I Have
I have a ViewPager with 5 fragments. I want to animate some TextViews inside the fragments whenever they become visible to the user.
I can't use onResume() as the fragments to the left and right are already created. I can't use setUserVisibilityHint() as it is called before onCreateView() so the views are not ready yet.
So what should be the way to animate the views whenever a particular fragment becomes visible to the user?
I'm not sure, but if you say that setUserVisibilityHint calls before onCreateView, than check view on null here (make reference on view - field), and if it not null - animate it. Also animate it always in onCreateView.
(1) I can't use onResume() as the fragments to the left and right are already created.
(2) I can't use setUserVisibilityHint() as it is called before onCreateView() so the views are not ready yet.
So what should be the way to animate the views whenever a particular fragment becomes visible to the user?
You're right on (1) and (2). However, setUserVisibilityHint() gets called Once Again with a True value after the Fragment comes to Front on Display. But on First Run the Fragment to be shown gets its setUserVisibilityHint() called before onCreateView().
SOL: You should use the above said behaviour of setUserVisibilityHint() along with onResume() to animate the views whenever a particular fragment becomes visible to the user.
Scenario 1: On First Run: Displayed Fragment's setUserVisibilityHint(boolean isVisibleToUser) gets called with
True param value. But as the Fragment's State is not Resumed we postpone and let the onResume() handle animation.
Scenario 2: For Other Fragments that are already in Resume State, setUserVisibilityHint(boolean isVisibleToUser) will get called with
True param it they come on to Display. Here you check for the
Fragment Animated or not and Do animation.
CODE
a) Declare two Global Boolean Fields: isAnimated and isOnDisplay
a.1) Set isAnimated boolean to True;
b) Override setUserVisibilityHint(boolean isVisibleToUser):
Here you set isOnDisplay boolean to isVisibleToUser and check is the Fragment Not Already Animated and is in Resumed State and is Visible to User.
{ if(!isAnimated && isResumed() && isVisibleToUser) // DO Animation }
c) Override onResume()
Check if the Fragment Not Already Animated and is Visible to User.
{ if(!isAnimated && isVisibleToUser) // DO Animation }
I know this answer might be a bit late, but I hope it can help others in a similar situation.
You could use FragmentViewPager library (I am the author), which deals with the issue you are facing for you. Its features are:
allows its Fragment pages to get notified when they are actually
visible/invisible to the user
supports multiple levels of FragmentViewPagers (nesting)
provides methods to control its paging
A basic usage would be:
Attach FragmentViewPager programmatically or via XML to an Activity
or Fragment, as you would with native ViewPager
Set FragmentViewPager's adapter. Your adapter should inherit
com.sbrukhanda.fragmentviewpager.adapters.FragmentPagerAdapter or
com.sbrukhanda.fragmentviewpager.adapters.FragmentStatePagerAdapter
Override onResumeFragments() of the hosting Activity and call
FragmentViewPager.notifyPagerVisible():
private FragmentViewPager mFragmentsPager;
#Override
public void onResumeFragments() {
super.onResumeFragments();
mFragmentsPager.notifyPagerVisible();
...
}
or onResume() of the hosting Fragment and call
FragmentViewPager.notifyPagerVisible():
private FragmentViewPager mFragmentsPager;
#Override
public void onResume() {
super.onResume();
mFragmentsPager.notifyPagerVisible();
...
}
Override onPause() of the hosting Activity or Fragment and call
FragmentViewPager.notifyPagerInvisible():
private FragmentViewPager mFragmentsPager;
#Override
public void onPause() {
super.onPause();
mFragmentsPager.notifyPagerInvisible();
...
}
Implement FragmentVisibilityListener on all Fragment pages that you
wish to receive callbacks for their visibility state
You are ready to go!
If you wish to see a more complete sample code, then check project's sample project.
If you want to do it in individual fragments, then you can use isVisible()
for each fragment in your fragment transition and create a listener. Whenever a fragment will become visible , listener will be invoked and each fragment will implement that listener and do your intended task in the overridden method.
Related
I have 4 fragments in my viewpager I want to send a call for data every time when my fragment is visible to user. I am using uservisibilityhint() function but it is called only first time in view page and then again it is never called whenever that fragment is visible . is there any way to call it manually every time when the fragment is visible so that I can now that my fragment is visible or not to the user . how can i do this thing
Use
#Override
public void setUserVisibleHint(boolean visible) {
super.setUserVisibleHint(visible);
if (visible && isResumed()) {
// Your code
}
}
Use a Listener on your ViewPager in the Activity like below:
viewPager.setOnPageChangeListener(new OnPageChangeListener...
and here in the listeners callback detect the current View using this method:
ViewPager.getCurrentItem()
and then wire an interface between your activity and four fragments so you can notify which View should try loading fresh data.
There is a ViewPager with Fragments generated dynamically.
Questions:
What is the way to catch the moment when user slides away from the fragment (so I can bring it into "clean", "init" state)?
or
How to catch moment when a Fragment is scrolled in?
Problems:
Have checked Fragment Lifecycle, but none of them is getting triggered when scrolled out/in (using ViewPager)
Lifecycle phases are triggered only if I scroll 2+ Fragments (those 3rd one is getting Paused/Resumed).
To get a callback when a fragment gets visible to the user you can override the setUserVisibleHint method, like this:
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(isVisibleToUser){
//Put your 'init' logic here
}
}
the variable isVisibleToUser will give the status of the visibility, so you can use the same method to handle when the fragment goes out.
addOnPageChangeListener
void addOnPageChangeListener (ViewPager.OnPageChangeListener listener)
Add a listener that will be invoked whenever the page changes or is incrementally scrolled. See ViewPager.OnPageChangeListener.
Components that add a listener should take care to remove it when finished. Other components that take ownership of a view may call clearOnPageChangeListeners() to remove all attached listeners.
You can implement this listener to track the movement of the fragments in the ViewPager.
There is also a method setOffscreenPageLimit() which Set the number of pages that should be retained to either side of the current page and it's default value is 1 and minimum value can be set to 0.
I have an activity MainActivity there are three fragments associated with this activity.
Now one of my fragment Timeline has a listview. Which I populate from a Database in the backend. I use an AsyncTask to fetch values from the DB and process them to the List. I trigger this AsyncTask in the onCreate of the Fragment Timeline.
Now from Timeline on click of any list item I navigate to a different Activity called as DetailActivity
The problem is whenever I press back from the DetailActivity the onCreate of my MainActivity is called and my list refreshes again - the whole DB operation is called again and my list does not retain its state.
I am calculating the visible items of my List before I navigate away from the Fragment but I am forced to use static values for these variables so that I retain the position. How to avoid this?
Below are the snippets of my onPause and onResume as laid down in the fragment Timeline
static int index;
static int top;
#Override
public void onPause(){
System.out.println("onPause");
index = lv.getFirstVisiblePosition();
View v = lv.getChildAt(0);
top = (v == null) ? 0 : v.getTop();
super.onPause();
uiHelper.onPause();
}
#Override
public void onResume(){
super.onResume();
//dbHelper.open();
System.out.println("onResumr");
lv.setSelectionFromTop(index, top);
ActionBar actionBar = getActivity().getActionBar();
actionBar.setTitle("Timeline");
uiHelper.onResume();
AppEventsLogger.activateApp(getActivity());
updateUI();
}
This also forces my AsyncTask to run again and again, which is an overhead.
Edit:
The root of this problem - After struggling for so many days I borrowed a friends phone to test and all was sorted on this new phone. I found out that I had turned on the Do not keep Activities option in my Developer Settings. The Dumb me!!
This is, unfortunately, the default behavior of the Fragment class. A Fragment is destroyed whenever the containing Activity is paused, and recreated whenever the containing Activity is resumed. If you use an Activity instead of a Fragment for the list, you would not experience the same behavior. With an Activity:
AsyncTasks and/or web services would not be called again.
The list would show the previously scrolled position.
If you want the same behavior with a Fragment, you need to override the onSaveInstanceState() method. And while interesting, it is not a small amount of work.
EDIT:
Make sure the Do not keep Activities option is unselected in your phone's Developer Settings. This, though, does not change the essential behavior of the Fragment class that I have outlined above.
You can call setRetainInstance(true) on your fragment. The lifecycle will be slightly different though.
A nice view of a fragment's lifecycle is available here
http://corner.squareup.com/2014/10/advocating-against-android-fragments.html
I have a ViewPager with multiple fragments. In one Fragment I play audio. When I swipe to another fragment I want to stop the audio playback. How do I detect that the another fragment is now visible in the ViewPager?
I've tried overriding onStop and onHiddenChanged. No success. There must be some "you're not active anymore" method to override. No?
I did some digging and it turns out that ViewPager will call both: setUserVisibleHint and setMenuVisibility. I would override setUserVisibleHint since the documentation for setUserVisibleHint states:
Set a hint to the system about whether this fragment's UI is currently visible to the user. This hint defaults to true and is persistent across fragment instance state save and restore.
An app may set this to false to indicate that the fragment's UI is scrolled out of visibility or is otherwise not directly visible to the user. This may be used by the system to prioritize operations such as fragment lifecycle updates or loader ordering behavior.
Try putting this code in your fragment:
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
// Make sure that we are currently visible
if (this.isVisible()) {
// If we are becoming invisible, then...
if (!isVisibleToUser) {
Log.d("MyFragment", "Not visible anymore. Stopping audio.");
// TODO stop audio playback
}
}
}
I am asking this cuz I am sort of curious.
1 ) Most google demos finds fragments by its ID if the fragment is already been created in xml.
So if we take that approach, the way we show fragments is by hiding it and showing it since the fragments are already created.
2) There are also examples provided by google where you can create the fragment with a constructor and inflate it. This acts weird by the way like getActivity() returns null if it is called with in that fragment.
So If i take the first approach I have to hide and show the fragments.
So why does not google provide hooks to the fragments like onHide or onShow
so that we can handle things properly instead if doing the clean up ourselves with functions that we implement and call explicitly.
If you want to hook op on onHide/onShow just override
public void onHiddenChanged(boolean hidden) {
}
in your fragment.
By Overrinde setUserVisibleHint you can easily track it.
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(isVisibleToUser){
//When fragment is visible
}
Log.i("my_fragment","setUserVisibleHint: "+isVisibleToUser);
}
I override the function below to determine whether a fragment is shown or hidden.
#Override
public void setMenuVisibility(final boolean visible)