para - manual de programacion android pdf
¿Cuándo puedo medir primero una vista? (8)
En mis proyectos estoy usando el siguiente fragmento:
public static void postOnPreDraw(Runnable runnable, View view) {
view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
try {
runnable.run();
return true;
} finally {
view.getViewTreeObserver().removeOnPreDrawListener(this);
}
}
});
}
Entonces, dentro de Runnable#run
proporcionado, puede estar seguro de que se midió View
y ejecutar el código relacionado.
Así que tengo un poco de confusión al tratar de establecer el fondo dibujable de una vista tal como se muestra. El código se basa en conocer el alto de la vista, por lo que no puedo llamar desde onCreate()
o onResume()
, porque getHeight()
devuelve 0. onResume()
parece ser lo más cerca que puedo llegar. ¿Dónde debería poner el código como el siguiente para que el fondo cambie cuando se muestre al usuario?
TextView tv = (TextView)findViewById(R.id.image_test);
LayerDrawable ld = (LayerDrawable)tv.getBackground();
int height = tv.getHeight(); //when to call this so as not to get 0?
int topInset = height / 2;
ld.setLayerInset(1, 0, topInset, 0, 0);
tv.setBackgroundDrawable(ld);
No sabía acerca de ViewTreeObserver.addOnPreDrawListener()
, y lo probé en un proyecto de prueba.
Con tu código, se vería así:
public void onCreate() {
setContentView(R.layout.main);
final TextView tv = (TextView)findViewById(R.id.image_test);
final LayerDrawable ld = (LayerDrawable)tv.getBackground();
final ViewTreeObserver obs = tv.getViewTreeObserver();
obs.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw () {
Log.d(TAG, "onPreDraw tv height is " + tv.getHeight()); // bad for performance, remove on production
int height = tv.getHeight();
int topInset = height / 2;
ld.setLayerInset(1, 0, topInset, 0, 0);
tv.setBackgroundDrawable(ld);
return true;
}
});
}
En mi proyecto de prueba onPreDraw()
ha sido llamado dos veces, y creo que en su caso puede causar un ciclo infinito.
Podría tratar de llamar a setBackgroundDrawable()
solo cuando la altura del TextView
cambie:
private int mLastTvHeight = 0;
public void onCreate() {
setContentView(R.layout.main);
final TextView tv = (TextView)findViewById(R.id.image_test);
final LayerDrawable ld = (LayerDrawable)tv.getBackground();
final ViewTreeObserver obs = mTv.getViewTreeObserver();
obs.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw () {
Log.d(TAG, "onPreDraw tv height is " + tv.getHeight()); // bad for performance, remove on production
int height = tv.getHeight();
if (height != mLastTvHeight) {
mLastTvHeight = height;
int topInset = height / 2;
ld.setLayerInset(1, 0, topInset, 0, 0);
tv.setBackgroundDrawable(ld);
}
return true;
}
});
}
Pero eso suena un poco complicado para lo que intentas lograr y no es realmente bueno para el rendimiento.
EDIT por kcoppock
Esto es lo que terminé haciendo con este código. La respuesta de Gautier me llevó a este punto, así que preferiría aceptar esta respuesta con modificaciones que responderla yo mismo. Terminé usando el método addOnGlobalLayoutListener () de ViewTreeObserver en su lugar, al igual que (esto está en onCreate ()):
final TextView tv = (TextView)findViewById(R.id.image_test);
ViewTreeObserver vto = tv.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
LayerDrawable ld = (LayerDrawable)tv.getBackground();
ld.setLayerInset(1, 0, tv.getHeight() / 2, 0, 0);
}
});
Parece que funciona a la perfección; Comprobé LogCat y no vi ninguna actividad inusual. ¡Espero que sea esto! ¡Gracias!
Puede anular onMeasure para TextView:
public MyTextView extends TextView {
...
public void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec,heightMeasureSpec); // Important !!!
final int width = getMeasuredHeight();
final int height = getMeasuredHeight();
// do you background stuff
}
...
}
Estoy casi seguro de que puedes hacerlo sin ninguna línea de código también. Creo que hay una posibilidad de crear una lista de capas.
Puede obtener todas las medidas de vista en el método de actividad principal de la clase a continuación en el método de creación ():
@Override
protected void onCreate(Bundle savedInstanceState){}
@Override
public void onWindowFocusChanged(boolean hasFocus){
int w = yourTextView.getWidth();
}
Entonces puedes redibujar, ordenar ... tus puntos de vista. :)
Respecto al observador de árbol de vista:
No se garantiza que el observador ViewTreeObserver devuelto siga siendo válido durante la vigencia de esta vista. Si la persona que llama de este método mantiene una referencia de larga duración a ViewTreeObserver, siempre debe verificar el valor de retorno de isAlive ().
Incluso si se trata de una referencia de corta vida (solo se usa una vez para medir la vista y hacer el mismo tipo de cosas que estás haciendo), ya no lo veo "vivo" y lanzo una excepción. De hecho, cada función en ViewTreeObserver arrojará una IllegalStateException si ya no está ''viva'', por lo que siempre debe verificar ''isAlive''. Tuve que hacer esto:
if(viewToMeasure.getViewTreeObserver().isAlive()) {
viewToMeasure.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if(viewToMeasure.getViewTreeObserver().isAlive()) {
// only need to calculate once, so remove listener
viewToMeasure.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
// you can get the view''s height and width here
// the listener might not have been ''alive'', and so might not have been removed....so you are only
// 99% sure the code you put here will only run once. So, you might also want to put some kind
// of "only run the first time called" guard in here too
}
});
}
Esto me parece bastante frágil. Muchas cosas, aunque raramente, parecen poder fallar.
Así que comencé a hacer esto: cuando publica un mensaje en una Vista, los mensajes solo se entregarán después de que la Vista se haya inicializado por completo (incluida la medición). Si solo quiere que su código de medición se ejecute una vez, y no desea observar cambios de diseño durante la vida de la vista (porque no se está redimensionando), entonces simplemente puse mi código de medición en una publicación como esta :
viewToMeasure.post(new Runnable() {
@Override
public void run() {
// safe to get height and width here
}
});
No he tenido ningún problema con la estrategia de publicación de vistas, y no he visto parpadeos en la pantalla u otras cosas que podría anticipar que podrían salir mal (aún ...)
He tenido bloqueos en el código de producción porque no se eliminó mi observador de diseño global o porque el observador de diseño no estaba "vivo" cuando intenté acceder a él
Tan sumariamente, 3 maneras de manejar el tamaño de la vista ... ¡mmm, tal vez sea así!
1) Como mencionó @Gautier Hayoun, usando el oyente OnGlobalLayoutListener. Sin embargo, recuerde llamar al primer código en listener: listViewProduct.getViewTreeObserver().removeOnGlobalLayoutListener(this);
para asegurarse de que este oyente no llame infinitamente. Y una cosa más, a veces este oyente no invocará en absoluto, porque su punto de vista ha sido dibujado en algún lugar que no conoce. Entonces, para estar seguros, registremos este oyente justo después de declarar una vista en el método onCreate () (o enViewCreated () en fragmento)
view = ([someView]) findViewById(R.id.[viewId]);
// Get width of view before it''s drawn.
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// Make this listener call once.
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
int width = listViewProduct.getWidth();
});
2) Si quieres hacer algo justo después de que la vista se haya dibujado y mostrado, simplemente usa el método de publicación (por supuesto, puedes obtener el tamaño aquí, ya que tu vista ya fue dibujada por completo). Por ejemplo,
listViewProduct.post(new Runnable() {
@Override
public void run() {
// Do your stuff here.
}
});
Además, puede usar postDelay con el mismo propósito, agregando un poco de tiempo de retraso. Probémoslo tú mismo. Lo necesitarás alguna vez. Por ejemplo, cuando desea desplazarse por la vista de desplazamiento automáticamente después de agregar más elementos en la vista de desplazamiento o expandir o hacer más visible desde el estado en vista de desplazamiento con animación (esto significa que este proceso tomará un poco de tiempo para mostrar todo ) Definitivamente cambiará el tamaño de la vista de desplazamiento, por lo que después de dibujar la vista de desplazamiento, también debemos esperar un poco para obtener un tamaño exacto después de que agrega más vista. ¡Ya es hora de que el método postDelay venga a rescatar!
3) Y si desea configurar su propia vista, puede crear una clase y extender desde cualquier Vista que le guste, que tenga el método onMeasure () para definir el tamaño de la vista.
Espero que esto ayude,
Mttdat.
Utilice OnPreDrawListener()
lugar de addOnGlobalLayoutListener()
, porque se llama antes.
tv.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener()
{
@Override
public boolean onPreDraw()
{
tv.getViewTreeObserver().removeOnPreDrawListener(this);
// put your measurement code here
return false;
}
});
al usar el oyente global, puede encontrarse con un ciclo infinito.
Lo arreglé llamando
whateverlayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
dentro del método onGlobalLayout dentro del oyente.