Determinando o tamanho de uma visualização do Android em tempo de execução

123

Estou tentando aplicar uma animação a uma exibição no meu aplicativo Android depois que minha atividade é criada. Para fazer isso, preciso determinar o tamanho atual da exibição e configurar uma animação para escalar do tamanho atual para o novo tamanho. Essa parte deve ser feita em tempo de execução, pois a visualização é dimensionada para tamanhos diferentes, dependendo da entrada do usuário. Meu layout é definido em XML.

Parece uma tarefa fácil, e há muitas perguntas sobre SO, embora nenhuma que tenha resolvido meu problema, obviamente. Então talvez eu esteja perdendo algo óbvio. Eu entendo o meu ponto de vista por:

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

Esta multa funciona, mas quando chamado getWidth(), getHeight(), getMeasuredWidth(), getLayoutParams().width, etc., todos eles retornam 0. Eu também tentou manualmente chamando measure()na visão seguido por uma chamada para getMeasuredWidth(), mas isso não tem efeito.

Eu tentei chamar esses métodos e inspecionar o objeto no depurador em meus da atividade onCreate()e no onPostCreate(). Como posso descobrir as dimensões exatas dessa exibição em tempo de execução?

Nik Reiman
fonte
1
Ah, também devo observar que a visualização em si definitivamente não tem 0 largura / altura. Ele aparece na tela muito bem.
Nik Reiman
Você pode postar a tag <ImageView ... /> do layout xml?
fhucho 23/09
Lutei com muitas soluções de SO para esse problema até perceber que, no meu caso, as dimensões do View correspondiam à tela física (meu aplicativo é "imersivo" e o View e todas as larguras e alturas de seus pais estão definidos match_parent). Nesse caso, uma solução mais simples que pode ser chamada com segurança mesmo antes de o View ser desenhado (por exemplo, do método onCreate () da sua Activity) é apenas para usar Display.getSize (); consulte stackoverflow.com/a/30929599/5025060 para obter detalhes. Sei que não é o que o @NikReiman pediu, apenas deixando uma nota para aqueles que podem usar esse método mais simples.
CODE-Leia

Respostas:

180

Use o ViewTreeObserver no modo de exibição para aguardar o primeiro layout. Somente após o primeiro layout o getWidth () / getHeight () / getMeasuredWidth () / getMeasuredHeight () funcionará.

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
fonte
21
Tenha cuidado com esta resposta, uma vez que será continuamente chamado se a visão subjacente é um vídeo / superfície
Kevin Parker
7
Você pode elaborar sobre esse @Kevin? Duas coisas, antes de tudo, como é um ouvinte, eu esperaria um único retorno de chamada e, em seguida, assim que recebermos o primeiro retorno, removeremos o ouvinte. Perdi alguma coisa?
Vikram Bodicherla
4
Antes de chamar qualquer método ViewTreeObserver, é recomendável verificar isAlive (): if (view.getViewTreeObserver (). IsAlive ()) {view.getViewTreeObserver (). RemoveGlobalOnLayoutListener (this); }
Peter Tran
4
alguma alternativa para removeGlobalOnLayoutListener(this);? porque está obsoleto.
Sibbs Gambling
7
@FarticlePilter, use removeOnGlobalLayoutListener (). É mencionado nos documentos.
Vikram Bodicherla
63

Na verdade, existem várias soluções, dependendo do cenário:

  1. O método seguro funcionará imediatamente antes de desenhar a vista, após a conclusão da fase de layout:
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); 
}

Uso da amostra:

    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. Em alguns casos, basta medir o tamanho da exibição manualmente:
view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
int width=view.getMeasuredWidth(); 
int height=view.getMeasuredHeight();

Se você souber o tamanho do contêiner:

    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. se você tiver uma exibição personalizada ampliada, poderá obter seu tamanho no método "onMeasure", mas acho que funciona bem apenas em alguns casos:
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. Se você escreve no Kotlin, pode usar a próxima função, que nos bastidores funciona exatamente como runJustBeforeBeingDrawneu escrevi:

    view.doOnPreDraw { actionToBeTriggered() }

    Observe que você precisa adicionar isso ao gradle (encontrado aqui ):

    implementation 'androidx.core:core-ktx:#.#'
desenvolvedor android
fonte
1
Parece que a solução 1 nem sempre funcionará OnPreDrawListener. Caso testado quando você deseja executar o código do Activity onCreate()e você imediatamente em segundo plano. Fácil de repetir, especialmente em dispositivos mais lentos. Se você substituir OnPreDrawListenerpor OnGlobalLayoutListener- você não verá o mesmo problema.
pergunta
@ questionioner Eu não entendo, mas estou feliz que ajudou no final.
android developer
Embora eu costumo usar ViewTreeObserverou post, no meu caso, o número 2 ajudou ( view.measure).
CoolMind 07/02
3
@CoolMind Você também pode usar um novo método, caso use o Kotlin. Resposta atualizada para incluí-lo também.
desenvolvedor android
por que o kotlin tem funções adicionais para isso? google são loucos, esta linguagem é inútil (apenas deve ficar com java), é muito mais velho do que rápido, mas apenas rápida tem bastante popularidade tiobe.com/tiobe-index (SWIFT 12 posição e Kotlin 38)
user924
18

Você está ligando getWidth()antes que a visualização seja realmente exibida na tela?

Um erro comum cometido pelos novos desenvolvedores do Android é usar a largura e a altura de uma exibição dentro de seu construtor. Quando o construtor de uma exibição é chamado, o Android ainda não sabe quão grande será a exibição; portanto, os tamanhos são definidos como zero. Os tamanhos reais são calculados durante o estágio de layout, que ocorre após a construção, mas antes de qualquer coisa ser desenhada. Você pode usar o onSizeChanged()método para ser notificado dos valores quando eles são conhecidos, ou pode usar os métodos getWidth()e getHeight()mais tarde, como no onDraw()método

Mark B
fonte
2
Isso significa que eu preciso substituir o ImageView apenas para assistir ao onSizeChange()evento e saber o tamanho real? Isso parece um pouco exagerado ... embora neste momento eu esteja desesperado apenas para fazer o código funcionar, então vale a pena tentar.
Nik Reiman
Eu estava pensando mais em esperar até que seu onCreate () termine em sua Atividade.
Mark B
1
Como mencionado, tentei colocar esse código no onPostCreate()qual é executado depois que a exibição é completamente criada e iniciada.
Nik Reiman
1
De onde é o texto citado?
Intrications
1
esta citação para exibição personalizada, não postá-lo para tal resposta quando perguntamos sobre o tamanho da vista em geral
user924
17

Com base nos conselhos de @ mbaird, encontrei uma solução viável subclassificando a ImageViewclasse e substituindo onLayout(). Em seguida, criei uma interface de observação que minha atividade implementou e passou uma referência para si própria para a classe, o que lhe permitiu informar a atividade quando realmente terminou o dimensionamento.

Não estou 100% convencido de que esta é a melhor solução (portanto, ainda não estou marcando esta resposta como correta), mas funciona e, de acordo com a documentação, é a primeira vez que se pode encontrar o tamanho real de uma exibição.

Nik Reiman
fonte
Bom saber. Se eu encontrar algo que permita que você circule tendo que subclassificar o View, eu o publicarei aqui. Estou um pouco surpreso porque o PostCreate () não funcionou.
Mark B
Bem, eu tentei isso e parece funcionar. Apenas não é realmente a melhor solução, pois esse método é chamado com freqüência, não apenas uma vez no início. :(
Tobias Reich
10

Aqui está o código para obter o layout, substituindo uma exibição se a API <11 (a API 11 inclui o recurso 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
fonte
6

Você pode verificar esta pergunta . Você pode usar o View's pós () método.

Macarse
fonte
5

Use o código abaixo, é o tamanho da visualização.

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

Isso funciona para mim no meu 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
fonte
4
Você não precisa postar atrasado quando estiver em um ouvinte de clique. Você não pode clicar em nada até que todas as visualizações tenham se medido e exibido na tela. Portanto, eles já serão medidos quando você clicar neles.
26515 afollestad
Este código está errado. O atraso de publicação não garante no próximo tick que sua visualização é medida.
22617 Ian Wong
Eu recomendaria não ir para postDelayed (). Não é provável que isso aconteça, mas e se a sua visualização não for medida em 1 segundo e você a chamar de executável para obter largura / altura da visualização? Ainda resultaria 0. Edit: Ah, e entre que 1 está em milissegundos, por isso falharia com certeza.
Alex
4

Eu também estava perdido em torno getMeasuredWidth()e getMeasuredHeight() getHeight()e getWidth()por um longo tempo .......... depois eu descobri que a obtenção de largura e altura da vista em onSizeChanged()é a melhor maneira de fazer isso ........ você pode dinamicamente obtenha sua largura e altura ATUAL da sua visualização substituindo o onSizeChanged(método).

pode querer dar uma olhada nisso, que tem um trecho de código elaborado. Nova postagem no blog: como obter as dimensões de largura e altura de um customView (estende o View) no Android http://syedrakibalhasan.blogspot.com/2011/02/how-to-get-width-and-height-dimensions.html

Rakib
fonte
0

Você pode obter a posição e a dimensão da visualização na tela

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

funciona perfekt para mim:

 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;
        }
    }
Átomo
fonte