I have a custom view which I'm creating that will scale the font size of its child TextViews
to fit all in a set width, so each is on its own line. Obviously it's going to need the width to figure that out.
I had overridden onMeasure()
like so:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int lineWidth = widthSize - getPaddingLeft() - getPaddingRight();
// Change the text size until the largest line of text fits.
while (lineWidth < calculateTextWidth()) {
for (TextView textView : this.childViews) {
float newSize = textView.getTextSize() - 1;
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, newSize);
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
calculateTextWidth()
calculates the width of the largest line of text and everything works with that. This code is working fine for FILL_PARENT
and WRAP_CONTENT
widths, but screws up when I try to give the component a weight and let it auto-set its weight that way.
MeasureSpec.getSize(widthMeasureSpec)
returns 0
, as getMinimumSuggestedWidth()
does, too. This gives a wonderful Activity Not Responding error, since lineWidth will always be less than calculateTextWidth()
would ever return, so the while
loop runs forever. The XML code I used was this, essentially:
<com.my.widgets.ScalingTextViewGroup
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center_horizontal"
>
<TextView
android:id="@+id/text_1"
android:text="Text 1"
android:textSize="20sp" />
<TextView
android:id="@+id/text_2"
android:text="Text 2"
android:textSize="18sp" />
<TextView
android:id="@+id/text_3"
android:text="Text 3"
android:textSize="18sp" />
<TextView
android:id="@+id/text_4"
android:text="Text 4"
android:textSize="18sp" />
</com.my.widgets.ScalingTextViewGroup>
I understand why it's returning 0 - obviously, it's being set to 0 - but how do I get it to use the layout_weight
? I feel like it's something that should have a simple answer, but I'm at a loss as to what it is.
To speed up your view, eliminate unnecessary code from routines that are called frequently. Start by working on onDraw() , which will give you the biggest payback. In particular you should eliminate allocations in onDraw() , because allocations may lead to a garbage collection that would cause a stutter.
onMeasure() is your opportunity to tell Android how big you want your custom view to be dependent the layout constraints provided by the parent; it is also your custom view's opportunity to learn what those layout constraints are (in case you want to behave differently in a match_parent situation than a wrap_content ...
A well-designed custom view is much like any other well-designed class. It encapsulates a specific set of functionality with an easy to use interface, it uses CPU and memory efficiently, and so on. In addition to being a well-designed class, though, a custom view should: Conform to Android standards.
I ended up figuring it out through some significant Googling. On this page I found out that onMeasure is actually called twice when android:layout_weight
is set. I found out later that it's mentioned in How Android Draws Views that measure()
can be called any number of times, it just didn't immediately sit in my brain. The first pass obviously can't give a value for the size of each child, since it doesn't know what the weights of all of the View's siblings are. It goes through it once and gives a width of 0 with a MeasureSpec.UNSPECIFIED to see if the child has any specific constraints, then goes through again to assign the actual weights with a MeasureSpec.EXACTLY. My Activity was getting screwed up at the first pass, so it never got to the layout step. Amending my onMeasure() to the following code fixed the problem.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode != MeasureSpec.UNSPECIFIED) {
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int lineWidth = widthSize - getPaddingLeft() - getPaddingRight();
// Change the text size until the largest line of text fits.
while (lineWidth < calculateTextWidth()) {
for (TextView textView : this.childViews) {
float newSize = textView.getTextSize() - 1;
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, newSize);
}
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With