I'm trying to get the height and width of an ImageView
in a Fragment
with the following ViewTreeObserver
:
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.OnGlobalLayoutListener; private ImageView imageViewPicture; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_general_activity_add_recipe, container, false); setHasOptionsMenu(true); ... final ViewTreeObserver observer = imageViewPicture.getViewTreeObserver(); observer.addOnGlobalLayoutListener (new OnGlobalLayoutListener () { @Override public void onGlobalLayout() { observer.removeGlobalOnLayoutListener(this); } }); return view; }
Running this code results in the following Exception:
10-12 23:45:26.145: E/AndroidRuntime(12592): FATAL EXCEPTION: main 10-12 23:45:26.145: E/AndroidRuntime(12592): java.lang.IllegalStateException: This ViewTreeObserver is not alive, call getViewTreeObserver() again 10-12 23:45:26.145: E/AndroidRuntime(12592): at android.view.ViewTreeObserver.checkIsAlive(ViewTreeObserver.java:509) 10-12 23:45:26.145: E/AndroidRuntime(12592): at android.view.ViewTreeObserver.removeGlobalOnLayoutListener(ViewTreeObserver.java:356) 10-12 23:45:26.145: E/AndroidRuntime(12592): at com.thimmey.rezepte.AddRecipeActivity_GeneralFragment$1.onGlobalLayout(AddActivity_GeneralFragment.java:83) 10-12 23:45:26.145: E/AndroidRuntime(12592): at android.view.ViewTreeObserver.dispatchOnGlobalLayout(ViewTreeObserver.java:566) 10-12 23:45:26.145: E/AndroidRuntime(12592): at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1736) 10-12 23:45:26.145: E/AndroidRuntime(12592): at android.view.ViewRootImpl.handleMessage(ViewRootImpl.java:2644) 10-12 23:45:26.145: E/AndroidRuntime(12592): at android.os.Handler.dispatchMessage(Handler.java:99) 10-12 23:45:26.145: E/AndroidRuntime(12592): at android.os.Looper.loop(Looper.java:137) 10-12 23:45:26.145: E/AndroidRuntime(12592): at android.app.ActivityThread.main(ActivityThread.java:4517) 10-12 23:45:26.145: E/AndroidRuntime(12592): at java.lang.reflect.Method.invokeNative(Native Method) 10-12 23:45:26.145: E/AndroidRuntime(12592): at java.lang.reflect.Method.invoke(Method.java:511) 10-12 23:45:26.145: E/AndroidRuntime(12592): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:993) 10-12 23:45:26.145: E/AndroidRuntime(12592): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:760) 10-12 23:45:26.145: E/AndroidRuntime(12592): at dalvik.system.NativeStart.main(Native Method)
The documentations says that removeGlobalOnLayoutListener
is deprecated but if I use removeOnGlobalLayoutListener
, as suggested, I get an undefined error.
What I am doing wrong?
try this :
ViewTreeObserver observer = imageViewPicture.getViewTreeObserver(); observer.addOnGlobalLayoutListener (new OnGlobalLayoutListener () { @Override public void onGlobalLayout() { imageViewPicture.getViewTreeObserver().removeGlobalOnLayoutListener(this); } });
The other solution will work just fine, but it doesn't explain why this is happening.
There are a few issues to address here:
GlobalOn
VS OnGlobal
Using the GlobalOn version will give you a deprecation warning, but if you check the source it's just calling the OnGlobal version, so they're equivalent. The difference is that you can only use the OnGlobal one from API Level 16, so if you're targeting an earlier version you'll have to use GlobalOn and deal with that deprecation warning.
Note that this question is about code in onCreateView
, where imageViewPicture
is not attached to the view hierarchy yet, it has just been inflated. If you take a quick look at View.getViewTreeObserver()
you can see that it creates a "floating" observer in this case. Then when the view is put in the hierarchy dispatchAttachedToWindow
is called which then merges the window's and the view's floating observers:
info.mTreeObserver.merge(mFloatingTreeObserver);
which moves all registered listeners to the window's observer and kills the floating observer.
The original observer you acquired is the floating one, which is dead, hence calling an add/remove on it results in the above exception.
As Danyal, I was also puzzled why removeGlobalOnLayoutListener
works on a different observer, but now it's clear with the temporary floating observer. As the floating one is merged to the window's observer the listeners are moved to the other observer, so calling View.getViewTreeObserver()
later will give you an observer containing your listener. The new observer is now responsible for handling your listener.
As to Zordid's comment on why in many cases it's ok to hold on in a local (closure) variable can be explained by a similar reasoning: the just inflated view in onCreateView
is not yet attached only a little after it's returned. Most of the you've seen is probably in a method after onCreateView
in the lifecycle. float's (OP) solution would work just fine if the observer related code was in onViewCreated
. Every lifecycle method has it's own responsibilities, so I would advise splitting the code up like this:
private ImageView imageViewPicture; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_general_activity_add_recipe, container, false); // assuming ... includes: this.imageViewPicture = view.findViewById(R.id.image); return view; } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); final ViewTreeObserver observer = imageViewPicture.getViewTreeObserver(); observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener () { @Override public void onGlobalLayout() { observer.removeGlobalOnLayoutListener(this); } }); }
I also like to wire my other listeners in onViewCreated
, this way the number of instance variables are minimized, so imageViewPicture
would be a local variable.
The same is probably true for Activity.setContentView
which attaches the inflated view immediately and is usually called in onCreate
so the hierarchy is live by the time you play with observers/listeners.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With