J'ai un ScrollView qui entoure toute ma mise en page afin que tout l'écran puisse défiler. Le premier élément que j'ai dans ce ScrollView est un bloc HorizontalScrollView qui a des fonctionnalités qui peuvent être parcourues horizontalement. J'ai ajouté un écouteur d'écoute à la vue de défilement horizontale pour gérer les événements tactiles et forcer la vue à "s'accrochage" à l'image la plus proche sur l'événement ACTION_UP.
L'effet que je recherche est donc comme l'écran d'accueil Android d'origine, où vous pouvez faire défiler de l'un à l'autre et il s'enclenche sur un écran lorsque vous soulevez votre doigt.
Tout cela fonctionne très bien, sauf pour un problème: je dois balayer de gauche à droite presque parfaitement horizontalement pour qu'un ACTION_UP s'enregistre. Si je glisse verticalement à tout le moins (ce que je pense que beaucoup de gens ont tendance à faire sur leur téléphone en balayant d'un côté à l'autre), je recevrai un ACTION_CANCEL au lieu d'un ACTION_UP. Ma théorie est que cela est dû au fait que la vue de défilement horizontal est dans une vue de défilement, et que la vue de défilement détourne le toucher vertical pour permettre le défilement vertical.
Comment puis-je désactiver les événements tactiles pour la vue de défilement à partir de ma vue de défilement horizontale tout en permettant un défilement vertical normal ailleurs dans la vue de défilement?
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;
}
}
}
MeetMe's HorizontalListView
bibliothèque.HomeFeatureLayout extends HorizontalScrollView
) ici velir.com/blog/index.php/2010/11/17/… Il y a quelques commentaires supplémentaires sur ce qui se passe pendant que la classe de défilement personnalisée est composée.Réponses:
Mise à jour: j'ai compris cela. Sur mon ScrollView, j'avais besoin de remplacer la méthode onInterceptTouchEvent pour intercepter l'événement tactile uniquement si le mouvement Y est> le mouvement X. Il semble que le comportement par défaut d'un ScrollView consiste à intercepter l'événement tactile chaque fois qu'il y a un mouvement Y. Ainsi, avec le correctif, ScrollView n'interceptera l'événement que si l'utilisateur fait délibérément défiler dans la direction Y et dans ce cas, transmettra ACTION_CANCEL aux enfants.
Voici le code de ma classe Scroll View qui contient HorizontalScrollView:
la source
mGestureDetector.onTouchEvent(ev)
sera appelé. Comme c'est le cas maintenant, il ne sera pas appelé sisuper.onInterceptTouchEvent(ev)
est faux. Je viens de tomber sur un cas où les enfants cliquables dans la vue de défilement peuvent saisir les événements tactiles et onScroll ne sera pas appelé du tout. Sinon, merci, bonne réponse!Merci Joel de m'avoir donné 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:
la source
Je pense que j'ai trouvé une solution plus simple, seulement cela utilise une sous-classe de ViewPager au lieu de (son parent) ScrollView.
MISE À JOUR 2013-07-16 : J'ai également ajouté un remplacement pour
onTouchEvent
. Cela pourrait éventuellement aider avec les problèmes mentionnés dans les commentaires, bien que YMMV.Ceci est similaire à la technique utilisée dans onScroll () de android.widget.Gallery . Cela est expliqué plus en détail dans la présentation Google I / O 2013 Writing Custom Views for Android .
Mise à jour 2013-12-10 : Une approche similaire est également décrite dans un article de Kirill Grouchnikov sur l'application (alors) Android Market .
la source
ScrollView
avec unLinearLayout
dans lequel leUninterceptableViewPager
est placé. En effet, c'estret
toujours faux ... Une idée de comment résoudre ce problème?TableRow
qui est à l'intérieur d'unTableLayout
qui est à l'intérieur d'unScrollView
(ouais, je sais ...), et il fonctionne comme prévu. Vous pourriez peut-être essayer deonScroll
remplacer au lieu deonInterceptTouchEvent
, comme Google le fait (ligne 1010)J'ai découvert que parfois un ScrollView retrouve le focus et l'autre perd le focus. Vous pouvez éviter cela, en accordant uniquement l'un des focus scrollView:
la source
Ça ne fonctionnait pas bien pour moi. Je l'ai changé et maintenant ça marche bien. Si quelqu'un est intéressé.
la source
Grâce à Neevek, sa réponse a fonctionné pour moi, mais elle ne bloque pas le défilement vertical lorsque l'utilisateur a commencé à faire défiler la vue horizontale (ViewPager) dans le sens horizontal, puis sans lever le défilement vertical, il commence à faire défiler la vue du conteneur sous-jacent (ScrollView) . Je l'ai corrigé en faisant un léger changement dans le code de Neevak:
la source
Cela est finalement devenu une partie de la bibliothèque de support v4, NestedScrollView . Donc, plus de piratages locaux ne sont nécessaires dans la plupart des cas, je suppose.
la source
La solution de Neevek fonctionne mieux que celle de Joel sur les appareils exécutant la version 3.2 et supérieure. Il y a un bogue dans Android qui provoquera java.lang.IllegalArgumentException: pointerIndex hors de portée si un détecteur de gestes est utilisé à l'intérieur d'une scollview. Pour dupliquer le problème, implémentez un scollview personnalisé comme l'a suggéré Joel et placez un pager de vue à l'intérieur. Si vous faites glisser (ne soulevez pas votre silhouette) dans une direction (gauche / droite) puis dans l'autre sens, vous verrez le crash. Toujours dans la solution de Joel, si vous faites glisser le téléavertisseur de vue en déplaçant votre doigt en diagonale, une fois que votre doigt quitte la zone d'affichage du contenu du téléavertisseur, le téléavertisseur revient à sa position précédente. Tous ces problèmes sont davantage liés à la conception interne d'Android ou à son absence qu'à la mise en œuvre de Joel, qui est elle-même un morceau de code intelligent et concis.
http://code.google.com/p/android/issues/detail?id=18990
la source