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
Related
I have to set an image in my activity and in my scenario the image covers fairly large portion of the activity. However I want to keep its width/height to be no more than 33% of its parent viewGroup's width.
I am dynamically changing the ImageView's dimension at runtime using this code.
#Override
protected void onResume() {
super.onResume();
//try to resize the imageview according to current width of container layout
int nWidthPix = findViewById(R.id.layout_info).getWidth();
ImageView imView = (ImageView)findViewById(R.id.imageView_main);
imView.setAdjustViewBounds(true);
int imViewWidth = nWidthPix/3;
imView.setMaxWidth(imViewWidth);
imView.setMaxHeight(imViewWidth);
//
}
The problem is that nWidthPix which is width of RelativeLayout which contains my ImageView is returned 0 in the first call to onResume() that is when the app is launched for first time. As a result image is not shown , in the subsequent calls to onResume() it returns 720 which is correct.
I am guessing that since the view is not really shown yet before the first call on onResume it does not know the width ? but then we can find views on the other hand even before the activity is shown for the first time. .. Not sure what's causing it.
Help
You can use the ViewTreeObserver of the root layout and set an OnGlobalLayoutListener to know when the layout finishes loading. Take a look at this answer for a reference.
View always report 0 as their width and height before they've been measured by the framework. onResume() is too soon to query this information, unfortunately.
If the parent view is the Activity itself, then you can get the window's dimensions (for example with this method) and use those values. Otherwise you need to postpone execution of this code until the layout pass is complete (via an OnGlobalLayoutListener).
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
Is there a way the this problem can be fixed? I tried invalidate() but, it still displays the same problem. What happens is that, after opening the page/ the Activity, the images behaves like the one in Figure A. It only renders to my desired layout (Figure B) after scrolling it back and forth.
What I'm trying to do is set the width and heigth of the image during runtime. So this is also in relation to my previous questions : Images in my HorizontalListView changes it size randomly and ImageView dynamic width and height which received a very little help.
Any tips regarding this matter, please?
EDIT: btw, my classes are:
MyCustomAdapter (extends baseadapter, this calls the displayimage() from ImageLoader ),
MyActivity and
ImageLoader (this is where my image url are loaded, decoded, displayed asynchronously)
Im also confused as to where i will set the height and width of the imageView. For now, i set it at ImageLoader. It was okay. but i dont know if i did the right thing.
If you want to set the width and height manually at runtime, grab a reference to the ImageView's LayoutParams AFTER the View has been measured by the layout system. If you do this too early in the rendering phase, your view's width and height as well as its parent view and so on will be 0.
I have some code in an open source library that might help you. The process is two parts:
Set up an OnPreDrawListener attached to the ViewTreeObserver for your control. My example does this inside of a custom control, but you can do this in your activity as well.
Inside the onPreDraw method, your image and it's parent will now have their width and height values assigned to them. You can make your calculations and then set your width and/or height manually to the LayoutParams object of your view (don't forget to set it back).
Check out this example where I'm applying an aspect ratio to a custom ImageView just before it's rendered to the screen. I don't know if this exactly fits your use case, but this will demonstrate how to add an OnPreDrawListener to a ViewTreeObserver, removing it when you're done, and applying dynamic sizing to a View at runtime
https://github.com/aguynamedrich/beacon-utils/blob/master/Library/src/us/beacondigital/utils/RemoteImageView.java#L78
Here's a modified version that removes my particular resizing logic. It also grabs the ViewTreeObserver from the imageView, which is a more likely scenario if you're not implementing a custom control and you only want to do this in the Activity
private void initResizeLogic() {
final ViewTreeObserver obs = imageView.getViewTreeObserver();
obs.addOnPreDrawListener(new OnPreDrawListener() {
public boolean onPreDraw() {
dynamicResize();
obs.removeOnPreDrawListener(this);
return true;
}
});
}
protected void dynamicResize() {
ViewGroup.LayoutParams lp = imageView.getLayoutParams();
// resize logic goes here...
// imageView.getWidth() and imageView.getHeight() now return
// their initial layout values
lp.height = someCalculatedHeight;
lp.width = someCalculatedWidth;
imageView.setLayoutParams(lp);
}
}
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).
The Android Documentation says that there is two sizes for a view, the measured dimensions and the drawing dimensions. The measured dimension is the one computed in the measure pass (the onMeasure method), while the drawing dimensions are the actual size on screen. Particularly, the documentation says that:
These values may, but do not have to, be different from the measured width and height.
So, my question is: what could make the drawing dimension be different of the measured dimension? If the onMeasure(int,int) method respects the layout requirements (given as the parameters widthMeasureSpec and heightMeasureSpec, how could the SDK decides that the view should have a different drawing size?
Additionally, how/where in the Android Source Code the measured width/height is used to compute the drawing width/height? I tryed to look into the View source code, but I can't figure out how the measuredWidth/Height is used to compute the final width/height. Maybe it has something to do with the padding, but I'm not sure.
As the name suggests the measuredWidth/height is used during measuring and layoutting phase.
Let me give an example,
A widget is asked to measure itself, The widget says that it wants to be 200px by 200px. This is measuredWidth/height.
During the layout phase, i.e. in onLayout method. The method can use the measuredWidth/height of its children or assign a new width/height by calling layout method of the view.
lets say the onLayout method calls childview.layout(0,0,150,150) now the width/height of the view is different than the measured width/height.
I would suggest not to use the measuredWidth/height outside onLayout method.
to summarize .
onMeasure -> sets up measuredWidth/measuredHeight
onLayout -> sets up the width/height of the widget.
additionallly
public void View.layout(int l, int t, int r, int b)
seems to be place where the assignment of position and size happens.