Computer >> 컴퓨터 >  >> 프로그래밍 >> Android

Android에서 ScrollView 끝 감지하기 – 스크롤 최상단·최하단 도달 감지 방법

Android 앱을 개발하다 보면 ScrollView가 맨 아래(끝)까지 스크롤되었는지, 혹은 맨 위로 올라갔는지를 감지해야 하는 경우가 자주 있습니다. 예를 들어 무한 스크롤로 다음 페이지 데이터를 불러오거나, 스크롤이 끝에 도달했을 때 특정 UI를 표시하는 기능 등이 대표적입니다.

이 글에서는 OnScrollChangedListener를 활용해 ScrollView의 최상단과 최하단 도달 여부를 감지하는 방법을 단계별로 살펴보겠습니다.

동작 원리

핵심 로직은 간단합니다. ScrollView 내부의 마지막 자식 뷰(View)의 하단 좌표와 현재 스크롤 위치를 비교하여, 그 차이가 0이 되면 스크롤이 끝에 도달한 것으로 판단합니다.

  • bottomDetector: 마지막 자식 뷰의 bottom 값에서 (ScrollView 높이 + 현재 스크롤 Y값)을 뺀 값 → 0이면 최하단 도달
  • topDetector: 현재 스크롤 Y값 → 0 이하면 최상단 도달

Step 1 – 새 프로젝트 생성

Android Studio에서 File ⇒ New Project를 선택하고, 필요한 정보를 모두 입력하여 새 프로젝트를 생성합니다.

Step 2 – 레이아웃 작성 (res/layout/activity_main.xml)

아래 코드를 activity_main.xml에 추가합니다. ScrollView 안에 WebView를 배치하여 긴 텍스트 콘텐츠를 스크롤할 수 있도록 구성했습니다.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <ScrollView
        android:id="@+id/scrollView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <WebView
            android:id="@+id/webView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
        </WebView>
    </ScrollView>
</RelativeLayout>

Step 3 – MainActivity.java 구현

다음으로 MainActivity.java에 아래 코드를 추가합니다. 이 클래스는 ViewTreeObserver.OnScrollChangedListener를 구현하여 스크롤 변화를 실시간으로 감지합니다.

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.webkit.WebView;
import android.widget.ScrollView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity implements View.OnTouchListener,
        ViewTreeObserver.OnScrollChangedListener {

    ScrollView scrollView;
    WebView webView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        scrollView = findViewById(R.id.scrollView);
        webView = findViewById(R.id.webView);

        scrollView.setOnTouchListener(this);
        scrollView.getViewTreeObserver().addOnScrollChangedListener(this);

        webView.loadData(getResources().getString(R.string.sample_data), "text/html", null);
    }

    public void onScrollChanged() {
        View view = scrollView.getChildAt(scrollView.getChildCount() - 1);
        int topDetector = scrollView.getScrollY();
        int bottomDetector = view.getBottom() - (scrollView.getHeight() + scrollView.getScrollY());

        if (bottomDetector == 0) {
            Toast.makeText(getBaseContext(), "Scroll View bottom reached", Toast.LENGTH_SHORT).show();
        }
        if (topDetector <= 0) {
            Toast.makeText(getBaseContext(), "Scroll View top reached", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        return false;
    }
}

Step 4 – 샘플 데이터 추가 (res/values/strings.xml)

WebView에 로드할 긴 HTML 텍스트를 strings.xml에 CDATA 형태로 추가합니다. 실제 프로젝트에서는 원하는 콘텐츠로 대체하면 됩니다.

<resources>
    <string name="app_name">Sample</string>
    <string name="sample_data">
    <![CDATA[<html>]]>
        <![CDATA[<style>]]>
            p.small {
                line-height: 1.5;
                font-size: 12px;
            }
        <![CDATA[</style>]]>
        <![CDATA[<body><p align="justify" class="small">]]>
            Chupa chups carrot cake pastry cupcake caramels sugar plum chocolate cupcake tootsie roll.
            Cupcake chocolate donut caramels sesame snaps brownie cake halvah.
            Sesame snaps sesame snaps gummies candy carrot cake.
            Fruitcake bonbon liquorice topping biscuit candy canes fruitcake marzipan cake.
            Tart tootsie roll tart gummi bears apple pie danish powder pastry gummies.
            Tiramisu sesame snaps icing pudding. Marzipan sweet roll cookie marshmallow.
            Tiramisu bear claw bear claw sesame snaps pie marzipan.
            Icing candy candy canes danish. Tart lollipop jujubes cake soufflé.
            Cheesecake bear claw marshmallow chocolate cake donut sweet.
            Tootsie roll topping chocolate bar cupcake cake oat cake oat cake bonbon cotton candy.
            Jelly-o croissant dragée lemon drops chocolate bar. Gingerbread cotton candy oat cake marshmallow.
        <![CDATA[<br><br>]]>
            Lemon drops carrot cake wafer jelly cheesecake chocolate cake gingerbread.
            Dessert cookie oat cake pie. Liquorice liquorice dragée lollipop danish jujubes pudding carrot cake.
            Jelly beans cotton candy sweet caramels gummies pastry. Wafer wafer muffin sweet roll.
            Chocolate topping gummi bears gummies. Oat cake bonbon brownie chocolate bar.
            Tart tart gingerbread chupa chups chocolate bar gummies. Croissant oat cake ice cream liquorice tootsie roll. Pudding wafer gummies.
            Apple pie marshmallow chupa chups. Cookie cheesecake carrot cake jelly beans tiramisu lemon drops.
            Bonbon sweet roll wafer.
        <![CDATA[<br><br>]]>
            Lemon drops muffin bear claw cake gingerbread apple pie lollipop cheesecake.
            Powder bear claw marshmallow. Tootsie roll gummi bears wafer brownie.
            Sesame snaps soufflé bear claw chocolate danish candy.
            Wafer oat cake ice cream pastry cake muffin toffee tootsie roll. Cake oat cake marshmallow.
            Gummi bears gingerbread cake danish muffin marzipan donut cotton candy.
            Ice cream topping cake candy gummi bears brownie. Marshmallow chocolate icing candy canes topping.
            Pastry gummies powder sesame snaps sugar plum sugar plum.
            Lemon drops gummies gummi bears tootsie roll powder jelly-o jelly-o.
            Lollipop jujubes sweet brownie croissant soufflé chupa chups bear claw.
            Sugar plum sugar plum lollipop croissant lemon drops sweet jujubes soufflé sweet roll.
        <![CDATA[<br><br>]]>
            Liquorice jelly-o candy lollipop croissant sweet icing brownie biscuit.
            Wafer dessert cake marzipan gummies jujubes bonbon tootsie roll ice cream.
            Chocolate bar bear claw candy canes lemon drops jelly apple pie cotton candy jujubes.
            Donut toffee candy canes dragée cake toffee. Icing cookie biscuit cheesecake dragée.
            Gummi bears tiramisu sesame snaps muffin halvah. Sweet jujubes chocolate cake muffin pie bonbon cupcake.
            Pastry jujubes pastry fruitcake. Jelly beans dessert gingerbread cupcake gingerbread marshmallow carrot cake.
            Macaroon carrot cake candy canes jelly powder. Soufflé macaroon liquorice candy canes lollipop.
            Marzipan fruitcake fruitcake gummi bears gummies chocolate cake. Jelly sesame snaps topping sweet.
        <![CDATA[<br><br>]]>
            Chupa chups macaroon muffin. Topping jujubes biscuit tart jujubes pudding.
            Brownie oat cake cookie. Lemon drops pudding tiramisu dessert marzipan.
            Danish cake candy canes bonbon. Gummi bears carrot cake apple pie lollipop.
            Tart candy canes chocolate cake lemon drops carrot cake topping cheesecake apple pie cake.
            Bear claw pudding macaroon croissant halvah caramels.
            Danish cake pastry sugar plum lollipop ice cream gummi bears gummi bears sugar plum.
            Chocolate macaroon tootsie roll. Halvah tootsie roll sweet roll powder gummi bears chocolate cake.
            Sugar plum gingerbread brownie. Wafer chocolate cake bonbon ice cream biscuit dragée tiramisu croissant apple pie.
        <![CDATA[</p></body></html>]]>
    </string>
</resources>

Step 5 – AndroidManifest.xml 설정

마지막으로 androidManifest.xml에 아래 코드를 추가합니다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android" package="app.com.sample">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

앱 실행 및 결과 확인

이제 애플리케이션을 실행해 보겠습니다. Android 스마트폰을 컴퓨터에 연결한 상태라고 가정합니다. Android Studio에서 프로젝트의 액티비티 파일 중 하나를 연 뒤, 툴바의 Run 아이콘을 클릭하세요. 실행 기기 목록에서 본인의 모바일 기기를 선택하면, 앱이 설치되고 아래와 같은 기본 화면이 표시됩니다.

Android에서 ScrollView 끝 감지하기 – 스크롤 최상단·최하단 도달 감지 방법

스크롤을 맨 아래로 내리면 "Scroll View bottom reached" 토스트 메시지가, 맨 위로 올리면 "Scroll View top reached" 토스트 메시지가 표시되는 것을 확인할 수 있습니다.

마무리

이처럼 OnScrollChangedListener와 간단한 좌표 계산만으로 ScrollView의 스크롤 위치를 손쉽게 감지할 수 있습니다. 이 기법은 무한 스크롤 구현, '맨 위로' 버튼 표시, 스크롤 기반 애니메이션 등 다양한 곳에 응용할 수 있으니 참고하시기 바랍니다.