Quand puis-je d’abord mesurer une vue?

J’ai donc un peu de confusion en essayant de définir l’arrière-plan dessiné d’une vue telle qu’elle est affichée. Le code repose sur la connaissance de la hauteur de la vue, donc je ne peux pas l’appeler depuis onCreate() ou onResume() , car getHeight() renvoie 0. onResume() semble être le plus proche possible. Où dois-je mettre un code tel que ci-dessous pour que l’arrière-plan change lors de l’affichage à l’utilisateur?

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

    Je ne connaissais pas ViewTreeObserver.addOnPreDrawListener() et je l’ai essayé dans un projet de test.

    Avec votre code, cela ressemblerait à ceci:

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

    Dans mon projet de test, onPreDraw() a été appelé deux fois et je pense que dans votre cas, cela pourrait provoquer une boucle infinie.

    Vous pouvez essayer d’appeler setBackgroundDrawable() uniquement lorsque la hauteur de TextView change:

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

    Mais cela semble un peu compliqué pour ce que vous essayez de réaliser et pas vraiment pour la performance.

    EDIT par kcoppock

    Voici ce que j’ai fini par faire de ce code. La réponse de Gautier m’a amené à ce point, alors je préfère accepter cette réponse avec modification plutôt que d’y répondre moi-même. J’ai fini par utiliser la méthode addOnGlobalLayoutListener () de ViewTreeObserver, comme ça (ceci est dans 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); } }); 

    Semble fonctionner parfaitement; J’ai vérifié LogCat et n’ai vu aucune activité inhabituelle. J’espère que c’est ça! Merci!

    Concernant l’observateur d’arbre de vue:

    L’observateur ViewTreeObserver renvoyé n’est pas garanti pour restr valide pendant la durée de vie de cette vue. Si l’appelant de cette méthode conserve une référence de longue durée à ViewTreeObserver, il doit toujours vérifier la valeur de retour de isAlive ().

    Même si c’est une référence de courte durée (utilisée seulement une fois pour mesurer la vue et faire le même genre de chose que vous faites), je l’ai vu ne plus être “vivant” et lancer une exception. En fait, chaque fonction de ViewTreeObserver lancera une exception IllegalStateException si elle n’est plus «vivante», vous devez donc toujours cocher «isAlive». Je devais le faire:

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

    Cela me semble assez fragile. Beaucoup de choses – quoique rarement – semblent pouvoir aller de travers.

    J’ai donc commencé à le faire: lorsque vous publiez un message dans une vue, les messages ne seront livrés qu’après l’initialisation complète de la vue (y compris la mesure). Si vous voulez seulement que votre code de mesure s’exécute une fois et que vous ne voulez pas réellement observer les changements de disposition pendant la durée de la vue (car il n’est pas redimensionné), alors je mets mon code de mesure dans un message comme celui-ci. :

      viewToMeasure.post(new Runnable() { @Override public void run() { // safe to get height and width here } }); 

    Je n’ai pas eu de problème avec la stratégie d’affichage des vues et je n’ai pas vu de scintillement de l’écran ou d’autres problèmes que vous prévoyez peut-être (encore …)

    J’ai eu des pannes dans le code de production parce que mon observateur de mise en page globale n’a pas été supprimé ou parce que l’observateur de mise en page n’était pas «vivant» lorsque j’essayais d’y accéder

    En utilisant le listener global, vous pouvez vous retrouver dans une boucle infinie.

    j’ai corrigé cela en appelant

     whateverlayout.getViewTreeObserver().removeGlobalOnLayoutListener(this); 

    à l’intérieur de la méthode onGlobalLayout dans l’écouteur.

    Dans mes projets, j’utilise l’extrait suivant:

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

    Ainsi, dans Runnable#run fourni, vous pouvez être sûr que View été mesuré et exécuter le code associé.

    Vous pouvez remplacer onMeasure pour 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 } ... } 

    Je suis presque sûr que vous pouvez le faire sans aucune ligne de code. Je pense qu’il y a une chance de créer une liste de calques.

    Vous pouvez obtenir toutes les mesures de vue dans la méthode de l’activité principale de la classe sous la méthode onCreate ():

     @Override protected void onCreate(Bundle savedInstanceState){} @Override public void onWindowFocusChanged(boolean hasFocus){ int w = yourTextView.getWidth(); } 

    Vous pouvez donc redessiner, sortinger … vos opinions. 🙂

    Utilisez OnPreDrawListener() au lieu de addOnGlobalLayoutListener() , car il est appelé plus tôt.

      tv.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { tv.getViewTreeObserver().removeOnPreDrawListener(this); // put your measurement code here return false; } }); 

    Donc, sommairement, 3 façons de gérer la taille de la vue …. hmmm, peut-être alors!

    1) Comme @Gautier Hayoun l’a mentionné, en utilisant l’écouteur OnGlobalLayoutListener. Cependant, n’oubliez pas d’appeler au premier code de listener: listViewProduct.getViewTreeObserver().removeOnGlobalLayoutListener(this); pour vous assurer que cet auditeur n’appellera pas à l’infini. Et encore une fois, cet auditeur n’appelle pas du tout, parce que votre vue a été dessinée dans un endroit que vous ne connaissez pas. Donc, pour être sûr, enregistrons cet écouteur juste après avoir déclaré une vue dans la méthode onCreate () (ou onViewCreated () dans fragment)

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