티스토리 뷰

반응형

Stack Overflow에 자주 검색, 등록되는 문제들과 제가 개발 중 찾아 본 문제들 중에서 나중에도 찾아 볼 것 같은 문제들을 정리하고 있습니다.

Stack Overflow에서 가장 먼저 확인하게 되는 가장 높은 점수를 받은 Solution과 현 시점에 도움이 될 수 있는 가장 최근에 업데이트(최소 점수 확보)된 Solution을 각각 정리하였습니다.

 

아래 word cloud를 통해 이번 포스팅의 주요 키워드를 미리 확인하세요.

HorizontalScrollView within ScrollView Touch Handling

ScrollView 안에 HorizontalScrollView가 있는 경우 터치 핸들링 처리 방법

 문제 내용 

I have a ScrollView that surrounds my entire layout so that the entire screen is scrollable. The first element I have in this ScrollView is a HorizontalScrollView block that has features that can be scrolled through horizontally. I've added an ontouchlistener to the horizontalscrollview to handle touch events and force the view to "snap" to the closest image on the ACTION_UP event.

제 전체 레이아웃을 감싸는 ScrollView가 있습니다. 이 ScrollView의 첫 번째 요소는 가로로 스크롤 할 수 있는 HorizontalScrollView 블록입니다. HorizontalScrollView에는 ACTION_UP 이벤트가 발생할 때 가장 가까운 이미지로 "스냅"되도록 하기 위해 ontouchlistener를 추가했습니다.

 

So the effect I'm going for is like the stock android homescreen where you can scroll from one to the other and it snaps to one screen when you lift your finger.

그래서 저는 Android 홈 화면과 같이 하나에서 다른 화면으로 스크롤할 수 있고 손가락을 뗄 때 하나의 화면으로 스냅되는 효과를 만들고자합니다.

 

This all works great except for one problem: I need to swipe left to right almost perfectly horizontally for an ACTION_UP to ever register. If I swipe vertically in the very least (which I think many people tend to do on their phones when swiping side to side), I will receive an ACTION_CANCEL instead of an ACTION_UP. My theory is that this is because the horizontalscrollview is within a scrollview, and the scrollview is hijacking the vertical touch to allow for vertical scrolling.

이것은 수직으로 스와이프를 하는 것이 거의 없어야만 ACTION_UP이 등록되는 문제가 있습니다. (많은 사람들이 전화기에서 좌우로 스와이프 할 때 수직으로 스와이프하려는 경향이 있다고 생각합니다.) 그렇게하면 ScrollView가 수직 스크롤링을 위해 수직 터치를 잡아 먹으므로 ACTION_CANCEL이 발생합니다.

 

How can I disable the touch events for the scrollview from just within my horizontal scrollview, but still allow for normal vertical scrolling elsewhere in the scrollview?

어떻게 하면 HorizontalScrollView 내에서 ScrollView의 터치 이벤트를 비활성화 할 수 있고, 그 외의 곳에서는 수직 스크롤링을 계속 할 수 있을까요?

 

Here's a sample of my code:

다음은 제 코드의 샘플입니다.
   public class HomeFeatureLayout extends HorizontalScrollView {
    private ArrayList<ListItem> items = null;
    private GestureDetector gestureDetector;
    View.OnTouchListener gestureListener;
    private static final int SWIPE_MIN_DISTANCE = 5;
    private static final int SWIPE_THRESHOLD_VELOCITY = 300;
    private int activeFeature = 0;

    public HomeFeatureLayout(Context context, ArrayList<ListItem> items){
        super(context);
        setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
        setFadingEdgeLength(0);
        this.setHorizontalScrollBarEnabled(false);
        this.setVerticalScrollBarEnabled(false);
        LinearLayout internalWrapper = new LinearLayout(context);
        internalWrapper.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
        internalWrapper.setOrientation(LinearLayout.HORIZONTAL);
        addView(internalWrapper);
        this.items = items;
        for(int i = 0; i< items.size();i++){
            LinearLayout featureLayout = (LinearLayout) View.inflate(this.getContext(),R.layout.homefeature,null);
            TextView header = (TextView) featureLayout.findViewById(R.id.featureheader);
            ImageView image = (ImageView) featureLayout.findViewById(R.id.featureimage);
            TextView title = (TextView) featureLayout.findViewById(R.id.featuretitle);
            title.setTag(items.get(i).GetLinkURL());
            TextView date = (TextView) featureLayout.findViewById(R.id.featuredate);
            header.setText("FEATURED");
            Image cachedImage = new Image(this.getContext(), items.get(i).GetImageURL());
            image.setImageDrawable(cachedImage.getImage());
            title.setText(items.get(i).GetTitle());
            date.setText(items.get(i).GetDate());
            internalWrapper.addView(featureLayout);
        }
        gestureDetector = new GestureDetector(new MyGestureDetector());
        setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if (gestureDetector.onTouchEvent(event)) {
                    return true;
                }
                else if(event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL ){
                    int scrollX = getScrollX();
                    int featureWidth = getMeasuredWidth();
                    activeFeature = ((scrollX + (featureWidth/2))/featureWidth);
                    int scrollTo = activeFeature*featureWidth;
                    smoothScrollTo(scrollTo, 0);
                    return true;
                }
                else{
                    return false;
                }
            }
        });
    }

    class MyGestureDetector extends SimpleOnGestureListener {
        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            try {
                //right to left 
                if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
                    activeFeature = (activeFeature < (items.size() - 1))? activeFeature + 1:items.size() -1;
                    smoothScrollTo(activeFeature*getMeasuredWidth(), 0);
                    return true;
                }  
                //left to right
                else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
                    activeFeature = (activeFeature > 0)? activeFeature - 1:0;
                    smoothScrollTo(activeFeature*getMeasuredWidth(), 0);
                    return true;
                }
            } catch (Exception e) {
                // nothing
            }
            return false;
        }
    }
}

 

 

 높은 점수를 받은 Solution 

Update: I figured this out. On my ScrollView, I needed to override the onInterceptTouchEvent method to only intercept the touch event if the Y motion is > the X motion. It seems like the default behavior of a ScrollView is to intercept the touch event whenever there is ANY Y motion. So with the fix, the ScrollView will only intercept the event if the user is deliberately scrolling in the Y direction and in that case pass off the ACTION_CANCEL to the children.

업데이트 : 이 문제를 해결했습니다. ScrollView에서는 onInterceptTouchEvent 메소드를 오버라이드하여 Y 모션이 X 모션보다 큰 경우에만 터치 이벤트를 가로채도록해야합니다. ScrollView의 기본 동작은 어떤 Y 모션도 있을 때마다 터치 이벤트를 가로챕니다. 따라서 수정하면 ScrollView는 사용자가 Y 방향으로 의도적으로 스크롤하는 경우에만 이벤트를 가로채고 ACTION_CANCEL을 하위 항목으로 전달합니다.

 

Here is the code for my Scroll View class that contains the HorizontalScrollView:

이것은 HorizontalScrollView를 포함하는 Scroll View 클래스에 대한 코드입니다.
public class CustomScrollView extends ScrollView {
    private GestureDetector mGestureDetector;

    public CustomScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mGestureDetector = new GestureDetector(context, new YScrollDetector());
        setFadingEdgeLength(0);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return super.onInterceptTouchEvent(ev) && mGestureDetector.onTouchEvent(ev);
    }

    // Return false if we're scrolling in the x direction  
    class YScrollDetector extends SimpleOnGestureListener {
        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {             
            return Math.abs(distanceY) > Math.abs(distanceX);
        }
    }
}

 

 

 가장 최근 달린 Solution 

This finally became a part of support v4 library, NestedScrollView. So, no longer local hacks is needed for most of cases I'd guess.

이것은 결국 대부분의 경우에 대해서 지역적 해결책이 더 이상 필요하지 않게 된 것으로 보입니다. 즉, 이제는 지원 v4 라이브러리의 NestedScrollView의 일부가 되었습니다.

 

 

 

출처 : https://stackoverflow.com/questions/2646028/horizontalscrollview-within-scrollview-touch-handling

반응형
댓글
공지사항
최근에 올라온 글