I am using ViewPager in my app. In each fragment there is a toolbar. On a single tap on the image the the toolbar is animated to the top out of the screen. But I have to notify all the remaining fragments to do the same thing. So that when the user scrolls to the next fragment he doesn't see the toolbar.
I tried adding setUserVisibleHint(), but it did not work as it was called only when the fragment was completely visible, thus showing the toolbar exiting to the user.
Then I tried it in onResume and setting pager.offscreenpagelimit=1, it worked fine for the fragment next to next but did not work for the next fragment.
Thanks!!
First Notify your activity from fragment using:
In Fragment on Animation End:
((YourActivity)getActivity()).hideToolbar();
In Activity:
public void hideToolbar() {
// Redraw view pager without toolbar (notify your adapter create pager without toolbar)
}
Well, why don't you get your toolbar out of your fragments and just create one in activity and change its state on page change (definitely it will not slide but may
You have 2 problems to face.
How to call existing fragments to hide/show their toolbars.
How to create another fragments with hidden toolbar.
First problem can be easly done by using Otto event library found here. Just paste this code in your viewpager fragments:
Bus bus = new Bus();
#Override
protected void onResume() {
super.onResume();
bus.register(this);
}
#Override
protected void onPause() {
super.onPause();
bus.unregister(this);
}
#Subscribe
public void onToolbarEvent(ToolbarEvent event) {
//do your toolbar logic
}
Then in your onClick event on image just put (of course creating bus object before)
bus.post(new ToolbarEvent());
ToolbarEvent can be just empty class. If you read about Otto events you will understand how it works.
Another problem is how to know that the toolbar should be hidden/shown, when viewpager instantiates new fragments? Simply add a boolean flag in your shared prefferences, so every time fragment is created, it can check if it can show toolbar or not e.g. in onViewCreated() method. The example how to use shared prefferences can be found here
Hope I helped a little bit.
Related
I am maintaining a backstack of fragments and pop the stack when back button is pressed. I need to reload data every time a fragment is made visible and do some cleanup when it gets hidden. For this I need to detect when a fragment is shown and hidden. This is a very common question but surprisingly none of accepted answers work for me. I
I am adding fragments to backstack using code like this:
public void pushFragment(Fragment f) {
getFragmentManager().beginTransaction()
.add(R.id.content_frame, f, null)
.addToBackStack(null)
.commit();
}
I am popping fragments off using this code:
public void onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 1) {
getFragmentManager().popBackStack();
} else {
super.onBackPressed();
}
}
I am trying to detect from a fragment class when it becomes visible or hidden (either because it is being popped off the stack or another fragment was pushed on top). So far I have tried these callbacks:
onViewCreated/onDestroyView: Only called when the fragment is added to stack and popped off the stack. Not called when the fragment gets hidden or visible because of other fragments on the stack.
onHiddenChanged: Never called. Many have said on SO that this works. But not working for me for some reason.
setUserVisibleHint: Never called
onStart/onPause etc: They don't really apply here because they simply reflect the lifecycle of the host activity.
Is there a Fragment callback that will let me detect when a fragment is being shown or hidden? I will rather not use a backstack listener because I want every fragment class to have its own show/hide logic.
Edit:
If I use replace() to add the fragment (instead of add()) then the view for the previously shown fragment gets destroyed. As a result if that fragment ever to appear on top of the stack again its view is recreated. In this situation onViewCreated/onDestroyView or onStart/onStop will be called every time a fragment is shown or hidden. I suppose I could use that approach. The down side is that the views are created and destroyed frequently. I might as well be using activities instead of fragments for master-detail navigation in that case.
Edit again
If you need a callback you can override onHiddenChanged like this:
public void onHiddenChanged(boolean hidden) {
if(hidden){// Do you stuff here}
}
As PPartisan mentioned you claimed that onHiddenChanged is never called.
The reason for that is because onHiddenChanged doesn't get called the first time an fragment is shown.
Called when the hidden state (as returned by isHidden()) of the fragment has changed. Fragments start out not hidden; this will be called whenever the fragment changes state from that.
To fix this: add this to your code:
FragmentTransaction mFragmentTransaction = getFragmentManager().beginTransaction();
if (f!= null) {
mFragmentTransaction .hide(f);
}
mFragmentTransaction.add(R.id.content_frame, f, null)
mFragmentTransaction.addToBackStack(null)
mFragmentTransaction.commit();
More on the on this SO thread
From the Android documentation:
void onStart () Called when the Fragment is visible to the user. This
is generally tied to Activity.onStart of the containing Activity's
lifecycle.
void onStop () Called when the Fragment is no longer started. This is
generally tied to Activity.onStop of the containing Activity's
lifecycle.
The correct way is using Fragment Lifecycle's methods.
This is get called then the fragment is changing visibility
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {}
I have a two problems and struggle for a few days. I am not sure what the right way is.
I want to combine a tab layout with a view pager containing three fragments (the tab layout and the view pager are the home screen) and a navigation drawer.
And I think that everything you can navigate to from the drawer should be a fragment accept settings or something similar.
So should I let the tab layout in the layout of the main activities layout or should I put the this layout also in a fragment (so view pager with fragments inside a so calling home fragment)?
When starting the app I check if the user is logged in and if he is download data from Firebase. I do this in another fragment (data fragment) there I setretaininstance to true.
So currently one of the tab fragments needs the data from Firebase but soon all of them will need it.
How do I pass the data (Lists) from the data fragment to the tab fragments or the so called home fragment and then to the tabs.
I already tried interfaces but I don't know how to handle confirmation changes.
Considering the two problems or the combination of both how would you solve this / which design approach is better only use fragments or let the home screen inside the main activity and change the view when the user navigates from the drawer to fragment?
PS. I will add some code as soon as possible.
Thanks.
TabLayout and the ViewPager should be in the Activity layout since it makes more sense, as the Activity manages the Fragments. Also, navigation drawer should navigates through Activities, and Activities between Fragments. Again, that's the logical thing for a decent code organization, and it prevents repetitions in layout (AKA more than one button to navigate to the same fragment).
For the Firebase issue, I suggest you start the call in the Activity, and then call a function in all fragments with the retrieved information. Something like this:
new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for(MyFragment f : mFragmentList)
f.addData(dataSnapshot);
}
}
If you really want the Fragment to make the call to Firebase, then you can create you own Event and Listener set.
public class SnapshotRetrievedEvent {
private static List<SnapshotRetrievedListener> listeners = new ArrayList<>();
public static void addListener(SnapshotRetrievedListener listener){
listeners.add(listener);
}
public static void notify(DataSnapshot snapshot) {
for(SnapshotRetrievedListener listener : listeners)
listener.onSnapshotRetrived( snapshot );
}
}
public interface SnapshotRetrievedListene {
void onSnapshotRetrieved(DataSnapshot snapshot);
}
Then in your Fragment waiting for the snapshot, you add the listener like this:
SnapshotRetrievedEvent.addListener(
new SnapshotRetrievedListener(){
#Override
public void onSnapshotRetrieved(DataSnapshot snapshot){
// Do stuff
}
}
);
onSnapshotRetrieved will be called when you call SnapshotRetrievedEvent.notify(snapshot) in your other Fragment.
Hope this helps
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.
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 just started learning android.Hope this isn't silly question.Here it is
My FragmentAdapter has a List of Fragments,Say 3 Fragments 1,2,3.
I have set this FragmentAdapter for my ViewPager using myViewPager.setAdapter(myFragmentAdapter);
So this application kicks off by displaying view of Fragment 1.How do I start the Application by displaying Fragment 2's View first.
You can use setCurrentItem(index) to switch your current page.
In this case, you would want to do:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ... init code ...
myViewPager.setCurrentItem(1);
}
See the ViewPager docs for more information. You may wish to use the variation of this method which allows you specify where there should be a smooth scroll, and set it as false, otherwise the user may see the animation the moment the app opens and it could be confusing. For example,
myViewPager.setCurrentItem(1, false);