Question

J'ai un ScrollView qui entoure ma mise en page toute sorte que tout l'écran est scrollable. Le premier élément j'ai dans ce ScrollView est un bloc de HorizontalScrollView qui a des caractéristiques qui peuvent être parcourues en horizontalement. J'ai ajouté un ontouchlistener au horizontalscrollview aux événements tactiles de poignée et de la force en vue de « snap » à l'image la plus proche de l'événement ACTION_UP.

Alors l'effet que je vais faire est comme le stock Android homescreen où vous pouvez passer d'un à l'autre et qu'il se mette à un écran lorsque vous soulevez votre doigt.

Tout cela fonctionne très bien sauf pour un problème: je dois glisser de gauche à droite presque parfaitement horizontale pour un ACTION_UP à jamais enregistrer. Si je Balayez vers la verticale dans tout le moins (que je pense que beaucoup de gens ont tendance à faire sur leur téléphone quand glisser côté à l'autre), je recevrai un ACTION_CANCEL au lieu d'un ACTION_UP. Ma théorie est que cela est parce que le horizontalscrollview est dans un scrollview et le scrollview est Détournement la touche verticale pour permettre le défilement vertical.

Comment puis-je désactiver les événements tactiles pour le scrollview d'un peu dans mon scrollview horizontal, mais encore permettre le défilement vertical normal ailleurs dans le scrollview?

Voici un exemple de mon 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;
        }
    }
}
Était-ce utile?

La solution

Mise à jour: je me suis dit cela. Sur mon ScrollView, je devais passer outre la méthode onInterceptTouchEvent pour intercepter seulement l'événement tactile si le mouvement Y est> le mouvement X. Il semble que le comportement par défaut d'un ScrollView est d'intercepter l'événement touche chaque fois qu'il ya une motion Y. Donc, avec le correctif, le ScrollView ne fera que intercepter l'événement si l'utilisateur défile délibérément dans la direction Y et dans ce passage de cas au large de la ACTION_CANCEL aux enfants.

Voici le code pour mon défilement classe qui contient la HorizontalScrollView:

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

Autres conseils

Merci Joel pour me donner un indice sur la façon de résoudre ce problème.

J'ai simplifié le code (sans besoin d'un GestureDetector ) pour obtenir le même effet:

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

Je pense avoir trouvé une solution plus simple, que celui-ci utilise une sous-classe de ViewPager au lieu de (son parent) ScrollView.

UPDATE 16/07/2013 : I ajouté un remplacement pour onTouchEvent aussi bien. Il pourrait éventuellement aider avec les questions mentionnées dans les commentaires, bien que 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;
    }
}

Ceci est similaire à la technique utilisée dans OnScroll de android.widget.Gallery () . Il est expliqué plus en détail par la présentation Google I / O 2013 écriture Vues personnalisées pour Android .

Mise à jour 10/12/2013 : Une approche similaire est également décrit dans un poste de Kirill Grouchnikov sur l'application Android Market (alors) .

J'ai découvert que parfois un Regagne ScrollView se concentrer et l'autre perd le focus. Vous pouvez empêcher que, en n'accordant un des foyers 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;
        }
    });

Il ne fonctionnait pas bien pour moi. Je l'ai changé et maintenant il fonctionne bien. Si quelqu'un intéressé.

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

Merci à Neevek sa réponse a fonctionné pour moi, mais il ne se verrouille pas le défilement vertical lorsque l'utilisateur a commencé à faire défiler l'affichage horizontal (ViewPager) dans le sens horizontal et sans soulever le défilement du doigt verticalement, il commence à faire défiler la vue du conteneur sous-jacent (ScrollView). Je l'ai fixé en faisant un léger changement dans le code de 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);

}

Celle-ci deviendra une partie de soutien aux bibliothèques v4, NestedScrollView . Donc, pas plus hacks local est nécessaire pour la plupart des cas, je suppose.

La solution de Neevek fonctionne mieux que Joël sur les appareils fonctionnant sous 3.2 et au-dessus. Il y a un bug dans Android qui provoque java.lang.IllegalArgumentException: pointerIndex hors de portée si un détecteur de mouvement est utilisé à l'intérieur d'un scollview. Pour dupliquer le problème, mettre en œuvre un scollview sur mesure que Joel a suggéré et mettre un téléavertisseur vue de l'intérieur. Si vous faites glisser (ne pas vous soulevez la figure) dans une direction (gauche / droite), puis à l'opposé, vous verrez l'accident. Aussi dans la solution de Joel, si vous faites glisser le pager vue en déplaçant votre doigt en diagonale, une fois que votre doigt laisser zone afficher le contenu du pager vue, le téléavertisseur ressort à sa position précédente. Toutes ces questions sont plus à voir avec la conception interne Android ou l'absence de mise en œuvre de la que Joel, qui est lui-même un morceau de code intelligent et concis.

http://code.google.com/p/android/ questions / détail? id = 18990

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top