Override a RelativeLayout's onMeasure() to constrain its aspect ratio - android

I have an extended RelativeLayout which, when programmatically positioned and sized using RelativeLayout.LayoutParams, needs to restrict itself to a given aspect ratio. Typically, I would wish for it to constrain itself to 1:1, so that if the RelativeLayout.LayoutParams contain a width of 200 and a height of 100, the custom RelativeLayout would constrain itself to 100 x 100.
I am already used to overriding onMeasure() in ordinary custom Views in order to achieve similar aims. For example, I have created my own SVG image converter, and the custom View that renders the SVG image has an overridden onMeasure() that ensures that the call to setMeasuredDimension() contains dimensions that (a) fit within the original measurement specifications, and (b) match the aspect ratio of the original SVG image.
Going back to my custom RelativeLayout which I wish to constrain itself in a similar way, I've tried overriding onMeasure() but I haven't had much success. Knowing that RelativeLayout's onMeasure() performs all of the child View placement, what I'm generally trying to do at the moment, but without the desired results, is to override onMeasure() such that I initially modify the dimension specifications first (i.e. apply my desired constraints) and then call super.onMeasure(). Like this:
#Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec){
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// Restrict the aspect ratio to 1:1, fitting within original specified dimensions
int chosenDimension = Math.min(chosenWidth, chosenHeight);
widthMeasureSpec = MeasureSpec.makeMeasureSpec(chosenDimension, MeasureSpec.AT_MOST);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(chosenDimension, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
What actually happens when I do this is that, bizarrely, the height is properly restricted as I intended, but the width is not. To illustrate:
Specifying a height of 200 and width of 100 in the RelativeLayout.LayoutParams results in my custom RelativeLayout having a height of 100 and width of 100. -> Correct.
Specifying a height of 100 and width of 200 in the RelativeLayout.LayoutParams results in my custom RelativeLayout having a height of 100 and width of 200. -> Not correct.
I realise that I could instead apply my aspect ratio constraint logic within the calling class that's placing the RelativeLayout in the first place (and in the meantime I may well do that to get around this), but really this is an implementation detail that I want the RelativeLayout itself to perform.
Clarification: The resultant width and height values I'm reading back are from using getWidth() and getHeight(). These values are read back some time in the future, after the layout process has been performed again.

I have got around this now by also setting the width and height of the LayoutParams currently held by the RelativeLayout in the onMeasure().
#Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec){
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// Restrict the aspect ratio to 1:1, fitting within original specified dimensions
int chosenDimension = Math.min(widthSize, heightSize);
widthMeasureSpec = MeasureSpec.makeMeasureSpec(chosenDimension, MeasureSpec.AT_MOST);
heightMeasureSpec = MeasureSpec.makeMeasureSpec(chosenDimension, MeasureSpec.AT_MOST);
getLayoutParams().height = chosenDimension;
getLayoutParams().width = chosenDimension;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
This now works as desired: The size of the RelativeLayout (and subsequent calls to getWidth() and getHeight()) now agree with size restrictions applied in my overridden onMeasure().

Related

Custom drawable with height = 0.8 * bounds.width() when bounds.height it's wrap content

I created my custom drawable. And to draw it, I'm using the bounds. But, when the user use:
android:layout_width="match_parent"
android:layout_height="wrap_content"
I get bounds.height() == 1. Then, I want to use as height = 0.8 * bounds.width.
I tried it, because when the user use WRAP_CONTENT I get bounds.height() = 1. So then I set my drawable height to 0.8*width(). But I don't get the proper size. I just get like the height is 1. I read that you need to call invalidate(), but how do I call it, because if I call invalidate() in draw() then the method is called infinitely.
You should use onMeasure method.
Lifecycle call it when it's need to invalidate size of view you working on.
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
ViewGroup.LayoutParams params = getLayoutParams();
//height and width expected from wrap_content and match_parent
int newHeight = MeasureSpec.getSize(heightMeasureSpec);
int newWidth = MeasureSpec.getSize(widthMeasureSpec);
int myHeight= newWidth * 0.8;
height = myHeight
width = newWidth
}
Consider using layout_constraintDimensionRatio of ConstraintLayout:
Official Guide
Or u can also fix the size issue in onMeasure().

widthMeasureSpec is 0 when in HorizontalScrollView

I'm writing a custom view in Android. Since I need precise size management, I've overridden onMeasure. Its documentation says, that View.MeasureSpec.getMode returns one of three values, defined as constants. But when my view was placed inside HorizontalScrollView, widthMeasureSpec was 0 - I was unable to get both mode and size from it.
What is even weirder is that this parameter was 0 even if I explicitly defined width for my view.
Why does it happen, how should I interpret the 0 value and what should I do in this particular case?
0 means the mode is UNSPECIFIED. This means you can be as big as you want, which makes sense since it is a ScrollView...intended for a View bigger than your actual screen.
Being unspecified, you don't have to care about the size, this is why it is 0.
If you look at the source of HorizontalScrollView you can also see that it just passes width: 0, UNSPECIFIED to the child:
#Override
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
ViewGroup.LayoutParams lp = child.getLayoutParams();
int childWidthMeasureSpec;
int childHeightMeasureSpec;
childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop
+ mPaddingBottom, lp.height);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
To get the child view as big as the parent you can use fillViewPort from xml or java which will lead to a call with mode EXACTLY:
if (!mFillViewport) {
return;
}
// ...
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
This is one way to handle your child view being as big as the ScrollView.

Keeping ratio of ImageView in Android

I have to set background image of ImageView and after time to set imageSource of this ImageView.
So what I did is to set image view background with setBackgroundImage().
Also I set
adjustViewBounds = true and scaleType = "fitCenter", and height = 55dp and width = wrap content.
So I expected at first time(when I set only set background) to have this scenario - the view will be with the same ration and will fit the smallest - width or height (of course if height is larger than 55dp, it will be scaled). But the result is scalled image (when the image should be larger than 55 dp i.e. height is scaled). The image is in RelativeLayout. Unfortunately, I cannot post the whole code.
Thanks in advance.
You have to subclass that ImageView and override onLayout() to keep width/height ratio like so:
#Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
double ratio = ...
int width = right - left;
int height = ratio * width;
ViewGroup.LayoutParams params = this.getLayoutParams();
params.height = height;
params.width = width;
this.setLayoutParams(params);
this.setMeasuredDimension(width, height);
super.onLayout(changed, left, top, right, top + height);
}

How to set height in Android grid programmaticaly according to width?

I have gridview which contains 2 columns. The width is set to fill available space.
I need height to be set to specific size according to width, because in every screen the width dimension is different, I can't set height in xml as constant. I need to set ratio, so height should be about 1.5 * width.
Thanks
You can set Height, but you want to get Width First.
You told 2 columns of grid so understand phone half of your phone width
Display display = getWindowManager().getDefaultDisplay();
int imgWidth = display.getWidth() / 2;
int imgHeight = (int)(imgWidth * 1.5f);
You get height of that.Now you can set your Grid View Width and Height
LayoutParams lp = HERE_YOUR_VIEW.getLayoutParams();
lp.width = imgWidth;
lp.height = imgHeight ;
HERE_YOUR_VIEW.setLayoutParams(lp);

Android Changing image size depending on Screen Size?

So I need to change the size of an image depending on the area of the screen. The image will have to be half of the screen height, because otherwise it overlaps some text.
So Height= 1/2 Screen Height.
Width = Height*Aspect Ratio (Just trying to keep the aspect ratio the same)
I found something that was:
Display myDisplay = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
int width =myDisplay.getWidth();
int height=myDisplay.getHeight();
But how would I change image height in java? or even XML if possible? I can't seem to find a working answer.
You can do this with LayoutParams in code. Unfortunately there's no way to specify percentages through XML (not directly, you can mess around with weights, but that's not always going to help, and it won't keep your aspect ratio), but this should work for you:
//assuming your layout is in a LinearLayout as its root
LinearLayout layout = (LinearLayout)findViewById(R.id.rootlayout);
ImageView image = new ImageView(this);
image.setImageResource(R.drawable.image);
int newHeight = getWindowManager().getDefaultDisplay().getHeight() / 2;
int orgWidth = image.getDrawable().getIntrinsicWidth();
int orgHeight = image.getDrawable().getIntrinsicHeight();
//double check my math, this should be right, though
int newWidth = Math.floor((orgWidth * newHeight) / orgHeight);
//Use RelativeLayout.LayoutParams if your parent is a RelativeLayout
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
newWidth, newHeight);
image.setLayoutParams(params);
image.setScaleType(ImageView.ScaleType.CENTER_CROP);
layout.addView(image);
Might be overcomplicated, maybe there's an easier way? This is what I'd first try, though.

Categories

Resources