web-dev-qa-db-fra.com

Quand puis-je mesurer une vue pour la première fois?

J'ai donc un peu de confusion à essayer de définir l'arrière-plan dessinable 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 à partir de onCreate() ou onResume(), car getHeight() renvoie 0. onResume() semble être le plus proche possible. Où dois-je mettre du code tel que ci-dessous afin 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);
59
kcoppock

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ée deux fois, et je pense que dans votre cas, cela peut provoquer une boucle infinie.

Vous pouvez essayer d'appeler la setBackgroundDrawable() uniquement lorsque la hauteur de la 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 bon pour les performances.

MODIFIER par kcoppock

Voici ce que j'ai fini par faire à partir de ce code. La réponse de Gautier m'a amené à ce point, donc 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 à la place, comme ceci (c'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 je n'ai vu aucune activité inhabituelle. J'espère que c'est ça! Merci!

69
Gautier Hayoun

Concernant l'observateur d'arbre de vue:

L'observateur ViewTreeObserver retourné n'est pas garanti pour rester 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 éphémère (utilisée une seule 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 lever une exception. En fait, chaque fonction sur ViewTreeObserver lèvera une exception IllegalStateException si elle n'est plus "vivante", vous devez donc toujours vérifier "isAlive". Je devais faire ça:

    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 mal tourner.

J'ai donc commencé à le faire: lorsque vous postez un message dans une vue, les messages ne seront livrés qu'après que la vue a été complètement initialisée (y compris la mesure). Si vous ne souhaitez exécuter votre code de mesure qu'une seule fois et que vous ne souhaitez pas réellement observer les changements de mise en page pendant la durée de vie de la vue (car il n'est pas redimensionné), je mets simplement mon code de mesure dans un article comme celui-ci. :

 viewToMeasure.post(new Runnable() {

        @Override
        public void run() {
            // safe to get height and width here               
        }

    });

Je n'ai eu aucun problème avec la stratégie de publication des vues, et je n'ai vu aucun scintillement d'écran ou d'autres choses que vous anticipez pourraient mal tourner (encore ...)

J'ai ai eu des plantages 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'ai essayé d'y accéder

64
Splash

en utilisant l'écouteur global, vous pouvez rencontrer une boucle infinie.

j'ai corrigé cela en appelant

whateverlayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);

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

11
kevinl

Dans mes projets, j'utilise l'extrait de code 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);
            }
        }
    });
}

Donc, à l'intérieur fourni Runnable#run vous pouvez être sûr que View a été mesuré et exécuter le code associé.

3
Dmitry Zaytsev

Vous pouvez remplacer onMeasure for 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 également le faire sans aucune ligne de code. Je pense qu'il y a une chance de créer une liste de couches.

2
Damian Kołakowski

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;
        }
    });
0
Ayaz Alifov

Vous pouvez obtenir toutes les mesures de vue dans la méthode de l'activité principale de la classe ci-dessous, la méthode onCreate ():

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

Ainsi, vous pouvez redessiner, trier ... vos vues. :)

0
nobjta_9x_tq

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

1) Comme l'a mentionné @Gautier Hayoun, en utilisant l'écouteur OnGlobalLayoutListener. Cependant, n'oubliez pas d'appeler au premier code de l'écouteur: listViewProduct.getViewTreeObserver().removeOnGlobalLayoutListener(this); pour vous assurer que cet écouteur n'appellera pas indéfiniment. Et encore une chose, parfois cet auditeur n'invoquera pas du tout, parce que votre vue a été dessinée quelque part 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 un 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();
      });

2) Si vous voulez faire quelque chose juste après que la vue a été dessinée et affichée, utilisez simplement la méthode post (bien sûr, vous pouvez obtenir la taille ici, car votre vue a déjà été entièrement dessinée). Par exemple,

listViewProduct.post(new Runnable() {
  @Override
  public void run() {
    // Do your stuff here.
  }
});

De plus, vous pouvez utiliser postDelay dans le même but, en ajoutant un peu de retard. Essayons vous-même. Vous en aurez besoin un jour. Par exemple, lorsque vous souhaitez faire défiler la vue de défilement automatiquement après avoir ajouté plus d'éléments dans la vue de défilement ou développer ou rendre une vue plus visible de l'état disparu dans la vue de défilement avec animation (cela signifie que ce processus prendra un peu de temps pour tout afficher). ). Cela changera certainement la taille de la vue de défilement, donc une fois la vue de défilement dessinée, nous devons également attendre un peu de temps pour obtenir une taille exacte après avoir ajouté plus de vue. Il est grand temps que la méthode postDelay vienne à la rescousse!

3) Et si vous souhaitez configurer votre propre vue, vous pouvez créer une classe et étendre à partir de n'importe quelle vue que vous aimez et qui ont toutes la méthode onMeasure () pour définir la taille de la vue.

J'espère que cela t'aides,

Mttdat.

0
Nguyen Tan Dat