6

I'm creating a grid that displays values that change very often. Because of this, I'm using a TextView that autoresizes when its content changes (Auto Scale TextView Text to Fit within Bounds). The resize takes place, but the view doesn't layout properly

layout after resize

The thing is, when I examine the activity with HierarchyViewer the layout displays as I want.

layout after hierarchyviewer

My guess is that HierarchyViewer invokes requestLayout() or invalidate() on the view, but I've tried that with no success. This code is invoked in the main activity with no effect.

new Handler().postDelayed(new Runnable() {            
            @Override
            public void run() {
              getWindow().getDecorView().requestLayout();
              getWindow().getDecorView().invalidate();
            }
          }, 5000);

I've also tried invalidating the view after resizing.

The TextView has gravity set to Center, and if no resize takes place, it looks ok.

Any hint will be welcome, thanks in advance!

Community
  • 1
  • 1
Maragues
  • 37,861
  • 14
  • 95
  • 96
  • Have you tried requesting the layout on the parent view rather than the decor view? – DeeV Oct 15 '12 at 13:57
  • Isn't the decor view the parent node of the View tree? I read that requesting the layout on a view causes all its children to request the layout. Anyway, I'd say I have tried that as I've tried lots of things, but I'll report back when I try your suggestions. Quoting the requestLayout docs on view.View, "Call this when something has changed which has invalidated the layout of this view. This will schedule a layout pass of the view tree." – Maragues Oct 15 '12 at 14:03
  • It does but I can't see anything wrong with your code. requestLayout() layouts a parent view and it's children, but I've only called it on views directly controlled by my app, so it's just a thought. – DeeV Oct 15 '12 at 14:28
  • Fixed! I had to call requestLayout on one of the views parent using a handler. I don't know why it didn't work using the decor view, I was calling it on the UI thread, but I'm happy anyway :D As I have to give out the bounty, please answer the question – Maragues Oct 15 '12 at 18:27
  • Looks like you have it figured out so I'll let you keep the bounty (assuming that's what happens when you accept the bounty on your own question). – DeeV Oct 16 '12 at 03:47
  • I can't give myself the bounty and I've lost the reputation anyway, so I'd like to thank you for puting me in the right path me and award you the bounty – Maragues Oct 16 '12 at 05:46
  • Ok. I added an answer. I put some more "girth" in to it so there's some use. – DeeV Oct 16 '12 at 12:08

3 Answers3

2

I solved it by overriding onLayout in one of the TextView's parent and using a Handler created in the constructor

public class CellView extends LinearLayout{
   public CellView(Context context) {
     super(context);

     mHandler = new Handler();

     View.inflate(context, R.layout.cellview, this);
  }

 @Override
  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    if(changed){
      mHandler.post(new Runnable() {          
        @Override
        public void run() {
          requestLayout();
        }
      });
    }

   super.onLayout(changed, left, top, right, bottom);
  }

I had tried calling requestLayout inside TextView's onLayout, tho it didn't work, I'm not sure why. It might be because the value was updated through an Observer, but the onTextChanged listener should happen in the UI Thread. I hope it serves someone else

Maragues
  • 37,861
  • 14
  • 95
  • 96
2

The way requestLayout() works is that when called on a view, it will schedule a layout pass on itself and all of it's children. This is desirable whenever a view has shifted or resized do to margin, padding, or content changes.

The documentation on the method getDecorView() isn't very clear on what exactly it gives you. However, per the documentation on the website:

Note that calling this function for the first time "locks in" various window characteristics as described in setContentView(View, android.view.ViewGroup.LayoutParams).

This leaves me to believe that there's something special about the view that getDecorView() retrieves. What you are probably doing is making the layout of the view permanent thus never changing when you make a requestLayout() pass.

This is apparently the proper way to get the root view of your entire activity.

However, for efficiency reasons, I recommend calling requestLayout() on the lowest child you possibly can. Like I said before, it schedules a layout pass on a view and it's children. If you do a layout pass on the top-most view, you're essientially rebuilding everything which includes the views that stay in place.

Community
  • 1
  • 1
DeeV
  • 35,865
  • 9
  • 108
  • 95
  • there you are, thanks for your time and nice research on requestLayout, tho I'll keep mine as selected answer as it addresses my specific problem. If in the future users vote yours, I'll award it to you – Maragues Oct 16 '12 at 13:42
0

You probably need to run the timer code in the UI thread using runOnUiThread as explained here.

Community
  • 1
  • 1
Narcís Calvet
  • 7,304
  • 5
  • 27
  • 47
  • Assuming he's creating the Handler in the UI thread, it is running in the UI thread. – DeeV Oct 15 '12 at 13:56
  • I'll check this later (not working on this right now). I'd say that running that code from outside the UI thread would cause an exception, but I might be creating the Handler outside the UI thread. Thanks to both (Narcís and DeeV) for the idea – Maragues Oct 15 '12 at 13:57