How to know LayoutParams of View before measurement? - android

I have custom view with child items, which are configureable via xml. However they are can be configurable in runtime via something like a Configuration class. After that I just notify parent view about changes and all is ok.BTW. My question in fact touches measurement: I can change child items size in runtime, but for the first launch I want to set to all of them size (width and height) accordingly to layout params defined in xml.
Maybe some code will add more clarification to you.
protected int getItemWidth() {
if (cell != null) {
int width = cell.getWidth();
return width == 0 ? <layout_width_defined_in_xml> : ScreenUtils.convertToDp(context, width);
} else {
return canvasWidth;
}
}
So, I want to know. Is possible to get layout params before measurement? And how to that?

The size defined in XML isn't always the right size the view should have when it's actually laid out. For example, a child of a LinearLayout may have android:layout_width="0", but might have nonzero width because of android:layout_weight. (There are other examples as well with other kinds of layouts.) Additionally, the values match_parent and wrap_content map to negative integer values in java code, which I don't think is helpful to you here.
If your custom view is interested in measuring and positioning child views, you should be overriding onMeasure() and onLayout(). If you aren't doing that or if you don't need to do that, getWidth() and getHeight() will tell you a view's actual size, and getMeasuredWidth() and getMeasuredHeight() will tell you the measured size (which can differ from the actual size). THe only caveat is that you have to wait for the first measure/layout before calling those 4 methods because otherwise those methods all return zero.
If you do want to inspect the layout parameters of a view, you can do
LayoutParams params = view.getLayoutParams();
int width = params.width;
int height = params.height;
As noted, either or both of those may be negative. You can compare them to LayoutParams.MATCH_PARENT and LayoutParams.WRAP_CONTENT to check for these cases, but again I'm not sure this is helpful to you if you aren't implementing onMeasure() and onLayout().

You can do this in callback ViewTreeObserver.OnGlobalLayoutListener. You can get view dimensions here and set size other views with these dimensions.

Related

What is the difference between MeasureSpec.getSize(widthMeasureSpec) and getWidth()?

I konw the difference between getWidth() and getMeasuredWidth(),
but I can not understand the difference between MeasureSpec.getSize(widthMeasureSpec) and getWidth(),
which width does MeasureSpec.getSize(widthMeasureSpec) get?
thx~~
MeasureSpec.getSize(widthMeasureSpec) returns the preferred width or the specified witdth of a view you want to create. This may or may not be the same as getWidth() after the view has been created, as views may change size based on the width of other views, screen orientation etc.
Ok lets see.
getMeasuredWidth() returns the width measurement set in onMeasure() via setMeasuredDimension().
getWidth() returns the final width of the view. The Parent view may accept the measurements set in onMeasure (set via setMeasuredDimension()). In which case getWidth and getMeasuredWidth will return the same value. If the Parent view decides that the measurements you set cannot be used, either because it's too big or too small then it will set the final width to something more appropiate. In which case getWidth will have the final valid width and will be different from getMeasuredWidth(). Always use getWidth() in onDraw(). only use getMeasuredWidth() if you want to know if the parent changed the width you previously set in onMeasure().
Finally to explain MeasureSpec.getSize(widthMeasureSpec) you need to understand the following from the official documentation:
If you need finer control over your view's layout parameters,
implement onMeasure(). This method's parameters are View.MeasureSpec
values that tell you how big your view's parent wants your view to be,
and whether that size is a hard maximum or just a suggestion. As an
optimization, these values are stored as packed integers, and you use
the static methods of View.MeasureSpec to unpack the information
stored in each integer. Custom View Components
As you can read from the snippet. WidthMeasureSpec and WeightMeasureSpec are not pixel measurments. To get the actual pixel value use MeasureSpec.getSize():
int width = MeasureSpec.getSize(widthMeasureSpec)
PD:This all applies to the height as well
For more Infomation see my answer here(its not marked as the answer):
MeasureSpec returns a 0 value

After changing a property on a LayoutParams object, do I need to call setLayoutParams again?

I have a View (containing an ad) that I need to scale to fit the screen width at a certain point in time (after the ad is loaded). So I have a method setHeight(myView) that calculates the correct height for the given screen width and changes the LayoutParams of the View accordingly. The crucial part of the code is this:
LayoutParams params = myView.getLayoutParams();
int width = myView.getWidth();
if (params != null && width > 0) {
params.height = (int) Math.round(ratio * width);
}
This seems to work for the most part, but sometimes the view is not scaled. It seems to only consistenly work if I add the following line at the bottom:
myView.setLayoutParams(params);
This seems to make sense, too, since Android Views call requestLayout() in their setLayoutParams() method. Conversely, I see no way how a change in a public field (params.height) would trigger a layout change.
On the other hand, I repeatedly find tutorials on the net where the params are simply changed and then not set to the view again.
So, my question is: Is it correct that, to immediately update the layout after changing a property of the LayoutParams, I need to call setLayoutParams again? And that simply changing the property will only lead to a layout change at some later point in time when the layout change is triggered from elsewhere?
A change to the layout params only take effect on the next layout pass.
requestLayout() schedules a layout pass
setLayoutParams() calls requestLayout() as you have observed
Sometimes a layout pass is scheduled by some other means. For example, immediately after inflation the layout params have also been inflated and the measure/layout message has just been posted to the UI thread message queue for later processing.
So, to be safe, call requestLayout() always after touching the layout params. setLayoutParams() works, too, though it's not strictly necessary when modifying the params in-place.
In Kotlin you can use KTX extension to update LayoutParams
view.updateLayoutParams { height = newValue }

Get size of UI before actually creating one?

I am working on an Android app that is largely driven by code, not xml.
The Adapter for a ListView wants to know the height of the item. But this in turn could depend on the size of UI elements within the item. For example, if the item contains a checkbox, the size of the checkbox could influence the layout of the list item, which in turn could change the height, depending on whether or not a line wrap became necessary.
Question -- is there a way to get the size of an Android UI element without actually creating it? Similar to the way one can use a Paint or TextPaint object to get the size of text before it is drawn.
Without creating it? No, but you could use the MeasureSpec class and measure the View manually after you create it. Why do you need to know the exact size? Can't you just set the LayoutParams to MATCH_PARENT (width) and WRAP_CONTENT (height) for your case?
That said, if you do need to know, you could use the width of your ListView for the width MeasureSpec, and then UNSPECIFIED for the height MeasureSpec:
// Tell the View it should be exactly as wide as the ListView...
int widthSpec = MeasureSpec.makeMeasureSpec(listViewWidth, MeasureSpec.EXACTLY);
// ... and as tall as it wants to be ...
int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
// ... measure it with these constraints ...
item.measure(widthSpec, heightSpec);
// ... and retrieve the measured height.
int itemHeight = item.getMeasuredHeight();

Getting width of a Custom View

I need to get the width of a custom view I'm creating by extending LinearLayout. My initial stab was to do
int width = this.getLayoutParams().width
However, this would only return -1.
Next, I attempted overriding onMeasure(int, int)
#Override
protected void onMeasure(int width, int height) {
super.onMeasure(width, height);
this.width = MeasureSpec.getSize(width);
}
This worked to get the width but it was being executed after I actually needed the width (I guess the view wasn't built yet?)
I specifically need it for Google Maps with this method:`map.moveCamera(CameraUpdateFactory.newLatLngBounds(bounds, width, height, MAP_PADDING));
Any suggestions on getting the width?
Unless you explicitly set your view's width/height to static value (like x-dp or match_parent), you cannot know the size of your view until onMeasure loop is completed. Also android can call onMeasure multiple times to find suitable size, so it's not the best place to find exact size. You may have to find a way to delay your map.moveCamera to onLayout function.
You can do it as follows:
// Name the activity's root view - your custom linear layout
View view = findViewById(R.id.customLinearLayout)
int width = view.getRootView().getWidth(); // this returns the actual width of the view
This has always worked for me

Creating dynamically sized square (equal height and width) UI elements in Android

As the title states, I am trying to set the width of a layout element to be equal to the height (which is set to match the parent). I've tried setting the width parameter programatically from the height parameter, but this shows up as -1 rather than the actual height (since it matches the parent). Anyone know how I can create square layout elements whose size is dynamic? Thanks.
So what I've been doing for things like this -- and I don't know that it's ideal, but it works quite well -- is use ViewTreeObserver.OnGlobalLayoutListener. Something like this:
View myView = findViewById(R.id.myview);
ViewTreeObserver observer = myView.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new SquareLayoutAdjuster());
class SquareLayoutAdjuster
implements ViewTreeObserver.OnGlobalLayoutListener {
#Override
public void onGlobalLayout() {
int dimension = myView.getHeight();
LayoutParams params = myView.getLayoutParams();
params.width = dimension;
myView.setLayoutParams(params);
observer.removeGlobalOnLayoutListener(this);
}
}
That's just the general idea, I don't know if it compiles as is, but basically in the onGlobalLayout() method, your views are guaranteed to be measured. You remove the listener at the end to prevent it from being called multiple times (unless you need that to happen for whatever reason).
the solution of KCOPPOCK works for me, thank you. But i got "java.lang.IllegalStateException: This ViewTreeObserver is not alive, call getViewTreeObserver() again" at runtime. So i use
findViewById(R.id.serie_main_layout).getViewTreeObserver().removeOnGlobalLayoutListener(this);
instead
observer.removeGlobalOnLayoutListener(this);
and all works perfectly.

Categories

Resources