Comment récupérer les dimensions d'une vue?

209

J'ai une vue composée de TableLayout, TableRow and TextView. Je veux qu'il ressemble à une grille. J'ai besoin d'obtenir la hauteur et la largeur de cette grille. Les méthodes getHeight()et getWidth()reviennent toujours 0. Cela se produit lorsque formater la grille dynamique et aussi lorsque j'utilise une version XML.

Comment récupérer les dimensions d'une vue?


Voici mon programme de test que j'ai utilisé dans Debug pour vérifier les résultats:

import android.app.Activity;
import android.os.Bundle;
import android.widget.TableLayout;
import android.widget.TextView;

public class appwig extends Activity {  
    @Override
    public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.maindemo);  //<- includes the grid called "board"
      int vh = 0;   
      int vw = 0;

      //Test-1 used the xml layout (which is displayed on the screen):
      TableLayout tl = (TableLayout) findViewById(R.id.board);  
      tl = (TableLayout) findViewById(R.id.board);
      vh = tl.getHeight();     //<- getHeight returned 0, Why?  
      vw = tl.getWidth();     //<- getWidth returned 0, Why?   

      //Test-2 used a simple dynamically generated view:        
      TextView tv = new TextView(this);
      tv.setHeight(20);
      tv.setWidth(20);
      vh = tv.getHeight();    //<- getHeight returned 0, Why?       
      vw = tv.getWidth();    //<- getWidth returned 0, Why?

    } //eof method
} //eof class
Sagar Zala
la source
au lieu d'utiliser getWidth / Height, utilisez getMeasuredWidth / Height après l'application de la mise en page à l'activité.
bhups
9
Les gars, il suffit d'appeler getHeight()et getWidth()après que le cycle de vie de l'activité ait demandé aux vues de se mesurer, en d'autres termes, de faire ce genre de choses onResume()et c'est tout. Vous ne devriez pas vous attendre à ce qu'un objet pas encore disposé connaisse ses dimensions, c'est tout le truc. Pas besoin de la magie suggérée ci-dessous.
empileur de classe
2
L' appel getHeight()/Width()à onResume()ne pas céder> 0 valeur pour moi.
Darpan
@ClassStacker Et Fragment?
zdd
@zdd Veuillez poser une nouvelle question complète et publier une référence ici dans un commentaire.
classe stacker

Réponses:

418

Je crois que l'OP est révolue depuis longtemps, mais au cas où cette réponse pourrait aider les futurs chercheurs, j'ai pensé publier une solution que j'ai trouvée. J'ai ajouté ce code dans ma onCreate()méthode:

MODIFIÉ : 07/05/11 pour inclure le code des commentaires:

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);
        ViewTreeObserver obs = tv.getViewTreeObserver();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
            obs.removeOnGlobalLayoutListener(this);
        } else {
            obs.removeGlobalOnLayoutListener(this);
        }
    }

});

Je reçois d'abord une référence finale à mon TextView(pour accéder à la onGlobalLayout()méthode). Ensuite, j'obtiens le ViewTreeObserverde mon TextView, et j'ajoute un OnGlobalLayoutListener, redéfinissant onGLobalLayout(il ne semble pas y avoir de méthode de superclasse à invoquer ici ...) et en ajoutant mon code qui nécessite de connaître les mesures de la vue dans cet écouteur. Tout fonctionne comme prévu pour moi, donc j'espère que cela pourra vous aider.

Kevin Coppock
la source
23
@George Bailey: Une chose que j'ai récemment vu quelqu'un d'autre publier (je ne l'ai pas testé moi-même, donc YMMV) pour surmonter les appels multiples était (dans onGlobalLayout ()): de tv.getViewTreeObserver().removeGlobalOnLayoutListener(this);cette façon, l'auditeur devrait être supprimé après la première fois ça arrive.
Kevin Coppock
3
Je veux aussi aider. Vous devrez peut-être faire quelque chose comme: mView.getViewTreeObserver (). RemoveGlobalOnLayoutListener (globalLayoutListener); si vous souhaitez redimensionner votre vue une seule fois. J'espère que cette aide
Henry Sou
7
La question est très ancienne, mais l'utilisation addOnLayoutChangeListenerfonctionne aussi! :)
FX
7
Notez également que depuis l'API 16, vous avez besoin de: if (android.os.Build.VERSION.SDK_INT> = 16) {view.getViewTreeObserver (). RemoveOnGlobalLayoutListener (this); } else {view.getViewTreeObserver (). removeGlobalOnLayoutListener (this); }
Edison
2
Comme removeGlobalOnLayoutListener est obsolète, il donne toujours des avertissements dans Eclipse. Est-ce normal? Avec le temps, des avertissements de code obsolètes se répandraient partout ...
Jonny
82

Je vais simplement ajouter une solution alternative, remplacer la méthode onWindowFocusChanged de votre activité et vous pourrez obtenir les valeurs de getHeight (), getWidth () à partir de là.

@Override
public void onWindowFocusChanged (boolean hasFocus) {
        // the height will be set at this point
        int height = myEverySoTallView.getMeasuredHeight(); 
}
JustDanyul
la source
4
Pour citer la documentation, "C'est le meilleur indicateur pour savoir si cette activité est visible pour l'utilisateur."
Cameron Lowell Palmer
6
Cela ne fonctionne pas dans le cas de Fragments que vous devez utiliser ViewTreeObserver.
Mihir
OP a spécifiquement demandé comment l'obtenir pour une vue
JustDanyul
scrollview hauteur totale et max Scrollview.getScrolly () seront les mêmes? c'est à dire dans mon cas, j'ai obtenu la hauteur avec la méthode ci-dessus est 702 et j'ai obtenu la valeur maximale de getScrolly () est 523,
Hitesh Dhamshaniya
Cette méthode ne fonctionne pas non plus lorsque votre vue est incluse à partir d'un autre fichier xml.
Jay Donga
19

Vous essayez d'obtenir la largeur et la hauteur d'un élément qui n'a pas encore été dessiné.

Si vous utilisez le débogage et arrêtez à un moment donné, vous verrez que l'écran de votre appareil est toujours vide, c'est parce que vos éléments n'étaient pas encore dessinés, donc vous ne pouvez pas obtenir la largeur et la hauteur de quelque chose, ce n'est pas encore exister.

Et, je peux me tromper, mais setWidth()n'est pas toujours respecté, Layoutexpose ses enfants et décide comment les mesurer (appel child.measure()), donc si vous définissezsetWidth() , vous n'êtes pas assuré d'obtenir cette largeur après que l'élément sera dessiné.

Ce dont vous avez besoin, c'est d'utiliser getMeasuredWidth() (la mesure la plus récente de votre vue) quelque part après que la vue a été réellement dessinée.

Regardez dans le Activitycycle de vie pour trouver le meilleur moment.

http://developer.android.com/reference/android/app/Activity.html#ActivityLifecycle

Je crois qu'une bonne pratique consiste à utiliser OnGlobalLayoutListenercomme ceci:

yourView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            if (!mMeasured) {
                // Here your view is already layed out and measured for the first time
                mMeasured = true; // Some optional flag to mark, that we already got the sizes
            }
        }
    });

Vous pouvez placer ce code directement dans onCreate(), et il sera invoqué lorsque les vues seront présentées.

Alex Orlov
la source
Malheureusement, cela ne fonctionne pas également dans OnResume. Les dimensions finales sont définies quelque part après l'appel de OnResume, au moins dans l'émulateur.
jki
C'est une horrible pratique !!! Cet auditeur sera appelé encore et encore et tuera votre performance. Au lieu d'utiliser des indicateurs, annulez l'écoute une fois terminé.
bleu pendant
15

Utilisez la méthode de publication de View comme celle-ci

post(new Runnable() {   
    @Override
    public void run() {
        Log.d(TAG, "width " + MyView.this.getMeasuredWidth());
        }
    });
Boris Rusev
la source
1
Cela fonctionne, mais vraiment, aucune discussion sur pourquoi? Btw, cela fonctionne parce que le runnable ne s'exécute que lorsque la mise en page a eu lieu.
Norman H
7

J'ai essayé d'utiliser onGlobalLayout()pour faire un formatage personnalisé d'un TextView, mais comme @George Bailey l'a remarqué, il onGlobalLayout()est en effet appelé deux fois: une fois sur le chemin de mise en page initial, et une deuxième fois après avoir modifié le texte.

View.onSizeChanged()fonctionne mieux pour moi car si j'y modifie le texte, la méthode n'est appelée qu'une seule fois (lors de la mise en page). Cela nécessitait une sous-classification de TextView, mais au niveau API 11+View. addOnLayoutChangeListener() pouvez éviter cette sous-classification.

Une dernière chose, afin d'obtenir une largeur correcte de la vue View.onSizeChanged(), le layout_widthdoit être défini sur match_parent, non wrap_content.

Y2i
la source
2

Essayez-vous d'obtenir des tailles dans un constructeur ou toute autre méthode exécutée AVANT d'obtenir l'image réelle?

Vous n'obtiendrez aucune dimension avant que tous les composants ne soient réellement mesurés (car votre xml ne connaît pas la taille de votre affichage, la position des parents, etc.)

Essayez d'obtenir des valeurs après onSizeChanged () (bien qu'il puisse être appelé avec zéro), ou attendez simplement quand vous obtiendrez une image réelle.

Alex Orlov
la source
J'ai mis à jour ma question et mon code d'origine pour être plus clair. Je vous remercie.
2

Comme FX l'a mentionné, vous pouvez utiliser un OnLayoutChangeListenerà la vue que vous souhaitez suivre lui-même

view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    @Override
    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
        // Make changes
    }
});

Vous pouvez supprimer l'écouteur dans le rappel si vous souhaitez uniquement la disposition initiale.

Zeophlite
la source
1

ViewTreeObserveret onWindowFocusChanged()ne sont pas si nécessaires du tout.

Si vous gonflez la TextViewdisposition en tant que et / ou y mettez du contenu et que vous la définissez, LayoutParamsvous pouvez utiliser getMeasuredHeight()et getMeasuredWidth().

MAIS vous devez être prudent avecLinearLayouts (peut-être aussi d'autres ViewGroups). Le problème est que vous pouvez obtenir la largeur et la hauteur après, onWindowFocusChanged()mais si vous essayez d'y ajouter des vues, vous ne pouvez pas obtenir ces informations tant que tout n'a pas été dessiné. J'essayais d'en ajouter plusieurs TextViewspour LinearLayoutsimiter un FlowLayout(style d'emballage) et je ne pouvais donc pas l'utiliser Listeners. Une fois le processus démarré, il doit se poursuivre de manière synchrone. Donc, dans ce cas, vous souhaiterez peut-être conserver la largeur dans une variable pour l'utiliser plus tard, car lors de l'ajout de vues à la mise en page, vous en aurez peut-être besoin.

Genom
la source
1

Même si la solution proposée fonctionne, ce n'est peut-être pas la meilleure solution pour tous les cas car basée sur la documentation de ViewTreeObserver.OnGlobalLayoutListener

Définition d'interface pour un rappel à invoquer lorsque l'état de la disposition globale ou la visibilité des vues dans l'arborescence des vues change.

ce qui signifie qu'il est appelé plusieurs fois et pas toujours la vue est mesurée (sa hauteur et sa largeur sont déterminées)

Une alternative consiste à utiliser ViewTreeObserver.OnPreDrawListenerce qui n'est appelé que lorsque la vue est prête à être dessinée et a toutes ses mesures.

final TextView tv = (TextView)findViewById(R.id.image_test);
ViewTreeObserver vto = tv.getViewTreeObserver();
vto.addOnPreDrawListener(new OnPreDrawListener() {

    @Override
    public void onPreDraw() {
        tv.getViewTreeObserver().removeOnPreDrawListener(this);
        // Your view will have valid height and width at this point
        tv.getHeight();
        tv.getWidth();
    }

});
Kayvan N
la source
0

Vous pouvez utiliser une diffusion appelée dans OnResume ()

Par exemple:

int vh = 0;   
int vw = 0;

public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.maindemo);  //<- includes the grid called "board"

      registerReceiver(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                TableLayout tl = (TableLayout) findViewById(R.id.board);  
                tl = (TableLayout) findViewById(R.id.board);
                vh = tl.getHeight();       
                vw = tl.getWidth();               
            }
      }, new IntentFilter("Test"));
}

protected void onResume() {
        super.onResume();

        Intent it = new Intent("Test");
        sendBroadcast(it);

}

Vous ne pouvez pas obtenir la hauteur d'une vue dans OnCreate (), onStart () ou même dans onResume () pour la raison que kcoppock a répondu

Lucas Ferreira Batista
la source
0

La hauteur et la largeur sont nulles car la vue n'a pas été créée au moment où vous demandez sa hauteur et sa largeur. Une solution la plus simple est

view.post(new Runnable() {
    @Override
    public void run() {
        view.getHeight(); //height is ready
        view.getWidth(); //width is ready
    }
});

Cette méthode est bonne par rapport à d'autres méthodes car elle est courte et nette.

Shivam Yadav
la source
-1

Réponse simple: Cela a fonctionné pour moi sans problème. Il semble que la clé consiste à s'assurer que la vue a le focus avant d'obtenir getHeight, etc. Faites-le en utilisant la méthode hasFocus (), puis en utilisant la méthode getHeight () dans cet ordre. Seulement 3 lignes de code requises.

ImageButton myImageButton1 =(ImageButton)findViewById(R.id.imageButton1); myImageButton1.hasFocus();

int myButtonHeight = myImageButton1.getHeight();

Log.d("Button Height: ", ""+myButtonHeight );//Not required

J'espère que ça aide.

Monk4D
la source
-3

Utilisez getMeasuredWidth () et getMeasuredHeight () pour votre vue.

Guide du développeur: Afficher

Vladimir Ivanov
la source
4
Comme indiqué précédemment ces méthodes renvoient uniquement un résultat valide efter la taille a été meassured. Dans une activité, cela est vrai lorsque la méthode onWindowFocusChanged est appelée.
slott
-8

CORRECTION: J'ai découvert que la solution ci-dessus est terrible. Surtout lorsque votre téléphone est lent. Et ici, j'ai trouvé une autre solution: calculer la valeur px de l'élément, y compris les marges et les rembourrages: dp à px: https://stackoverflow.com/a/6327095/1982712

ou dimens.xml en px: https://stackoverflow.com/a/16276351/1982712

sp à px: https://stackoverflow.com/a/9219417/1982712 (inverser la solution)

ou dimens en px: https://stackoverflow.com/a/16276351/1982712

et c'est tout.

pimkle
la source
10
C'est généralement une mauvaise idée d'espérer que tout sera réglé correctement après un certain nombre de millisecondes arbitrairement choisi. Le périphérique peut être lent, d'autres processus / threads peuvent être en cours d'exécution, peuvent ne pas fonctionner dans le débogueur, peuvent ne pas fonctionner dans la prochaine version du système d'exploitation, ...
Kristopher Johnson