Programing

ScrollView Touch 처리 내의 HorizontalScrollView

lottogame 2020. 4. 20. 19:17
반응형

ScrollView Touch 처리 내의 HorizontalScrollView


전체 화면을 스크롤 할 수 있도록 전체 레이아웃을 둘러싼 ScrollView가 있습니다. 이 ScrollView에있는 첫 번째 요소는 가로로 스크롤 할 수있는 기능이있는 HorizontalScrollView 블록입니다. 터치 이벤트를 처리하고보기가 ACTION_UP 이벤트에서 가장 가까운 이미지로 "스냅"되도록 수평 터치 스크린에 ontouchlistener를 추가했습니다.

그래서 내가 가고있는 효과는 주식 안드로이드 홈 화면과 같습니다. 여기서 한 화면에서 다른 화면으로 스크롤 할 수 있으며 손가락을 들어 올리면 한 화면에 스냅됩니다.

이것은 하나의 문제를 제외하고는 모두 잘 작동합니다 .ACTION_UP이 등록되도록 왼쪽에서 오른쪽으로 거의 완벽하게 가로로 스 와이프해야합니다. 내가 수직으로 스 와이프하면 (많은 사람들이 좌우로 스 와이프 할 때 휴대 전화에서하는 경향이 있다고 생각합니다), ACTION_UP 대신 ACTION_CANCEL을받습니다. 내 이론은 가로 스크롤보기가 스크롤보기 내에 있고 스크롤보기가 세로 스크롤을 허용하기 위해 세로 터치를 가로 채고 있기 때문입니다.

가로 스크롤보기 내에서 스크롤보기의 터치 이벤트를 비활성화하고 스크롤보기의 다른 곳에서 일반 세로 스크롤을 허용하는 방법은 무엇입니까?

다음은 내 코드 샘플입니다.

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

업데이트 : 나는 이것을 알아 냈습니다. ScrollView에서 Y 모션이> X 모션 인 경우 터치 이벤트 만 가로 채기 위해 onInterceptTouchEvent 메서드를 재정의해야했습니다. ScrollView의 기본 동작은 Y 모션이있을 때마다 터치 이벤트를 가로 채는 것 같습니다. 따라서 수정으로 ScrollView는 사용자가 의도적으로 Y 방향으로 스크롤하는 경우에만 이벤트를 가로 채고 그 경우 ACTION_CANCEL을 자식에게 전달합니다.

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

이 문제를 해결하는 방법에 대한 힌트를 주신 Joel에게 감사합니다.

동일한 효과를 얻기 위해 코드를 단순화했습니다 ( GestureDetector 필요 없음 ).

public class VerticalScrollView extends ScrollView {
    private float xDistance, yDistance, lastX, lastY;

    public VerticalScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                xDistance = yDistance = 0f;
                lastX = ev.getX();
                lastY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                final float curX = ev.getX();
                final float curY = ev.getY();
                xDistance += Math.abs(curX - lastX);
                yDistance += Math.abs(curY - lastY);
                lastX = curX;
                lastY = curY;
                if(xDistance > yDistance)
                    return false;
        }

        return super.onInterceptTouchEvent(ev);
    }
}

더 간단한 해결책을 찾았습니다. 이것 만 (상위) ScrollView 대신 ViewPager의 하위 클래스를 사용합니다.

업데이트 2013-07-16 : 나는 또한 재정의를 추가했습니다 onTouchEvent. YMMV이지만 주석에 언급 된 문제에 도움이 될 수 있습니다.

public class UninterceptableViewPager extends ViewPager {

    public UninterceptableViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean ret = super.onInterceptTouchEvent(ev);
        if (ret)
            getParent().requestDisallowInterceptTouchEvent(true);
        return ret;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean ret = super.onTouchEvent(ev);
        if (ret)
            getParent().requestDisallowInterceptTouchEvent(true);
        return ret;
    }
}

이것은 android.widget.Gallery의 onScroll ()에서 사용되는 기술 과 유사합니다 . 자세한 내용은 Google I / O 2013 프레젠테이션 Android 용 맞춤보기 작성에서 설명합니다 .

2013-12-10 업데이트 : Kirill Grouchnikov의 게시물에 Android 마켓 앱에 대한 비슷한 접근 방식이 설명되어 있습니다 .


하나의 ScrollView가 초점을 다시 얻고 다른 하나는 초점을 잃는 것을 발견했습니다. scrollView 포커스 중 하나만 부여하여이를 방지 할 수 있습니다.

    scrollView1= (ScrollView) findViewById(R.id.scrollscroll);
    scrollView1.setAdapter(adapter);
    scrollView1.setOnTouchListener(new View.OnTouchListener() {

        @Override
        public boolean onTouch(View v, MotionEvent event) {
            scrollView1.getParent().requestDisallowInterceptTouchEvent(true);
            return false;
        }
    });

그것은 나를 위해 잘 작동하지 않았다. 나는 그것을 바꾸었고 이제는 원활하게 작동합니다. 관심이 있다면

public class ScrollViewForNesting extends ScrollView {
    private final int DIRECTION_VERTICAL = 0;
    private final int DIRECTION_HORIZONTAL = 1;
    private final int DIRECTION_NO_VALUE = -1;

    private final int mTouchSlop;
    private int mGestureDirection;

    private float mDistanceX;
    private float mDistanceY;
    private float mLastX;
    private float mLastY;

    public ScrollViewForNesting(Context context, AttributeSet attrs,
            int defStyle) {
        super(context, attrs, defStyle);

        final ViewConfiguration configuration = ViewConfiguration.get(context);
        mTouchSlop = configuration.getScaledTouchSlop();
    }

    public ScrollViewForNesting(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public ScrollViewForNesting(Context context) {
        this(context,null);
    }    


    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {      
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mDistanceY = mDistanceX = 0f;
                mLastX = ev.getX();
                mLastY = ev.getY();
                mGestureDirection = DIRECTION_NO_VALUE;
                break;
            case MotionEvent.ACTION_MOVE:
                final float curX = ev.getX();
                final float curY = ev.getY();
                mDistanceX += Math.abs(curX - mLastX);
                mDistanceY += Math.abs(curY - mLastY);
                mLastX = curX;
                mLastY = curY;
                break;
        }

        return super.onInterceptTouchEvent(ev) && shouldIntercept();
    }


    private boolean shouldIntercept(){
        if((mDistanceY > mTouchSlop || mDistanceX > mTouchSlop) && mGestureDirection == DIRECTION_NO_VALUE){
            if(Math.abs(mDistanceY) > Math.abs(mDistanceX)){
                mGestureDirection = DIRECTION_VERTICAL;
            }
            else{
                mGestureDirection = DIRECTION_HORIZONTAL;
            }
        }

        if(mGestureDirection == DIRECTION_VERTICAL){
            return true;
        }
        else{
            return false;
        }
    }
}

Neevek 덕분에 그의 대답은 나를 위해 일했지만 사용자가 가로보기 (ViewPager)를 가로 방향으로 스크롤하기 시작했을 때 세로 스크롤을 잠그지 않고 손가락 스크롤을 세로로 올리지 않으면 기본 컨테이너보기 (ScrollView)를 스크롤하기 시작합니다 . Neevak의 코드를 약간 변경하여 수정했습니다.

private float xDistance, yDistance, lastX, lastY;

int lastEvent=-1;

boolean isLastEventIntercepted=false;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            xDistance = yDistance = 0f;
            lastX = ev.getX();
            lastY = ev.getY();


            break;

        case MotionEvent.ACTION_MOVE:
            final float curX = ev.getX();
            final float curY = ev.getY();
            xDistance += Math.abs(curX - lastX);
            yDistance += Math.abs(curY - lastY);
            lastX = curX;
            lastY = curY;

            if(isLastEventIntercepted && lastEvent== MotionEvent.ACTION_MOVE){
                return false;
            }

            if(xDistance > yDistance )
                {

                isLastEventIntercepted=true;
                lastEvent = MotionEvent.ACTION_MOVE;
                return false;
                }


    }

    lastEvent=ev.getAction();

    isLastEventIntercepted=false;
    return super.onInterceptTouchEvent(ev);

}

이것은 마침내 지원 v4 라이브러리 인 NestedScrollView 의 일부가되었습니다 . 따라서 대부분의 경우 로컬 해킹이 더 이상 필요하지 않습니다.


Neevek의 솔루션은 3.2 이상을 실행하는 장치에서 Joel의 솔루션보다 잘 작동합니다. 안드로이드에 java.lang.IllegalArgumentException : scollview 내에서 제스처 탐지기가 사용되는 경우 pointerIndex가 범위를 벗어난 버그가 있습니다. 이 문제를 복제하려면 Joel이 제안한대로 사용자 지정 scollview를 구현하고 뷰 호출기를 안에 넣으십시오. 한 방향 (왼쪽 / 오른쪽)으로 끌고 (그림을 올리지 마십시오) 반대 방향으로 끌면 충돌이 나타납니다. 또한 Joel의 솔루션에서 손가락을 대각선으로 움직여보기 호출기를 드래그하면 손가락이보기 호출기의 컨텐츠보기 영역을 벗어나면 호출기가 이전 위치로 돌아갑니다. 이 모든 문제는 Joel의 구현보다 Android의 내부 디자인 또는 부족과 관련이 있습니다.이 자체는 똑똑하고 간결한 코드입니다.

http://code.google.com/p/android/issues/detail?id=18990

참고 URL : https://stackoverflow.com/questions/2646028/horizontalscrollview-within-scrollview-touch-handling

반응형