30

Is there a way to handle a view visibility change (say, from GONE to VISIBLE) without overriding the view?

Something like View.setOnVisibilityChangeListener();?

1
  • I don't know for sure, but I'd say there isn't such a thing, as it would put a lot of work onto the whole system to permanently track the visibility of all views all the time, in order to notify a possible listener.
    – Ridcully
    Commented Sep 25, 2015 at 8:10

6 Answers 6

72

You can use a GlobalLayoutListener to determine if there are any changes in the views visibility.

myView.setTag(myView.getVisibility());
myView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        int newVis = myView.getVisibility();
        if((int)myView.getTag() != newVis)
        {
            myView.setTag(myView.getVisibility());
            //visibility has changed
        }
    }
});
2
  • 4
    This works but it just works for layout changes respectively for changes from GONE to VISIBLE and vice versa because INVISIBLE does not trigger a layout change. Am I right?
    – ToBe
    Commented Nov 16, 2017 at 10:13
  • @ToBe in this line myView.setTag(myView.getVisibility()); you can set whatever you want to handle afterwards Commented Feb 23, 2018 at 17:13
11

In addition to @string.Empty's solution, this is an extension implementation for Kotlin.

fun View.visibilityChanged(action: (View) -> Unit) {
    this.viewTreeObserver.addOnGlobalLayoutListener {
        val newVis: Int = this.visibility
        if (this.tag as Int? != newVis) {
            this.tag = this.visibility

            // visibility has changed
            action(this)
        }
    }
}

Implement it like this

myView.visibilityChanged { view ->
    when (view.visibility) {
        VISIBLE -> { /* Do something here */ }
        GONE -> { /* or here */ }
    }

}
2
  • The OnGlobalLayoutListener triggers when the layout changes. So if you do not check for the change in visibility within the listener then it will trigger unexpectedly. Commented Mar 29, 2022 at 15:20
  • 1
    @string.Empty I updated the implementation for kotlin and used yours with tagging.
    – DevinM
    Commented Aug 16, 2022 at 17:49
3

Instead of subclassing you can use decoration:

class WatchedView {

    static class Listener {
        void onVisibilityChanged(int visibility);
    }

    private View v;
    private Listener listener;

    WatchedView(View v) {
        this.v = v;
    }

    void setListener(Listener l) {
        this.listener = l;
    }

    public setVisibility(int visibility) {
        v.setVisibility(visibility);
        if(listener != null) {
            listener.onVisibilityChanged(visibility);
        }
    }

}

Then

 WatchedView v = new WatchedView(findViewById(R.id.myview));
 v.setListener(this);
6
  • 1
    This is like just creating a helper class for the view. Overriding the view would be better than this in my opinion. But yes this would work, assuming that specific setVisibility() is being used every time Commented Sep 25, 2015 at 8:21
  • 3
    @Nicolas Tyler Submitter specifically said "without overriding the view" so your statement "Overriding the view would be better" is false. Besides, overriding the views will result in massive xml changes. In general your comment is devoid of meaning
    – Alexander
    Commented Sep 25, 2015 at 8:25
  • 1
    its not false, its just not what the OP asked for. The OP was looking for a listener for visibility changes. And preferably not having to override the view. Commented Sep 25, 2015 at 8:26
  • This solution seems to be better. It fits to all views.
    – no_cola
    Commented Feb 19, 2019 at 7:37
  • 1
    This doesn't listen for visibility changes. It has no idea when visibility changes whenever something other than your decoration changes the visibility.
    – Trevor
    Commented Feb 21, 2023 at 22:31
2

Take a look at ViewTreeObserver.OnGlobalLayoutListener. As written in documentation, its callback method onGlobalLayout() is invoked when the global layout state or the visibility of views within the view tree changes. So you can try to use it to detect when view visibility changes.

0

We can match the visibility of TextView by a Flow/LiveData. Then listen to View visibility via the Flow/LiveData

class ViewModel : ViewModel() {

    private val _textNameVisibility = MutableStateFlow(View.VISIBLE)
    val textNameVisibility: LiveData<Int> = _textNameVisibility.asLiveData()


    fun setTextNameVisibility(visibility: Int) {
        _textNameVisibility.value = visibility
    }
}

On Activity/Fragment

viewModel.textNameVisibility.observe(this) {
    tvName.visibility = it
    Log.i("TAG", "View visibility change" + it)
}

Note that, we need to use setTextNameVisibility to change visibility of the View.
And StateFlow won't emit the same value (similar to distinctUntilChanged) so don't need care about previous visibility

-1
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.ImageView
import android.widget.Toast
import java.lang.ref.WeakReference


class MyImageView : ImageView {
    var mListener: WeakReference<VisibilityChangeListener>? = null

    interface VisibilityChangeListener {
        fun onVisibilityChanged(visibility: Int)
    }

    constructor(context: Context?) : super(context) {}
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {}
    constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super(
        context,
        attrs,
        defStyle
    ) {
    }

    fun setVisibilityChangeListener(listener: VisibilityChangeListener) {
        mListener = WeakReference(listener)
    }

    override fun onVisibilityChanged(changedView: View, visibility: Int) {
        super.onVisibilityChanged(changedView, visibility)
        Toast.makeText(context,visibility.toString(), Toast.LENGTH_SHORT).show()

        if (mListener != null && changedView === this) {
            val listener = mListener!!.get()
            listener?.onVisibilityChanged(visibility)
        }
    }
}
1
  • Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
    – Community Bot
    Commented Dec 7, 2022 at 20:53

Not the answer you're looking for? Browse other questions tagged or ask your own question.