Os modos getWidth () e getHeight () da exibição retornam 0


513

Estou criando todos os elementos no meu projeto android dinamicamente. Estou tentando obter a largura e a altura de um botão para poder girá-lo. Estou apenas tentando aprender a trabalhar com o idioma do Android. No entanto, ele retorna 0.

Eu fiz algumas pesquisas e vi que isso precisa ser feito em outro lugar que não o onCreate()método. Se alguém puder me dar um exemplo de como fazê-lo, isso seria ótimo.

Aqui está meu código atual:

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

Qualquer ajuda é apreciada.

Respostas:


197

Você está ligando getWidth()muito cedo. A interface do usuário ainda não foi dimensionada e apresentada na tela.

De qualquer forma, duvido que você queira fazer o que está fazendo - os widgets animados não alteram suas áreas clicáveis ​​e, portanto, o botão ainda responde aos cliques na orientação original, independentemente da rotação.

Dito isto, você pode usar um recurso de dimensão para definir o tamanho do botão e referenciá-lo no arquivo de layout e no código-fonte, para evitar esse problema.


82
ngreenwood6, qual é a sua outra solução?
— Andrew

12
@ Andrew - se você deseja que o negreenwood6 seja notificado sobre o seu acompanhamento, você deve iniciar sua mensagem como eu fiz para você (acho que as três primeiras letras são suficientes) - O CommonsWare é notificado automaticamente, pois ele escreveu essa resposta, mas o ngreen não a menos que você os dirija.
— Peter Ajtai #

11
@Override public void onWindowFocusChanged (boolean hasFocus) {// TODO Método gerado automaticamente stub super.onWindowFocusChanged (hasFocus); // Aqui você pode obter o tamanho! }
— Sana

5
@ ngreenwood6 Então, qual foi a sua solução?
— Nima Vaziri

5
Use esse ouvinte para obter tamanho, quando sua tela estiver pronta. view.getViewTreeObserver (). addOnGlobalLayoutListener (new ViewTreeObserver.OnGlobalLayoutListener () {}
— Sinan Dizdarević

816

O problema básico é que você precisa aguardar a fase de desenho para as medições reais (especialmente com valores dinâmicos como wrap_contentou match_parent), mas geralmente essa fase ainda não foi concluída onResume(). Portanto, você precisa de uma solução alternativa para aguardar essa fase. Existem diferentes soluções possíveis para isso:


1. Ouça eventos de desenho / layout: ViewTreeObserver

Um ViewTreeObserver é acionado para diferentes eventos de desenho. Normalmente, OnGlobalLayoutListeneré isso que você deseja para obter a medição, para que o código no ouvinte seja chamado após a fase de layout, para que as medidas estejam prontas:

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

Nota: O ouvinte será removido imediatamente, caso contrário, será acionado em todos os eventos de layout. Se você precisar oferecer suporte a aplicativos SDK Nvl <16, use-o para cancelar o registro do ouvinte:

public void removeGlobalOnLayoutListener (ViewTreeObserver.OnGlobalLayoutListener victim)


2. Adicione um executável à fila de layout: View.post ()

Não é muito conhecido e minha solução favorita. Basicamente, basta usar o método de postagem do View com seu próprio executável. Isso basicamente enfileira seu código após a medida, o layout etc. da exibição, conforme declarado por Romain Guy :

A fila de eventos da interface do usuário processará os eventos em ordem. Depois que setContentView () for chamado, a fila de eventos conterá uma mensagem solicitando uma retransmissão; portanto, tudo o que você postar na fila acontecerá após a aprovação do layout

Exemplo:

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

A vantagem sobre ViewTreeObserver:

  • seu código é executado apenas uma vez e você não precisa desativar o Observer após a execução, o que pode ser um aborrecimento
  • sintaxe menos detalhada

Referências:


3. Substituir o método onLayout do Views

Isso é prático apenas em determinadas situações em que a lógica pode ser encapsulada na própria exibição, caso contrário, essa é uma sintaxe bastante detalhada e complicada.

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

Lembre-se também de que onLayout será chamado várias vezes; portanto, considere o que você faz no método ou desative seu código após a primeira vez.


4. Verifique se passou pela fase de layout

Se você tiver um código executando várias vezes ao criar a interface do usuário, poderá usar o seguinte método de suporte v4 lib:

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

Retorna true se a visualização passou por pelo menos um layout desde a última vez que foi anexada ou desanexada de uma janela.


Adicional: Obtendo medidas definidas estática

Se basta obter a altura / largura definida estaticamente, você pode fazer isso com:

Mas lembre-se de que isso pode ser diferente da largura / altura real após o desenho. O javadoc descreve a diferença perfeitamente:

O tamanho de uma vista é expresso com uma largura e uma altura. Uma vista possui realmente dois pares de valores de largura e altura.

O primeiro par é conhecido como largura e altura medidas. Essas dimensões definem o tamanho que uma exibição deseja ter dentro de seu pai (consulte Layout para obter mais detalhes.) As dimensões medidas podem ser obtidas chamando getMeasuredWidth () e getMeasuredHeight ().

O segundo par é simplesmente conhecido como largura e altura, ou às vezes largura e altura do desenho. Essas dimensões definem o tamanho real da vista na tela, no tempo de desenho e após o layout. Esses valores podem, mas não precisam, ser diferentes da largura e altura medidas. A largura e a altura podem ser obtidas chamando getWidth () e getHeight ().


1
Estou usando o view.post em um fragmento. Preciso medir a vista cuja altura dos pais é 0dp e tem um peso definido. Tudo o que tento retorna uma altura zero (também tentei o ouvinte de layout global). Quando coloco o código view.post no onViewCreated com um atraso de 125, ele funciona, mas não sem o atraso. Ideias?
— Psest328

6
Pena que você só pode votar uma vez uma vez, uma boa explicação. Ocorreu um problema com a rotação da visualização se eu encerrasse o aplicativo e o reiniciasse a partir dos aplicativos recentes. Se eu o deslizei de lá e fiz um novo reinício, tudo estava bem. O view.post () salvou meu dia!
— Matthias

1
Obrigado. Eu normalmente uso View.post () (segundo método), mas se for chamado de um thread em segundo plano, às vezes não funciona. Então, não esqueça de escrever runOnUiThread(new Runnable() {...}. Atualmente eu uso uma variação do 1º método: addOnLayoutChangeListener. Não se esqueça de removê-lo e dentro de: removeOnLayoutChangeListener. Também funciona a partir do thread de segundo plano.
— CoolMind 01/08

2
Infelizmente para mim 'View.post ()' não está funcionando o tempo todo. Eu tenho 2 projetos que são muito semelhantes. Em um projeto está funcionando, por outro não está funcionando. . :( Em ambos situação que eu chamar o método de 'onCreate ()' método de uma Actividade Qualquer sugestão porquê.?
— Adrian Buciuman

1
Eu acho o problema. Em um projeto, a visualização possui android:visibility="gone"e, no outro, a propriedade de visibilidade era invisible. Se a visibilidade é gonea largura será sempre 0.
— Adrian Buciuman

262

Podemos usar

@Override
 public void onWindowFocusChanged(boolean hasFocus) {
  super.onWindowFocusChanged(hasFocus);
  //Here you can get the size!
 }

10
então como podemos trabalhar em fragmentos? isso funciona apenas na atividade?
— Olkunmustafa 4/15/15

8
Isso deve fazer a coisa, mas não deve ser a resposta. Um usuário deseja obter dimensões em qualquer ponto do tempo, em vez de ser desenhado na janela. Além disso, esse método é chamado várias vezes ou sempre que houver uma alteração (Exibir ocultar, desaparecer, adicionar novas visualizações etc.) na janela. Portanto, use-o com cuidado :)
— Dr. Andro

1
Isso é um hack e parece que usar o ViewTreeObserver seria melhor.
— I_am_jorf

Não pareça rude, mas deixar um comentário gerado automaticamente (especialmente em duas linhas de código) não dá muita confiança de que você realmente sabe o que está fazendo.
— Natix

Infelizmente, não funcionou para mim. Tentando viewTreeObserver funcionou para mim.
— Narendra Singh

109

Usei essa solução, que acho melhor do que onWindowFocusChanged (). Se você abrir um DialogFragment e girar o telefone, onWindowFocusChanged será chamado somente quando o usuário fechar a caixa de diálogo):

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

Editar: como removeGlobalOnLayoutListener está obsoleto, você deve fazer agora:

@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 :)
}

4
Esta é de longe a melhor solução! porque funciona também em soluções dinâmicas em que a largura e a altura não são conhecidas e calculadas. ou seja, em um layout relativo com base nas posições / tamanhos de outros elementos. Bem feito!
— George Pligoropoulos

2
Isto requer API de 16 ou superior (feijão geléia)
— tomsv

7
NÃO EXIGE API 16! O único problema é o método removeGlobalOnLayoutListener obsoleta de API 16, mas não há solução simples compatível com todos os níveis API - stackoverflow.com/questions/16189525/...
— Gingo

Se você precisar remover o ouvinte para que ele não se chame muitas vezes e esteja tendo problemas com APIs diferentes, recomendo que você defina um booleano falso no início da classe e, depois de capturar os valores, defina-o como true para que não captura os valores novamente.
— Mansour Fahad

Se você remover o ouvinte após a primeira execução, poderá obter dimensões incorretas: 02-14 10: 18: 53.786 15063-15063 / PuzzleFragment: height: 1647 width: 996 02-14 10: 18: 53.985 15063-15063 / PuzzleFragment: height : 1500 width: 996
— Leos Literak

76

Como Ian afirma neste segmento de desenvolvedores do Android :

De qualquer forma, o acordo é que o layout do conteúdo de uma janela acontece depois que todos os elementos são construídos e adicionados às visualizações pai. Tem que ser assim, porque até você saber quais componentes um View contém, e o que eles contêm, e assim por diante, não há uma maneira sensata de defini-lo.

Resumindo, se você chamar getWidth () etc. em um construtor, ele retornará zero. O procedimento é criar todos os seus elementos de exibição no construtor e aguardar a chamada do método onSizeChanged () do seu View - é quando você descobre seu tamanho real pela primeira vez, e é quando define os tamanhos dos elementos da GUI.

Esteja ciente também de que onSizeChanged () às vezes é chamado com parâmetros de zero - verifique esse caso e retorne imediatamente (para que você não divida por zero ao calcular seu layout, etc.). Algum tempo depois, ele será chamado com os valores reais.


8
onSizeChanged funcionou para mim. onWindowFocusedChanged não é chamado na minha exibição personalizada.
— Aaronmarino

isso parece uma boa solução, mas o que devo sempre criar minha exibição personalizada para substituir o método onSizeChanged?
— M.ElSaka

Bottom line, if you call getWidth() etc. in a constructor, it will return zero.isso é um bingo. A raiz dos meus problemas.
— MikeNereson

Sugiro esta solução para fragmentos, porque algumas outras soluções retornam 0 no meu caso.
— WindRider 27/11

66

Se você precisar obter a largura de algum widget antes que ele seja exibido na tela, use getMeasuredWidth () ou getMeasuredHeight ().

myImage.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
int width = myImage.getMeasuredWidth();
int height = myImage.getMeasuredHeight();

5
Por alguma razão, essa foi a única resposta que funcionou para mim - obrigado!
— Farbod Salamat-Zadeh

Mesmo para mim, a solução mais simples, que trabalhou para mim
— Tima

Trabalhe como um encanto!
— Morales Batovski

1
@CommonsWare Como essa solução fornece a altura e a largura antes do observador da árvore de exibição no retorno de chamada de layouts globais, uma explicação seria útil.
— Mightian 5/01/19

22

Eu preferiria usar em OnPreDrawListener()vez de addOnGlobalLayoutListener(), uma vez que é chamado um pouco mais cedo do que outros ouvintes.

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

            // put your code here
            return true;
        }
    });

3
Note que os return falsemeios para cancelar o passe desenho atual . Para prosseguir, em return truevez disso.
— Pang

9

Uma extensão Kotlin para observar no layout global e executar uma determinada tarefa quando a altura estiver pronta dinamicamente.

Uso:

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

Implementação:

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

7

O AndroidX possui várias funções de extensão que ajudam você nesse tipo de trabalho, dentro androidx.core.view

Você precisa usar o Kotlin para isso.

O que melhor se encaixa aqui é doOnLayout:

Executa a ação especificada quando essa visualização é apresentada. Se a vista foi projetada e não solicitou um layout, a ação será executada imediatamente, caso contrário, a ação será executada depois que a vista for projetada em seguida.

A ação será invocada apenas uma vez no próximo layout e, em seguida, removida.

No seu exemplo:

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

Dependência: androidx.core:core-ktx:1.0.0



1
essas extensões são realmente importantes e úteis
— Ahmed na

5

Um liner, se você estiver usando RxJava e RxBindings . Abordagem semelhante sem o clichê. Isso também resolve o hack para suprimir avisos, como na resposta de Tim Autin .

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

É isso. Não é necessário cancelar a assinatura, mesmo que nunca tenha sido chamado.


4

Isso acontece porque a visualização precisa de mais tempo para ser inflada. Portanto, em vez de chamar view.widthe view.heightno segmento principal, você deve view.post { ... }se certificar de que o seu viewjá foi inflado. Em Kotlin:

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

Em Java, você também pode chamar getWidth()e getHeight()métodos em a Runnablee passar o método Runnablepara view.post().

view.post(new Runnable() {
        @Override
        public void run() {
            view.getWidth(); 
            view.getHeight();
        }
    });

Esta não é a resposta correta, pois a visualização pode não ser medida e definida quando o código publicado é executado. Seria um caso extremo, mas .postnão garante que a exibição seja apresentada.
— d.aemon 12/03

1
Essa resposta foi ótima e funcionou para mim. Muito obrigado Ehsan
— MMG 15/04

3

A altura e a largura são zero porque a visualização não foi criada quando você solicita a altura e a largura. Uma solução mais simples é

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

Este método é bom em comparação com outros métodos, pois é curto e nítido.


3

Talvez isso ajude alguém:

Crie uma função de extensão para a classe View

nome do arquivo: 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()
            }
        })
    }
}

Isso pode ser usado em qualquer visualização com:

view.afterLayout {
    do something with view.height
}

2

A resposta com postestá incorreta, porque o tamanho pode não ser recalculado.
Outra coisa importante é que a visão e todos os seus ancestrais devem estar visíveis . Para isso eu uso uma propriedade View.isShown.

Aqui está minha função kotlin, que pode ser colocada em algum lugar nos utils:

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

E o uso é:

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

1

Se você estiver usando o 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
            }
        })

0

As visualizações desaparecidas retornam 0 como altura se o aplicativo estiver em segundo plano. Este meu código (1oo% funciona)

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

0

Precisamos esperar que a visualização seja desenhada. Para esse fim, use OnPreDrawListener. Exemplo 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)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.