I have made a custom View with onDraw()
overridden that draws a bitmap on the canvas. When I specify that I want it wrap_content in the layout file it still fills u
This is the order that these commonly used view methods are run in:
1. Constructor // choose your desired size
2. onMeasure // parent will determine if your desired size is acceptable
3. onSizeChanged
4. onLayout
5. onDraw // draw your view content at the size specified by the parent
If your view could be any size it wanted, what size would it choose? This will be your wrap_content
size and will depend on the content of your custom view. Examples:
If your desired size uses heavy calculations, then do that in your constructor. Otherwise, you can just assign it in onMeasure
. (onMeasure
, onLayout
, and onDraw
may be called multiple times so that is why it isn't good to do heavy work here.)
onMeasure
is the place where the child tells the parent how big it would like to be and the parent decides if that is acceptable. This method often gets called a few times, each time passing in different size requirements, seeing if some compromise can be reached. In the end, though, the child needs to respect to the parent's size requirements.
I always go back to this answer when I need a refresher on how to set up my onMeasure
:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int desiredWidth = 100;
int desiredHeight = 100;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
//Measure Width
if (widthMode == MeasureSpec.EXACTLY) {
//Must be this size
width = widthSize;
} else if (widthMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
width = Math.min(desiredWidth, widthSize);
} else {
//Be whatever you want
width = desiredWidth;
}
//Measure Height
if (heightMode == MeasureSpec.EXACTLY) {
//Must be this size
height = heightSize;
} else if (heightMode == MeasureSpec.AT_MOST) {
//Can't be bigger than...
height = Math.min(desiredHeight, heightSize);
} else {
//Be whatever you want
height = desiredHeight;
}
//MUST CALL THIS
setMeasuredDimension(width, height);
}
In the example above the desired width and height were just set to some defaults. You could instead calculate them beforehand and set them here using a class member variable.
After onMeasure
, the size of your view is known. This size may or may not be what you requested, but it is what you have to work with now. Use that size to draw the content on your view in onDraw
.
invalidate()
. This will cause onDraw
to be called again (but not all of those other previous methods).requestLayout()
. This will start the process of measuring and drawing all over again from onMeasure
. It is usually combined with a call to invalidate().invalidate()
) after everything has been loaded. This seems like a bit of a waste though, because you are requiring the entire view hierarchy to be laid out twice in a row.If you can't measure the bitmap prior to the onMeasure call, then you could return a size of zero until the Bitmap is loaded. Once it is loaded, invalidate the parent ViewGroup to force another measure (can't remember if invalidate() on the View itself will force an onMeasure).