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.
Related
I am using CardView as a item of Recycler Adapter.
At first, all items will not show the whole content, (I cut it and put the '... ...' instead)
but after click it, the clicked item will scale its height to fit the content height. ( Here I set the text to the whole content and hope the card can scale to fit it )
looks like:
I know how to animate the height to the specific target height..
but in this case, I don't know how to measure the height needed to show the whole content, each item should have different target height.
How can I do it?
What you can do is ask the View to measure itself giving no constraint on its height. Please note the call to view.getWidth(), you can do that only after the View had been laid out, since you're calling it in onClick() it should be fine.
int widthSpec = View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY);
int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
view.measure(widthSpec, heightSpec);
int targetHeight = view.getMeasuredHeight();
Assuming that your View is a TextView with these attributes set:
android:layout_height="wrap_content"
android:maxLines="1"
android:ellipsize="end"
the full example would be
// this is the height measured with maxLines 1 and height
// to wrap_content
final int startHeight = view.getHeight();
// you want to measure the TextView with all text lines
view.setMaxLines(Integer.MAX_VALUE);
int widthSpec = View.MeasureSpec.makeMeasureSpec(view.getWidth(), View.MeasureSpec.EXACTLY);
int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
view.measure(widthSpec, heightSpec);
// final height of the TextView
int targetHeight = view.getMeasuredHeight();
// this is the value that will be animated from 0% to 100%
final int heightSpan = targetHeight-startHeight;
// remove that wrap_content and set the starting point
view.getLayoutParams().height = startHeight;
view.setLayoutParams(view.getLayoutParams());
Animation animation = new Animation(){
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
view.getLayoutParams().height = (int) (startHeight + heightSpan*interpolatedTime);
view.setLayoutParams(view.getLayoutParams());
}
};
animation.setDuration(1000);
view.startAnimation(animation);
you can do it very simple.
when set a text to TextView for the first time, write this:
int minLineNumber = 2;
textView.setMaxLines(minLineNumber); // 2 is your number of line for the first time
and when click on that:
textView.setMaxLines(Integer.MAX_VALUE); // set the height like wrap_content
if you want the animation, you have sth like this:
ObjectAnimator animation = ObjectAnimator.ofInt(
textView,
"maxLines",
textView.getLineCount());
int duration = (textView.getLineCount() - minLineNumber) * 50;
animation.setDuration(duration);
animation.start();
if the text in your TextView to too much, it's better to use a fixed duration, not depend on height.
Maybe I'm not entering the right keywords, but I'm not finding an answer. I want to know what the dimensions of a TextView would be if I were set it with a certain string. However, I want to know before everything gets laid out in the activity.
My TextView has a fixed width and a variable height. I can get the height like this:
myTextView.setText(myString);
// ... UI gets laid out ...
myTextView.getHeight()
I want to change the width of the TextView if the height gets past a certain point. (But not before then.) And rather than waiting until after the UI gets laid out, I want to know beforehand what the height would be if it had myString and then change the width if I needed to.
I looked at the Layout class but I couldn't figure out what to do. I wonder if it might have something to do with overriding the TextView's onMeasure but I really don't know how to attempt that. Any help is appreciated.
Update
Thanks to both #user3249477 and #0xDEADC0DE for their answers. I'm marking #user3249477's answer as the solution for now (although since I need multiple resizes of the view I'm not sure about repeatedly turning the visibility on and off) but also +1 to #0xDEADC0DE for giving me the keywords I needed to further look into this problem.
I need to do more research and testing on this. Here are some links that I have found helpful so far:
OnLayoutChangeListener:
View.OnLayoutChangeListener
Capture Layout resize before API 11
After changing a property on a LayoutParams object, do I need to call setLayoutParams again?
measureText() and getTextBounds():
Android Paint: .measureText() vs .getTextBounds()
Paint.getTextBounds() returns to big height
Gettextbounds in android
Overriding onSizeChanged of the parent view also looks intriguing: https://stackoverflow.com/a/14399163/3681880
Set your TextView to invisible:
android:visibility="invisible"
and measure it. Once you're done set it to visible:
TextView myTextView = (TextView) findViewById(R.id.text);
final int maxHeight = 500;
myTextView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
v.removeOnLayoutChangeListener(this);
LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) v.getLayoutParams();
Log.e("TAG", "H: " + v.getHeight() + " W: " + v.getWidth());
if (v.getWidth() > maxHeight) {
params.width += 100;
v.setLayoutParams(params);
}
v.setVisibility(View.VISIBLE);
}
});
You could do it without overriding. If you get the TextViews Paint with getPaint(), you can use measureText(string) the get the minimal with of the TextView when it is drawn with that Paint. I looks like this:
TextView textView = new TextView(this);
float textWidth = textView.getPaint().measureText("Some Text");
Update
To get the height, you can call getTextBounds() on the Paint object like this:
String text = "Some Text";
Rect textBounds = new Rect();
textView.getPaint().getTextBounds(text, 0, text.length(), textBounds);
float height = textBounds.height();
float width = textBounds.width();
I am confused on when to use setWidth, and setHeight? It usually don't work.
What always work is setLayoutParams.
This will work.
sampleButton = new Button(this);
sampleButton.setLayoutParams(new LinearLayout.LayoutParams(65, 65));
This will not work.
sampleButton = new Button(this);
sampleButton.setHeight(65);
sampleButton.setWidth(65);
Or maybe there are some initialisation for this code to work?
So, just looked at the Button's source code (which is a subclass of TextView):
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.1_r2/android/widget/TextView.java#TextView.setHeight%28int%29
here is the method for setHeight
public void setHeight(int pixels) {
mMaximum = mMinimum = pixels;
mMaxMode = mMinMode = PIXELS;
requestLayout();
invalidate();
}
now in the onMeasure method, mMaximum and mMaxMode are used here
int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
}
It looks like the setheight doesn't really overwrite the internal height parameters, more just sets flags for layout.
Changing the layout params (which are what are actually referenced when the view is laying itself out) seem to inform the view that it actually needs to be that hight
TLDR; setHeight has more to do with the line height of the text than the height of the view
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().
I would like to know how to measure child view with width and height value which are defined in child's xml file.
I know that in onMeasure() method of my custom ViewGroup I should call:
child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
But how to get childWidth and childHeight from xml code?
I know this is an old question. Essentially you want to use measureChild function provided by the ViewGroup. This function will take care of measuring it properly.
You can get them from LayoutParams of the child:
LayoutParams l = child.getLayoutParams();
childWidth = l.width;
childHeight = l.height;
ViewGroup's constructors are capable of reading the layout_height and layout_width parameters from xml.
Try this
RelativeLayout.LayoutParams params =
(android.widget.RelativeLayout.LayoutParams)child.getLayoutParams();
childWidth = params.width;
childHeight = params.height;
in the above code replace the RelativeLayout with the your parent layout