Maneira autorizada de substituir onMeasure ()?

87

Qual é a maneira correta de substituir onMeasure ()? Já vi várias abordagens. Por exemplo, o Professional Android Development usa MeasureSpec para calcular as dimensões e termina com uma chamada para setMeasuredDimension (). Por exemplo:

@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth/2, parentHeight);
}

Por outro lado, de acordo com esta postagem , a maneira "correta" é usar MeasureSpec, chamar setMeasuredDimensions (), seguido por uma chamada para setLayoutParams () e terminar com uma chamada para super.onMeasure (). Por exemplo:

@Override 
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth/2, parentHeight);
this.setLayoutParams(new *ParentLayoutType*.LayoutParams(parentWidth/2,parentHeight));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

Então, qual é o caminho certo? Nenhuma das abordagens funcionou 100% para mim.

Acho que realmente o que estou perguntando é: alguém conhece um tutorial que explica onMeasure (), layout, dimensões das visualizações filho etc.?

U Avalos
fonte
15
você achou a resposta útil / correta? por que não marcá-lo como a resposta?
superjos

Respostas:

68

As outras soluções não são abrangentes. Eles podem funcionar em alguns casos e são um bom lugar para começar, mas podem não ter garantia de funcionamento.

Quando onMeasure é chamado, você pode ou não ter o direito de alterar o tamanho. Os valores que são passados ​​para seu onMeasure ( widthMeasureSpec, heightMeasureSpec) contêm informações sobre o que sua visualização secundária tem permissão para fazer. Atualmente, existem três valores:

  1. MeasureSpec.UNSPECIFIED - Você pode ser tão grande quanto quiser
  2. MeasureSpec.AT_MOST- Tão grande quanto você quiser (até o tamanho das especificações), este é o parentWidthseu exemplo.
  3. MeasureSpec.EXACTLY- Sem escolha. O pai escolheu.

Isso é feito para que o Android possa fazer várias passagens para encontrar o tamanho certo para cada item, veja aqui para mais detalhes.

Se você não seguir essas regras, não há garantia de que sua abordagem funcione.

Por exemplo, se você deseja verificar se tem permissão para alterar o tamanho, você pode fazer o seguinte:

final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
boolean resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
boolean resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;

Usando essas informações, você saberá se pode modificar os valores como em seu código. Ou se você for obrigado a fazer algo diferente. Uma maneira rápida e fácil de determinar o tamanho desejado é usar um dos seguintes métodos:

int resolveSizeAndState (int size, int measureSpec, int childMeasuredState)

int resolveSize (int size, int measureSpec)

Enquanto o primeiro está disponível apenas no Honeycomb, o segundo está disponível em todas as versões.

Nota: você pode achar que resizeWidthou resizeHeightsão sempre falsos. Eu descobri que esse era o caso, se eu estivesse solicitando MATCH_PARENT. Consegui corrigir isso solicitando WRAP_CONTENTno layout pai e, durante a fase NÃO ESPECIFICADA, solicitando um tamanho de Integer.MAX_VALUE. Isso fornece o tamanho máximo que seus pais permitem na próxima passagem por onMeasure.

Grimmace
fonte
39

A documentação é a autoridade neste assunto: http://developer.android.com/guide/topics/ui/how-android-draws.html e http://developer.android.com/guide/topics/ui/custom -components.html

Para resumir: no final do seu onMeasuremétodo substituído , você deve chamarsetMeasuredDimension .

Você não deve ligar super.onMeasureapós ligar setMeasuredDimension, isso apenas apagará tudo o que você definiu. Em algumas situações, você pode querer chamar o super.onMeasureprimeiro e depois modificar os resultados chamandosetMeasuredDimension .

Não chame setLayoutParamsno onMeasure. O layout acontece em uma segunda passagem após a medição.

satur9nine
fonte
Minha experiência é que, se eu substituir onMeasure sem chamar super.onMeasure, OnLayout não será chamado nas visualizações filho.
William Jockusch
2

Acho que depende do pai o que você está substituindo.

Por exemplo, se você está estendendo um ViewGroup (como FrameLayout), quando você mediu o tamanho, você deve chamar como abaixo

super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
                MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));

porque você pode querer que o ViewGroup faça o trabalho de descanso (faça algumas coisas na visualização infantil)

Se você estiver estendendo uma View (como ImageView), você pode simplesmente chamar this.setMeasuredDimension(width, height); , porque a classe pai fará algo como você normalmente faz.

Em uma palavra, se você quiser alguns recursos que sua classe pai oferece gratuitamente, você deve ligar super.onMeasure()(passar a especificação de medida de modo MeasureSpec.EXACTLY normalmente), caso contrário, this.setMeasuredDimension(width, height);basta ligar .

Yon
fonte
1

aqui está como resolvi o problema:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        ....

        setMeasuredDimension( measuredWidth, measuredHeight );

        widthMeasureSpec = MeasureSpec.makeMeasureSpec( measuredWidth, MeasureSpec.EXACTLY );
        heightMeasureSpec = MeasureSpec.makeMeasureSpec( measuredHeight, MeasureSpec.EXACTLY);

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

}

Além disso, era necessário que o componente ViewPager

Yuli Reiri
fonte
3
Esta não é a solução adequada. super.onMeasureirá limpar as dimensões definidas usando setMeasuredDimension.
tomrozb
@tomrozb não é humptydevelopers.com/2013/05/… e você pode verificar as fontes do Android
Yuli Reiri
@YuliReiri: Solução perfeita!
Shayan Tabatabaee
0

Se mudar o tamanho das visualizações dentro de onMeasuretudo que você precisa é a setMeasuredDimensionchamada. Se você estiver mudando o tamanho fora de onMeasurevocê precisa ligar setLayoutParams. Por exemplo, alterar o tamanho de uma visualização de texto quando o texto é alterado.

Kyle O.
fonte
Você ainda pode chamar setMinWidth () e setMaxWidth () e fazer com que o onMeasure padrão cuide disso. Descobri que é melhor não chamar setLayoutParams de dentro de uma classe View personalizada. É realmente usado para ViewGroups ou Activities para substituir o comportamento da visão filha.
Dorrin
0

Depende do controle que você está usando. As instruções na documentação funcionam para alguns controles (TextView, Button, ...), mas não para outros (LinearLayout, ...). A maneira que funcionou muito bem para mim foi ligar para o zelador assim que terminar. Com base no artigo no link abaixo.

http://humptydevelopers.blogspot.in/2013/05/android-view-overriding-onmeasure.html

Aviral
fonte
-1

Eu acho que setLayoutParams e recalcular as medidas é uma solução alternativa para redimensionar as visualizações filho corretamente, pois isso geralmente é feito no onMeasure da classe derivada.

No entanto, isso raramente funciona corretamente (por qualquer motivo ...), é melhor chamar measureChildren (ao derivar um ViewGroup) ou tentar algo semelhante quando necessário.

M. Schenk
fonte
-5

você pode pegar este pedaço de código como um exemplo de onMeasure () ::

public class MyLayerLayout extends RelativeLayout {

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

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int parentHeight = MeasureSpec.getSize(heightMeasureSpec);

        int currentChildCount = getChildCount();
        for (int i = 0; i < currentChildCount; i++) {
            View currentChild = getChildAt(i);

            //code to find information

            int widthPercent = currentChildInfo.getWidth();
            int heightPercent = currentChildInfo.getHeight();

//considering we will pass height & width as percentage

            int myWidth = (int) Math.round(parentWidth * (widthPercent / 100.0));
            int myHeight = (int) Math.round(parentHeight * (heightPercent / 100.0));

//Considering we need to set horizontal & vertical position of the view in parent

            AlignmentTraitValue vAlign = currentChildInfo.getVerticalLocation() != null ? currentChildlayerInfo.getVerticalLocation() : currentChildAlignmentTraitValue.TOP;
            AlignmentTraitValue hAlign = currentChildInfo.getHorizontalLocation() != null ? currentChildlayerInfo.getHorizontalLocation() : currentChildAlignmentTraitValue.LEFT;
            int topPadding = 0;
            int leftPadding = 0;

            if (vAlign.equals(currentChildAlignmentTraitValue.CENTER)) {
                topPadding = (parentHeight - myHeight) / 2;
            } else if (vAlign.equals(currentChildAlignmentTraitValue.BOTTOM)) {
                topPadding = parentHeight - myHeight;
            }

            if (hAlign.equals(currentChildAlignmentTraitValue.CENTER)) {
                leftPadding = (parentWidth - myWidth) / 2;
            } else if (hAlign.equals(currentChildAlignmentTraitValue.RIGHT)) {
                leftPadding = parentWidth - myWidth;
            }
            LayoutParams myLayoutParams = new LayoutParams(myWidth, myHeight);
            currentChildLayoutParams.setMargins(leftPadding, topPadding, 0, 0);
            currentChild.setLayoutParams(myLayoutParams);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}
Sayan
fonte
6
Este código está fundamentalmente errado. Primeiro, não leva em conta o modo de layout (veja a resposta de Grimmace). Isso é ruim, mas você não verá o efeito disso porque também chama super.onMeasure () no final, o que substitui os valores definidos (consulte a resposta de satur9nine) e acaba com o propósito de substituir onMeasure ().
spaaarky21