Getting width of a Custom View - android

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

Related

How to know LayoutParams of View before measurement?

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.

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();

onMeasure(): wrap_content, how do I know the size to wrap?

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 up the entire screen. onMeasure() says this:
The base class implementation of measure defaults to the background size, unless a larger size is allowed by the MeasureSpec. Subclasses should override onMeasure(int, int) to provide better measurements of their content.
Ok cool so I know I need to override onMeasure() and work with MeasureSpec. According to this answer
UNSPECIFIED means the layout_width or layout_height value was set to wrap_content. You can be whatever size you would like.
Now I get to my problem, how do I at onMeasure() measure my bitmap that is not created yet and measure/wrap it? I know the other Android views MUST do something because they do not block out the entire screen if set to wrap_content. Thanks in advance!
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
Choosing a desired size
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 custom view is an image, then your desired size would probably be the pixel dimensions of the bitmap plus any padding. (It is your responsibility to figure padding into your calculations when choosing a size and drawing the content.)
If you custom view is an analog clock, then the desired size could be some default size that it would look good at. (You can always get the the dp to px size for the device.)
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.)
Negotiating the final size
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.
Using the chosen size
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.
Notes
Any time that you make a change to your view that affects the appearance but not the size, then call invalidate(). This will cause onDraw to be called again (but not all of those other previous methods).
Any time that you make a change to your view that would affect the size, then call requestLayout(). This will start the process of measuring and drawing all over again from onMeasure. It is usually combined with a call to invalidate().
If for some reason you really can't determine an appropriate desired size beforehand, then I suppose you can do as #nmw suggests and just request zero width, zero height. Then request a layout (not just 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).

How do I find the width/height of ImageView?

My question is similar to this question. I've used this code to extend ImageView to form TouchImageView. In my onCreate() method, I want to call
setImage(Bitmap bm, int displayWidth, int displayHeight)
but I don't know the width or height to use. When I call getHeight(), it gives me zero.
I tried to avoid the 0 using kcoppock's answer to the previously mentioned question, but was unsuccessful. The onPreDraw() listener is called several times per second and I can't figure out how to only call it once.
That's a brittle approach for something like this. Android Views are measured by their parent View using the onMeasure method. By providing an initial size to a setImage method to scale to (and especially if you set it based on the display size) you're inviting complexity whenever you want to nest this View within others. Larger screen devices like tablets are a good example of this.
A more idiomatic approach would implement the setImage method of TouchImageView without the width/height parameters at all. Instead it would defer setting the scale and matrices until measurement and implement onMeasure something like this:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mNeedsInitialScale) { // Set by setImage when a new image is set
// The measured width and height were set by super.onMeasure above
setInitialScale(getMeasuredWidth(), getMeasuredHeight());
mNeedsInitialScale = false;
}
}
Where the setInitialScale method does everything that the linked setImage method does after the call to super.setImageBitmap. setImage should also set the mNeedsInitialScale field to true.
This way you will never need to worry about the initial size to use, it will be obtained automatically from the View during normal measurement whenever a new image is set.
the ImageViews dimensions are 0 until your Activity draw it on the screen. You could use Activitys onFocusChanged to set the Bitmap and get correct Dimensions
if you are just looking for DisplayWidth and height you can call
Display d = getSystemService(WINDOW_SERVICE).getDefaultDisplay();
and ask Display for its dimensions

How can I create a custom View with a Relative Size?

I would like to design an application which could work with devices of any screen resolution.
My idea to do so was to create one Custom View for each type of View I use, and use the layout_width and layout_height specified in the XML as a percentage of the parent size (for example, layout_width="100dp" is equivalent to fill_parent, and layout_width="50dp" means the View's width will be half the parent's one).
Here is the custom TextView class I made:
public class RSTextView extends TextView {
public RSTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int w, int h) {
int parentWidth = ((View)this.getParent()).getWidth();
int parentHeight = ((View)this.getParent()).getHeight();
Log.i(null, "Width: " + w + "; Height: " + parentHeight);
this.setMeasuredDimension(parentWidth * MeasureSpec.getSize(w) / 100,
parentHeight * MeasureSpec.getSize(h) / 100);
}
}
However, it does not work. The parentWidth and parentHeight size is always 0.
Is there any better way to create an application with a relative size?
And if it is the right way to go, how can I retrieve the View's parents' size from the onMeasure method?
Since you're checking a view's height during the layout phase, you probably want to be using getMeasuredHeight, not getHeight, but I'm not sure you need to be doing what you're doing at all. It's hard to tell from your description, but it seems like you might be trying to recreate the behavior of LinearLayout weights. For example, if you have a horizontal LinearLayout with 3 children, you can easily set the first to take up half its width and the others each to take up 25% by assigning them 0px layout_widths and layout_weights of 2, 1 and 1 respectively (or any other weights of those proportions).
If you do decide to go the custom View route, though, don't overload what layout_height and layout_width do -- use custom XML attributes to take the argument for the percentages (I'd dig into LinearLayout's code to see how layout_weight is implemented).

Categories

Resources