Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle weights in onMeasue for custom views?

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.

like image 529
Chris Vandevelde Avatar asked Jan 25 '12 23:01

Chris Vandevelde


People also ask

How do I optimize custom view?

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.

What is onMeasure custom view?

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 ...

What are custom views?

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.


1 Answers

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);
}
like image 90
Chris Vandevelde Avatar answered Nov 11 '22 21:11

Chris Vandevelde