La vue getWidth () et getHeight () renvoie 0

513

Je crée dynamiquement tous les éléments de mon projet Android. J'essaie d'obtenir la largeur et la hauteur d'un bouton afin de pouvoir le faire pivoter. J'essaie juste d'apprendre à travailler avec la langue Android. Cependant, il renvoie 0.

J'ai fait des recherches et j'ai vu que cela devait être fait ailleurs que dans la onCreate()méthode. Si quelqu'un peut me donner un exemple de la façon de procéder, ce serait formidable.

Voici mon code actuel:

package com.animation;

import android.app.Activity;
import android.os.Bundle;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.Button;
import android.widget.LinearLayout;

public class AnimateScreen extends Activity {


//Called when the activity is first created.
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    LinearLayout ll = new LinearLayout(this);

    LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
    layoutParams.setMargins(30, 20, 30, 0);

    Button bt = new Button(this);
    bt.setText(String.valueOf(bt.getWidth()));

    RotateAnimation ra = new RotateAnimation(0,360,bt.getWidth() / 2,bt.getHeight() / 2);
    ra.setDuration(3000L);
    ra.setRepeatMode(Animation.RESTART);
    ra.setRepeatCount(Animation.INFINITE);
    ra.setInterpolator(new LinearInterpolator());

    bt.startAnimation(ra);

    ll.addView(bt,layoutParams);

    setContentView(ll);
}

Toute aide est appréciée.

ngreenwood6
la source

Réponses:

197

Vous appelez getWidth()trop tôt. L'interface utilisateur n'a pas encore été dimensionnée et affichée à l'écran.

Je doute que vous vouliez faire ce que vous faites, de toute façon - les widgets animés ne modifient pas leurs zones cliquables, et donc le bouton répondra toujours aux clics dans l'orientation d'origine, quelle que soit la façon dont il a pivoté.

Cela étant dit, vous pouvez utiliser une ressource de dimension pour définir la taille du bouton, puis référencer cette ressource de dimension à partir de votre fichier de mise en page et de votre code source, pour éviter ce problème.

CommonsWare
la source
82
ngreenwood6, quelle est votre autre solution?
Andrew
12
@Andrew - si vous voulez que negreenwood6 soit informé de votre suivi, vous devez commencer votre message comme je l'ai fait pour vous (je pense que les trois premières lettres suffisent) - CommonsWare est averti automatiquement, car il a écrit cette réponse, mais ngreen ne le fait pas sauf si vous leur parlez.
Peter Ajtai
11
@Override public void onWindowFocusChanged (boolean hasFocus) {// TODO Stub de méthode généré automatiquement super.onWindowFocusChanged (hasFocus); // Ici, vous pouvez obtenir la taille! }
Sana
5
@ ngreenwood6 Alors quelle était votre solution?
Nima Vaziri
5
Utilisez cet écouteur pour obtenir la taille, quand votre écran est-il prêt. view.getViewTreeObserver (). addOnGlobalLayoutListener (nouveau ViewTreeObserver.OnGlobalLayoutListener () {}
Sinan Dizdarević
816

Le problème de base est que vous devez attendre la phase de dessin pour les mesures réelles (en particulier avec des valeurs dynamiques comme wrap_contentou match_parent), mais généralement cette phase n'est pas terminée onResume(). Vous avez donc besoin d'une solution de contournement pour attendre cette phase. Il existe différentes solutions possibles à cela:


1. Écoutez les événements Draw / Layout: ViewTreeObserver

Un ViewTreeObserver est déclenché pour différents événements de dessin. Habituellement, OnGlobalLayoutListenerc'est ce que vous voulez pour obtenir la mesure, donc le code dans l'auditeur sera appelé après la phase de mise en page, donc les mesures sont prêtes:

view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                view.getHeight(); //height is ready
            }
        });

Remarque: l'écouteur sera immédiatement supprimé car sinon, il se déclenchera à chaque événement de mise en page. Si vous devez prendre en charge les applications SDK Lvl <16, utilisez-le pour désinscrire l'auditeur:

public void removeGlobalOnLayoutListener (ViewTreeObserver.OnGlobalLayoutListener victim)


2. Ajoutez un exécutable à la file d'attente de mise en page: View.post ()

Pas très connu et ma solution préférée. Fondamentalement, utilisez simplement la méthode de publication de View avec votre propre exécutable. Cela met essentiellement votre code en file d'attente après la mesure, la disposition, etc. de la vue, comme indiqué par Romain Guy :

La file d'attente d'événements de l'interface utilisateur traitera les événements dans l'ordre. Une fois que setContentView () est invoqué, la file d'attente d'événements contiendra un message demandant une sortie relais, donc tout ce que vous publierez dans la file d'attente se produira après le passage de la mise en page

Exemple:

final View view=//smth;
...
view.post(new Runnable() {
            @Override
            public void run() {
                view.getHeight(); //height is ready
            }
        });

L'avantage sur ViewTreeObserver:

  • votre code n'est exécuté qu'une seule fois et vous n'avez pas à désactiver l'observateur après l'exécution, ce qui peut être un problème
  • syntaxe moins verbeuse

Références:


3. Écraser la méthode onLayout des vues

Ceci n'est pratique que dans certaines situations où la logique peut être encapsulée dans la vue elle-même, sinon c'est une syntaxe assez bavarde et encombrante.

view = new View(this) {
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        view.getHeight(); //height is ready
    }
};

Sachez également que onLayout sera appelé plusieurs fois, alors pensez à ce que vous faites dans la méthode ou désactivez votre code après la première fois


4. Vérifiez si la phase de mise en page a été franchie

Si vous avez du code qui s'exécute plusieurs fois lors de la création de l'interface utilisateur, vous pouvez utiliser la méthode de support v4 lib suivante:

View viewYouNeedHeightFrom = ...
...
if(ViewCompat.isLaidOut(viewYouNeedHeightFrom)) {
   viewYouNeedHeightFrom.getHeight();
}

Renvoie true si la vue a traversé au moins une disposition depuis sa dernière connexion ou déconnexion à une fenêtre.


Supplémentaire: Obtention de mesures définies statiquement

S'il suffit juste d'obtenir la hauteur / largeur définie statiquement, vous pouvez simplement le faire avec:

Mais attention, cela peut être différent de la largeur / hauteur réelle après le dessin. Le javadoc décrit parfaitement la différence:

La taille d'une vue est exprimée avec une largeur et une hauteur. Une vue possède en réalité deux paires de valeurs de largeur et de hauteur.

La première paire est connue sous le nom de largeur et hauteur mesurées. Ces dimensions définissent la taille d'une vue au sein de son parent (voir Présentation pour plus de détails.) Les dimensions mesurées peuvent être obtenues en appelant getMeasuredWidth () et getMeasuredHeight ().

La deuxième paire est simplement appelée largeur et hauteur, ou parfois largeur et hauteur de dessin. Ces dimensions définissent la taille réelle de la vue à l'écran, au moment du dessin et après la mise en page. Ces valeurs peuvent, mais ne doivent pas nécessairement, être différentes de la largeur et de la hauteur mesurées. La largeur et la hauteur peuvent être obtenues en appelant getWidth () et getHeight ().

Patrick Favre
la source
1
J'utilise le view.post dans un fragment. J'ai besoin de mesurer la vue dont la hauteur du parent est 0dp et a un poids défini. Tout ce que j'essaie renvoie une hauteur de zéro (j'ai également essayé l'écouteur de disposition globale). Lorsque je mets le code view.post dans onViewCreated avec un délai de 125, cela fonctionne, mais pas sans délai. Des idées?
Psest328
6
dommage que vous ne puissiez voter qu'une seule fois, une très bonne explication. J'ai eu un problème avec une rotation de la vue si je quittais mon application et la redémarrais à partir des applications récentes. Si je l'ai glissé à partir de là et fait un nouveau redémarrage, tout allait bien. Le view.post () m'a sauvé la journée!
Matthias
1
Merci. J'utilise habituellement View.post () (2e méthode), mais s'il est appelé à partir d'un thread d'arrière-plan, cela ne fonctionne parfois pas. Alors n'oubliez pas d'écrire runOnUiThread(new Runnable() {...}. Actuellement , j'utilise une variante de la 1ère méthode: addOnLayoutChangeListener. N'oubliez pas de le supprimer puis à l'intérieur: removeOnLayoutChangeListener. Il fonctionne également à partir du fil d'arrière-plan.
CoolMind
2
Malheureusement pour moi, 'View.post ()' ne fonctionne pas tout le temps. J'ai 2 projets qui sont très similaires. Dans un projet, cela fonctionne, de l'autre ne fonctionne pas. :(. Dans les deux situations, j'appelle la méthode de la méthode 'onCreate ()' d'une activité. Une suggestion pourquoi?
Adrian Buciuman
1
Je trouve le problème. Dans un projet, la vue a android:visibility="gone"et sur l'autre projet la propriété de visibilité était invisible. Si la visibilité est gonela largeur sera toujours 0.
Adrian Buciuman
262

On peut utiliser

@Override
 public void onWindowFocusChanged(boolean hasFocus) {
  super.onWindowFocusChanged(hasFocus);
  //Here you can get the size!
 }
Sana
la source
10
alors comment pouvons-nous travailler dans les fragments? cela ne fonctionne que dans l'activité?
Olkunmustafa
8
Cela devrait faire l'affaire mais ce ne devrait pas être la réponse. Un utilisateur souhaite obtenir des dimensions à tout moment au lieu de se dessiner sur la fenêtre. De plus, cette méthode est appelée plusieurs fois ou à chaque fois qu'il y a un changement (Afficher masquer, disparu, ajouter de nouvelles vues, etc.) dans la fenêtre. Alors utilisez-le soigneusement :)
Dr aNdRO
1
Il s'agit d'un hack et il semble que l'utilisation de ViewTreeObserver serait préférable.
i_am_jorf
Ne pas vouloir paraître grossier, mais laisser un commentaire généré automatiquement (en particulier dans 2 lignes de code) ne donne pas beaucoup confiance que vous savez réellement ce que vous faites.
Natix
Malheureusement, cela n'a pas fonctionné pour moi. Essayer viewTreeObserver a fonctionné pour moi.
Narendra Singh
109

J'ai utilisé cette solution, qui je pense est meilleure que sur onWindowFocusChanged (). Si vous ouvrez un DialogFragment, puis faites pivoter le téléphone, onWindowFocusChanged sera appelé uniquement lorsque l'utilisateur ferme la boîte de dialogue):

    yourView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

        @Override
        public void onGlobalLayout() {
            // Ensure you call it only once :
            yourView.getViewTreeObserver().removeGlobalOnLayoutListener(this);

            // Here you can get the size :)
        }
    });

Edit: comme removeGlobalOnLayoutListener est déconseillé, vous devriez maintenant faire:

@SuppressLint("NewApi")
@SuppressWarnings("deprecation")
@Override
public void onGlobalLayout() {

    // Ensure you call it only once :
    if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
        yourView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
    }
    else {
        yourView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
    }

    // Here you can get the size :)
}
Tim Autin
la source
4
c'est de loin la meilleure solution! car il fonctionne également sur des solutions dynamiques où la largeur et la hauteur ne sont pas connues et calculées. c'est-à-dire dans une disposition relative basée sur les positions / tailles d'autres éléments. Bien joué!
George Pligoropoulos
2
Cela nécessite API 16 ou supérieur (Jelly bean)
tomsv
7
Il N'EXIGE PAS D'API 16! Le seul problème est la méthode removeGlobalOnLayoutListener déconseillée à l'API 16, mais il existe une solution simple compatible avec tous les niveaux de l'API - stackoverflow.com/questions/16189525/…
gingo
Si vous devez supprimer l'écouteur pour qu'il ne s'appelle pas plusieurs fois et que vous rencontriez des problèmes avec différentes API, je vous recommande de définir un faux booléen en tête de la classe, puis après avoir capturé les valeurs, définissez-le sur true pour que il ne capture pas à nouveau les valeurs.
Mansour Fahad
Si vous supprimez l'auditeur après la première exécution, vous risquez de vous tromper de dimensions: 02-14 10: 18: 53.786 15063-15063 / PuzzleFragment: hauteur: 1647 largeur: 996 02-14 10: 18: 53.985 15063-15063 / PuzzleFragment: hauteur : 1500 largeur: 996
Leos Literak
76

Comme l'indique Ian dans ce fil de discussion Android Developers :

Quoi qu'il en soit, l'accord est que la mise en page du contenu d'une fenêtre se produit après que tous les éléments sont construits et ajoutés à leurs vues parentes. Il doit en être ainsi, car jusqu'à ce que vous sachiez quels composants contient une vue, ce qu'ils contiennent, etc., il n'y a aucun moyen raisonnable de la disposer.

En bout de ligne, si vous appelez getWidth () etc. dans un constructeur, il retournera zéro. La procédure consiste à créer tous vos éléments de vue dans le constructeur, puis à attendre que la méthode onSizeChanged () de votre vue soit appelée - c'est à ce moment que vous découvrez votre taille réelle, c'est donc à ce moment que vous définissez la taille de vos éléments GUI.

Sachez également que onSizeChanged () est parfois appelé avec des paramètres de zéro - vérifiez ce cas et revenez immédiatement (afin de ne pas obtenir de division par zéro lors du calcul de votre disposition, etc.). Quelque temps plus tard, il sera appelé avec les valeurs réelles.

kerem yokuva
la source
8
onSizeChanged a fonctionné pour moi. onWindowFocusedChanged n'est pas appelé dans ma vue personnalisée.
aaronmarino
cela semble une bonne solution, mais que dois-je toujours créer ma vue personnalisée pour remplacer la méthode onSizeChanged?
M.ElSaka
Bottom line, if you call getWidth() etc. in a constructor, it will return zero.c'est un bingo. La racine de mes problèmes.
MikeNereson
Je suggère cette solution pour les fragments, car d'autres solutions renvoient 0 dans mon cas.
WindRider
66

Si vous devez obtenir la largeur d'un widget avant qu'il ne s'affiche à l'écran, vous pouvez utiliser getMeasuredWidth () ou getMeasuredHeight ().

myImage.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
int width = myImage.getMeasuredWidth();
int height = myImage.getMeasuredHeight();
sulu.dev
la source
5
Pour une raison quelconque, c'était la seule réponse qui a fonctionné pour moi - merci!
Farbod Salamat-Zadeh
Même chose pour moi, la solution la plus simple, qui a fonctionné pour moi
Tima
Travaillez comme un charme!
Morales Batovski
1
@CommonsWare comment cette solution donne-t-elle la hauteur et la largeur avant que l'observateur d'arbre de vue sur le rappel des dispositions globales, une explication serait utile.
Mightian
22

Je préfère utiliser OnPreDrawListener()au lieu de addOnGlobalLayoutListener(), car il est appelé un peu plus tôt que les autres auditeurs.

    view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener()
    {
        @Override
        public boolean onPreDraw()
        {
            if (view.getViewTreeObserver().isAlive())
                view.getViewTreeObserver().removeOnPreDrawListener(this);

            // put your code here
            return true;
        }
    });
Ayaz Alifov
la source
3
Notez que les return falsemoyens d' annuler le passage de dessin en cours . Pour continuer, à la return trueplace.
Pang
9

Une extension Kotlin pour observer sur la mise en page globale et effectuer une tâche donnée lorsque la hauteur est prête dynamiquement.

Usage:

view.height { Log.i("Info", "Here is your height:" + it) }

La mise en oeuvre:

fun <T : View> T.height(function: (Int) -> Unit) {
    if (height == 0)
        viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
                function(height)
            }
        })
    else function(height)
}
Renetik
la source
7

AndroidX dispose de plusieurs fonctions d'extension qui vous aident dans ce type de travail, à l'intérieur androidx.core.view

Vous devez utiliser Kotlin pour cela.

Celui qui convient le mieux ici est doOnLayout:

Exécute l'action donnée lorsque cette vue est présentée. Si la vue a été mise en page et qu'elle n'a pas demandé de mise en page, l'action sera exécutée immédiatement sinon, l'action sera effectuée après la prochaine mise en page de la vue.

L'action ne sera invoquée qu'une seule fois sur la mise en page suivante, puis supprimée.

Dans votre exemple:

bt.doOnLayout {
    val ra = RotateAnimation(0,360,it.width / 2,it.height / 2)
    // more code
}

Dépendance: androidx.core:core-ktx:1.0.0

Vlad
la source
Ceci est la seule bonne réponse. android.googlesource.com/platform/frameworks/support/+/…
hrules6872
1
ces extensions sont vraiment importantes et utiles
Ahmed na
5

Un liner si vous utilisez RxJava & RxBindings . Approche similaire sans passe-partout. Cela résout également le piratage pour supprimer les avertissements comme dans la réponse de Tim Autin .

RxView.layoutChanges(yourView).take(1)
      .subscribe(aVoid -> {
           // width and height have been calculated here
      });

Ça y est. Pas besoin de se désinscrire, même si jamais appelé.

Odys
la source
4

Cela se produit parce que la vue a besoin de plus de temps pour être gonflée. Ainsi, au lieu d'appeler view.widthet view.heightsur le thread principal, vous devez utiliser view.post { ... }pour vous assurer que votre viewa déjà été gonflé. À Kotlin:

view.post{width}
view.post{height}

En Java , vous pouvez également appeler getWidth()et getHeight()méthodes dans une Runnableet passer la Runnableà la view.post()méthode.

view.post(new Runnable() {
        @Override
        public void run() {
            view.getWidth(); 
            view.getHeight();
        }
    });
Ehsan Nokandi
la source
Ce n'est pas la bonne réponse car la vue peut ne pas être mesurée et présentée lors de l'exécution du code publié. Ce serait un cas de bord, mais .postne garantit pas la vue à présenter.
d.aemon
1
Cette réponse a été excellente et a fonctionné pour moi. Merci beaucoup Ehsan
MMG
3

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
3

Peut-être que cela aide quelqu'un:

Créer une fonction d'extension pour la classe View

nom de fichier: ViewExt.kt

fun View.afterLayout(what: () -> Unit) {
    if(isLaidOut) {
        what.invoke()
    } else {
        viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
            override fun onGlobalLayout() {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
                what.invoke()
            }
        })
    }
}

Cela peut ensuite être utilisé sur n'importe quelle vue avec:

view.afterLayout {
    do something with view.height
}
Pepijn
la source
2

La réponse avec postest incorrecte, car la taille peut ne pas être recalculée.
Une autre chose importante est que la vue et tous ses ancêtres doivent être visibles . Pour cela j'utilise une propriété View.isShown.

Voici ma fonction kotlin, qui peut être placée quelque part dans les utils:

fun View.onInitialized(onInit: () -> Unit) {
    viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            if (isShown) {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
                onInit()
            }
        }
    })
}

Et l'utilisation est:

myView.onInitialized {
    Log.d(TAG, "width is: " + myView.width)
}
pavelperc
la source
1

Si vous utilisez Kotlin

  leftPanel.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {

            override fun onGlobalLayout() {

                if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
                    leftPanel.viewTreeObserver.removeOnGlobalLayoutListener(this)
                }
                else {
                    leftPanel.viewTreeObserver.removeGlobalOnLayoutListener(this)
                }

                // Here you can get the size :)
                leftThreshold = leftPanel.width
            }
        })
Hitesh Sahu
la source
0

Les vues disparues renvoient 0 comme hauteur si l'application est en arrière-plan. Ce mon code (1oo% fonctionne)

fun View.postWithTreeObserver(postJob: (View, Int, Int) -> Unit) {
    viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            val widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
            val heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
            measure(widthSpec, heightSpec)
            postJob(this@postWithTreeObserver, measuredWidth, measuredHeight)
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) {
                @Suppress("DEPRECATION")
                viewTreeObserver.removeGlobalOnLayoutListener(this)
            } else {
                viewTreeObserver.removeOnGlobalLayoutListener(this)
            }
        }
    })
}

la source
0

Nous devons attendre que la vue soit tracée. À cette fin, utilisez OnPreDrawListener. Exemple de Kotlin:

val preDrawListener = object : ViewTreeObserver.OnPreDrawListener {

                override fun onPreDraw(): Boolean {
                    view.viewTreeObserver.removeOnPreDrawListener(this)

                    // code which requires view size parameters

                    return true
                }
            }

            view.viewTreeObserver.addOnPreDrawListener(preDrawListener)
Artem Botnev
la source