Déterminer la taille d'une vue Android lors de l'exécution

123

J'essaie d'appliquer une animation à une vue dans mon application Android après la création de mon activité. Pour ce faire, je dois déterminer la taille actuelle de la vue, puis configurer une animation à l'échelle de la taille actuelle à la nouvelle taille. Cette partie doit être effectuée au moment de l'exécution, car la vue est mise à l'échelle à différentes tailles en fonction de l'entrée de l'utilisateur. Ma mise en page est définie en XML.

Cela semble être une tâche facile, et il y a beaucoup de questions SO à ce sujet, mais aucune n'a résolu mon problème, évidemment. Alors peut-être que je manque quelque chose d'évident. Je saisis mon point de vue en:

ImageView myView = (ImageView) getWindow().findViewById(R.id.MyViewID);

Cela fonctionne très bien, mais lors de l' appel getWidth(), getHeight(), getMeasuredWidth(), getLayoutParams().width, etc., ils ont tous le retour 0. J'ai également essayé d' appeler manuellement measure()sur la vue suivie d'un appel à getMeasuredWidth(), mais cela n'a aucun effet.

J'ai essayé d'appeler ces méthodes et d'inspecter l'objet dans le débogueur dans mon activité onCreate()et dans onPostCreate(). Comment puis-je déterminer les dimensions exactes de cette vue au moment de l'exécution?

Nik Reiman
la source
1
Oh, je dois également noter que la vue elle-même n'a certainement pas 0 largeur / hauteur. Il apparaît très bien à l'écran.
Nik Reiman
Pouvez-vous publier la balise <ImageView ... /> à partir d'une mise en page XML?
fhucho
J'ai lutté avec de nombreuses solutions SO à ce problème jusqu'à ce que je réalise que dans mon cas, les dimensions de la vue correspondaient à l'écran physique (mon application est "immersive" et la vue et toutes les largeurs et hauteurs de ses parents sont réglées sur match_parent). Dans ce cas, une solution plus simple qui peut être appelée en toute sécurité avant même que la vue ne soit dessinée (par exemple, à partir de la méthode onCreate () de votre activité) consiste simplement à utiliser Display.getSize (); voir stackoverflow.com/a/30929599/5025060 pour plus de détails. Je sais que ce n'est pas ce que @NikReiman a demandé, laissant simplement une note à ceux qui pourraient utiliser cette méthode plus simple.
CODE-REaD

Réponses:

180

Utilisez le ViewTreeObserver sur la vue pour attendre la première mise en page. Ce n'est qu'après la première mise en page que getWidth () / getHeight () / getMeasuredWidth () / getMeasuredHeight () fonctionnera.

ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
if (viewTreeObserver.isAlive()) {
  viewTreeObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
      view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
      viewWidth = view.getWidth();
      viewHeight = view.getHeight();
    }
  });
}
Vikram Bodicherla
la source
21
Soyez prudent avec cette réponse car elle sera appelée en permanence si la vue sous-jacente est une vidéo / surface
Kevin Parker
7
Pouvez-vous nous en dire plus sur @Kevin? Deux choses, tout d'abord, puisqu'il s'agit d'un auditeur, je m'attendrais à un seul rappel, et ensuite, dès que nous recevons le premier rappel, nous supprimons l'auditeur. Ai-je oublié quelque chose?
Vikram Bodicherla
4
Avant d'appeler des méthodes ViewTreeObserver, il est recommandé de vérifier isAlive (): if (view.getViewTreeObserver (). IsAlive ()) {view.getViewTreeObserver (). RemoveGlobalOnLayoutListener (this); }
Peter Tran
4
une alternative à removeGlobalOnLayoutListener(this);? car il est obsolète.
Sibbs Gambling
7
@FarticlePilter, utilisez à la place removeOnGlobalLayoutListener (). Il est mentionné dans la documentation.
Vikram Bodicherla
64

Il existe en fait plusieurs solutions, selon le scénario:

  1. La méthode sûre fonctionnera juste avant de dessiner la vue, une fois la phase de mise en page terminée:
public static void runJustBeforeBeingDrawn(final View view, final Runnable runnable) {
    final OnPreDrawListener preDrawListener = new OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
            view.getViewTreeObserver().removeOnPreDrawListener(this);
            runnable.run();
            return true;
        }
    };
    view.getViewTreeObserver().addOnPreDrawListener(preDrawListener); 
}

Exemple d'utilisation:

    ViewUtil.runJustBeforeBeingDrawn(yourView, new Runnable() {
        @Override
        public void run() {
            //Here you can safely get the view size (use "getWidth" and "getHeight"), and do whatever you wish with it
        }
    });
  1. Dans certains cas, il suffit de mesurer manuellement la taille de la vue:
view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
int width=view.getMeasuredWidth(); 
int height=view.getMeasuredHeight();

Si vous connaissez la taille du conteneur:

    val widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(maxWidth, View.MeasureSpec.AT_MOST)
    val heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(maxHeight, View.MeasureSpec.AT_MOST)
    view.measure(widthMeasureSpec, heightMeasureSpec)
    val width=view.measuredWidth
    val height=view.measuredHeight
  1. si vous avez une vue personnalisée que vous avez étendue, vous pouvez obtenir sa taille sur la méthode "onMeasure", mais je pense que cela ne fonctionne bien que dans certains cas:
protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
    final int newHeight= MeasureSpec.getSize(heightMeasureSpec);
    final int newWidth= MeasureSpec.getSize(widthMeasureSpec);
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
  1. Si vous écrivez en Kotlin, vous pouvez utiliser la fonction suivante, qui en coulisse fonctionne exactement comme runJustBeforeBeingDrawncelle que j'ai écrite:

    view.doOnPreDraw { actionToBeTriggered() }

    Notez que vous devez ajouter ceci à gradle (trouvé via ici ):

    implementation 'androidx.core:core-ktx:#.#'
développeur android
la source
1
Il semble que la solution n ° 1 ne fonctionnera pas toujours avec OnPreDrawListener. Cas testé lorsque vous souhaitez exécuter du code à partir d'activité onCreate()et que vous appliquez immédiatement en arrière-plan. Facile à répéter en particulier sur un appareil plus lent. Si vous remplacez OnPreDrawListenerpar OnGlobalLayoutListener- vous ne verrez pas le même problème.
questionneur
@questioner Je ne comprends pas, mais je suis heureux que cela vous ait aidé à la fin.
développeur android
Bien que j'utilise habituellement ViewTreeObserveroupost , dans mon cas, le numéro 2 a aidé ( view.measure).
CoolMind
3
@CoolMind Vous pouvez également utiliser une nouvelle méthode, au cas où vous utiliseriez Kotlin. Réponse mise à jour pour l'inclure également.
développeur android
pourquoi kotlin a-t-il des fonctions supplémentaires pour cela? google sont fous, ce langage est inutile (il faut juste rester avec java), il est beaucoup plus ancien que swift mais seulement swift a assez de popularité tiobe.com/tiobe-index (swift 12 position et kotlin 38)
user924
18

Appelez-vous getWidth()avant que la vue ne soit réellement présentée à l'écran?

Une erreur courante commise par les nouveaux développeurs Android est d'utiliser la largeur et la hauteur d'une vue à l'intérieur de son constructeur. Lorsqu'un constructeur de vue est appelé, Android ne sait pas encore quelle sera la taille de la vue, donc les tailles sont définies sur zéro. Les tailles réelles sont calculées pendant la phase de mise en page, qui se produit après la construction mais avant que quoi que ce soit ne soit dessiné. Vous pouvez utiliser la onSizeChanged()méthode pour être notifié des valeurs lorsqu'elles sont connues, ou vous pouvez utiliser les méthodes getWidth()et getHeight()plus tard, comme dans la onDraw()méthode.

Marque B
la source
2
Cela signifie-t-il que je dois remplacer ImageView juste pour attraper l' onSizeChange()événement et connaître la taille réelle? Cela semble un peu exagéré ... bien qu'à ce stade, je cherche désespérément à faire fonctionner le code, donc ça vaut le coup.
Nik Reiman
Je pensais plus à attendre que votre onCreate () soit terminé dans votre activité.
Mark B du
1
Comme mentionné, j'ai essayé de mettre ce code dans onPostCreate()lequel s'exécute une fois que la vue est complètement créée et démarrée.
Nik Reiman
1
D'où vient le texte cité?
Intrications
1
cette citation pour une vue personnalisée, ne la postez pas pour une telle réponse lorsque nous vous posons des questions sur la taille de la vue en général
user924
17

Sur la base des conseils de @ mbaird, j'ai trouvé une solution viable en sous-classant la ImageViewclasse et en la remplaçant onLayout(). J'ai ensuite créé une interface d'observateur que mon activité a implémentée et passé une référence à elle-même à la classe, ce qui lui a permis de dire à l'activité quand elle avait réellement fini de dimensionner.

Je ne suis pas convaincu à 100% que ce soit la meilleure solution (d'où je ne marque pas encore cette réponse comme correcte), mais cela fonctionne et selon la documentation, c'est la première fois que l'on peut trouver la taille réelle d'une vue.

Nik Reiman
la source
Bon à savoir. Si je trouve quelque chose qui vous permettrait de contourner le sous-classement de la vue, je le posterai ici. Je suis un peu surpris que onPostCreate () ne fonctionne pas.
Mark B
Eh bien, j'ai essayé cela et cela semble fonctionner. Ce n'est pas vraiment la meilleure solution car cette méthode est souvent appelée non seulement une fois au début. :(
Tobias Reich
10

Voici le code pour obtenir la mise en page via le remplacement d'une vue si l'API <11 (l'API 11 inclut la fonctionnalité View.OnLayoutChangedListener):

public class CustomListView extends ListView
{
    private OnLayoutChangedListener layoutChangedListener;

    public CustomListView(Context context)
    {
        super(context);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b)
    {
        if (layoutChangedListener != null)
        {
            layoutChangedListener.onLayout(changed, l, t, r, b);
        }
        super.onLayout(changed, l, t, r, b);
    }

    public void setLayoutChangedListener(
        OnLayoutChangedListener layoutChangedListener)
    {
        this.layoutChangedListener = layoutChangedListener;
    }
}
public interface OnLayoutChangedListener
{
    void onLayout(boolean changed, int l, int t, int r, int b);
}
Gregm
la source
6

Vous pouvez vérifier cette question . Vous pouvez utiliser le Viewde » post () méthode.

Macarse
la source
5

Utilisez le code ci-dessous, cela donne la taille de la vue.

@Override
public void onWindowFocusChanged(boolean hasFocus) {
       super.onWindowFocusChanged(hasFocus);
       Log.e("WIDTH",""+view.getWidth());
       Log.e("HEIGHT",""+view.getHeight());
}
Asif Patel
la source
5

Cela fonctionne pour moi dans mon onClickListener:

yourView.postDelayed(new Runnable() {               
    @Override
    public void run() {         
        yourView.invalidate();
        System.out.println("Height yourView: " + yourView.getHeight());
        System.out.println("Width yourView: " + yourView.getWidth());               
    }
}, 1);
Biro Csaba Norbert
la source
4
Vous n'avez pas besoin de postDelayed lorsqu'il est dans un écouteur de clic. Vous ne pouvez cliquer sur rien tant que les vues ne se sont pas toutes mesurées et ne sont pas apparues à l'écran. Ainsi, ils seront déjà mesurés au moment où vous pourrez cliquer dessus.
après
Ce code est faux. Le délai de publication ne garantit pas que votre vue sera mesurée au prochain tick.
Ian Wong
Je recommanderais de ne pas opter pour postDelayed (). Il est peu probable que cela se produise, mais que se passe-t-il si votre vue n'est pas mesurée en 1 seconde et que vous appelez cet exécutable pour obtenir la largeur / hauteur de la vue. Cela donnerait toujours 0. Edit: Oh et btw que 1 est en millisecondes, donc il échouerait à coup sûr.
Alex
4

J'étais aussi perdu getMeasuredWidth()et getMeasuredHeight() getHeight()et getWidth()pendant longtemps .......... plus tard, j'ai trouvé que onSizeChanged()la meilleure façon de faire cela est d' obtenir la largeur et la hauteur de la vue ........ vous pouvez dynamiquement obtenez votre largeur ACTUELLE et votre hauteur ACTUELLE de votre vue en remplaçant la onSizeChanged(méthode).

pourrait vouloir jeter un oeil à ceci qui a un extrait de code élaboré. Nouvel article de blog: comment obtenir les dimensions de largeur et de hauteur d'un customView (étend la vue) dans Android http://syedrakibalhasan.blogspot.com/2011/02/how-to-get-width-and-height-dimensions.html

Rakib
la source
0

Vous pouvez obtenir à la fois la position et la dimension de la vue à l'écran

 val viewTreeObserver: ViewTreeObserver = videoView.viewTreeObserver;

if (viewTreeObserver.isAlive) {
    viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        override fun onGlobalLayout() {
            //Remove Listener
            videoView.viewTreeObserver.removeOnGlobalLayoutListener(this);

            //View Dimentions
            viewWidth = videoView.width;
            viewHeight = videoView.height;

            //View Location
            val point = IntArray(2)
            videoView.post {
                videoView.getLocationOnScreen(point) // or getLocationInWindow(point)
                viewPositionX = point[0]
                viewPositionY = point[1]
            }

        }
    });
}
Hitesh Sahu
la source
-1

fonctionne parfaitement pour moi:

 protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        base.OnElementPropertyChanged(sender, e);
        CTEditor ctEdit = Element as CTEditor;
        if (ctEdit == null) return;           
        if (e.PropertyName == "Text")
        {
            double xHeight = Element.Height;
            double aHaight = Control.Height;
            double height;                
            Control.Measure(LayoutParams.MatchParent,LayoutParams.WrapContent);
            height = Control.MeasuredHeight;
            height = xHeight / aHaight * height;
            if (Element.HeightRequest != height)
                Element.HeightRequest = height;
        }
    }
Atome
la source