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.