स्क्रॉल व्यू टच हैंडलिंग के भीतर क्षैतिज दृश्य


226

मेरे पास एक स्क्रॉल दृश्य है जो मेरे पूरे लेआउट को घेरता है ताकि पूरी स्क्रीन स्क्रॉल करने योग्य हो। इस स्क्रॉल दृश्य में जो पहला तत्व है, वह एक क्षैतिज क्षैतिज अवरोधक खंड है जिसमें ऐसी विशेषताएँ हैं जिन्हें क्षैतिज रूप से स्क्रॉल किया जा सकता है। मैंने स्पर्श घटनाओं को संभालने और 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;
        }
    }
}

मैंने इस पोस्ट में सभी तरीकों की कोशिश की है, लेकिन उनमें से कोई भी मेरे लिए काम नहीं करता है। मैं MeetMe's HorizontalListViewपुस्तकालय का उपयोग कर रहा हूं ।
घुमंतू

कुछ इसी तरह के कोड ( HomeFeatureLayout extends HorizontalScrollView) के साथ एक लेख है यहाँ velir.com/blog/index.php/2010/11/17/… कस्टम स्क्रॉल क्लास के रूप में क्या चल रहा है, इस बारे में कुछ अतिरिक्त टिप्पणियां हैं।
सीजेबीएस

जवाबों:


280

अद्यतन: मैंने यह पता लगाया। मेरे स्क्रॉल दृश्य पर, मुझे केवल स्पर्श घटना को रोकने के लिए onInceptceptTouchEvent विधि को ओवरराइड करने की आवश्यकता है यदि Y गति> X गति है। ऐसा लगता है कि स्क्रॉल दृश्य का डिफ़ॉल्ट व्यवहार जब भी कोई वाई गति होती है तो स्पर्श घटना को रोकना होता है। तो फिक्स के साथ, स्क्रॉलव्यू केवल उस घटना को रोक देगा, जब उपयोगकर्ता जानबूझकर Y दिशा में स्क्रॉल कर रहा है और उस स्थिति में बच्चों को ACTION_CANCEL पास करें।

यहाँ मेरे स्क्रॉल दृश्य वर्ग के लिए कोड है जिसमें क्षैतिज क्षैतिज दृश्य शामिल हैं:

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

मैं इसका उपयोग कर रहा हूं, लेकिन एक्स दिशा में स्क्रॉल करने के समय मुझे सार्वजनिक बूलियन onInterceptTouchEvent (MotionEvent ev) {return super.onInterceptTouchvent (ev) && mGestureDetector.onTouchEvent (ev) पर अपवाद पूर्ण अपवाद मिल रहा है; } मैंने स्क्रोलव्यू के अंदर हॉरिजॉन्टल स्क्रॉल का इस्तेमाल किया है
विपिन साहू

@ यह काम कर रहा है धन्यवाद, मैं स्क्रॉलव्यू कंस्ट्रक्टर में जेस्चर डिटेक्टर को आरंभीकृत करना भूल गया
विपिन साहू

स्क्रोलव्यू के अंदर ViewPager को लागू करने के लिए मुझे इस कोड का उपयोग कैसे करना चाहिए
Harsha MV

इस कोड के साथ मेरे कुछ मुद्दे थे जब मुझे इसमें हमेशा एक विस्तारित ग्रिड दृश्य था, कभी-कभी यह स्क्रॉल नहीं करता था। मुझे YScrollDetector में onDown (...) पद्धति को हमेशा के लिए ओवरराइड करना पड़ा, क्योंकि यह पूरे दस्तावेज़ में सुझाए गए अनुसार सही है (जैसे कि developer.android.com/training/custom-views/… ) इसने मेरी समस्या हल कर दी।
नेमेनजा कोवेसेविक

3
मैं बस एक छोटे बग में भाग गया, जो ध्यान देने योग्य है। मेरा मानना ​​है कि onInceptceptTouchEvent में कोड को दो बूलियन कॉल को अलग करना चाहिए, यह गारंटी देने के लिए कि mGestureDetector.onTouchEvent(ev)उन्हें कॉल किया जाएगा। जैसा कि अभी है, यह super.onInterceptTouchEvent(ev)झूठ नहीं कहा जाएगा । मैं सिर्फ एक मामले में भाग गया, जहां स्क्रॉलव्यू में क्लिक करने योग्य बच्चे स्पर्श की घटनाओं को पकड़ सकते हैं और ऑनक्रॉस को बिल्कुल भी नहीं बुलाया जाएगा। अन्यथा, धन्यवाद, महान जवाब!
जीली

176

इस समस्या को हल करने के तरीके के बारे में मुझे एक सुराग देने के लिए आपको जोएल धन्यवाद।

मैंने समान प्रभाव को प्राप्त करने के लिए कोड (बिना 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);
    }
}

उत्कृष्ट, इस रिफ्लेक्टर के लिए धन्यवाद। सूची दृश्य के निचले भाग पर स्क्रॉल करते समय मुझे उपरोक्त दृष्टिकोण के साथ समस्याएँ मिल रही थीं क्योंकि बाल तत्वों पर किसी भी स्पर्श व्यवहार को किसी भी तरह से बाधित नहीं किया जाना शुरू किया गया था, वाई / एक्स आंदोलन अनुपात। अजीब!
डोरी

1
धन्यवाद! कस्टम दृश्य सूची के साथ एक सूची दृश्य के अंदर एक ViewPager के साथ भी काम किया।
शारिक शेख

2
बस इसके साथ स्वीकृत उत्तर को बदल दिया है और यह अब मेरे लिए बहुत बेहतर काम कर रहा है। धन्यवाद!
डेविड स्कॉट

1
@VipinSahu, स्पर्श चाल की दिशा बताने के लिए, आप वर्तमान X निर्देशांक और अंतिम X का डेल्टा ले सकते हैं, यदि यह 0 से बड़ा है, तो स्पर्श बाएं से दाएं की ओर बढ़ रहा है, अन्यथा दाएं से बाएं। और फिर आप अगली गणना के लिए अंतिम X के रूप में वर्तमान X को सहेजते हैं।
neevek

1
क्षैतिज स्क्रॉलव्यू के बारे में क्या?
जिन विन हेटेट

60

मुझे लगता है कि मुझे एक सरल समाधान मिला, केवल यह (इसके मूल) स्क्रॉलव्यू के बजाय व्यूपेजर के उपवर्ग का उपयोग करता है।

अद्यतन 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 : इसी तरह के दृष्टिकोण को किरिल ग्रोचनिकोव के एक पोस्ट में (तब) एंड्रॉइड मार्केट ऐप के बारे में बताया गया है


बूलियन रिट = super.onInterceptTouchEvent (ev); केवल कभी मेरे लिए झूठे लौटे। एक स्क्रॉलव्यू में UninterceptableViewPager का उपयोग करना
scottyab

यह मेरे लिए काम नहीं करता, हालांकि मुझे यह पसंद है कि यह सरलता है। मैं एक के ScrollViewसाथ उपयोग करता हूं LinearLayoutजिसमें UninterceptableViewPagerएक रखा गया है। वास्तव में, retहमेशा गलत है ... कोई सुराग कैसे इसे ठीक करना है?
पीटरडक

@scottyab & @Peterdk: ठीक है, मेरा एक में है TableRowजो अंदर है TableLayoutजो एक के अंदर है ScrollView(हाँ, मुझे पता है ...), और यह इरादा के अनुसार काम कर रहा है। हो सकता है कि आप onScrollइसके बजाय ओवरराइड करने की कोशिश कर सकें onInterceptTouchEvent, जैसे Google करता है (पंक्ति 1010)
जियोगोस काइलाफस

मैं सिर्फ सच के रूप में हार्डकोड रिटायर्ड हूं और यह ठीक काम करता है। यह पहले भी गलत हो रहा था, लेकिन मेरा मानना ​​है कि क्योंकि स्क्रॉलव्यू में एक रैखिक लेआउट है जो सभी बच्चों को रखता है
अम्नि

11

मुझे पता चला है कि somethimes एक स्क्रॉल दृश्य ध्यान केंद्रित करता है और दूसरा ध्यान केंद्रित करता है। आप केवल स्क्रॉल दृश्य फ़ोकस देने के द्वारा इसे रोक सकते हैं:

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

क्या यू एडाप्टर गुजर रहे हैं?
हर्षा एमवी

मैंने फिर से जाँच की और महसूस किया कि यह वास्तव में एक स्क्रॉलव्यू है जिसका मैं उपयोग नहीं कर रहा हूँ, लेकिन एक व्यूपेजर और मैं इसे फ्रागमेंटस्टैटेगैजर एडेप्टर से गुजार रहा हूँ जिसमें गैलरी के लिए सभी चित्र शामिल हैं।
मारियस उल्लसित

8

यह मेरे लिए अच्छा काम नहीं कर रहा था। मैंने इसे बदल दिया और अब यह सुचारू रूप से काम करता है। अगर किसी को दिलचस्पी है।

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

यह मेरे प्रोजेक्ट का जवाब है। मेरे पास एक दृश्य पेजर है जो एक गैलरी के रूप में कार्य करता है और एक स्क्रॉलव्यू में क्लिक किया जा सकता है। मैं ऊपर दिए गए समाधान का उपयोग करता हूं, यह क्षैतिज स्क्रॉल पर काम करता है, लेकिन मैं पेजर की छवि पर क्लिक करने के बाद एक नई गतिविधि शुरू करता हूं और वापस जाता हूं, पेजर स्क्रॉल नहीं कर सकता। यह अच्छी तरह से काम करता है, tks!
longkai

मेरे लिए पूरी तरह से काम करता है। मेरे पास एक स्क्रॉल दृश्य के अंदर एक कस्टम "स्वाइप टू अनलॉक" दृश्य था जो मुझे वही परेशानी दे रहा था। इस समाधान से समस्या हल हो गई।
हाइब्रिड

6

नीवेक के लिए धन्यवाद उनके जवाब ने मेरे लिए काम किया, लेकिन यह ऊर्ध्वाधर स्क्रॉलिंग को लॉक नहीं करता है जब उपयोगकर्ता ने क्षैतिज दिशा (ViewPager) को क्षैतिज दिशा में स्क्रॉल करना शुरू कर दिया है और फिर उंगली को ऊपर उठाए बिना ऊर्ध्वाधर रूप से अंतर्निहित कंटेनर दृश्य (स्क्रॉल दृश्य) को स्क्रॉल करना शुरू कर देता है । मैंने इसे नीवाक के कोड में थोड़ा बदलाव करके इसे ठीक किया:

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

}

5

यह अंत में समर्थन v4 पुस्तकालय, NestedScrollView का एक हिस्सा बन गया । इसलिए, अब मुझे लगता है कि ज्यादातर मामलों के लिए स्थानीय हैक की आवश्यकता नहीं है।


1

नीवेक का समाधान 3.2 और ऊपर चलने वाले उपकरणों पर जोएल से बेहतर काम करता है। एंड्रॉइड में एक बग है जो java.lang.IllegalArgumentException का कारण बनेगा: पॉइंटरइंडेक्स रेंज से बाहर अगर एक जेस्चर डिटेक्टर का उपयोग स्कोलव्यू के अंदर किया जाता है। इस मुद्दे की नकल करने के लिए, कस्टम स्कोव्यू को लागू करें जैसा कि जोएल ने सुझाव दिया था और एक दृश्य पेजर को अंदर रखा था। यदि आप एक दिशा (बाएं / दाएं) को खींचते हैं (आप आंकड़ा नहीं उठाते हैं) और फिर इसके विपरीत, तो आप दुर्घटना देखेंगे। जोएल के समाधान में भी, यदि आप अपनी उंगली को तिरछे घुमाकर व्यू पेजर को खींचते हैं, तो एक बार आपकी उंगली व्यू पेजर के कंटेंट व्यू एरिया को छोड़ देती है, पेजर वापस अपनी पिछली स्थिति में आ जाएगा। ये सभी मुद्दे एंड्रॉइड के आंतरिक डिजाइन या जोएल के कार्यान्वयन की तुलना में इसकी कमी के साथ करने के लिए अधिक हैं, जो स्वयं स्मार्ट और संक्षिप्त कोड का एक टुकड़ा है।

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.