web-dev-qa-db-fra.com

Android getMeasuredHeight renvoie des valeurs erronées!

J'essaie de déterminer la dimension réelle en pixels de certains éléments de l'interface utilisateur!

Ces éléments sont gonflés à partir d'un fichier .xml et sont initialisés avec la largeur et la hauteur d'immersion de sorte que l'interface graphique supporte finalement plusieurs tailles d'écran et dpi ( comme recommandé par Android specs =).

<LinearLayout
xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:layout_width="wrap_content"
Android:layout_height="150dip"
Android:orientation="vertical">

<ImageView
    Android:id="@+id/TlFrame" 
    Android:layout_width="110dip" 
    Android:layout_height="90dip"
    Android:src="@drawable/timeline_nodrawing"
    Android:layout_margin="0dip"
    Android:padding="0dip"/></LinearLayout>

Ce xml précédent représente une trame. Mais j'en ajoute beaucoup dynamiquement dans une disposition horizontale décrite ici:

<HorizontalScrollView 
        Android:id="@+id/TlScroller"
        Android:layout_width="wrap_content" 
        Android:layout_height="wrap_content"
        Android:orientation="horizontal"
        Android:layout_alignParentBottom="true"
        Android:layout_alignParentLeft="true"
        Android:layout_margin="0dip"
        Android:padding="0dip"
        Android:scrollbars="none"
        Android:fillViewport="false"
        Android:scrollbarFadeDuration="0"
        Android:scrollbarDefaultDelayBeforeFade="0"
        Android:fadingEdgeLength="0dip"
        Android:scaleType="centerInside">

        <!-- HorizontalScrollView can only Host one direct child -->
        <LinearLayout 
            Android:id="@+id/TimelineContent" 
            Android:layout_width="fill_parent" 
            Android:layout_height="wrap_content"
            Android:orientation="horizontal"
            Android:layout_alignParentTop="true"
            Android:layout_alignParentLeft="true"
            Android:layout_margin="0dip"
            Android:padding="0dip"
            Android:scaleType="centerInside"/>

    </HorizontalScrollView > 

La méthode définie pour ajouter une trame dans mon Java:

private void addNewFrame()
{       
    LayoutInflater inflater     = (LayoutInflater) _parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    ViewGroup root      = (ViewGroup) inflater.inflate(R.layout.tl_frame, null);
    TextView frameNumber = (TextView) root.findViewById(R.id.FrameNumber);
    Integer val = new Integer(_nFramesDisplayed+1); //+1 to display ids starting from one on the user side 
    frameNumber.setText(val.toString());

    ++_nFramesDisplayed;
    _content.addView(root);
// _content variable is initialized like this in c_tor
// _content = (LinearLayout) _parent.findViewById(R.id.TimelineContent);
}

Ensuite, à l'intérieur de mon code, j'essaie d'obtenir la taille réelle réelle en pixels car j'en ai besoin pour dessiner des trucs opengl dessus.

LayoutInflater inflater     = (LayoutInflater) _parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    ViewGroup root      = (ViewGroup) inflater.inflate(R.layout.tl_frame, null);
    ImageView frame = (ImageView) root.findViewById(R.id.TlFrame);

    frame.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
    frame.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);

    final int w = frame.getMeasuredWidth();
    final int h = frame.getMeasuredHeight();

Tout semble bien fonctionner, sauf que ces valeurs sont bien plus grandes que la taille réelle des pixels de l'ImageView.

Informations signalées par getWindowManager (). GetDefaultDisplay (). GetMetrics (métriques); sont les suivants: densité = 1,5 densitéDpi = 240 largeurPixel = 600 hauteurPixel = 1024

Maintenant, je connais la règle de Android est: pixel = dip * (dpi/160). Mais rien n'a de sens avec la valeur renvoyée. Pour cette ImageView de (90dip X 110dip), le les valeurs renvoyées de la méthode measure () sont (270 x 218) que j'ai supposées être en pixel!

Quelqu'un a une idée pourquoi? La valeur est-elle renvoyée en pixels?

Au fait: j'ai testé le même code mais avec une TextView à la place d'une ImageView et tout semble bien fonctionner! Pourquoi !?!?

37
oberthelot

Vous appelez measure de manière incorrecte.

measure prend MeasureSpec valeurs spécialement emballées par MeasureSpec.makeMeasureSpec. measure ignore LayoutParams. Le parent effectuant la mesure doit créer un MeasureSpec basé sur sa propre stratégie de mesure et de disposition et les LayoutParams de l'enfant.

Si vous voulez mesurer la façon dont WRAP_CONTENT fonctionne généralement dans la plupart des mises en page, appelez measure comme ceci:

frame.measure(MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST),
        MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST));

Si vous n'avez pas de valeurs maximales (par exemple si vous écrivez quelque chose comme un ScrollView qui a un espace infini), vous pouvez utiliser le mode UNSPECIFIED:

frame.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
        MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
53
adamp

Faites ça:

frame.measure(0, 0);

final int w = frame.getMeasuredWidth();
final int h = frame.getMeasuredHeight();

Résolu!

17
Derzu

D'accord ! En quelque sorte répondre à ma propre question ici ... Mais pas complètement

1 - Il semble que sur certains appareils, les mesures ImageView ne fournissent pas de valeurs exactes. J'ai vu de nombreux rapports à ce sujet sur les appareils Nexus et Galaxy par exemple.

2 - Un contourner que j'ai trouvé:

Définissez la largeur et la hauteur de votre ImageView sur "wrap_content" dans le code xml.

Gonflez la disposition à l'intérieur de votre code (généralement dans l'initialisation de l'interface utilisateur, je suppose).

LayoutInflater inflater     = (LayoutInflater) 
_parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
ViewGroup root      = (ViewGroup) inflater.inflate(R.layout.tl_frame, null);
ImageView frame = (ImageView) root.findViewById(R.id.TlFrame);

Calculez votre propre ratio pour la vue de votre image, sur la base de la typique calcul Android

//ScreenDpi can be acquired by getWindowManager().getDefaultDisplay().getMetrics(metrics);
 pixelWidth = wantedDipSize * (ScreenDpi / 160)

Utilisez la taille calculée pour définir dynamiquement votre ImageView dans votre code

frame.getLayoutParams().width = pixeWidth;

Et le tour est joué! votre ImageView a maintenant la taille de dip souhaitée;)

5
oberthelot
view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
 @SuppressLint("NewApi")
 @SuppressWarnings("deprecation")
 @Override
  public void onGlobalLayout() {
   //now we can retrieve the width and height
   int width = view.getWidth();
   int height = view.getHeight();


   //this is an important step not to keep receiving callbacks:
   //we should remove this listener
   //I use the function to remove it based on the api level!

    if(Android.os.Build.VERSION.SDK_INT >= Android.os.Build.VERSION_CODES.JELLY_BEAN){
        view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
    }else{
        view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
    }
  }
 });

On devrait aller avec Comment obtenir la largeur/hauteur d'une vue

4
Amit Yadav

Vous devez créer une vue de texte personnalisée et l'utiliser dans vos présentations et utiliser la fonction de hauteur getActual pour définir la hauteur au moment de l'exécution

public class TextViewHeightPlus extends TextView {
    private static final String TAG = "TextViewHeightPlus";
    private int actualHeight=0;


    public int getActualHeight() {
        return actualHeight;
    }

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

    public TextViewHeightPlus(Context context, AttributeSet attrs) {
        super(context, attrs);
        setCustomFont(context, attrs);
    }

    public TextViewHeightPlus(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

    }

   @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        actualHeight=0;

        actualHeight=(int) ((getLineCount()-1)*getTextSize());

    }

}
2
Pramod

Probablement, en raison de ce que vous avez dans le fichier AndroidManifest.xml ( link ) et de quel répertoire drawable-XXX provient le fichier xml, Android charge les ressources avec une opération de mise à l'échelle. Vous décidez d'utiliser l'unité de dimension "dip" ( link ) qui est virtuelle et la valeur réelle (px) peut être différente.

1

Malheureusement, dans Activity méthodes de cycle de vie telles que Activity # onCreate (Bundle) , une passe de mise en page n'a pas encore été effectuée, vous ne pouvez donc pas encore récupérer la taille des vues dans votre hiérarchie de vues. Cependant, vous pouvez explicitement demander à Android de mesurer une vue à l'aide de View # measure (int, int) .

Comme le souligne @ adamp answer , vous devez fournir View # measure (int, int) with MeasureSpec values, mais cela peut être un peu intimidant trouver le bon MeasureSpec .

La méthode suivante essaie de déterminer les valeurs MeasureSpec correctes et mesure les valeurs passées dans view:

public class ViewUtil {

    public static void measure(@NonNull final View view) {
        final ViewGroup.LayoutParams layoutParams = view.getLayoutParams();

        final int horizontalMode;
        final int horizontalSize;
        switch (layoutParams.width) {
            case ViewGroup.LayoutParams.MATCH_PARENT:
                horizontalMode = View.MeasureSpec.EXACTLY;
                if (view.getParent() instanceof LinearLayout
                        && ((LinearLayout) view.getParent()).getOrientation() == LinearLayout.VERTICAL) {
                    ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
                    horizontalSize = ((View) view.getParent()).getMeasuredWidth() - lp.leftMargin - lp.rightMargin;
                } else {
                    horizontalSize = ((View) view.getParent()).getMeasuredWidth();
                }
                break;
            case ViewGroup.LayoutParams.WRAP_CONTENT:
                horizontalMode = View.MeasureSpec.UNSPECIFIED;
                horizontalSize = 0;
                break;
            default:
                horizontalMode = View.MeasureSpec.EXACTLY;
                horizontalSize = layoutParams.width;
                break;
        }
        final int horizontalMeasureSpec = View.MeasureSpec
                .makeMeasureSpec(horizontalSize, horizontalMode);

        final int verticalMode;
        final int verticalSize;
        switch (layoutParams.height) {
            case ViewGroup.LayoutParams.MATCH_PARENT:
                verticalMode = View.MeasureSpec.EXACTLY;
                if (view.getParent() instanceof LinearLayout
                        && ((LinearLayout) view.getParent()).getOrientation() == LinearLayout.HORIZONTAL) {
                    ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
                    verticalSize = ((View) view.getParent()).getMeasuredHeight() - lp.topMargin - lp.bottomMargin;
                } else {
                    verticalSize = ((View) view.getParent()).getMeasuredHeight();
                }
                break;
            case ViewGroup.LayoutParams.WRAP_CONTENT:
                verticalMode = View.MeasureSpec.UNSPECIFIED;
                verticalSize = 0;
                break;
            default:
                verticalMode = View.MeasureSpec.EXACTLY;
                verticalSize = layoutParams.height;
                break;
        }
        final int verticalMeasureSpec = View.MeasureSpec.makeMeasureSpec(verticalSize, verticalMode);

        view.measure(horizontalMeasureSpec, verticalMeasureSpec);
    }

}

Ensuite, vous pouvez simplement appeler:

ViewUtil.measure(view);
int height = view.getMeasuredHeight();
int width = view.getMeasuredWidth();

Alternativement, comme @Amit Yadav suggéré , vous pouvez utiliser OnGlobalLayoutListener pour qu'un auditeur soit appelé après l'exécution de la mise en page. Ce qui suit est une méthode qui gère la désinscription de l'écouteur et des changements de nom de méthode entre les versions:

public class ViewUtil {

    public static void captureGlobalLayout(@NonNull final View view,
                                           @NonNull final ViewTreeObserver.OnGlobalLayoutListener listener) {
        view.getViewTreeObserver()
                .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        final ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                            viewTreeObserver.removeOnGlobalLayoutListener(this);
                        } else {
                            //noinspection deprecation
                            viewTreeObserver.removeGlobalOnLayoutListener(this);
                        }
                        listener.onGlobalLayout();
                    }
                });
    }

}

Ensuite vous pouvez:

ViewUtil.captureGlobalLayout(rootView, new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        int width = view.getMeasureWidth();
        int height = view.getMeasuredHeight();
    }
});

rootView peut être la vue racine de votre hiérarchie de vues et view peut être n'importe quelle vue de votre hiérarchie dont vous souhaitez connaître les dimensions.

1
Travis