Android: ¿cómo verificar si una Vista dentro de ScrollView está visible?

Tengo un ScrollView que contiene una serie de Views . Me gustaría poder determinar si una vista está actualmente visible (si alguna parte de ella se muestra actualmente en ScrollView ). Esperaría que el siguiente código hiciera esto, sorprendentemente no:

 Rect bounds = new Rect(); view.getDrawingRect(bounds); Rect scrollBounds = new Rect(scroll.getScrollX(), scroll.getScrollY(), scroll.getScrollX() + scroll.getWidth(), scroll.getScrollY() + scroll.getHeight()); if(Rect.intersects(scrollBounds, bounds)) { //is visible } 

Use View#getHitRect lugar de View#getDrawingRect en la vista que está probando. Puede usar View#getDrawingRect en ScrollView lugar de calcular de forma explícita.

Código de la View#getDrawingRect :

  public void getDrawingRect(Rect outRect) { outRect.left = mScrollX; outRect.top = mScrollY; outRect.right = mScrollX + (mRight - mLeft); outRect.bottom = mScrollY + (mBottom - mTop); } 

Código de View#getHitRect :

 public void getHitRect(Rect outRect) { outRect.set(mLeft, mTop, mRight, mBottom); } 

Esto funciona:

 Rect scrollBounds = new Rect(); scrollView.getHitRect(scrollBounds); if (imageView.getLocalVisibleRect(scrollBounds)) { // Any portion of the imageView, even a single pixel, is within the visible window } else { // NONE of the imageView is within the visible window } 

Si desea detectar que la vista está TOTALMENTE visible:

 private boolean isViewVisible(View view) { Rect scrollBounds = new Rect(); mScrollView.getDrawingRect(scrollBounds); float top = view.getY(); float bottom = top + view.getHeight(); if (scrollBounds.top < top && scrollBounds.bottom > bottom) { return true; } else { return false; } } 

Para ampliar un poco la respuesta de Bill Mote utilizando getLocalVisibleRect, es posible que desee verificar si la vista solo está parcialmente visible:

 Rect scrollBounds = new Rect(); scrollView.getHitRect(scrollBounds); if (!imageView.getLocalVisibleRect(scrollBounds) || scrollBounds.height() < imageView.getHeight()) { // imageView is not within or only partially within the visible window } else { // imageView is completely visible } 

Hoy enfrenté el mismo problema. Mientras busqué en Google y leí la referencia de Android, encontré esta publicación y un método que terminé usando;

 public final boolean getLocalVisibleRect (Rect r) 

Es bueno que no solo proporcionen Rect sino también boolean indicando si View es visible en absoluto. En el lado negativo, este método no está documentado 🙁

Si desea detectar si su View es completamente visible , intente con este método:

 private boolean isViewVisible(View view) { Rect scrollBounds = new Rect(); mScrollView.getDrawingRect(scrollBounds); float top = view.getY(); float bottom = top + view.getHeight(); if (scrollBounds.top < top && scrollBounds.bottom > bottom) { return true; //View is visible. } else { return false; //View is NOT visible. } } 

Estrictamente hablando, puede obtener la visibilidad de una vista con:

 if (myView.getVisibility() == View.VISIBLE) { //VISIBLE } else { //INVISIBLE } 

Los posibles valores constantes de la visibilidad en una vista son:

VISIBLE Esta vista es visible. Usar con setVisibility (int) y android: visibilidad.

INVISIBLE Esta vista es invisible, pero aún ocupa espacio para propósitos de diseño. Usar con setVisibility (int) y android: visibilidad.

GONE Esta vista es invisible y no requiere espacio para el diseño. Usar con setVisibility (int) y android: visibilidad.

 public static int getVisiblePercent(View v) { if (v.isShown()) { Rect r = new Rect(); v.getGlobalVisibleRect(r); double sVisible = r.width() * r.height(); double sTotal = v.getWidth() * v.getHeight(); return (int) (100 * sVisible / sTotal); } else { return -1; } } 

Mi solución es usar elemento de desplazamiento NestedScrollView :

  final Rect scrollBounds = new Rect(); scroller.getHitRect(scrollBounds); scroller.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() { @Override public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) { if (myBtn1 != null) { if (myBtn1.getLocalVisibleRect(scrollBounds)) { if (!myBtn1.getLocalVisibleRect(scrollBounds) || scrollBounds.height() < myBtn1.getHeight()) { Log.i(TAG, "BTN APPEAR PARCIALY"); } else { Log.i(TAG, "BTN APPEAR FULLY!!!"); } } else { Log.i(TAG, "No"); } } } }); } 

Puede usar FocusAwareScrollView que notifica cuando la vista se vuelve visible:

 FocusAwareScrollView focusAwareScrollView = (FocusAwareScrollView) findViewById(R.id.focusAwareScrollView); if (focusAwareScrollView != null) { ArrayList viewList = new ArrayList<>(); viewList.add(yourView1); viewList.add(yourView2); focusAwareScrollView.registerViewSeenCallBack(viewList, new FocusAwareScrollView.OnViewSeenListener() { @Override public void onViewSeen(View v, int percentageScrolled) { if (v == yourView1) { // user have seen view1 } else if (v == yourView2) { // user have seen view2 } } }); } 

Aquí hay clase:

 import android.content.Context; import android.graphics.Rect; import android.support.v4.widget.NestedScrollView; import android.util.AttributeSet; import android.view.View; import java.util.ArrayList; import java.util.List; public class FocusAwareScrollView extends NestedScrollView { private List onScrollViewListeners = new ArrayList<>(); public FocusAwareScrollView(Context context) { super(context); } public FocusAwareScrollView(Context context, AttributeSet attrs) { super(context, attrs); } public FocusAwareScrollView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public interface OnScrollViewListener { void onScrollChanged(FocusAwareScrollView v, int l, int t, int oldl, int oldt); } public interface OnViewSeenListener { void onViewSeen(View v, int percentageScrolled); } public void addOnScrollListener(OnScrollViewListener l) { onScrollViewListeners.add(l); } public void removeOnScrollListener(OnScrollViewListener l) { onScrollViewListeners.remove(l); } protected void onScrollChanged(int l, int t, int oldl, int oldt) { for (int i = onScrollViewListeners.size() - 1; i >= 0; i--) { onScrollViewListeners.get(i).onScrollChanged(this, l, t, oldl, oldt); } super.onScrollChanged(l, t, oldl, oldt); } @Override public void requestChildFocus(View child, View focused) { super.requestChildFocus(child, focused); } private boolean handleViewSeenEvent(View view, int scrollBoundsBottom, int scrollYOffset, float minSeenPercentage, OnViewSeenListener onViewSeenListener) { int loc[] = new int[2]; view.getLocationOnScreen(loc); int viewBottomPos = loc[1] - scrollYOffset + (int) (minSeenPercentage / 100 * view.getMeasuredHeight()); if (viewBottomPos <= scrollBoundsBottom) { int scrollViewHeight = this.getChildAt(0).getHeight(); int viewPosition = this.getScrollY() + view.getScrollY() + view.getHeight(); int percentageSeen = (int) ((double) viewPosition / scrollViewHeight * 100); onViewSeenListener.onViewSeen(view, percentageSeen); return true; } return false; } public void registerViewSeenCallBack(final ArrayList views, final OnViewSeenListener onViewSeenListener) { final boolean[] viewSeen = new boolean[views.size()]; FocusAwareScrollView.this.postDelayed(new Runnable() { @Override public void run() { final Rect scrollBounds = new Rect(); FocusAwareScrollView.this.getHitRect(scrollBounds); final int loc[] = new int[2]; FocusAwareScrollView.this.getLocationOnScreen(loc); FocusAwareScrollView.this.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() { boolean allViewsSeen = true; @Override public void onScrollChange(NestedScrollView v, int x, int y, int oldx, int oldy) { for (int index = 0; index < views.size(); index++) { //Change this to adjust criteria float viewSeenPercent = 1; if (!viewSeen[index]) viewSeen[index] = handleViewSeenEvent(views.get(index), scrollBounds.bottom, loc[1], viewSeenPercent, onViewSeenListener); if (!viewSeen[index]) allViewsSeen = false; } //Remove this if you want continuous callbacks if (allViewsSeen) FocusAwareScrollView.this.setOnScrollChangeListener((NestedScrollView.OnScrollChangeListener) null); } }); } }, 500); } } 

Sé que es muy tarde. Pero tengo una buena solución. A continuación se muestra el fragmento de código para obtener el porcentaje de visibilidad de la vista en la vista de desplazamiento.

En primer lugar, configure la función de escucha táctil en la vista de desplazamiento para obtener una callback para la parada de desplazamiento.

 @Override public boolean onTouch(View v, MotionEvent event) { switch ( event.getAction( ) ) { case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: new Handler().postDelayed(new Runnable() { @Override public void run() { if(mScrollView == null){ mScrollView = (ScrollView) findViewById(R.id.mScrollView); } int childCount = scrollViewRootChild.getChildCount(); //Scroll view location on screen int[] scrollViewLocation = {0,0}; mScrollView.getLocationOnScreen(scrollViewLocation); //Scroll view height int scrollViewHeight = mScrollView.getHeight(); for (int i = 0; i < childCount; i++){ View child = scrollViewRootChild.getChildAt(i); if(child != null && child.getVisibility() == View.VISIBLE){ int[] viewLocation = new int[2]; child.getLocationOnScreen(viewLocation); int viewHeight = child.getHeight(); getViewVisibilityOnScrollStopped(scrollViewLocation, scrollViewHeight, viewLocation, viewHeight, (String) child.getTag(), (childCount - (i+1))); } } } }, 150); break; } return false; } 

En el fragmento de código anterior, recibimos una callback para los eventos táctiles de la vista de desplazamiento y publicamos un elemento ejecutable después de 150 milisegundos (No obligatorio) después de obtener la callback para el desplazamiento detenido. En ese ejecutable obtendremos la ubicación de la vista de desplazamiento en la pantalla y la altura de la vista de desplazamiento. A continuación, obtenga la instancia de view view directa del niño de la vista de desplazamiento y obtenga los recuentos secundarios. En mi caso, el hijo directo de la vista de desplazamiento es LinearLayout llamado scrollViewRootChild . A continuación, itere todas las vistas secundarias de scrollViewRootChild . En el fragmento de código anterior, puede ver que estoy obteniendo la ubicación del niño en la pantalla en una matriz de enteros llamada viewLocation , obtengo el alto de vista en el nombre de la variable viewHeight . Luego llamé a un método privado getViewVisibilityOnScrollStopped . Puede obtener la comprensión del funcionamiento interno de este método leyendo la documentación.

 /** * getViewVisibilityOnScrollStopped * @param scrollViewLocation location of scroll view on screen * @param scrollViewHeight height of scroll view * @param viewLocation location of view on screen, you can use the method of view claas's getLocationOnScreen method. * @param viewHeight height of view * @param tag tag on view * @param childPending number of views pending for iteration. */ void getViewVisibilityOnScrollStopped(int[] scrollViewLocation, int scrollViewHeight, int[] viewLocation, int viewHeight, String tag, int childPending) { float visiblePercent = 0f; int viewBottom = viewHeight + viewLocation[1]; //Get the bottom of view. if(viewLocation[1] >= scrollViewLocation[1]) { //if view's top is inside the scroll view. visiblePercent = 100; int scrollBottom = scrollViewHeight + scrollViewLocation[1]; //Get the bottom of scroll view if (viewBottom >= scrollBottom) { //If view's bottom is outside from scroll view int visiblePart = scrollBottom - viewLocation[1]; //Find the visible part of view by subtracting view's top from scrollview's bottom visiblePercent = (float) visiblePart / viewHeight * 100; } }else{ //if view's top is outside the scroll view. if(viewBottom > scrollViewLocation[1]){ //if view's bottom is outside the scroll view int visiblePart = viewBottom - scrollViewLocation[1]; //Find the visible part of view by subtracting scroll view's top from view's bottom visiblePercent = (float) visiblePart / viewHeight * 100; } } if(visiblePercent > 0f){ visibleWidgets.add(tag); //List of visible view. } if(childPending == 0){ //Do after iterating all children. } } 

Si siente alguna mejora en este código, por favor contribuya.

Utilizando la respuesta de @Qberticus, que fue hasta el punto, pero por cierto, compuse un montón de códigos para comprobar si cada vez que se llama una vista de desplazamiento y se desplaza, desencadena la respuesta @Qberticus y puede hacer lo que quiera, en mi caso tengo una red social que contiene videos, así que cuando la vista se dibuja en la pantalla, reproduzco la misma idea de video como Facebook e Instagram. Aquí está el código:

 mainscrollview.getViewTreeObserver().addOnScrollChangedListener(new OnScrollChangedListener() { @Override public void onScrollChanged() { //mainscrollview is my scrollview that have inside it a linearlayout containing many child views. Rect bounds = new Rect(); for(int xx=1;xx<=postslayoutindex;xx++) { //postslayoutindex is the index of how many posts are read. //postslayoutchild is the main layout for the posts. if(postslayoutchild[xx]!=null){ postslayoutchild[xx].getHitRect(bounds); Rect scrollBounds = new Rect(); mainscrollview.getDrawingRect(scrollBounds); if(Rect.intersects(scrollBounds, bounds)) { vidPreview[xx].startPlaywithoutstoppping(); //I made my own custom video player using textureview and initialized it globally in the class as an array so I can access it from anywhere. } else { } } } } });