Federico Ponzi
Federico Ponzi

Reputation: 2785

Handle a view visibility change without overriding the view

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

Something like View.setOnVisibilityChangeListener();?

Upvotes: 32

Views: 44668

Answers (6)

habfarago
habfarago

Reputation: 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)
        }
    }
}

Upvotes: -1

DevinM
DevinM

Reputation: 1322

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 */ }
    }

}

Upvotes: 12

Linh
Linh

Reputation: 61019

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

Upvotes: 0

Alexander
Alexander

Reputation: 48272

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);

Upvotes: 3

Nicolas Tyler
Nicolas Tyler

Reputation: 10552

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
        }
    }
});

Upvotes: 72

greenfrvr
greenfrvr

Reputation: 643

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.

Upvotes: 2

Related Questions