I have an activity which have multiple piece of UI panel(you can think them as view in android), these panels will be invisible by default.
Now when user trigger action1, PanelA will display, when trigger action2, PanelB will display(at different location of the screen).
Both PanelA and PanelB is visible at the moment, now when user hit the back menu, the PanelB should disappear, and PanelA should disappear when hit the back menu again.
At first, I use View to hold different panels, however I found it is difficult to keep the state consist, for example, the activity will be a little different between PanelA and PanelB.
Then I found the fragment, however after I tried and tested, I found that the addTobackStack() can not apply to my us-case, since the PanelA and PanelB are at different location, android can not save their state by default.
So I wonder if there is any other solution for my requirement?
You need to manually handle this scenario inside onBackPressed() method of an Activity.
For instance -
#Override
public void onBackPressed() {
if (panelB.isOpened()) {
panelB.close()
} else if (panelA.isOpened()) {
panelA.close()
} else {
super.onBackPressed();
}
}
When panelB is opened, it will close only panelB and wont do anything else. Same goes for panelA, if its opened and when both the panel are closed then it will exit the app like normal.
I highly recommend to use DialogFragments here as you can call show() and dismiss() any point of time on that and they can handle custom views pretty well.
Hope it helps.
Related
Hi folks I need to throw up a series of DialogFragments one after another using the navigation component. I have encountered some pretty unusual system behavior which looks like a race condition. I show the dialogs with a global action after an item is clicked, and use the fragment result api to determine if another one should be shown.
I am using a custom layout so there's no positive / negative listeners etc., and my own continue / cancel buttons send a result back to the host fragment.
ItemsFragment.kt:
navController.navigate(RegisterItemsDirections.openModsDialog(item.id, 0))
fragment.setFragmentResultListener(ItemsFragment.MODIFIERS_REQUEST) { key, bundle ->
//kill the current dialog
navController.navigateUp()
//some logic to determine if we need another dialog...
if(needAnotherDialog){
//navigate to the next one
navController.navigate(RegisterItemsDirections.openModsDialog(item.id, lastModGroupSelectionIndex + 1))
}
}
ModsDialogFragment.kt, when the user clicks "continue"
setFragmentResult(MODIFIERS_REQUEST, bundleOf(MODIFIERS_RESULT to selectedMods))
So the issue only appears on 3rd or more dialogs on my devices, I can see that the 1st and 2nd dialogs are completely destroyed and detached. When it displays the 3rd one, the first one attaches itself again, and appears beneath the 3rd one which I can't explain.
I've tried:
Popping the back stack in the global action's nav xml
Navigating up or dismissing the dialog fragment in the dialog itself (before calling setFragmentResult), which is the most logical place to put it
Popping the backstack instead of navigating up, basically the same thing in this case
When I don't dismiss / nav up any of the dialogs and allow them all to just stack, there's no issue. When I delay the navigation by 500ms there is no issue. So navving up and then immediately navigating to another instance of the dialog are fighting with eachother producing very strange results. What could be the solution here that doesn't involve a random delay?
Navigation version is 2.3.3 and I'm having a lot of trouble trying to update it due to AGP upgrades being incompatible with a jar I need so I'm not sure if this has been fixed.
I figured out the issue, it's down to dumb copy pasting.
I took the donut tracker sample code and made it stack dialogs in the same way and there was no issue.
The difference between the two was I am subclassing DialogFragment and for some unknown reason doing this:
override fun show(manager: FragmentManager, tag: String?) {
val transaction = manager.beginTransaction()
val prevFragment = manager.findFragmentByTag(tag)
if (prevFragment != null) {
transaction.remove(prevFragment)
}
transaction.addToBackStack(null)
show(transaction, tag)
}
This code predates the Nav component library I believe, and it completely f***s with the fragment manager used by the navigation component. So the moral of the story is to not do bizarre things in super classes, or better yet not super class at all.
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 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).
I am trying to work out how to show the "up" arrow in Xamarin.Forms without a pushing a page onto the stack. I.E. I just want to perform an action when the back button is pressed. I am completely stuck on this so any help would be appreciated.
I have tried creating a custom renderer which handles a view property called DisplayHomeAsBack. Which in the renderer calls the following:
FormsAppCompatActivity context = ((FormsAppCompatActivity)Forms.Context);
Android.Support.V7.App.ActionBar actionBar = context.SupportActionBar;
if (actionBar != null)
{
actionBar.SetDisplayHomeAsUpEnabled(element.DisplayHomeAsBack);
}
Unfortunately it seems this does absolutely nothing, even though all online tutorials and stackoverflow question for android suggest this method.
The plan is that I can then use the "OnBackButtonPressed" override in MasterDetailPage, which should allow me to perform this action. Unfortunately displaying the back button has been the larger hurdle so far!
Any idea of a better way to do this or how I can get the current mechanism to work?
EDIT
I have created a project and uploaded it to this question on the Xamarin support forums, if it helps.
http://forums.xamarin.com/discussion/comment/186330#Comment_186330
Sorry to keep you waiting so long!
Warning that I did not actually run this code and changed it from my own so I would be surprised if it worked perfectly without some changes.
So below should add a back button where there was not one before (so like when there is not really a page to go back to) and then we will add a custom action to perform when it gets pressed.
I would suggest you push a new page onto the stack without using animation so it is transparent to the user and also makes all of this much simpler, but if you absolutely do not want to do that, the below method should work.
MainActivity:
//Use this to subscribe to the event which will create the back button
public override bool OnCreateOptionsMenu(IMenu menu) {
if(menu != null && App.AppMasterPage != null) { //You will need this to make sure you are on your MasterDetailPage, just store a global reference to it in the App class or where ever
Xamarin.Forms.MessagingCenter.Unsubscribe<string>(this, "CreateBackButton");
Xamarin.Forms.MessagingCenter.Subscribe<string>(this, "CreateBackButton", stringWeWillNotUse => { //Use this to subscribe to the event that creates the back button, then when you want the back button to show you just run Xamarin.Forms.MessagingCenter.Send<string>(this, "CreateBackButton")
ActionBar.DisplayOptions = ActionBarDisplayOptions.ShowTitle | ActionBarDisplayOptions.ShowHome | ActionBarDisplayOptions.UseLogo | ActionBarDisplayOptions.HomeAsUp; //You may need to play with these options to get it working but the important one is 'HomeAsUp' which should add the back button
});
} else {
Xamarin.Forms.MessagingCenter.Unsubscribe<string>(this, "CreateBackButton");
}
return base.OnCreateOptionsMenu(menu);
}
Now the next step is do do a custom action when it is pressed. I think you can either override OnBackPressed() or OnOptionsItemSelected() in MainActivity or maybe you can override the MasterDetailPage method. I am not sure.
Which ever one works for you, inside of that override, I would simply check to see if you are on your App.AppMasterPage like we did above, and if so, send a MessagingCenter message which your App.AppMasterPage has already subscribed to in order for it to handle the custom action.
If you get stuck let me know!
I know it sounds like a bit of a hack, but the best "solution" I have found so far is to add a page behind the current page (behind the root) so it is not visible. Then when the user presses the back button, handle it by removing that page.
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
}
}