Android: как проверить, видна ли View внутри ScrollView?
У меня есть ScrollView
, который содержит ряд Views
. Я хотел бы иметь возможность определить, отображается ли вид в настоящее время (если какая-либо его часть отображается в настоящее время ScrollView
). Я бы ожидал, что приведенный ниже код сделает это, на удивление это не так:
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
}
Ответы
Ответ 1
Используйте View#getHitRect
вместо View#getDrawingRect
в просмотре, который вы тестируете. Вы можете использовать View#getDrawingRect
в ScrollView
вместо вычисления явно.
Код из View#getDrawingRect
:
public void getDrawingRect(Rect outRect) {
outRect.left = mScrollX;
outRect.top = mScrollY;
outRect.right = mScrollX + (mRight - mLeft);
outRect.bottom = mScrollY + (mBottom - mTop);
}
Код из View#getHitRect
:
public void getHitRect(Rect outRect) {
outRect.set(mLeft, mTop, mRight, mBottom);
}
Ответ 2
Это работает:
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
}
Ответ 3
Если вы хотите обнаружить, что вид FULLY отображается:
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;
}
}
Ответ 4
Чтобы развернуть бит на ответ Билла Мота, используя getLocalVisibleRect, вы можете проверить, только ли вид частично видимый:
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
}
Ответ 5
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;
}
}
Ответ 6
Сегодня я столкнулся с такой же проблемой. В то время как Googling и чтение ссылки на Android я нашел этот пост и метод, который я использовал вместо этого;
public final boolean getLocalVisibleRect (Rect r)
Приятно, что они не только предоставляют Rect, но и логические, указывающие, что View View вообще отсутствует. На отрицательной стороне этот метод недокументирован: (
Ответ 7
Если вы хотите определить, полностью ли visible
ваш View
, попробуйте использовать этот метод:
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.
}
}
Строго говоря, вы можете получить видимость вида с:
if (myView.getVisibility() == View.VISIBLE) {
//VISIBLE
} else {
//INVISIBLE
}
Возможные постоянные значения видимости в представлении:
VISIBLE Этот вид виден. Используйте с setVisibility (int) и android: видимость.
НЕВИДИМОЕ Это представление невидимо, но оно все еще занимает место для макета. Используйте с setVisibility (int) и android: видимость.
GONE Этот вид невидим, и он не занимает места для макета. Используйте с setVisibility (int) и android: видимость.
Ответ 8
Мое решение использует 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");
}
}
}
});
}
Ответ 9
Вы можете использовать FocusAwareScrollView
, который уведомляет, когда вид становится видимым:
FocusAwareScrollView focusAwareScrollView = (FocusAwareScrollView) findViewById(R.id.focusAwareScrollView);
if (focusAwareScrollView != null) {
ArrayList<View> 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
}
}
});
}
Вот класс:
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<OnScrollViewListener> 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<View> 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);
}
}
Ответ 10
Это расширение поможет определить вид полностью видимым.
Он также будет работать, если ваш View
является потомком ребенка... из ScrollView
(например: ScrollView
→ LinearLayout
→ ContraintLayout
->... → YourView
).
fun ScrollView.isViewVisible(view: View): Boolean {
val scrollBounds = Rect()
this.getDrawingRect(scrollBounds)
var top = 0f
var temp = view
while (temp !is ScrollView){
top += (temp).y
temp = temp.parent as View
}
val bottom = top + view.height
return scrollBounds.top < top && scrollBounds.bottom > bottom
}
Заметка
1) view.getY()
и view.getX()
возвращают значение x, y в FIRST PARENT.
2) Вот пример того, как getDrawingRect
будет возвращать
Ссылка
Ответ 11
Я знаю его очень поздно. Но у меня есть хорошее решение. Ниже приведен фрагмент кода для получения процента видимости просмотров в режиме прокрутки.
Прежде всего установите сенсорный прослушиватель в режиме прокрутки для получения обратного вызова для остановки прокрутки.
@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;
}
В приведенном выше фрагменте кода мы получаем обратные вызовы для просмотра событий касания и прокрутки после 150 миллисов (не обязательно) после получения обратного вызова для прокрутки. В этом runnable мы получим местоположение прокрутки на экране и прокрутите высоту просмотра. Затем получите экземпляр прямой просмотра дочерней группы просмотра прокрутки и получите подсчеты для детей. В моем случае прямой дочерний элемент прокрутки - LinearLayout с именем scrollViewRootChild. Затем повторите все дочерние представления scrollViewRootChild. В приведенном выше фрагменте кода вы можете видеть, что я получаю местоположение дочернего элемента на экране в целочисленном массиве с именем viewLocation, получить высоту представления в имени переменной viewHeight. Затем я вызвал частный метод getViewVisibilityOnScrollStopped. Вы можете понять внутреннюю работу этого метода, прочитав документацию.
/**
* 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 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 top is inside the scroll view.
visiblePercent = 100;
int scrollBottom = scrollViewHeight + scrollViewLocation[1]; //Get the bottom of scroll view
if (viewBottom >= scrollBottom) { //If view bottom is outside from scroll view
int visiblePart = scrollBottom - viewLocation[1]; //Find the visible part of view by subtracting view top from scrollview bottom
visiblePercent = (float) visiblePart / viewHeight * 100;
}
}else{ //if view top is outside the scroll view.
if(viewBottom > scrollViewLocation[1]){ //if view bottom is outside the scroll view
int visiblePart = viewBottom - scrollViewLocation[1]; //Find the visible part of view by subtracting scroll view top from view bottom
visiblePercent = (float) visiblePart / viewHeight * 100;
}
}
if(visiblePercent > 0f){
visibleWidgets.add(tag); //List of visible view.
}
if(childPending == 0){
//Do after iterating all children.
}
}
Если вы чувствуете какое-либо улучшение в этом коде, внесите свой вклад.
Ответ 12
В итоге я реализовал комбинацию из двух ответов Java (@bill-mote fooobar.com/questions/48338/... и @denys-vasylenko fooobar.com/questions/48338/...) в мой проект в виде набора расширений Kotlin, которые поддерживают либо стандартные вертикальные элементы управления ScrollView или HorizontalScrollView.
Я просто бросил их в файл Kotlin с именем Extensions.kt, без класса, только методы.
Я использовал их, чтобы определить, к какому элементу привязываться, когда пользователь перестает прокручивать различные виды прокрутки в моем проекте:
fun View.isPartiallyOrFullyVisible(horizontalScrollView: HorizontalScrollView) : Boolean {
@Suppress("CanBeVal") var scrollBounds = Rect()
horizontalScrollView.getHitRect(scrollBounds)
return getLocalVisibleRect(scrollBounds)
}
fun View.isPartiallyOrFullyVisible(scrollView: ScrollView) : Boolean {
@Suppress("CanBeVal") var scrollBounds = Rect()
scrollView.getHitRect(scrollBounds)
return getLocalVisibleRect(scrollBounds)
}
fun View.isFullyVisible(horizontalScrollView: HorizontalScrollView) : Boolean {
@Suppress("CanBeVal") var scrollBounds = Rect()
horizontalScrollView.getDrawingRect(scrollBounds)
val left = x
val right = left + width
return scrollBounds.left < left && scrollBounds.right > right
}
fun View.isFullyVisible(scrollView: ScrollView) : Boolean {
@Suppress("CanBeVal") var scrollBounds = Rect()
scrollView.getDrawingRect(scrollBounds)
val top = y
val bottom = top + height
return scrollBounds.top < top && scrollBounds.bottom > bottom
}
fun View.isPartiallyVisible(horizontalScrollView: HorizontalScrollView) : Boolean = isPartiallyOrFullyVisible(horizontalScrollView) && !isFullyVisible(horizontalScrollView)
fun View.isPartiallyVisible(scrollView: ScrollView) : Boolean = isPartiallyOrFullyVisible(scrollView) && !isFullyVisible(scrollView)
Пример использования, перебирая потомки прокрутки LinearLayout и записывая результаты:
val linearLayoutChild: LinearLayout = getChildAt(0) as LinearLayout
for (i in 0 until linearLayoutChild.childCount) {
with (linearLayoutChild.getChildAt(i)) {
Timber.d("child$i left=$left width=$width isPartiallyOrFullyVisible=${isPartiallyOrFullyVisible([email protected])} isFullyVisible=${isFullyVisible([email protected])} isPartiallyVisible=${isPartiallyVisible([email protected])}")
}
}
Ответ 13
Используя ответ @Qberticus, который был до такой степени, но отличный btw, я собрал кучу кодов, чтобы проверить, будет ли когда-либо прокручиваться запрос и прокручивается он вызывает ответ @Qberticus, и вы можете делать все, что хотите, в моем случае У меня есть социальная сеть, содержащая видео, поэтому, когда представление нарисовано на экране, я воспроизвожу видео, подобное идее, например, facebook и Instagram.
Здесь код:
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
{
}
}
}
}
});