What's the correct way of overriding onMeasure()? I've seen various approaches. For example, Professional Android Development uses MeasureSpec to calculate the dimensions, then ends with a call to setMeasuredDimension(). For example:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(parentWidth/2, parentHeight);
}
On the other hand, as per this post, the "correct" way is to use MeasureSpec, call setMeasuredDimensions(), followed by a call to setLayoutParams(), and ending with a call to super.onMeasure(). For example:
@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);
}
So which is the right way? Neither approach has worked 100% for me.
I guess really what I'm asking is does anyone know of a tutorial that explains onMeasure(), layout, dimensions of child views etc.?
The other solutions are not comprehensive. They may work in some cases, and are a good place to start, but they may are not guaranteed to work.
When onMeasure gets called you may or may not have the rights to change the size. The values that are passed to your onMeasure (widthMeasureSpec
, heightMeasureSpec
) contain information about what your child view is allowed to do. Currently there are three values:
MeasureSpec.UNSPECIFIED
- You can be as big as you'd likeMeasureSpec.AT_MOST
- As big as you want (up to the spec size), This is parentWidth
in your example.MeasureSpec.EXACTLY
- No choice. Parent has chosen.This is done so that Android can make multiple passes to find the right size for each item, see here for more details.
If you do not follow these rules, your approach is not guaranteed to work.
For example if you want to check if you're allowed to change the size at all you can do the following:
final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
boolean resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
boolean resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;
Using this information you will know whether you can modify the values as in your code. Or if you are required to do something different. A quick and easy way to resolve your desired size is to use one of the following methods:
int resolveSizeAndState (int size, int measureSpec, int childMeasuredState)
int resolveSize (int size, int measureSpec)
While the first is only available on Honeycomb, the second is available on all versions.
Note: You may find that resizeWidth
or resizeHeight
are always false. I found this to be the case if I was requesting MATCH_PARENT
. I was able to fix this by requesting WRAP_CONTENT
on my parent layout and then during the UNSPECIFIED phase requesting a size of Integer.MAX_VALUE
. Doing so gives you the max size your parent allows on the next pass through onMeasure.
The documentation is the authority on this matter: http://developer.android.com/guide/topics/ui/how-android-draws.html and http://developer.android.com/guide/topics/ui/custom-components.html
To summarize: at the end of your overridden onMeasure
method you should call setMeasuredDimension
.
You should not call super.onMeasure
after calling setMeasuredDimension
, that will just erase whatever you set. In some situations you might want to call the super.onMeasure
first and then modify the results by calling setMeasuredDimension
.
Don't call setLayoutParams
in onMeasure
. Layout happens in a second pass after measuring.
I think it depends on the parent which you are overriding.
For example, if you are extending a ViewGroup (like FrameLayout), when you have measured the size, you should call like below
super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
because you may want to ViewGroup to do rest work (do some stuffs on child view)
If you are extending a View (like ImageView), you can just call this.setMeasuredDimension(width, height);
, because the parent class will just do something like you have done usually.
In a word, if you want some features your parent class offers free you should call super.onMeasure()
(pass MeasureSpec.EXACTLY mode measure spec usually), otherwise call this.setMeasuredDimension(width, height);
is enough.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With