3

i have a simple question:

suppose i have some views on a scrollView (or a horizontalScrollView) . is there any way to add a listener that will tell me when such a view is getting inside and outside the visible area ?

the only similar question i've seen is this: Android: how to check if a View inside of ScrollView is visible? but i want to be informed when such an event occurs (becoming hidden/visible) .

Community
  • 1
  • 1
android developer
  • 114,585
  • 152
  • 739
  • 1,270

3 Answers3

5

Subclass the view classes you are using (I did this for ImageView as I was only adding those to my scroll view):

public class PeekImageView extends ImageView implements ViewTreeObserver.OnScrollChangedListener {
    private static final String LOG_TAG = "PeekImageView";
    private InViewportListener inViewportListener;
    private boolean isInViewport = false;

    public PeekImageView(Context context) {
        super(context);
    }

    public PeekImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public PeekImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public interface InViewportListener {
        void onViewportEnter(PeekImageView view);
        void onViewportExit(PeekImageView view);
    }

    public void setInViewportListener(InViewportListener listener) {
        this.inViewportListener = listener;
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        ViewTreeObserver vto = getViewTreeObserver();
        if (vto != null) {
            vto.addOnScrollChangedListener(this);
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        ViewTreeObserver vto = getViewTreeObserver();
        if (vto != null) {
            vto.removeOnScrollChangedListener(this);
        }
    }

    @Override
    public void onScrollChanged() {
        Rect bounds = new Rect();
        boolean inViewport = getLocalVisibleRect(bounds);
        Log.d(LOG_TAG, "is in view " + bounds + " : " + inViewport + " ; " + bounds);
        if (inViewportListener != null && isInViewport != inViewport) {
            if (inViewport) {
                inViewportListener.onViewportEnter(this);
            } else {
                inViewportListener.onViewportExit(this);
            }
        }
        isInViewport = inViewport;
    }
}

Attaching an InViewportListener to an instance of this PeekImageView will get you notified whenever the view enters or leaves the visible part of the window (the viewport).

Risadinha
  • 16,058
  • 2
  • 88
  • 91
  • Is it possible to do it only for the container itself, instead of extending each view that can be within it? – android developer Feb 19 '14 at 15:51
  • Try it by adding the OnScrollChangeListener to the ViewTreeObserver of the container and see if and when it is called. If it is not called then you will need to extend the child views. – Risadinha Feb 19 '14 at 16:48
  • I've found a nicer way. will post about it now. – android developer Feb 19 '14 at 23:15
  • There is [OnAttachStateChangeListener](https://developer.android.com/reference/android/view/View.html#addOnAttachStateChangeListener%28android.view.View.OnAttachStateChangeListener%29) for api >= 12 – palindrom Jan 20 '16 at 10:31
1

You could do something like:

1) keep a list/array of views that are contained in your ScrollView.

2) Set a listener on the scroll view for when the scroll is changed: Synchronise ScrollView scroll positions - android

3) In the listener loop through these views using the Android: how to check if a View inside of ScrollView is visible? method to see if they have gone of the screen

This is a basic method but it'll work, how fast it is depends on whats on your screen etc, but it starts you in the right direction

Community
  • 1
  • 1
Blundell
  • 75,855
  • 30
  • 208
  • 233
  • is it maybe possible to extend the scrollView and check when views are in the bounding box of the scrollView itself, and store for each of them whether they were previously in it or no? – android developer Feb 19 '14 at 13:38
0

I've found a nice way to be notified of what i've asked about here.

it works for scrollView with vertical LinearLayout, but if you wish you can make it work for other cases too, depending on the case.

i'm not sure if i should handle onSizeChanged() method too, and if so, what to do there, but in all other cases, this code works fine.

here's the code:

MainActivity.java (for testing) :

public class MainActivity extends Activity
  {
  @Override
  protected void onCreate(final Bundle savedInstanceState)
    {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    final CustomScrollView scrollView=(CustomScrollView)findViewById(R.id.scrollView1);
    scrollView.setOnChildViewVisibilityChangedListener(new onChildViewVisibilityChangedListener()
      {
        @Override
        public void onChildViewVisibilityChanged(final int index,final View v,final boolean becameVisible)
          {
          Log.d("Applog","index:"+index+" visible:"+becameVisible);
          }
      });
    final ViewGroup container=(ViewGroup)findViewById(R.id.linearLayout);
    for(int i=0;i<20;++i)
      {
      final TextView tv=new TextView(this);
      tv.setText("item "+i);
      tv.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,300));
      container.addView(tv);
      }
    }
  }

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <com.example.scrollviewvisibilitydetector.CustomScrollView
        android:id="@+id/scrollView1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"  >

        <LinearLayout android:id="@+id/linearLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical" >
        </LinearLayout>
    </com.example.scrollviewvisibilitydetector.CustomScrollView>

</RelativeLayout>

CustomScrollView.java (the real deal...) :

public class CustomScrollView extends ScrollView
  {
  Set<Integer>                         _shownViewsIndices =new HashSet<Integer>();
  onChildViewVisibilityChangedListener _onChildViewVisibilityChangedListener;

  public interface onChildViewVisibilityChangedListener
    {
    public void onChildViewVisibilityChanged(int index,View v,boolean becameVisible);
    }

  public CustomScrollView(final Context context)
    {
    super(context);
    }

  public CustomScrollView(final Context context,final AttributeSet attrs)
    {
    super(context,attrs);
    }

  public CustomScrollView(final Context context,final AttributeSet attrs,final int defStyle)
    {
    super(context,attrs,defStyle);
    }

  public void setOnChildViewVisibilityChangedListener(final onChildViewVisibilityChangedListener onChildViewVisibilityChangedListener)
    {
    _onChildViewVisibilityChangedListener=onChildViewVisibilityChangedListener;
    }

  @Override
  protected void onLayout(final boolean changed,final int l,final int t,final int r,final int b)
    {
    super.onLayout(changed,l,t,r,b);
    checkViewsVisibility(l,t);
    }

  private void checkViewsVisibility(final int l,final int t)
    {
    final ViewGroup viewGroup=(ViewGroup)getChildAt(0);
    final int childCount=viewGroup.getChildCount();
    if(childCount==0)
      return;
    final int parentBottom=t+getHeight();
    // prepare to use binary search to find a view that is inside the bounds
    int min=0,max=childCount-1,piv=-1;
    int childTop,childBottom;
    View v;
    // check previously shown views
    for(final Iterator<Integer> iterator=_shownViewsIndices.iterator();iterator.hasNext();)
      {
      final Integer cur=iterator.next();
      v=viewGroup.getChildAt(cur);
      childTop=v.getTop();
      childBottom=v.getBottom();
      if(childTop<=parentBottom&&childBottom>=t)
        {
        if(piv==-1)
          piv=cur;
        }
      else
        {
        if(_onChildViewVisibilityChangedListener!=null)
          _onChildViewVisibilityChangedListener.onChildViewVisibilityChanged(cur,v,false);
        iterator.remove();
        }
      }
    if(piv==-1)
      {
      // check first view
      v=viewGroup.getChildAt(min);
      childTop=v.getTop();
      childBottom=v.getBottom();
      if(childTop<=parentBottom&&childBottom>=t)
        piv=min;
      else
        {
        // check last view
        v=viewGroup.getChildAt(max);
        childTop=v.getTop();
        childBottom=v.getBottom();
        if(childTop<=parentBottom&&childBottom>=t)
          piv=min;
        }
      if(piv==-1)
        while(true)
          {
          piv=(min+max)/2;
          v=viewGroup.getChildAt(piv);
          childTop=v.getTop();
          childBottom=v.getBottom();
          if(childTop<=parentBottom&&childBottom>=t)
            break;
          if(max-min==1)
            return;
          if(childBottom<t)
            // view above bounds
            min=piv;
          else max=piv;
          }
      }
    //
    for(int i=piv;i<childCount;++i)
      {
      v=viewGroup.getChildAt(i);
      childTop=v.getTop();
      childBottom=v.getBottom();
      // _shownViewsIndices.
      if(childTop<=parentBottom&&childBottom>=t&&!_shownViewsIndices.contains(i))
        {
        _shownViewsIndices.add(i);
        if(_onChildViewVisibilityChangedListener!=null)
          _onChildViewVisibilityChangedListener.onChildViewVisibilityChanged(i,v,true);
        }
      }
    for(int i=piv-1;i>=0;--i)
      {
      v=viewGroup.getChildAt(i);
      childTop=v.getTop();
      childBottom=v.getBottom();
      if(childTop<=parentBottom&&childBottom>=t&&!_shownViewsIndices.contains(i))
        {
        _shownViewsIndices.add(i);
        if(_onChildViewVisibilityChangedListener!=null)
          _onChildViewVisibilityChangedListener.onChildViewVisibilityChanged(i,v,true);
        }
      }
    }

  @Override
  protected void onScrollChanged(final int l,final int t,final int oldl,final int oldt)
    {
    super.onScrollChanged(l,t,oldl,oldt);
    checkViewsVisibility(l,t);
    }
  }
android developer
  • 114,585
  • 152
  • 739
  • 1,270