itsource

Android: ScrollView 내부의 View가 보이는지 확인하는 방법은 무엇입니까?

mycopycode 2023. 8. 10. 18:46
반응형

Android: ScrollView 내부의 View가 보이는지 확인하는 방법은 무엇입니까?

나는 있습니다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
}

효과:

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
}

사용하다View#getHitRect대신에View#getDrawingRect당신이 테스트하는 관점에서.사용할 수 있습니다.View#getDrawingRect명시적으로 계산하는 대신에.

코드 출처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);
}

보기가 완전히 표시되는지 감지하려면 다음을 수행합니다.

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

이 확장 기능은 보기를 완전히 볼 수 있는 상태로 탐지하는 데 도움이 됩니다.
또한 다음과 같은 경우에도 작동합니다.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 값을 반환합니다.

다음은 방법에 대한 예입니다.getDrawingRect 링크를 반환합니다.

내 솔루션은 사용 중입니다.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");
                }
            }

        }
    });
}

getLocalVisibleRect를 사용하여 Bill Mote의 답변을 조금 확장하려면 보기가 부분적으로만 표시되는지 확인해야 합니다.

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

저는 오늘 같은 문제에 직면했습니다.구글링하고 안드로이드 레퍼런스를 읽는 동안 이 게시물과 대신 사용하게 된 방법을 발견했습니다.

public final boolean getLocalVisibleRect (Rect r)

Rect뿐만 아니라 View가 표시되는지 여부를 나타내는 부울도 제공하는 것이 좋습니다.부정적인 측면에서 이 방법은 문서화되어 있지 않습니다.

당신이 감지하고 싶은 것은View완전한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; //View is visible.
    } else {
        return false; //View is NOT visible.
    }
}

엄밀히 말하면, 보기의 가시성은 다음과 같습니다.

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

보기에서 가시성의 가능한 상수 값은 다음과 같습니다.

VISABLE 이 보기가 표시됩니다.setVisibility(int) 및 Android:visibility와 함께 사용합니다.

보이지 않음 이 보기는 보이지 않지만 레이아웃을 위해 공간을 차지합니다.setVisibility(int) 및 Android:visibility와 함께 사용합니다.

GONE 이 보기는 보이지 않으며 레이아웃을 위해 공간을 차지하지 않습니다.setVisibility(int) 및 Android:visibility와 함께 사용합니다.

코틀린웨이;

스크롤 뷰의 스크롤을 나열하고 하위 뷰가 화면에 표시되는 경우 액션을 얻는 확장입니다.

@SuppressLint("ClickableViewAccessibility")
fun View.setChildViewOnScreenListener(view: View, action: () -> Unit) {
    val visibleScreen = Rect()

    this.setOnTouchListener { _, motionEvent ->
        if (motionEvent.action == MotionEvent.ACTION_MOVE) {
            this.getDrawingRect(visibleScreen)

            if (view.getLocalVisibleRect(visibleScreen)) {
                action()
            }
        }

        false
    }
}

스크롤 가능한 보기에 이 확장 기능 사용

nestedScrollView.setChildViewOnScreenListener(childView) {
               action()
            }

사용할 수 있습니다.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);
    }
}

저는 프로젝트에서 Java 답변 중 두 가지(@bill-mote https://stackoverflow.com/a/12428154/3686125 및 @denys-vasylenko https://stackoverflow.com/a/25528434/3686125 )의 조합을 표준 수직 ScrollView 또는 HorizontalScrollView 컨트롤을 지원하는 Kotlin 확장 세트로 구현했습니다.

저는 방금 Extensions.kt라는 이름의 Kotlin 파일에 이것들을 던졌습니다. 클래스는 없고, 메소드만 있습니다.

사용자가 프로젝트의 다양한 스크롤 보기에서 스크롤을 멈출 때 스냅할 항목을 결정하기 위해 다음을 사용했습니다.

fun View.isPartiallyOrFullyVisible(horizontalScrollView: HorizontalScrollView) : Boolean {
    val scrollBounds = Rect()
    horizontalScrollView.getHitRect(scrollBounds)
    return getLocalVisibleRect(scrollBounds)
}

fun View.isPartiallyOrFullyVisible(scrollView: ScrollView) : Boolean {
    val scrollBounds = Rect()
    scrollView.getHitRect(scrollBounds)
    return getLocalVisibleRect(scrollBounds)
}

fun View.isFullyVisible(horizontalScrollView: HorizontalScrollView) : Boolean {
    val 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 {
    val 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)

스크롤 뷰의 선형 레이아웃 하위 및 로그 출력을 통해 반복하는 사용 예:

val linearLayoutChild: LinearLayout = getChildAt(0) as LinearLayout
val scrollView = findViewById(R.id.scroll_view) //Replace with your scrollview control or synthetic accessor
for (i in 0 until linearLayoutChild.childCount) {
    with (linearLayoutChild.getChildAt(i)) {
        Log.d("ScrollView", "child$i left=$left width=$width isPartiallyOrFullyVisible=${isPartiallyOrFullyVisible(scrollView)} isFullyVisible=${isFullyVisible(scrollView)} isPartiallyVisible=${isPartiallyVisible(scrollView)}")
    }
}

내 방식:

scrollView.viewTreeObserver?.addOnScrollChangedListener {
     scrollView.getDrawingRect(Rect())
     myViewInsideScrollView.getLocalVisibleRect(Rect()) 
}

저는 매우 늦은 것을 압니다.하지만 좋은 해결책이 있습니다.다음은 스크롤 보기에서 보기 가시성 비율을 얻기 위한 코드 조각입니다.

먼저 스크롤 뷰에서 스크롤 중지를 위한 콜백을 위한 터치 수신기를 설정합니다.

@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밀리(필수 사항 아님) 이후 실행 가능한 테이블을 게시하고 있습니다.이 실행 테이블에서는 화면에서 스크롤 뷰 위치와 스크롤 뷰 높이를 확인할 수 있습니다.그런 다음 스크롤 뷰의 직접 하위 뷰 그룹 인스턴스를 가져오고 하위 카운트를 가져옵니다.내 경우 스크롤 뷰의 직접 자식은 scrollViewRootChild라는 이름의 선형 레이아웃입니다.그런 다음 ScrollViewRootChild의 모든 자식 보기를 반복합니다.위의 코드 조각에서 viewLocation이라는 정수 배열의 화면에서 아이의 위치를 가져오고 있습니다. 변수 이름 보기에서 높이 가져오기.그런 다음 개인 메서드 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'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.
    }
}

이 코드에서 개선점을 느끼면 도움을 주시기 바랍니다.

요점은 있었지만 대단한 @Qberticus 답변을 사용하여 스크롤 뷰가 호출되고 스크롤될 때마다 @Qberticus 답변이 트리거되는지 확인하기 위해 많은 코드를 조합했습니다.저의 경우, 저는 비디오를 포함하는 소셜 네트워크를 가지고 있어서 화면에 뷰가 그려질 때 페이스북과 인스타그램과 같은 비디오 아이디어를 재생합니다.코드는 다음과 같습니다.

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
                        {

                        }


                        }
                    }
                    }
                });

언급URL : https://stackoverflow.com/questions/4628800/android-how-to-check-if-a-view-inside-of-scrollview-is-visible

반응형