[Android] 스크롤이 당겨졌을 때 이벤트

2015. 11. 20. 16:29Coders

요즘은 안드로이드도 개발 하고 있습니다.


개발 도중, 데이터 목록을 화면에 뿌려주는 액티비티가 있어서, 서드파티 라이브러리를 사용할까 하다가, ScrollView 네에 LinearLayout을 Vertical 로 넣고, 각 데이터 로우는 따로 Layout을 만들어 동적으로 집어 넣는 코드를 구현했습니다.
데이터를 바인딩할 때에는 코드 단에서 각 로우의 layout을 inflate하여 기존 ScrollView 에 포함된 LinearLayout 의 child로 등록해 주는 방법 이지요.

헌데, 목록을 한꺼번에 불러오긴 그렇고 한번에 대략 10건씩 서버에서 읽어와 뿌려주는데, 하단에 버튼을 다는 것 보다 좋은 게 없을까 싶어 수많은 쇼핑몰의 모바일 웹에서 활용되곤 하는 하단 스크롤이 닿으면 자료를 더 읽어오는 이벤트를 잡아서 처리하기로 했습니다.

그래서, 검색을 좀 해 봤는데요.
키워드는 OnScrollChangedListener:onScrollChanged 이벤트였습니다.
this.mScrollView.getViewTreeObserver().addOnScrollChangedListener(this);
헌데, 인터넷에 떠 도는 소스는, 몇 가지 문제가 있습니다. 뷰가 아닌 Fragment 를 사용할 때에는 계속해서 최하단 이벤트가 발생하며, 또한 최하단으로 스크롤을 당겼다가 다시한번 당겼을 때 등등 이벤트를 잡기가 어려웠습니다.

그래서 도움을 줄 만한 리스너가 뭐가 있을까 싶어서 결국 OnTouchListener:onTouch 이벤트를 사용하기로 결정했습니다. 터치 리스너는 down, move, up 의 이벤트를 다 받기 때문에, 그 부분에 코딩을 좀 해 주면, 좀 더 그럴싸 하게 구동되게 됩니다. 좀 쓸만한 내용인 듯 해서 글 작성 합니다.

대략적인 로직은, 다음과 같습니다. (쓰고보니 간단하네요.)
  1. onScrollChanged 에서 스크롤이 최하단으로 당겨지면, "준비" 값을 true로.
  2. onTouch 의 ACTION_UP 에서 "준비" 값이 true 이면 Fire! 하고 "준비" 값을 false로.
  3. 1,2 까지는 대충 작동합니다만, 거기까지만 해 주면, 스크롤이 최하단에 있을 때 사용자가 다시 터치하여 상단으로 당겼을 때 이벤트가 발생하지 않게 됩니다. 추가적으로 onTouch 의 ACTION_MOVE에서, "준비" 값이 false 인데 스크롤이 최하단이면 "준비" 값을 true로 바꿔줘서 나중에 ACTION_UP 에서 Fire! 하도록 세팅.
소스 입니다.
package com.withsoju.myapp.worker;

import java.util.ArrayList;
import java.util.List;

import android.annotation.SuppressLint;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewTreeObserver.OnScrollChangedListener;
import android.widget.ScrollView;

/**
 * @author with_soju
 * 스크롤 하단 터치 인식 클래스
 */
public class SojuScrollGridBottom
	implements OnScrollChangedListener, OnTouchListener {

	//스크롤뷰
	ScrollView mScrollView;
	
	//스크롤 관련1
	boolean mIsReady;

	//리스너
	List<onscrollbottomlistener> mScrollBottomListenerList;
	
	/**
	 * @param scrollView
	 * 생성자 : 스크롤뷰에 리스너를 붙여주기 위해 스크롤뷰를 받아요.
	 */
	public SojuScrollGridBottom(ScrollView scrollView)
	{
		this.mScrollView = scrollView;
		this.mIsReady = false;
		this.mScrollBottomListenerList = new ArrayList<onscrollbottomlistener>();
	
		//스크롤 리스너 등록
		this.mScrollView.getViewTreeObserver().addOnScrollChangedListener(this);
		
		//터치 리스너 등록
		this.mScrollView.setOnTouchListener(this);
	}
	
	/**
	 * @param listener
	 * 외부 이벤트 등록
	 */
	public void setOnScrollBottomListener(onScrollBottomListener listener)
	{
		if (this.mScrollBottomListenerList.contains(listener) == false)
			this.mScrollBottomListenerList.add(listener);
	}
	
	/**
	 * @param listener
	 * 외부 이벤트 제거
	 */
	public void removeOnScrollBottomListener(onScrollBottomListener listener)
	{
		if (this.mScrollBottomListenerList.contains(listener))
			this.mScrollBottomListenerList.remove(listener);
	}
	
	
	@SuppressLint("ClickableViewAccessibility")
	@Override
	public boolean onTouch(View v, MotionEvent event) {
		// TODO Auto-generated method stub
		int action = event.getAction();
		
		if (action == MotionEvent.ACTION_UP){
        	
        	if (this.mIsReady && this.mScrollBottomListenerList.size() > 0)
        	{
        		for(onScrollBottomListener item : this.mScrollBottomListenerList)
        			item.onScrollBottom(this.mScrollView);
        	}
        	
        	this.mIsReady = false;
        }
        else if (action == MotionEvent.ACTION_MOVE){
        	
        	if (this.mIsReady == false && this.isScrollBottom())
        		this.mIsReady = true;
        }
		
		return false;
	}

	@Override
	public void onScrollChanged() {
		// TODO Auto-generated method stub
		//스크롤이 맨 밑으로 당겨졌는지 offset 체크
		this.mIsReady = this.isScrollBottom();
	}
	
	/**
	 * @return
	 * 스크롤뷰의 스크롤이 최 하단인지 확인
	 */
	private boolean isScrollBottom()
	{
		ScrollView sv = this.mScrollView;
		View lastChild = (View)sv.getChildAt(sv.getChildCount() -1);
		int diff = (lastChild.getBottom() - (sv.getHeight() + sv.getScrollY()));
		
		return (diff == 0);
	}
	
	/**
	 * @author with_soju
	 * 리스너
	 */
	public interface onSojuScrollBottomListener
	{
		/**
		 * @param view
		 * 스크롤이 마지막에 닿았을 때.
		 */
		public void onScrollBottom(ScrollView view);
	}
}

그리고, 이를 Activity 에 적용할 때에는 다음과 같이 생성하면서 onSojuScrollBottomListener 인터페이스의 onScrollBottom 메서드에 코딩을 해 주면 됩니다.

new SojuScrollGridBottom((ScrollView)this.findViewById(R.id.with_soju_scroll_view))
.setOnScrollBottomListener(new onScrollBottomListener()
	{
		@Override
		public void onScrollBottom(ScrollView view) {
			// TODO Auto-generated method stub
			// 여기에다 하고 싶은 코딩(데이터를 더 가져온다거나)을 하면 됩니다.
		}
});

글을 마칩니다. (Coder 에 첫 안드로이드 글 이군요.)