Pamir Cevikogullari
Pamir Cevikogullari

Reputation: 482

How to reference a lambda from inside it?

I am trying to get height of a view in onCreate method but I couldn't find any way to remove OnGlobalLayoutListener.

In Java (working):

containerLayout.getViewTreeObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {  
    @Override  
    public void onGlobalLayout() {  
        containerLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this); 
        int width  = layout.getMeasuredWidth();
        int height = layout.getMeasuredHeight(); 

    }  
});

In Kotlin (not accepting "this"):

   containerLayout.viewTreeObserver.addOnGlobalLayoutListener {
            containerLayout.viewTreeObserver.removeOnGlobalLayoutListener(this)
            Toast.makeText(applicationContext, "size is "+ containerLayout.height,Toast.LENGTH_LONG).show()
        }

Is there any reference or example for this problem? Thanks.

Upvotes: 31

Views: 11207

Answers (4)

Ben
Ben

Reputation: 265

Simple / clear approach (no generics or anonymous objects):

You could create the listener in advance, then add / use / remove as needed:

private var layoutChangedListener: ViewTreeObserver.OnGlobalLayoutListener? = null

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    // create the listener
    layoutChangedListener = ViewTreeObserver.OnGlobalLayoutListener {

        // do something here

        // remove the listener from your view
        myContainerView.viewTreeObserver.removeOnGlobalLayoutListener(layoutChangedListener)
        layoutChangedListener = null
    }

    // add the listener to your view
    myContainerView.viewTreeObserver.addOnGlobalLayoutListener(layoutChangedListener)
}

use case: I had to be able to remove the listener, in order to adjust the layout inside the listener itself - not removing the listener may result in a deadlock

Upvotes: 4

Akexorcist
Akexorcist

Reputation: 2297

What's about extension like this?

import android.annotation.SuppressLint
import android.os.Build
import android.view.View
import android.view.ViewTreeObserver

inline fun View.doOnGlobalLayout(crossinline action: (view: View) -> Unit) {
    val vto = viewTreeObserver
    vto.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
        @SuppressLint("ObsoleteSdkInt")
        @Suppress("DEPRECATION")
        override fun onGlobalLayout() {
            action(this@doOnGlobalLayout)
            when {
                vto.isAlive -> {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                        vto.removeOnGlobalLayoutListener(this)
                    } else {
                        vto.removeGlobalOnLayoutListener(this)
                    }
                }
                else -> {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                        viewTreeObserver.removeOnGlobalLayoutListener(this)
                    } else {
                        viewTreeObserver.removeGlobalOnLayoutListener(this)
                    }
                }
            }
        }
    })
}

Finally, you can call OnGlobalLayoutListener from View directly

val view: View = ...
view.doOnGlobalLayout {
  val width = view?.measuredWidth
  val height = view?.measuredHeight
}

Upvotes: 6

hotkey
hotkey

Reputation: 147911

Another solution is to implement and use self-reference:

class SelfReference<T>(val initializer: SelfReference<T>.() -> T) {
    val self: T by lazy {
        inner ?: throw IllegalStateException()
    }

    private val inner = initializer()
}

fun <T> selfReference(initializer: SelfReference<T>.() -> T): T {
    return SelfReference(initializer).self
}

Then the usage would be

containerLayout.viewTreeObserver.addOnGlobalLayoutListener(selfReference { 
    OnGlobalLayoutListener {
        containerLayout.viewTreeObserver.removeOnGlobalLayoutListener(self)
        // ...
    }
}

Instead of this, self property is used.

Upvotes: 1

hotkey
hotkey

Reputation: 147911

Referencing a lambda from inside it is not supported.

As a workaround, you might use anonymous object instead of lambda SAM-converted to Java functional interface OnGlobalLayoutListener:

containerLayout.viewTreeObserver.addOnGlobalLayoutListener(object: OnGlobalLayoutListener {
    override fun onGlobalLayout() {
        // your code here. `this` should work
    }
})

Upvotes: 36

Related Questions