在不覆盖视图的情况下处理视图可见性更改

30

有没有一种方法可以处理视图可见性的变化(例如,从 GONE 变为 VISIBLE),而无需覆盖视图呢?

类似于 View.setOnVisibilityChangeListener(); 这样的东西吗?


我不确定,但我认为这样的东西是不存在的,因为它会给整个系统带来很多工作量,需要一直跟踪所有视图的可见性,以便通知可能的监听器。 - Ridcully
6个回答

72

您可以使用GlobalLayoutListener来确定视图的可见性是否有任何更改。

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

4
这个方法有效,但只对布局变化以及从GONE到VISIBLE和相反方向的变化有效,因为INVISIBLE不会触发布局变化。我理解的对吗? - ToBe
在这行代码中,@ToBe,你可以设置任何你想要处理的内容,例如:myView.setTag(myView.getVisibility()); - Agna JirKon Rx

10

除了 @string.Empty 的解决方案之外,这是针对 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)
        }
    }
}

按照以下方式实现

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

}

OnGlobalLayoutListener会在布局更改时触发。因此,如果您不在侦听器中检查可见性的更改,则它会意外触发。 - string.Empty
1
@string.Empty 我更新了Kotlin的实现,并使用了您的标记。 - DevinM

3

不需要使用子类化,可以使用装饰器:

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

}

那么,
 WatchedView v = new WatchedView(findViewById(R.id.myview));
 v.setListener(this);

1
这就像是为视图创建一个帮助类。在我看来,覆盖视图比这更好。但是,假设每次都使用特定的 setVisibility(),那么这种方法也可以起作用。 - string.Empty
3
提交者明确表示“不要覆盖视图”,因此您的说法“覆盖视图会更好”是错误的。此外,覆盖视图将导致大量的XML更改。总的来说,您的评论缺乏意义。 - Alexander Kulyakhtin
1
不是错误,只是不符合原帖的要求。原帖作者正在寻找一个监听器来监听可见性的变化,最好不要覆盖视图。 - string.Empty
这个解决方案似乎更好。它适用于所有视图。 - no_cola
我更倾向于使用第一个提交的解决方案,因为这个解决方案每次创建视图时都需要对其进行子类化。 - Omar Beshary
1
这个程序不会监听可见性的变化。除了你的装饰改变可见性之外,它无法感知到其他任何东西导致的可见性变化。 - Trevor

2

看一下 ViewTreeObserver.OnGlobalLayoutListener。根据文档,它的回调方法 onGlobalLayout() 当全局布局状态或视图树中视图的可见性更改时被调用。因此,您可以尝试使用它来检测视图可见性变化。


0
我们可以通过Flow/LiveData来匹配TextView的可见性。然后通过Flow/LiveData监听视图的可见性。
class ViewModel : ViewModel() {

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


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

关于 Activity/Fragment

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

请注意,我们需要使用setTextNameVisibility来更改视图的可见性。
StateFlow不会发出相同的值(类似于distinctUntilChanged),因此不需要关心先前的可见性。

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

您的答案可以通过提供更多的支持信息来改善。请[编辑]以添加更多细节,例如引用或文档,以便他人可以确认您的答案是否正确。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接