Android view object reuse -- prevent old size from showing up when View reappears
Asked Answered
W

5

12

EDIT: One more piece of possibly relevant info: The use case in which I see the problem is tab switching. That is, I create view X on tab A, remove it when leaving tab A, then recycle it into tab B. That's when the problem happens. This is also exactly when I need the performance gain . . .

I am working on the performance of my Android app. I've noticed that I can speed things up by reusing View objects of a class we'll call MyLayout. (It's actually a custom FrameLayout subclass, but that likely doesn't matter. Also, this is NOT ListView related.) That is, when I'm done with a View, rather than letting GC get ahold of it, I put it into a pool. When the same activity wants another MyLayout object, I grab one from the pool, if available. This does indeed speed up the app. But I'm having a hard time clearing the old size information. The result is that when I grab the View back, things are usually fine, but in some cases, the new View briefly appears before it is laid out with the new size information. This happens even though I set new LayoutParams shortly before or after adding the View back into the hierarchy (I've tried both ways; neither helps). So the user sees a brief (maybe 100ms) flash of the old size, before it goes to the correct size.

I'm wondering if/how I can work around this. Below, via C#/Xamarin, are some things I've tried, none of which help:

When recycling:

//Get myLayoutParams, then:
myLayoutParams.Width = 0;
myLayoutParams.Height = 0;
this.SetMeasuredDimension(0, 0);
this.RequestLayout();

Immediately before or after bringing back -- within the same event loop that adds the layout to its new parent:

// a model object has already computed the desired x, y, width, and height
// It's taken into account screen size and the like; the Model's sizes
// are definitely what I want.
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams (model.width, model.height);
layoutParams.LeftMargin = model.x;
layoutParams.TopMargin = model.y; 
this.LayoutParameters = layoutParams;

I've also tried bringing it back like the below, but the problem still remains:

FrameLayout.LayoutParams layoutParams = . . .  // the same LayoutParams as above
parent.AddView(viewThatIsBeingRecycled, layoutParams);

EDIT: Per request, some of the sequences I have tried. All suffer from the same problem. The basic issue is that even though the LayoutParams are correct, the layout itself is not correct as the actual layout has not happened yet.

Recycling time:

attempt A:

this.RemoveFromHierarchy();
// problem is that the width and height are retained

attempt B:

//Get myLayoutParams, then:
myLayoutParams.Width = 0;
myLayoutParams.Height = 0;
this.SetMeasuredDimension(0, 0);
this.RequestLayout();
this.RemoveFromHierarchy();
//problem is that even though layout has been requested, it does not actually happen.  
//Android seems to decide that since the view is no longer in the hierarchy,
//it doesn't need to do the actual layout.  So the width and height
//remain, just as they do in attempt A above.

When adding the view back:

All attempts call one of the following subroutine to sync the LayoutParams to the model:

public static void SyncExistingLayoutParamsToModel(FrameLayout.LayoutParams layoutParams, Model model) {
  layoutParams.TopMargin = model.X;
  layoutParams.LeftMargin = model.Y;
  layoutParams.Width = model.Width;
  layoutParams.Height = model.Height;
}

public static FrameLayout.LayoutParams CreateLayoutParamsFromModel(Model model) {
  FrameLayout.LayoutParams r = new FrameLayout.LayoutParams(model.Width, model.Height);
  r.LeftMargin = x;
  r.TopMargin = y;
  return r;
}

Attempt A:

newParent.AddView(viewThatIsBeingRecycled);
// get layoutParams of the view, then:
SyncExistingLayoutParamsToModel(myLayoutParams, model);

Attempt B: same as A, but in the opposite order:

// get layoutParams of the view, then:
SyncExistingLayoutParamsToModel(myLayoutParams, model);
newParent.AddView(viewThatIsBeingRecycled);

Attempt C: same as A, but with fresh layoutParams:

newParent.AddView(viewThatIsBeingRecycled);
FrameLayout.LayoutParams layoutParams = CreateLayoutParamsFromModel(model);
viewThatIsBeingRecycled.LayoutParams = layoutParams;

Attempt D: same as B, but with fresh layoutParams:

FrameLayout.LayoutParams layoutParams = CreateLayoutParamsFromModel(model);
viewThatIsBeingRecycled.LayoutParams = layoutParams;
newParent.AddView(viewThatIsBeingRecycled);

Attempt E: using the AddView that takes a layoutParams argument:

FrameLayout.LayoutParams layoutParams = CreateLayoutParamsFromModel(model);
newParent.AddView(viewThatIsBeingRecycled, layoutParams);

In all five cases, the problem is that even though the layoutParams are correct, the view is made visible to the user before the layout adjusts itself to the new layoutParams.

Willard answered 30/5, 2015 at 14:44 Comment(8)
how about setting visibility invisible and right after that 100ms reset to visible.Tatianatatianas
Trying to avoid hacks like that . . .Willard
Can you post more of your code? Specifically the sequences of removing/adding the view and setting LayoutParams.Adorn
All of your examples work fine in native android with default views. Are using a custom view? If so what methods have you overridden? Another idea I have is to call forceLayout instead of requestLayout when you're about to add the view.Adorn
how the tabs are implemented? using fragments? If so are u reusing same view in multiple fragments?Ecdysiast
@WilliamJockusch, to investigate the issue, would it be possible for you to replace your MyLayout object with just the stock FrameLayout? Probably just commenting out everything in the class so it will behave just like its parent class would be sufficient. I am hoping it will help us to establish that the issue is not triggered by something special that you did in the class.Roentgenoscope
have you solved it Sir?Washer
No, have not solved, hacking around it for nowWillard
A
2

I ran into the exact same problem except on native android, not xamarin.

Having my own test scene, made things a lot easier to debug the problem. I seem to have fixed it by setting right and left of the specific view to 0, just after removing it from its parent and before adding to another one:

((ViewGroup)view.getParent()).removeView(view);

view.setRight(0);
view.setLeft(0);

otherLayout.addView(view);
Adorn answered 2/6, 2015 at 13:13 Comment(5)
I've tried both before and after actually, I'll edit the question to make that clear. The problem appears to be that even though the new LayoutParams are set, the view sometimes does not actually adjust its layout until after it becomes visible to the user. I am 100% certain that by the time it is visible, the correct LayoutParams are there, but the layout the user sees does not reflect that.Willard
For anyone reading this later, this didn't actually solve my problem. Nothing against the answer or the answerer; bounty was auto-awarded, no complaints there, but if you run into this, I'm not swearing by this answer.Willard
@WilliamJockusch I actually ran into this problem just now. When temporarily moving a view to DecorView, I could see its old size before it resized to the new one. I fixed it with setRight(0); setLeft(0); on the view after removing it from and before adding it to the new parent. Can you give it a go please?Adorn
Amazing, I just tried that, doing it right after removing, and it appears to solve the problem, thanks!Willard
@WilliamJockusch great! I was surprised too. I'll update my answer and if you please, select it as the correct one.Adorn
E
2

Try executing these events (setLayoutParams and addView) in a message queue.

Solution 1 )

FrameLayout.LayoutParams layoutParams = CreateLayoutParamsFromModel(model);
viewThatIsBeingRecycled.setLayoutParams(layoutParams);
viewThatIsBeingRecycled.post(new Runnable() {
            @Override
            public void run() {
                newParent.AddView(viewThatIsBeingRecycled);
            }
        });

Solution 2 )

FrameLayout.LayoutParams layoutParams = CreateLayoutParamsFromModel(model);
viewThatIsBeingRecycled.setLayoutParams(layoutParams);
viewThatIsBeingRecycled.setVisibility(View.INVISIBLE);
newParent.AddView(viewThatIsBeingRecycled);
newParent.post(new Runnable() {
            @Override
            public void run() {
               viewThatIsBeingRecycled.setVisibility(View.VISIBLE); 
            }
        });

I am not sure if this works in your case. If setLayoutParams or addView is considered to be a message in internal OS implementation, then it places the next event in a queue so that it will be executed once previous event is executed.

Ecdysiast answered 3/6, 2015 at 10:9 Comment(0)
W
2

Apply Gravity with the View to which these layout parameters are associated

EDIT

Also in your attempt B, instead of this.requestLayout(); call that on the Parent View that will be getView() of the Fragment or the Activity content View... Telling the child to layout itself out makes the parent dirty,hence it will call requestLayout() for itself to layout out, that is what i think is the reason for the delay- since they run in a successive manner, but if you make it a direct call all it will be universal layout, which will eliminate the delay

Hope it helps

Washer answered 4/6, 2015 at 1:15 Comment(0)
N
2

I hope it will help you:

view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
   @Override
   public void onGlobalLayout() {
       //set layout params here
   }
 });
Natachanatal answered 9/6, 2015 at 8:18 Comment(0)
S
1

As usual, to update the position/arrangement of any view, It must be invalidate().

Don't want to give you hackish way to get through but still I want you to take a look at this post. It may be possible that your requestLayout() not causing View's invalidate.

Also try with adding android:hardwareAccelerated="true" in manifest.

Spindlelegs answered 3/6, 2015 at 9:17 Comment(1)
Tried invalidate and requestLayout all over the place; it doesn't help. Layout does happen; the problem is that things happen in the wrong order, with the view becoming visible to the user before layout happens. I'm starting to wonder if this could be related to Mono and/or to the fact that I am switching tabs. Hardware acceleration is not an option, unfortunately, as I am drawing paths in the app.Willard

© 2022 - 2024 — McMap. All rights reserved.