Ich habe eine ScrollView, die mein gesamtes Layout umgibt, sodass der gesamte Bildschirm scrollbar ist. Das erste Element in dieser ScrollView ist ein HorizontalScrollView-Block mit Funktionen, durch die horizontal gescrollt werden kann. Ich habe der horizontalen Bildlaufansicht einen Ontouchlistener hinzugefügt, um Berührungsereignisse zu verarbeiten und die Ansicht zu zwingen, am nächsten Bild des ACTION_UP-Ereignisses zu "fangen".
Der Effekt, den ich anstrebe, ist also wie der Standard-Android-Homescreen, auf dem Sie von einem zum anderen scrollen können und der auf einem Bildschirm einrastet, wenn Sie Ihren Finger heben.
Dies alles funktioniert bis auf ein Problem hervorragend: Ich muss fast perfekt horizontal von links nach rechts wischen, damit sich ein ACTION_UP jemals registriert. Wenn ich im geringsten vertikal wische (was meiner Meinung nach viele Leute auf ihren Handys tun, wenn sie von einer Seite zur anderen wischen), erhalte ich eine ACTION_CANCEL anstelle einer ACTION_UP. Meine Theorie ist, dass dies daran liegt, dass sich die horizontale Bildlaufansicht innerhalb einer Bildlaufansicht befindet und die Bildlaufansicht die vertikale Berührung entführt, um ein vertikales Bildlauf zu ermöglichen.
Wie kann ich die Berührungsereignisse für die Bildlaufansicht nur in meiner horizontalen Bildlaufansicht deaktivieren und trotzdem das normale vertikale Bildlauf an anderer Stelle in der Bildlaufansicht zulassen?
Hier ist ein Beispiel meines Codes:
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
Bibliothek.HomeFeatureLayout extends HorizontalScrollView
) hier velir.com/blog/index.php/2010/11/17/… Es gibt einige zusätzliche Kommentare dazu, was passiert , wenn die benutzerdefinierte Bildlaufklasse zusammengesetzt wird.Antworten:
Update: Ich habe das herausgefunden. In meiner ScrollView musste ich die onInterceptTouchEvent-Methode überschreiben, um das Berührungsereignis nur abzufangen, wenn die Y-Bewegung> die X-Bewegung ist. Es scheint, als ob das Standardverhalten einer ScrollView darin besteht, das Berührungsereignis abzufangen, wenn eine Y-Bewegung vorliegt. Mit dem Fix fängt die ScrollView das Ereignis nur ab, wenn der Benutzer absichtlich in die Y-Richtung scrollt und in diesem Fall die ACTION_CANCEL an die untergeordneten Elemente weitergibt.
Hier ist der Code für meine Scroll View-Klasse, die die HorizontalScrollView enthält:
quelle
mGestureDetector.onTouchEvent(ev)
dieser aufgerufen wird. So wie es jetzt ist, wird es nicht aufgerufen, wennsuper.onInterceptTouchEvent(ev)
es falsch ist. Ich bin gerade auf einen Fall gestoßen, in dem anklickbare Kinder in der Bildlaufansicht die Berührungsereignisse erfassen können und onScroll überhaupt nicht aufgerufen wird. Ansonsten danke, tolle Antwort!Vielen Dank, Joel, dass Sie mir einen Hinweis gegeben haben, wie ich dieses Problem lösen kann.
Ich habe den Code vereinfacht (ohne dass ein GestureDetector erforderlich ist ), um den gleichen Effekt zu erzielen:
quelle
Ich glaube, ich habe eine einfachere Lösung gefunden. Nur diese verwendet eine Unterklasse von ViewPager anstelle von (der übergeordneten) ScrollView.
UPDATE 2013-07-16 : Ich habe auch eine Überschreibung für hinzugefügt
onTouchEvent
. Es könnte möglicherweise bei den in den Kommentaren genannten Problemen helfen, obwohl YMMV.Dies ähnelt der in onScroll () von android.widget.Gallery verwendeten Technik . Weitere Informationen finden Sie in der Google I / O 2013-Präsentation Schreiben von benutzerdefinierten Ansichten für Android .
Update 10.12.2013 : Ein ähnlicher Ansatz wird auch in einem Beitrag von Kirill Grouchnikov über die (damalige) Android Market App beschrieben .
quelle
ScrollView
mit einem,LinearLayout
in das dasUninterceptableViewPager
gelegt wird. In der Tat istret
immer falsch ... Gibt es einen Hinweis, wie man das behebt?TableRow
die sich in einer befindet,TableLayout
die sich in einer befindetScrollView
(ja, ich weiß ...), und sie funktioniert wie beabsichtigt. Vielleicht könnten Sie versuchen,onScroll
stattdessen zu überschreibenonInterceptTouchEvent
, wie es Google tut (Zeile 1010)Ich habe herausgefunden, dass manchmal eine ScrollView den Fokus wiedererlangt und die andere den Fokus verliert. Sie können dies verhindern, indem Sie nur einen der scrollView-Fokusse gewähren:
quelle
Es hat bei mir nicht gut funktioniert. Ich habe es geändert und jetzt funktioniert es reibungslos. Wenn jemand interessiert ist.
quelle
Dank Neevek hat seine Antwort für mich funktioniert, aber das vertikale Scrollen wird nicht gesperrt, wenn der Benutzer mit dem Scrollen der horizontalen Ansicht (ViewPager) in horizontaler Richtung begonnen hat. Ohne den Finger vertikal zu scrollen, wird die zugrunde liegende Containeransicht (ScrollView) gescrollt. . Ich habe das Problem behoben, indem ich Neevaks Code leicht geändert habe:
quelle
Dies wurde schließlich Teil der Support v4-Bibliothek NestedScrollView . Daher werden in den meisten Fällen keine lokalen Hacks mehr benötigt.
quelle
Die Lösung von Neevek funktioniert besser als die von Joel auf Geräten mit Version 3.2 und höher. Es gibt einen Fehler in Android, der java.lang.IllegalArgumentException verursacht: pointerIndex außerhalb des Bereichs, wenn ein Gestenmelder in einer Scollview verwendet wird. Um das Problem zu duplizieren, implementieren Sie eine benutzerdefinierte Ansicht, wie Joel vorgeschlagen hat, und fügen Sie einen Ansichtspager ein. Wenn Sie Ihre Figur in eine Richtung (links / rechts) und dann in die entgegengesetzte Richtung ziehen (nicht anheben), sehen Sie den Absturz. Wenn Sie in Joels Lösung den Ansichtspager ziehen, indem Sie Ihren Finger diagonal bewegen, springt der Pager an seine vorherige Position zurück, sobald Ihr Finger den Inhaltsansichtsbereich des Ansichtspagers verlässt. All diese Probleme haben mehr mit dem internen Design oder dem Fehlen von Android zu tun als mit Joels Implementierung, die selbst ein Stück intelligenter und prägnanter Code ist.
http://code.google.com/p/android/issues/detail?id=18990
quelle