Question

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.

Was it helpful?

Solution

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);
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top