I have an animation which starts correctly the first time the fragment is displayed. However after an orientation change, it won't restart. The animation is an animation-list resource set as the background of an ImageView.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View root = inflater.inflate(R.layout.fragment_lead_manual,
container, false);
final ImageView badgeEntryView = (ImageView) root
.findViewById(R.id.manual_image);
mAnimation = (AnimationDrawable) badgeEntryView.getBackground();
return root;
}
#Override
public void onResume() {
super.onResume();
mAnimation.start();
}
#Override
public void onPause() {
super.onPause();
mAnimation.stop();
}
EDIT: I forgot to add that the animation is inside a tab, which makes things more difficult. However, I've figured out the problem and will add the answer below.
There are two cases that need to be solved, based on when the tab is created:
FIRST the tab is created, and SECOND its Activity is attached to the Window
FIRST the Activity is attached to the Window, and SECOND the tab is created
Case one occurs if the tab is the first one displayed or during rotation. Case two occurs when the user switches to that tab because it's not the first. Let's handle each case separately:
Case 1: a) Create tab b) Attach to window
Calling AnimationDrawable.start() before it is attached to the window (i.e. inside onCreate() or onResume()) breaks the animation. As stated in the Android docs :
It's important to note that the start() method called on the AnimationDrawable cannot be called during the onCreate() method of your Activity, because the AnimationDrawable is not yet fully attached to the window. If you want to play the animation immediately, without requiring interaction, then you might want to call it from the onWindowFocusChanged() method in your Activity, which will get called when Android brings your window into focus.
It's more difficult with Fragments, but basically the same. We override the method in the Activity and then call over to the Fragment:
#Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
final FragmentManager fm = getFragmentManager();
ManualLeadFragment manualFragment = (ManualLeadFragment) fm
.findFragmentByTag(TAG_MANUAL);
if (manualFragment != null) {
manualFragment.startAnimation();
}
}
}
And then in the Fragment, implement startAnimation():
void startAnimation() {
mAnimation.start();
}
Case 2: a) Attach to window b) Create tab
In this case, the call to onWindowFocusChanged() has already occurred and so the animation won't start. So we still need to start it during onResume(), but slightly differently:
#Override
public void onResume() {
super.onResume();
if (isVisible()) {
startAnimation();
}
}
This calls into the same startAnimation() method as in Case 1, but because the Fragment is already attached to the Window, it can be called during onResume().
Summary
AnimationDrawable.start() can only be called when the Fragment is visible. Sometimes it is visible during onResume(), and the animation can be started at that point. Other times it is not yet visible at that time, and then the overridden onWindowFocusChanged() method is called when when it becomes visible, and the animation is started then.
Related
I am extremely new to Android development. I basically have an activity with some buttons, for example "seeTreePicture" and "seeSeaPicture". When I press a button, I want to use a fragment I called "ContentViewer" to display a random tree/sea picture, and also have buttons under the picture to destroy the ContentViewer fragment instance and go back to the menu. The issue is, if I try to use a Fragment Transaction anywhere other than onCreate() of the activity, I get a null pointer exception when I try to access the view in the fragment.
My activity and things related to the fragment:
public class SeeActivity extends AppCompatActivity {
DisplayFragment displayFragment;
Button seeTreeButton;
Button seeSeaButton;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_see);
seeTreeButton = findViewById(R.id.seeTreeButton);
seeSeaButton = findViewById(R.id.seeSeaButton);
displayFragment = new DisplayFragment();
seeTreeButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.fragmentContainer, displayFragment);
fragmentTransaction.commit();
fragmentTransaction.addToBackStack(null);
displayFragment.changeImage(randomTree);
}
});
}
}
In my fragment, change image simply changes the image source of the ImageView:
public void changeImage(int treeResource)
{
img = getView().findViewById(R.id.imageView);
img.setImageResource(treeResource);
}
I get a null pointer exception for trying to access the view from getView() in the fragment, meaning that onCreateView wasn't invoked. Yet if I put the same transaction in the onCreate() method of the activity, it works. What am I doing wrong?
The issue with your code is that you are accessing getView() of your fragment before the fragment has gone through the initialization of the view. The reason why you don't have the crash when you execute the transaction in your activity's onCreate() method is that by the time you click on a button your fragment has already gone through onCreateView() and initialized its view. Check out fragment lifecycle guide and bear in mind that you should not access your fragment view before it was created or after it was destroyed. For more information about why your fragment view is not initialized instantly check out this guide.
As for the solution, consider setting arguments for your fragment before adding it to your transaction like here:
Bundle args = new Bundle();
args.putInt(DisplayFragment.IMG_RESOURCE_ARG, randomTree);
displayFragment.setArguments(args);
Then in your onCreateView() or onViewCreated() methods of your fragment restore the arguments like here and set the image resource:
#Override
public void onViewCreated(#NonNull View view, Bundle savedInstanceState) {
int resourceId = requireArguments().getInt(IMG_RESOURCE_ARG);
img = view.findViewById(R.id.imageView);
img.setImageResource(resourceId);
}
Because the fragment transaction doesn't occur instantly. It occurs async. So the actual work of creating views hasn't occurred yet. Your options are to either use commitNow() instead of commit (which will do it synchronously, but require much more time and possibly cause your app to visibly pause) or to wait for the fragment transaction to actually complete. That can easily be done by putting it in a Runnable and passing that runnable to runOnCommit
I have some code which I run in normal activities in method onFocusChanges, it is important to run it just there as the code requires the activity to be loaded first as it get images width and heights from the view:
in MainActivity
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
SizeModifier sizeModifier = SizeModifier.getInstance(this);
sizeModifier.adjust
}
so all this is working just fine, the problem is this main view contains a fragment changes according to button press like that
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.main_view_fragment,fragment).commit();
the fragment of course has activity and xml, I want now when the fragment open to run the previous code when focus changes but the problem is fragments does not have onFocusChange, I tried all onStart onResume onCreateActivity
but non seems to work as I want, of course the method is called but all images widths are returned 0,
so is there away to be sure that images will return correct width or some alternative to onFocusChanges
Ok, finally I found what could do the purpose,
simply for fragments we add this simple code to the onCreateView after inflating the view like this:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
rootView = inflater .inflate(R.layout.activity_courses_fragment, container, false);
// >>>> this is the thread which will run when every thing is ready
rootView.post(new Runnable() {
#Override
public void run() {
// >>> this method now can get width with no problems
SizeModifier.getInstance(getContext()).adjust(rootView);
}
});
return rootView;
}
special thanks to #Staffan for this idea as solution for another question
I have an Activity that displays various fragments using the supportFragmentManager. When I attempt to get a view in the fragment or the parent activity for that matter, and attempt to measure it's position on the screen it only seems to be available for measurement sometime after onResume in the fragment lifecycle or after onActivityCreated/onResume/onAttachedToWindow in the Activity. Typically it is available after about 100-200ms. Is there any lifecycle event documented/undocumented or solid method of knowing when this has occurred, like maybe a canvas drawing event. The fragment in question needs to measure a parent activity view, but it isn't always available in onResume right away. I really hate having to do some kind of hack like having a handler wait 200ms.
You can use ViewTreeObserver.addOnGlobalLayoutListener().
#Override
public void onCreate(Bundle savedInstanceState) {
//...
someView.getViewTreeObserver().addOnGlobalLayoutListener(getOnLayoutListener(someView));
//...
}
private ViewTreeObserver.OnGlobalLayoutListener getOnLayoutListener(final View unHookView) {
return new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
unHookView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
else
unHookView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
//YOUR CODE HERE
}
};
}
I have a ViewPager (instantiated with FragmentStatePagerAdapter) with some Fragment attached to it.
In a specific usecase I need to reset instanceBean and UI for most of the fragments in the pager.
After some googling I have tried some solutions like this but the side effects were not easy manageable. Other solution like this doesn't match my needs.
So I decided to go straight with the manual reset of the UI and instanceBean obj like in the code below:
The code
Single fragment reset
public void initFragment() {
notaBean = new NoteFragmentTO();
fromSpinnerListener = false;
}
public void resetFragment() {
initFragment();
NoteFragment.retainInstanceState = false;
}
This is done with the following code from the parent Activity:
Fragment reset from parent
private void resetAfterSaving() {
mIndicator.setCurrentItem(POSITION_F*****);
f*****Info.resetFragment();
mIndicator.setCurrentItem(POSITION_NOTE);
noteInfo.resetFragment();
mIndicator.setCurrentItem(POSITION_M*****);
m*****Info.resetFragment();
mIndicator.setCurrentItem(POSITION_V*****);
v*****.resetFragment();
}
AfterViews method:
#AfterViews
public void afterView() {
if (mSavedInstanceState != null) {
restoreState(mSavedInstanceState);
}
NoteFragment.retainInstanceState = true;
// Inits the adapters
noteAdapter = new NoteArrayAdapter(this, noteDefaultList);
sp_viol_nota_default.setAdapter(noteAdapter);
//sp_viol_nota_default.seton
et_viol_nota.setOnFocusChangeListener(new OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
String readText = et_viol_nota.getText().toString().trim();
notaBean.setNota(readText == "" ? null : readText);
}
}
});
}
OnSavedInstanceState
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelableArrayList(KEY_NOTE_D_LIST, (ArrayList<VlzAnagraficaNoteagente>) noteDefaultList);
outState.putInt(KEY_NOTE_D_POSITION, !NoteFragment.retainInstanceState ? 0 : notePosition);
notaBean.setNota(!NoteFragment.retainInstanceState ? "" : et_viol_nota.getText().toString().trim());
outState.putParcelable(NoteFragmentTO.INTENT_KEY, notaBean);
}
Why do I set every page before resetting them?
Because like explained here:
When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment.
and because until I don't select the relative fragment the #AfterViews method (that is everything processed right after OnCreateView of the fragment) is not executed.
This throws NullPointerException for a thousand of reason (Usually in the #AfterViews method You launch RestoreState method, initializes adapter, do UI stuff).
Setting the relative page before the reset let #AfterViews method be processed.
Before checking what would happened when rotating the device, all the fragment I need are correcly reset.
When rotating the device, the error comes out:
The views (mainly EditText) go back to their previous state BEFORE my reset.
What happens?
When switching between the page, at a certain point the page will be destroyed and OnSavedInstanceState is called everytime for each page.
I have already handled the OnSavedInstanceState (like above) that when the boolean is false saves the state like if it had just been created.
I found that until within AfterView method the EditText has its text set to blank (like I want) but going on with the debug the EditText goes back to its previous state, so at the end it will show the last text it had.
Question
How can I keep the manually set (in OnSavedInstanceState) EditText text after destroying/recreating a fragment?
Each time my fragment become visible to the user I want to execute a peace of code that will call a web service, fetch some data and display it on the screen. I got the web service part etc working but not sure in what event I must add my code.... I tried:
onStart
onResume
onAttach
But my code doesn't fire everytime.
Am using the Android v4 comp lib with SherlockFragment as my base class.
You can use
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) { }
else { }
}
Have a look at this
This may be very old but I found setUserVisibleHint() didn't work for many of my use cases. Instead I had to resort to a hack using the ViewTreeObserver.
Basically, after your fragment is initialised, you get a view within it and do the following:
myViewInFragment.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
myMethodWhenFragmentFirstBecomesVisible();
myViewInFragment.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}
onCreateView()
Called Every time when you change the Fragment and new Fragment become visible..
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
Below method is used determine when Fragment becomes visible in the front of a user.
private boolean loding= false; // your boolean flage
#Override
public void setUserVisibleHint(boolean isFragmentVisible) {
super.setUserVisibleHint(true);
if (this.isVisible()) {
// we check that the fragment is becoming visible first time or not
if (isFragmentVisible && !loding) {
//Task to doing while displaying fragment in front of user
loding = true;
}
}}
onResume() is called every time your fragment becomes visible to the user. There is something else wrong with your code if it doesn't
onCreateView() is called the first time the fragment needs to draw its UI
Update: This accepted answer was working 5 years ago - it doesn't anymore