Como recuperar as dimensões de uma vista?

209

Eu tenho uma visão composta TableLayout, TableRow and TextView. Quero que pareça uma grade. Preciso obter a altura e a largura dessa grade. Os métodos getHeight()e getWidth()sempre retornam 0. Isso acontece quando eu formato a grade dinamicamente e também quando uso uma versão XML.

Como recuperar as dimensões para uma vista?


Aqui está o meu programa de teste que usei no Debug para verificar os resultados:

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
fonte
em vez de usar getWidth / Height, use getMeasuredWidth / Height após o layout ser aplicado à atividade.
bhups
9
Pessoal, tudo o que você precisa fazer é ligar getHeight()e getWidth()depois que o ciclo de vida da atividade solicitar que os pontos de vista se medam, em outras palavras, faça esse tipo de coisa onResume()e pronto. Você não deve esperar que um objeto ainda não definido conheça suas dimensões, esse é o truque. Não há necessidade da mágica sugerida abaixo.
class stacker
2
Chamando getHeight()/Width()em onResume()não deu> 0 valor para mim.
Darpan 4/09/15
@ClassStacker E o Fragment?
Zdd 01/02
@zdd Faça uma nova pergunta completa e poste uma referência aqui em um comentário.
Class stacker

Respostas:

418

Acredito que o OP já se foi há muito tempo, mas, caso essa resposta possa ajudar futuros pesquisadores, pensei em publicar uma solução que encontrei. Eu adicionei este código no meu onCreate()método:

EDITADO: 07/05/11 para incluir o código dos comentários:

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

});

Primeiro, recebo uma referência final ao meu TextView(para acessar no onGlobalLayout()método). Em seguida, obtenho o valor ViewTreeObserverde meu TextViewe adiciono uma OnGlobalLayoutListenersubstituição onGLobalLayout(não parece haver um método de superclasse para chamar aqui ...) e adiciono meu código que requer conhecer as medidas da exibição nesse ouvinte. Tudo funciona como esperado para mim, então espero que isso possa ajudar.

Kevin Coppock
fonte
23
@ George Bailey: Uma coisa que eu vi recentemente alguém postar (eu ainda não testei, então YMMV) para superar as várias chamadas foi (dentro de onGlobalLayout ()): tv.getViewTreeObserver().removeGlobalOnLayoutListener(this);dessa maneira, o ouvinte deve ser removido após a primeira vez ocorre.
precisa saber é o seguinte
3
Também quero ajudar de volta. Pode ser necessário fazer algo como: mView.getViewTreeObserver (). RemoveGlobalOnLayoutListener (globalLayoutListener); se você deseja redimensionar sua visualização apenas uma vez. Espero que isso ajuda
Henry Sou
7
A questão é muito antiga, mas o uso também addOnLayoutChangeListenerfunciona! :)
FX
7
Além disso, observe que, desde a API 16, você precisa: if (android.os.Build.VERSION.SDK_INT> = 16) {view.getViewTreeObserver (). RemoveOnGlobalLayoutListener (this); } else {view.getViewTreeObserver (). removeGlobalOnLayoutListener (this); }
Edison
2
Como removeGlobalOnLayoutListener está obsoleto, ele ainda fornece avisos no Eclipse. Isso é normal? Com o tempo, avisos de código obsoletos inundariam todo o ...
Jonny /
82

Vou apenas adicionar uma solução alternativa, substituir o método onWindowFocusChanged da sua atividade e você poderá obter os valores de getHeight (), getWidth () a partir daí.

@Override
public void onWindowFocusChanged (boolean hasFocus) {
        // the height will be set at this point
        int height = myEverySoTallView.getMeasuredHeight(); 
}
JustDanyul
fonte
4
Para citar a documentação, "Este é o melhor indicador para saber se essa atividade é visível para o usuário".
Cameron Lowell Palmer
6
Isso não funciona no caso de fragmentos para o qual você deve usar ViewTreeObserver.
Mihir
OP perguntou especificamente como obtê-lo para uma visão embora
JustDanyul 25/06
altura total de scrollview e max Scrollview.getScrolly () será o mesmo? ou seja, no meu caso, obtive Altura com o método acima é 702 e obtive o valor máximo de getScrolly () é 523,
Hitesh Dhamshaniya
Esse método também não funciona quando sua visualização está sendo incluída em outro arquivo xml.
Jay Donga
19

Você está tentando obter a largura e a altura de um elemento que ainda não foi desenhado.

Se você usar depuração e parar em algum momento, verá que a tela do seu dispositivo ainda está vazia, porque seus elementos ainda não foram desenhados, portanto você não pode obter a largura e a altura de algo que ainda não existir.

E, eu posso estar errado, mas setWidth()nem sempre é respeitado, Layoutdefine seus filhos e decide como medi-los (chamando child.measure()); portanto, se você definir setWidth(), não terá garantia de obter essa largura após o elemento ser desenhado.

O que você precisa é usar getMeasuredWidth()(a medida mais recente da sua Visualização) em algum lugar após a visualização ter sido realmente desenhada.

Examine o Activityciclo de vida para encontrar o melhor momento.

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

Acredito que uma boa prática é usar OnGlobalLayoutListenerassim:

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

Você pode inserir esse código diretamente onCreate()e ele será chamado quando as visualizações forem dispostas.

Alex Orlov
fonte
Infelizmente, também não está funcionando no OnResume. As dimensões finais são definidas em algum lugar após a chamada OnResume, pelo menos no emulador.
jki
Essa é uma prática horrível !!! Este ouvinte será chamado repetidamente e matará sua performance. Em vez de usar sinalizadores, cancele o registro do ouvinte uma vez feito.
bluewhile
15

Use o método de postagem da View como este

post(new Runnable() {   
    @Override
    public void run() {
        Log.d(TAG, "width " + MyView.this.getMeasuredWidth());
        }
    });
Boris Rusev
fonte
1
Isso funciona, mas realmente, nenhuma discussão sobre o porquê? Btw, isso funciona porque o executável não é executado até que o layout ocorra.
Norman H
7

Tentei usar onGlobalLayout()uma formatação personalizada de a TextView, mas como @George Bailey notou, na onGlobalLayout()verdade é chamado duas vezes: uma vez no caminho de layout inicial e segunda após a modificação do texto.

View.onSizeChanged()funciona melhor para mim porque, se eu modificar o texto, o método será chamado apenas uma vez (durante a passagem do layout). Essa subclassificação necessária de TextView, mas no nível 11 da API, View. addOnLayoutChangeListener()pode ser usada para evitar subclassificações.

Mais uma coisa, para obter a largura correta da visualização View.onSizeChanged(), layout_widthdeve ser definido como match_parent, não wrap_content.

Y2i
fonte
2

Você está tentando obter tamanhos em um construtor ou qualquer outro método executado antes de obter a imagem real?

Você não obterá nenhuma dimensão antes que todos os componentes sejam realmente medidos (já que o seu XML não sabe sobre o tamanho da tela, a posição dos pais e o que for)

Tente obter valores após onSizeChanged () (embora possa ser chamado com zero) ou simplesmente aguarde quando você obtém uma imagem real.

Alex Orlov
fonte
Atualizei minha pergunta e código originais para ficar mais claro. Obrigado.
2

Como o FX mencionou, você pode usar um OnLayoutChangeListenerpara a visualização que deseja rastrear

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

Você pode remover o ouvinte no retorno de chamada se desejar apenas o layout inicial.

Zeophlite
fonte
1

ViewTreeObservere onWindowFocusChanged()não são tão necessários.

Se você inflar o TextViewlayout as e / ou colocar algum conteúdo nele e definir LayoutParams, poderá usar getMeasuredHeight()e getMeasuredWidth().

MAS você tem que ter cuidado comLinearLayouts (talvez também com outros ViewGroups). O problema é que você pode obter a largura e a altura depois, onWindowFocusChanged()mas se tentar adicionar algumas visualizações, não poderá obter essas informações até que tudo tenha sido desenhado. Eu estava tentando adicionar vários TextViewspara LinearLayoutsimitar um FlowLayout(estilo de quebra) e, portanto, não podia usar Listeners. Depois que o processo é iniciado, ele deve continuar de forma síncrona. Portanto, nesse caso, convém manter a largura em uma variável para usá-la posteriormente, pois, ao adicionar visualizações ao layout, você poderá precisar.

Genom
fonte
1

Embora a solução proposta funcione, ela pode não ser a melhor solução para todos os casos, porque com base na documentação para ViewTreeObserver.OnGlobalLayoutListener

Definição de interface para que um retorno de chamada seja chamado quando o estado do layout global ou a visibilidade das visualizações na árvore de visualizações mudar.

o que significa que é chamado muitas vezes e nem sempre a vista é medida (tem altura e largura determinadas)

Uma alternativa é usar o ViewTreeObserver.OnPreDrawListenerque é chamado apenas quando a vista está pronta para ser desenhada e tem todas as suas medidas.

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
fonte
0

Você pode usar uma transmissão chamada OnResume ()

Por exemplo:

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

}

Você não pode obter a altura de uma exibição em OnCreate (), onStart () ou mesmo em onResume () pelo motivo pelo qual o kcoppock respondeu

Lucas Ferreira Batista
fonte
0

A altura e a largura são zero porque a visualização não foi criada no momento em que você solicita sua altura e 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.

Shivam Yadav
fonte
-1

Resposta simples: Isso funcionou para mim sem nenhum problema. Parece que a chave é garantir que o View tenha foco antes de getHeight etc. Faça isso usando o método hasFocus () e, em seguida, usando o método getHeight () nessa ordem. São necessárias apenas 3 linhas de código.

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

int myButtonHeight = myImageButton1.getHeight();

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

Espero que ajude.

Monk4D
fonte
-3

Use getMeasuredWidth () e getMeasuredHeight () para sua visualização.

Guia do desenvolvedor: View

Vladimir Ivanov
fonte
4
Conforme declarado anteriormente, esses métodos retornam apenas um resultado válido após o tamanho ter sido medido. Dentro de uma atividade, isso é verdadeiro quando o método onWindowFocusChanged é chamado.
slott
-8

CORREÇÃO: Descobri que a solução acima é terrível. Especialmente quando o telefone está lento. E aqui, encontrei outra solução: calcule o valor de px do elemento, incluindo as margens e os preenchimentos: dp para px: https://stackoverflow.com/a/6327095/1982712

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

sp para px: https://stackoverflow.com/a/9219417/1982712 (reverta a solução)

ou diminui para px: https://stackoverflow.com/a/16276351/1982712

e é isso.

pimkle
fonte
10
Geralmente, é uma má idéia esperar que tudo seja definido corretamente após um número arbitrariamente escolhido de milissegundos. Dispositivo pode ser lenta, outros processos / threads pode ser executado, pode não funcionar no depurador, pode não funcionar em próxima versão do OS, ...
Kristopher Johnson