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.