I recently started learning how to build apps. The situation is as follows - When you press back button on the main screen, the original fragment ("window settings") is replaced by fragment, which is exit confirmation (Next - FEC), where there are two buttons: "stay" (in the code - non-jump button) and "home" (I heard about DialogFragment, but for now I decided to do with regular Fragment). The goal is to have a "reminder" animation every 10 seconds after the FEC appears (I named it this way and apply it to the FragmentContainerView). To do this, with each new FEC display, I create a new Thread, where I launch an infinite loop (in which there is a delay itself and the animation starts) which, by the value of the boolean variable (flag) flag_notify, checks for exit from loop (and, in theory, the thread ends).
I change the value of this flag when the "stay" button is pressed, which also removes the FEC. (Activity implements an interface created in FEC, which has methods stayButtonListener (for "stay") and goButtonListener (here there is a transition to another activity)
The problem is that if you click on the back button (which, remember, displays the FEC), then on "stay" several times with an interval of less than 10 seconds (i.e. faster than the thread has time to complete), the animation will start to work unpredictable (as I understand it, each thread waits for "its own" 10 seconds and it turns out out of sync).
Anyone have any suggestions on how best to implement this?
I've also heard about Handler, synchronized, wait, post, but never figured out how to apply it.
Attaching my current code (see comments):
XML Fragment Container:
<androidx.fragment.app.FragmentContainerView
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:background="#color/setting_field_bg"
tools:layout="#layout/fragment_setting_field">
</androidx.fragment.app.FragmentContainerView>
The flag I mentioned
private static boolean flag_notify = false; // flag for the thread to animate the reminder
In the onCreate method of the activity
// bottom fragment = start
// Initialize the fragment container
container = (FragmentContainerView) findViewById(R.id.container);
fragmentManager = getSupportFragmentManager(); // Initialize the fragment manager
// Initialize the fragment to confirm the exit
confirmationDialogFragment = new ConfirmationDialogFragment();
// Initialize fragment for window settings
settingFieldFragment = new SettingFieldFragment();
addSettingFieldFragment(); // Initially display a fragment of the window settings
// bottom fragment = end
Home button
// Button to return to the main screen = start
return_home = (CardView) findViewById(R.id.return_home);
// Click handling = start
return_home.setOnTouchListener((v, event) -> {
if (event.getAction() == MotionEvent.ACTION_DOWN){
// Touched the button
return_home.startAnimation(button_pressed_animation); // Start the animation of the pressed button
}
else if (event.getAction() == MotionEvent.ACTION_UP) {
// Broke away from the button
return_home.clearAnimation(); // Remove the animation so the button doesn't freeze
// Overlay a fragment to confirm the exit
addConfirmationDialogFragment();
}
return true;
});
// Click handling = end
// Button to return to the main screen = end
Next - the main methods
// Method for transaction initialization = start
private FragmentTransaction mInitFragmentTransaction(int enter_anim, int exit_anim,
boolean add_to_back_stack){
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); // Initialize a new transaction
fragmentTransaction.setReorderingAllowed(true); // For correct operation, always specify
if (enter_anim != 0 && exit_anim != 0){
// So the animations are set
fragmentTransaction.setCustomAnimations(enter_anim, exit_anim); // Set animations
}
if (add_to_back_stack){
// So we need to push the fragment onto the stack
fragmentTransaction.addToBackStack(null); // Push the fragment onto the stack
}
return fragmentTransaction; // Return transaction
}
// Method for transaction initialization = end
// Method for setting screen availability = start
private void mDisplayEnable(boolean enable) {
return_home.setEnabled(enable); // Button to return to the main screen
person_avatar.setEnabled(enable); // Avatar menu button
playsetEnabled(enable); // start button
content_field.
setEnable(enable); // User content field
content_field_text.setEnabled(enable); // User text field
}
// Method for setting screen availability = end
// overlay fragment to confirm exit = start
private void addConfirmationDialogFragment(){
FragmentTransaction fragmentTransaction = mInitFragmentTransaction(R.anim.confirmaton_fragment_start, R.anim.setting_fragment_end,
true); // Basic transaction initialization
fragmentTransaction.hide(settingFieldFragment); // Hide the window settings fragment to "Lock" it
fragmentTransaction.add(R.id.container, confirmationDialogFragment); // Overlay
mDisplayEnable(false); // "Lock" Our Layout
fragmentTransaction.commit(); // Confirm the transaction
// Start a thread to repeat the reminder animation for the fragment container = start
flag_notify=false; // Update flag value
new Thread(() -> {
// Infinite loop to repeat reminder animation = start
while (true){
try {
Thread.sleep(10000); // Set a delay of 10 seconds
Log.d(LOG_TAG, "" + flag_notify); // Display the value of the flag in the logs
Log.d(LOG_TAG, "Current number of threads: " + Thread.activeCount());
if (flag_notify){
Thread.currentThread().interrupt();
break;
}
// Actions on the main thread = start
runOnUiThread(() -> {
// Start the reminder animation for the fragment container
container.startAnimation(notify_animation);
});
// Actions on the main thread = end
} catch (InterruptedException e) {
e.printStackTrace(); // Automatic code
}
}
// Infinite loop to repeat reminder animation = end
}).start(); // Run
// Start a thread to repeat the reminder animation for the fragment container = end
}
// overlay fragment to confirm exit = end
// Display fragment for window settings = start
private void addSettingFieldFragment(){
FragmentTransaction fragmentTransaction = mInitFragmentTransaction(0, 0,
false); // Basic transaction initialization
// Display fragment for window settings
fragmentTransaction.replace(R.id.container, settingFieldFragment);
fragmentTransaction.commit(); // Confirm the transaction
}
// Display fragment for window settings = end
// Handling the non-jump button click listener method in the exit confirmation fragment = start
#Override
public void stayButtonListener() {
flag_notify=true; // Set the flag to terminate the thread
// Remove the reminder animation from the fragment container [just in case]
container.clearAnimation();
// Initialize a new transaction
FragmentTransaction fragmentTransaction = mInitFragmentTransaction(R.anim.setting_fragment_start, R.anim.confirmation_fragment_end,
false); // Basic transaction initialization
fragmentTransaction.remove(confirmationDialogFragment); // Remove the exit confirmation fragment
fragmentTransaction.show(settingFieldFragment); // Display a fragment of the window settings
fragmentTransaction.commit(); // Confirm the transaction
mDisplayEnable(true); // "Unlock" Our Layout
}
// Handling non-jump button click listener method in exit confirmation fragment = end
// Handling the home button click listener method in the exit confirmation fragment = start
#Override
public void goButtonListener() {
flag_notify=true; // Set the flag to terminate the thread
finish(); // Close the activity and go to the main screen
}
// Handling the home button click listener method in the exit confirmation fragment = end
....................
Related
I'm setting a new android app, and I want to exploit the bottom navigation bar in my phone (previous,home) so that i don't have to create a customized one, I want to know if it is possible to set an onClickEvent on these buttons
You have to handle both button separately.
For hardware back button (Previous) you have to override onBackPress method in your Activity class. So, you will get the back press event you can change the behavior of back button if you don't want back event just remove super.onBackPress(). If you want to do this from fragment you just need to add your logic like you can get current fragment instance from FragmentManager like below and call fragment method what ever you want to do on that fragment.
\\ To get current fragment
\\ NOTE: I add back stack name as class name.
public static Fragment getCurrentFragment(FragmentManager fragmentManager) {
int count = fragmentManager.getBackStackEntryCount();
if (count > 0) {
FragmentManager.BackStackEntry backStackEntry = fragmentManager.getBackStackEntryAt(fragmentManager.getBackStackEntryCount() - 1);
String tag = backStackEntry.getName();
return fragmentManager.findFragmentByTag(tag);
}
return null;
}
#Override
public void onBackPressed() {
// enter code here
super.onBackPressed(); // Remove this line if you don't want to go back.
}
For home button handling you can use this answer or this post.
Please comment me if you have more questions.
I have an app that is mainly based on fragments. Each fragment involves it's own json parsing and image loading from json.Also to be noted that I have called addToBackStack(null) on ech fragment when it's getting replaced.
As I switch between the fragments, and press the back button, it takes quite some time 4-5 secs to be exact to load the previous fragment. Is there any way that the load time can be minimized?
I have used async task to fetch the data,async task is performed in fragment's onAttach() method to prevent json parsing when back button is pressed.
This could easily be a simulator issue, but if not it could be because you are loading all of the information on the main thread instead of in an async task. Making an Async task loads the information in the background and updates the UI when finished
public void currentFragment(int position, Bundle bundle) {
switch (position) {
case 105:
MainFragment workoutFragment = MainFragment.newInstance("", "");
workoutFragment.setArguments(bundle);
replaceFragment(workoutFragment, "front");
break;
case 108:
break;
default:
replaceFragment(new MainFragment(), "Main");
break;
}
}
private void replaceFragment(Fragment fragment, String tag) {
String backStateName = fragment.getClass().getName();
try {
boolean fragmentPopped = fm.popBackStackImmediate(backStateName, 0);
if (!fragmentPopped && fm.findFragmentByTag(backStateName) == null) {
//fragment not in back stack, create it.
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.fragment_container, fragment, backStateName);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.addToBackStack(backStateName);
ft.commitAllowingStateLoss();
//ft.commit();
}
} catch (Exception e) {
e.printStackTrace();
}
}
// inside Backpress #Override
// public void onBackPressed()
// if (fm.getBackStackEntryCount() == 1) {
// super.onBackPressed();
// }
// to add fragment call like this
// currentFragment(101,null);
// what it do is popup from stack not add again and again that is best method to achiveve this if still getting problem then ask me again
If you have no need to reload images in fragments on backpress ,then you can use this
trick
in Each Fragment
View mView; // declare it globally
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if(mView == null){ // it will not reload image it will set first time view which is loaded.
mView = inflater.inflate(R.layout.fragment_dashboard, null);
}
return rootView;
}
This will be load immediately . it will restore previous view on
backpress
One reason could be the amount of data that you're loading. To overcome situation, you can user Handler with Runnable.
// Declaration
private Handler layoutHandler = new Handler();
private Runnable mRunnable;
// Initialization
mRunnable = new Runnable() {
#Override
public void run() {
hideProgress();
// Perform an action such as binding your data
}
};
Now make delay before loading a data... using
showProgress();
layoutHandler.postDelayed(mRunnable, 800);
I recommend to save your data using savedInstanceState rather then loading data everytime.
I have 4 fragments in a FragmentPagerAdapter.
When I am in the 4th fragment and press the back button, it can be returned to previous fragment, it may be the 3rd or 2nd fragment.
But If the previous one is the 3rd one, the app will automatically exit after clicking the back button. What I want is the app can stay on the 3rd fragment after user click the back button on the 4th fragment without automatically exiting. The reason why it happens is because that the two neighbored fragments will taking actions synchronously when user click back button.
How to implement it? Thx.
public void onBackPressed() {
moveTaskToBack(true);
System.out.println(123456);
ViewPager mViewPager = (ViewPager) this.findViewById(R.id.pager);
App atp = this.getApplication();
int count = atp.getFragmentStack().size();
if (count == 0) {
//additional code
} else {
mViewPager.setCurrentItem(2);
}
}
UPDATES(answering comment 1):
My app use FragmentPagerAdapter ,
so I want to use
ViewPager mViewPager = (ViewPager) this.findViewById(R.id.pager);
mViewPager.setCurrentItem(2);
to chang the fragment. Commit does not work, I also need to change the selection bar.
You need to add to back stack your fragments or replace:
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(here_your_fragment);
//OR ADD
//fragmentTransaction.add(here_your_fragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
So for you solution is:
In activity create variable
public int fragmentPosition;
Then, put the value of your activity or fragment into your adaper. In method where your fragments instantiate or gets pass the position of your fragment to the value of fragmentPosition, like this
yourActivityOrFragment.fragmentPosition = position;
And after that, in method onBackPressed() check for position of fragments
public void onBackPressed() {
if (fragmentPosition == 3) {
finish();
} else {
super.onBackPressed();
}
}
I've five fragments in my activity. On the click of fragment drawer list, I'm calling setFrament() method.
It removes the previous fragments from the activity, and adds the new one as per the requirement.
Here's my code for setFragment method.
protected void setFragment(final int position) {
// Remove currently active fragment
if (mActiveFragment != null) {
Fragment previous;
while ((previous = mFragmentManager
.findFragmentByTag(mActiveFragment.toString())) != null) {
mFragmentManager.beginTransaction().remove(previous).commit();
Log.e("in loop", "stuck");
}
}
// It's enum, generated according to position
FragmentType type = getFragmentType(position);
Fragment fragment = mFragmentManager.findFragmentByTag(type.toString());
if (fragment == null) {
fragment = createFragment(type);
}
mFragmentManager
.beginTransaction()
.replace(R.id.containerFrameLayout, fragment,
type.toString()).commit();
// Sets the current selected fragment checked in
// Drawer listview
mFragmentDrawerList.setItemChecked(position, true);
// set Actionbar title
getActionBar().setTitle(type + "");
mActiveFragment = type;
}
Here, while changing the fragment, it keeps prining "stuck" in while loop forever,
my question is,
why remove(Fragment) method doesn't remove the previous fragment from the activity ?
commit() is an asynchronous call that is only executed when the Android system regains control. The fragments won't be removed until some time after you leave the method.
If you want to remove the previous fragment (assuming there's only one), then you can add them to the backstack. Then you can call popBackStack(null, 0) which will pop everything on the back stack. The side catch though is pressing the "back" button will also pop the backstack if the user were to do that. You'll have to override the onBackPressed() and handle it yourself if you don't want that to happen.
EDIT:
One method would be to keep track of all IDs or TAGs and call remove on them individually.
LinkedList<Integer> fragmentIds = new LinkedList<Integer>();
/*** Add fragment to FragmentManager ***/
fragmentIds.add(/** ID of fragment */);
/** Removing all fragments from FragmentManager **/
FragmentManager fm = getFragmentManager();
FragmentTransaction transaction = fm.beginTransaction();
Fragment fragToRemove;
for(Integer id : fragmentIds)
{
fragToRemove = fm.findFragmentById(id);
transaction.remove(fragToRemove);
}
transaction.commit()
fragmentIds.clear();
However, you don't have to call remove on any of them so long as you use replace() method on the same container. replace() will pop the previous fragment and add in the new one. So long as the transaction isn't pushed to the backstack, the previous fragment is detached from the Activity and discarded.
I'm using an OnClickListener to do a fragment replacement. I'm toggling 3 LinearLayouts to 'GONE' within the OnClickListener also.
I would like to add a function to set the 3 LinearLayouts back to VISIBLE when the back button is pressed. The fragments swap back, but the LinearLayouts don't change their state.
Any help is appreciated, thanks!
final OnClickListener swapFragments = new OnClickListener() {
#Override
public void onClick(View v) {
if (myAdapter.isEmpty() != true) {
FragmentTransaction ft = getFragmentManager()
.beginTransaction();
FragmentTwoTop ftt = new FragmentTwoTop();
FragmentTwoBottom ftb = new FragmentTwoBottom();
ft.replace(R.id.leftTopHolder, ftt, "fragmenttwotop");
ft.replace(R.id.leftBottomtHolder, ftb, "fragmenttwobottom");
layoutOne.setVisibility(View.GONE);
layoutTwo.setVisibility(View.GONE);
layoutThree.setVisibility(View.GONE);
ft.addToBackStack("swapfragments");
ft.commit();
} else {
}
}
};
You could try adding a listener to the backstack: http://developer.android.com/reference/android/app/FragmentManager.html#addOnBackStackChangedListener(android.app.FragmentManager.OnBackStackChangedListener)
It will be called any time "something" is added or removed from/to the backstack.
You can then check the type of the class of the fragment (or maybe your holding the current fragment in a class - your Activity - variable) to decide if it's necessary to execute your animation.
The method in which you manage the layouts could simply check the visibility (getVisibility) and if it's VISIBLE then set to GONE, if it's GONE set to VISIBLE.