studio requestfocus clase android android-view

requestfocus - textview en android



Fragmento-removeGlobalOnLayoutListener IllegalStateException (3)

Estoy tratando de obtener la altura y el ancho de un ImageView en un Fragment con el siguiente 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; }

La ejecución de este código da como resultado la siguiente excepción:

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)

La documentación dice que removeGlobalOnLayoutListener está en desuso pero si uso removeOnGlobalLayoutListener , como se sugirió, obtengo un error indefinido.

¿Que estoy haciendo mal?


Es mejor tratar de verificar si un getViewTreeObserver está vivo. Creo que el siguiente código funcionará. Basado en https://.com/a/15301092/2914140 , https://.com/a/26193736/2914140 y algunos otros.

** Actualización **

Después de leer Eliminar el oyente de ViewTreeObserver , https://.com/a/40013262/2914140 Reescribí un poco.

public static void captureGlobalLayout(@NonNull final View view, @NonNull final ViewTreeObserver.OnGlobalLayoutListener listener) { ViewTreeObserver vto = view.getViewTreeObserver(); if (vto.isAlive()) { vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { ViewTreeObserver vto = view.getViewTreeObserver(); if (vto.isAlive()) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { vto.removeOnGlobalLayoutListener(this); } else { //noinspection deprecation vto.removeGlobalOnLayoutListener(this); } listener.onGlobalLayout(); } } }); } else { view.post(new Runnable() { @Override public void run() { listener.onGlobalLayout(); } }); } }

** Antigua respuesta **

Extraño, pero incluso si view.getViewTreeObserver().isAlive() es verdadero, entonces la siguiente llamada a view.getViewTreeObserver () puede producir de nuevo la misma excepción. Entonces, rodee un código con try-catch. Posiblemente puede omitir todo esto y conservar solo el view.post(...) .

if (view.getViewTreeObserver().isAlive()) { try { view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { if (view.getViewTreeObserver().isAlive()) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { view.getViewTreeObserver().removeOnGlobalLayoutListener(this); } else { view.getViewTreeObserver().removeGlobalOnLayoutListener(this); } } yourCode(); } }); } catch (IllegalStateException e) { e.printStackTrace(); // The same as below branch. getActivity().runOnUiThread(new Runnable() { @Override public void run() { view.post(new Runnable() { @Override public void run() { yourCode(); } }); } }); } } else { getActivity().runOnUiThread(new Runnable() { @Override public void run() { // Try to wait until the view is ready. view.post(new Runnable() { @Override public void run() { yourCode(); } }); } }); }

Probablemente view.post(...) es suficiente, pero lo llamé desde el subproceso en segundo plano, así que si haces lo mismo, mejor debería invocarlo desde runOnUiThread(new Runnable() ...


prueba esto :

ViewTreeObserver observer = imageViewPicture.getViewTreeObserver(); observer.addOnGlobalLayoutListener (new OnGlobalLayoutListener () { @Override public void onGlobalLayout() { imageViewPicture.getViewTreeObserver().removeGlobalOnLayoutListener(this); } });


La otra solución funcionará bien, pero no explica por qué sucede esto.

Hay algunos asuntos que tratar aquí:

GlobalOn VS OnGlobal

El uso de la versión GlobalOn le dará una advertencia de desaprobación, pero si verifica la fuente, solo está llamando a la versión OnGlobal, por lo que son equivalentes. La diferencia es que solo puede usar OnGlobal one desde el Nivel 16 de API, por lo que si está apuntando a una versión anterior, tendrá que usar GlobalOn y lidiar con esa advertencia de desaprobación.

¿Por qué está muerto?

Tenga en cuenta que esta pregunta es sobre el código en onCreateView , donde imageViewPicture no está adjunta a la jerarquía de vistas, simplemente se ha inflado. Si echa un vistazo rápido a View.getViewTreeObserver() puede ver que crea un observador "flotante" en este caso. Luego, cuando la vista se coloca en la jerarquía, se llama a dispatchAttachedToWindow que combina los observadores flotantes de la ventana y de la vista:

info.mTreeObserver.merge(mFloatingTreeObserver);

que mueve todos los oyentes registrados al observador de la ventana y mata al observador flotante.

El observador original que adquirió es el flotante, que está muerto, por lo que llamar a agregar / eliminar en él da como resultado la excepción anterior.

¿Son los mismos observadores?

Como Danyal, también me sorprendió el motivo removeGlobalOnLayoutListener cual removeGlobalOnLayoutListener funciona en un observador diferente, pero ahora está claro con el observador flotante temporal. A medida que el flotante se fusiona con el observador de la ventana, los oyentes se mueven al otro observador, por lo que llamar a View.getViewTreeObserver() más tarde le dará un observador que contiene su oyente. El nuevo observador ahora es responsable de manejar a su oyente.

Pero a veces funciona, y otra solución.

En cuanto al comentario de Zordid sobre por qué, en muchos casos, está bien conservar una variable local (de cierre) puede explicarse por un razonamiento similar: la vista inflada en onCreateView aún no se adjunta solo un poco después de que se devuelve. La mayoría de los que has visto está probablemente en un método después de onCreateView en el ciclo de vida. La solución de float (OP) funcionaría bien si el código relacionado con el observador estuviera en onViewCreated . Cada método de ciclo de vida tiene sus propias responsabilidades, por lo que aconsejaría dividir el código de esta manera:

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); } }); }

También me gusta conectar a mis otros oyentes en onViewCreated , de esta manera se minimiza el número de variables de instancia, por lo que imageViewPicture sería una variable local.

Es probable que onCreate lo mismo con Activity.setContentView que adjunta la vista inflada inmediatamente y generalmente se llama en onCreate por lo que la jerarquía está onCreate para cuando juegues con los observadores / oyentes.