I add a fragment with a shared element transition like so
currentFragment.setEnterTransition(TransitionInflater.from(context).inflateTransition(android.R.transition.fade));
currentFragment.setExitTransition(TransitionInflater.from(context).inflateTransition(android.R.transition.fade));
Transition transition = TransitionInflater.from(context).inflateTransition(android.R.transition.slide_right);
transition.setDuration(context.getResources().getInteger(R.integer.fragment_transition_duration));
and then
targetFragment.setSharedElementEnterTransition(TransitionInflater.from(context).inflateTransition(R.transition.change_image_transform));
targetFragment.setEnterTransition(TransitionInflater.from(context).inflateTransition(android.R.transition.fade));
targetFragment.setReturnTransition(transition);
The issue I'm seeing is that once I've navigated to several of these fragments in a row, if I then tap the back button - calling the default onBackPressed() - repeatedly and very quickly, the activity shows the wrong fragment as visible. When I tap on the screen, the click events go to the correct fragment (maybe beneath the visible fragment), but I cannot see that correct fragment.
If I tap the back button more slowly, I get the correct behavior. Has anyone ran into a scenario like this before?
Edit : this is what FragmentActivity is doing
/**
* Take care of popping the fragment back stack or finishing the activity
* as appropriate.
*/
public void onBackPressed() {
if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
supportFinishAfterTransition();
}
}
Another thing to mention is that when I remove the Return Transition I do not have this problem.
Ok, I found the answer if anyone is interested. I don't know why this works, but it seems to fix my issue.
If you have a fragment which has a return transition set on it, then in your onDestroyView() call the method setReturningTransition(null).
Related
I have an activity with two containers for fragments, container_left and container_right.
At the beginning of navigation, a list fragment is in container_left, and the fragment that loads into container_right is a screen with several buttons.
When someone selects a button in the fragment in container_right, that fragment replaces the one in container_left, and a new fragment is loaded into container_right. At this point, I have saved this transaction to the backstack. If the back button is pressed at this point, the original list loads into container_left, and the button fragment loads into container_right. But if the person selects another button (now in the left hand frame), it adds a different fragment to the right hand container. I don't want to add the new transaction to the backstack, as I don't want to save the transactions where only fragment_container_right changes. I want the back button to only change the positions of the fragments.
The problem is, the transaction in the backstack is looking for the original fragment that was removed in the right frame, and since that has changed, it doesn't remove the new fragment, so the button fragment appears ON TOP of the new fragment in container_right.
I have been messing with this for a while, but I can't figure this one out.
How can I set this up so that when it pops the stack, any fragments currently in container_right are removed, even if they aren't the ones that were there when the transaction was committed?
here is a sample of what I have so far for loading the frames. As you can see, I check to see if the control buttons are in the left frame before committing, so it isn't loading a new instance on every button push, and I do the same for each fragment loading into container_right, so I'm not creating a new fragment if someone hits the button for a fragment that is already loaded. The only issue left is the backstack transaction.
if (!(controlsInLeftFrame)) {
getFragmentManager().beginTransaction().
replace(R.id.fragment_container_right, fragAD).
replace(R.id.fragment_container_left, fragRECB).addToBackStack(null).commit();
} else if (!(getFragmentManager().findFragmentById(R.id.fragment_container_right) instanceof FragAttributeDescription)){
getFragmentManager().beginTransaction().
replace(R.id.fragment_container_right, fragAD).commit();
}
}
if (message.equals("Movement")) {
FragRaceEditorMovement fragRM = new FragRaceEditorMovement();
fragRM.setArguments(bundle);
if (!(controlsInLeftFrame)) {
getFragmentManager().beginTransaction().
replace(R.id.fragment_container_right, fragRM).
replace(R.id.fragment_container_left, fragRECB).addToBackStack(null).commit();
} else if (!(getFragmentManager().findFragmentById(R.id.fragment_container_right) instanceof FragRaceEditorMovement)){
getFragmentManager().beginTransaction().
replace(R.id.fragment_container_right, fragRM).commit();
}
Okay, I figured it out.
overriding onBackPressed() worked. I had tried it once before, but realized that I had forgotten to put super.onBackPressed() AFTER removing the right hand fragment.
public void onBackPressed() {
getFragmentManager().beginTransaction().remove(getFragmentManager().findFragmentById(R.id.fragment_container_right)).commit();
super.onBackPressed();
}
this appears to have solved the problem adequately
I'm using a navigation drawer with fragments similar to the gmail app. But I am encountering two issues:
1: Suppose I select item x from the nav drawer. The corresponding fragment(fragment x) is displayed with no problem. However, when I change the orientation, the activity is recreated and fragment 1 (default fragment) is displayed , even though the navigation drawer shows item x as checked.
2: Again Suppose I select item x. Again the corresponding fragment(fragment x) is displayed with no problem. Now I close the app by pressing the home button and open up 10 other apps. After some time when I re-open my app, the activity is recreated and fragment 1 (default fragment) is displayed, even though the navigation drawer shows item x as checked.
Both the problems are similar and probably require the the same solution. How do I solve this?
In the Android Manifest, add
android:configChanges="keyboardHidden|orientation|screenSize"
This will stop the Activity from refreshing on such changes, including the case you mentioned, which is an orientation change.
For the Activity to resume after restoring from a minimised position, override the onPause and onResume methods in the Activity.
#Override public void onPause() {
super.onPause(); // Always call the superclass method first }
#Override public void onResume() {
super.onResume();
//Do the things you want to do }
For reference: https://developer.android.com/training/basics/activity-lifecycle/pausing.html
I have 2 fragments in a ViewPager and I want the window to adjust differently to the soft keyboard on the 2nd fragment. Here's what I'm trying:
#Override
public void onPageSelected(int position) {
if(position == 1){ // desired for 2nd fragment
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
} else { // desired for 1st fragment
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED);
}
}
Observed behavior:
Enter 1st fragment and default softInputMode is working, as expected.
Swipe to 2nd fragment and breakpoint shows that the softInputMode should be set to ADJUST_NOTHING, but everything still behaves like the default.
Swipe back to 1st fragment and it behaves with ADJUST_NOTHING.
Swiping back and forth now reveals both fragments to behave like ADJUST_NOTHING, even though breakpoints show these calls are being made.
To top it off, I can switch fragments all I want and the input mode will behave as default until I pull up the soft keyboard. Then it starts its migration toward ADJUST_NOTHING. I'm quite baffled.
I don't have any relevant flags in the manifest, although in my Activity onCreate() I do set the input mode to SOFT_INPUT_STATE_ALWAYS_HIDDEN.
The solution I found works well enough, although I hope there's a smoother way out there somewhere. I did two things.
First, I stopped using SOFT_INPUT_ADJUST_UNSPECIFIEDas my "default" input state because the WindowManager seems to treat changes to and from this state a bit differently. 'SOFT_INPUT_ADJUST_RESIZE' gives the behavior I desire, so I changed my method to
#Override
public void onPageSelected(int position) {
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN
);
hide_keyboard(activity);
if(position == 1){ // desired for 2nd fragment
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING);
} else { // desired for 1st fragment
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
}
}
This fixed the "getting stuck in a certain input mode" issue. Still, I wasn't getting the input mode I wanted on my 2nd fragment, so I added a callto set the input mode right before launching the DialogFragments with text input fields from my 2nd fragment.
getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING); // fragment behind doesn't get readjusted from keyboard.
editCatFragment.show(getActivity().getSupportFragmentManager(), "editCatFrag");
This actually doesn't guarantee the mode I want the first time the fragment is launched, but makes it work the 2nd time which, due to how my app is designed, actually works out okay.
Hopefully this helps someone and hopefully there's a better way of solving this problem. Thanks!
I have an app with a Home screen that has 2 fragments (for now) and a navigation drawer. Currently I load the fragment A (Explore) on startup and load fragment B when clicked. From then on, I show and hide fragments. It's faster than recreating fragments on every click and my fragment A takes some time to load.
I've noticed that when I go to fragment B and go to another activity (let's call it activity 2) from there and leave the app and wait for it to be killed (or do something crazy like change the device language), and then come back to the same activity, it's still there. When I press back to go back to fragment B, sometimes (50% of times) the fragment B is drawn over fragment A. On clicking fragment A in the drawer, fragment A appears fine, but on clicking fragment B, there's another instance of fragment A and on top of that fragment B.
I've spent more than 2 days on this problem and got nowhere.
Here's my code for selecting the fragment:
private void selectItem(int position, boolean addExploreFragment) {
Log.d(tag, "selectItem: " + position);
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
//add explore fragment - this is called on app startup, but also when the app is killed and resumed which results in 2 explore fragments
if (addExploreFragment){
fragmentTransaction.replace(R.id.content_frame, mExploreFragment, EXPLORE_FRAGMENT_TAG);
Log.d(tag, "Replaced frame and added "+ mFragmentTags[position]);
} else {
//add fragment for the first time
if (getSupportFragmentManager().findFragmentByTag(mFragmentTags[position]) == null && position != 0) {
fragmentTransaction.add(R.id.content_frame, mFragments[position], mFragmentTags[position]);
Log.d(tag, "Added Fragment: "+ mFragmentTags[position]);
}
//shows and hides fragments
for (int i = 0; i < mFragments.length; i++) {
if (i == position) {
fragmentTransaction.show(mFragments[i]);
Log.d(tag, "Showing Fragment: "+ mFragmentTags[i]);
} else {
if (getSupportFragmentManager().findFragmentByTag(mFragmentTags[i]) != null) {
fragmentTransaction.hide(mFragments[i]);
Log.d(tag, "Hid Fragment: "+ mFragmentTags[i]);
}
}
}
}
fragmentTransaction.commit();
//not null check for calling selectItem(0) before loading the drawer
if (mDrawerList != null){
mDrawerList.setItemChecked(position, true);
}
}
I know for sure, the explore fragment is getting created twice and the two instances behave independently of each other (just sharing).
I'm lost what to do next. This is an issue which can be reproduced very easily on low end devices but on a device like Nexus 4 (my test device), the issue can be reproduced by changing the device language.
Has anyone got any ideas about this? Basically if the addExploreFragment block doesn't get called when there is already an exploreFragment, this issue could be solved, I think, but I've been unable to do so. Also, I tried removing all the fragments and then adding the exploreFragment but same thing happens (50% of times).
Thanks! and sorry for the long post, I felt I should share all the details.
Update: When I change the device language and come back to the app on Activity 2 and go back to Home activity, it has the fragment B open which is good, but fragment A get recreated because it's a heavy fragment and the system probably removed it from memory. Again, that's ok that it gets recreated IF it got removed by the system but why does it get recreated when it's not removed. I believe it's something with my code, on every 2nd attempt (without closing the app) this happens, 2 instances of the heavy fragment A. Out of ideas.
But shouldn't fragmentTransaction.replace remove all the previously added fragments and then add exploreFragment. It's not working like that. Neither fragment A nor Fragment B are getting removed.
I found out something new and rather odd to me. When you use fragmentTransaction.add, the listeners you have, like DrawerItemClickListener, on the previous fragment, are still active. And this is even if you use fragmentTransaction.commit.
So...I suspect when the add method is used, you actually clicked on another hidden button or hidden UI that has an event listener on the previous fragment. I don't like this of course and the effect may be very confusing. Yes, this happened to me and I didn't understand why for a while.
For now, I think the easiest code fix would be to use the replace method instead of add. The replace() makes listeners inactive. If it works, then you can make a better/elegant fix.
Let me know what happens....
I started to notice your post
when I go to fragment B and go to another activity
When you interact or start another Activity, you start a new set of Fragments. Look at this Google webpage # Fragments Lifecycle.
For clarification of my claim, there is a quote saying
A fragment must always be embedded in an activity and the fragment's
lifecycle is directly affected by the host activity's lifecycle.
You might as well read few paragraphs of it, at least.
I am not sure what your solution should be. Perhaps make the fragments distinctive, different and clear between the two Activities you have.
the title pretty much explains it.
I have horizontal scrolling set up, the first screen has buttons to the other Fragments as well as the whole horizontal scroll system. What I would like is for the user to be able to press the back button when on one of these fragments and for the app to return to the first screen with all the buttons.
From there I want the back button to be an AlertDialog asking the user if they would like to exit the app. At the moment this is what is happening (On all Fragments when you press the back button the AlertDialog I created pops up).
I've looked into Fragment transactions and "addToBackStack()" but I don't know how to implement it. I've looked at the dev guide and certain questions on this site but getting one or two lines of code doesn't help in implementing it.
I have a FragmentActivity with a FragmentPagerAdapter set up and each Fragment has its own Java file. I have 5 Fragments that are all called in the FragmentActivity and FragmentPagerAdapter.
I don't think I need to show you guys any of my code for the moment since it's all set up in the normal manner. Please let me know if you do though.
The bit of code I found on other questions and one in particular was the following:
FragmentTransaction tx = fragmentManager.beginTransation();
tx.replace( R.id.fragment, new MyFragment() ).addToBackStack( "tag" ).commit();
It's a bit hard to go on just that though.
I would really appreciate your help.
EDIT: my code removed - wasn't needed.
If you use the ViewPager from your question which I answered earlier and you want to come back to the first fragment of the ViewPager when the user presses the BACK button then override the onBackPressed method like this:
#Override
public void onBackPressed() {
if (getSupportFragmentManager().findFragmentByTag("outDialog") != null
&& ((DialogFragment) getSupportFragmentManager()
.findFragmentByTag("outDialog")).isVisible()) {
// we have the out dialog visible and the user clicked back so let
// the
// normal events happen
super.onBackPressed();
return;
}
int currentPosition = mViewPager.getCurrentItem();
if (currentPosition != 0) {
// if the page the ViewPager shows isn't the first one then move it
// to the first one
mViewPager.setCurrentItem(0);
} else {
// we are at the first position already and the user wants out, so
// annoy him with a dialog that asks him once again if he wants out.
DialogFragment askHim = new DialogFragment();
askHim.show(getSupportFragmentManager(), "outDialog");
// in the dialog listener, if the user presses ok, finish the activity
}
}
did you try to override Activity.onBackPressed() ?
I used addToBackStack() and it works well, but I have no idea whether it works with PagerAdapter.
I think you can override Activity.onBackPressed() and in that method, you can check whether current page is front page or not and do whatever job you want to do.
Here are pseudo code that I think.
public void onBackPressed() {
if( pager.getCurrentPage() == 0 ) { //I'm not sure this method exists or not. just example. :-)
//exit code here
} else {
// show first page
}
}