2

Let's just say I need to get and set a View's height. In Android, it's known you can get a view height only after it's drawn. If you're using Java, many answers, one of the most well-known way is like this one below, taken from this answer:

view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            view.getHeight(); //height is ready
        }
    });

Thus I search C#/Xamarin version, and found this works:

int viewHeight = 0;
ViewTreeObserver vto = view.ViewTreeObserver;
vto.GlobalLayout += (sender, args) =>
{
    viewHeight = view.Height;
};

Thing is, it fired again and again. In Java version, it can be removed with view.getViewTreeObserver().removeOnGlobalLayoutListener(this);

How to do it in C#\Xamarin? Should I resort to using boolean properties to know whether it's executed or not? Is there not way to do it like the android one?

Community
  • 1
  • 1
Konayuki
  • 674
  • 1
  • 9
  • 21

3 Answers3

4

If you are using C# Events, avoid using anonymous events if you need to unsubscribe, or you can implement the IOnGlobalLayoutListener and add/remove the listener:

C# EventHandler Style:

Create an EventHandler method for the event to invoke:

void Globallayout_handler(object sender, EventArgs e)
{
    // ViewTreeObserver.IOnGlobalLayoutListener events
}

Subscribe:

var viewTreeObserver = aView.ViewTreeObserver;
viewTreeObserver.GlobalLayout += Globallayout_handler;

Unsubscribe:

var viewTreeObserver = aView.ViewTreeObserver;
viewTreeObserver.GlobalLayout -= Globallayout_handler;

Java Listener Style in C#:

Add and implement ViewTreeObserver.IOnGlobalLayoutListener:

public class CustomButtonRenderer : Xamarin.Forms.Platform.Android.AppCompat.ButtonRenderer,
ViewTreeObserver.IOnGlobalLayoutListener
{
   ~~~~

    public void OnGlobalLayout()
    {
       // ViewTreeObserver.IOnGlobalLayoutListener events
    }
}

Now you can use the Java way to add and remove this listener:

aView.ViewTreeObserver.RemoveOnGlobalLayoutListener(this); 

aView.ViewTreeObserver.AddOnGlobalLayoutListener(this); 
SushiHangover
  • 73,120
  • 10
  • 106
  • 165
  • Thank you for answering! It still fired again and again. Do you have any example on its application? – Konayuki Mar 06 '17 at 03:38
  • Finally I understand how to implement it right. I used the first way and it only fired once (because I remove the listener in the handler method). Thank you very much, sorry for saying it doesn't work at first eventho the fault was in me m(_ _)m – Konayuki Sep 22 '17 at 02:41
3

Even though the answer given by ShshiHangover is correct in principle, the unsubscribing didn't work for me as expected (using the regular method #1).

The reason is probably that the ViewTreeObserver in the called method can be different from the one the event handler subscribed to, so removing it may not work (i.e., the handler method is called continuously).

The correct way of doing this is to unsubscribe from the event sender object while ensuring that IsAlive yields true:

void ViewTreeObserver_GlobalLayout(object sender, EventArgs e)
{
    ViewTreeObserver vto = (ViewTreeObserver)sender;
    if (vto.IsAlive) {
         vto.GlobalLayout -= ViewTreeObserver_GlobalLayout;
    }
}
Daniel Veihelmann
  • 1,275
  • 10
  • 13
1

Neither @Daniel or @SushiHangover methods would actually unsubscribe for me (maybe an sdk bug?). My only solution was to set a bool flag on first run. It would be nice to know how to actually unsubscribe however...

Getting the ViewTreeObserver via sender never seems to be IsAlive whereas getting the tree from the View does. However either way the event doesn't get properly removed.

    private void Setup()
    {
        cameraView = FindViewById<SurfaceView>(Resource.Id.camera_view);

        //need to wait for view to inflate to get size
        isSetup = false;
        ViewTreeObserver vto = cameraView.ViewTreeObserver;
        vto.GlobalLayout += Vto_GlobalLayout;
    }

    void Vto_GlobalLayout(object sender, System.EventArgs e)
    {
        //this didn't work either
        //ViewTreeObserver vto = cameraView.ViewTreeObserver;
        //vto.GlobalLayout -= Vto_GlobalLayout;

        ViewTreeObserver vto = (ViewTreeObserver)sender;
        if (vto.IsAlive)
            vto.GlobalLayout -= Vto_GlobalLayout; //even after removing it seems to continue to fire...
        if (!isSetup)
        {
            isSetup = true;
            DoYourCodeNow();
        }
    }
vdidxho
  • 165
  • 1
  • 14
  • I'm having the same problem. If I save the ViewTreeObserver I subscribed on in a field it isn't alive anymore when the event triggers and I go to unsubscribe. – DerSeegler Mar 16 '20 at 14:49