Mam ScrollView, który otacza cały mój układ, więc cały ekran można przewijać. Pierwszym elementem, który mam w tym ScrollView, jest blok HorizontalScrollView, który ma funkcje, które można przewijać w poziomie. Dodałem ontouchlistener do widoku przewijania w poziomie, aby obsługiwać zdarzenia dotykowe i zmusić widok do „przyciągania” do najbliższego obrazu w zdarzeniu ACTION_UP.
Tak więc efekt, który zamierzam, jest podobny do zwykłego ekranu głównego Androida, w którym można przewijać od jednego do drugiego, i przesuwa się na jeden ekran po podniesieniu palca.
To wszystko działa świetnie, z wyjątkiem jednego problemu: muszę przesuwać od lewej do prawej prawie idealnie poziomo, aby ACTION_UP kiedykolwiek się zarejestrował. Jeśli przynajmniej przesunę w pionie (co myślę, że wiele osób ma tendencję do robienia na swoich telefonach podczas przesuwania na boki), otrzymam ACTION_CANCEL zamiast ACTION_UP. Moja teoria jest taka, że dzieje się tak, ponieważ widok poziomy jest przewijany w widoku przewijania, a widok przewijania przejmuje dotyk pionowy, aby umożliwić przewijanie w pionie.
Jak mogę wyłączyć zdarzenia dotykowe dla widoku przewijania bezpośrednio z mojego poziomego widoku przewijania, ale nadal pozwolić na normalne przewijanie w pionie w innym miejscu widoku przewijania?
Oto przykład mojego kodu:
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
biblioteki.HomeFeatureLayout extends HorizontalScrollView
) tutaj velir.com/blog/index.php/2010/11/17/... Istnieją dodatkowe komentarze na temat tego, co się dzieje, gdy tworzona jest niestandardowa klasa przewijania.Odpowiedzi:
Aktualizacja: rozgryzłem to. W moim ScrollView musiałem przesłonić metodę onInterceptTouchEvent, aby przechwycić zdarzenie dotyku tylko wtedy, gdy ruch Y jest> ruchem X. Wygląda na to, że domyślnym zachowaniem ScrollView jest przechwytywanie zdarzenia dotyku, ilekroć występuje KAŻDY ruch Y. Dzięki tej poprawce ScrollView przechwyci zdarzenie tylko wtedy, gdy użytkownik celowo przewinie w kierunku Y iw takim przypadku przekaże ACTION_CANCEL dzieciom.
Oto kod mojej klasy Scroll View, która zawiera HorizontalScrollView:
źródło
mGestureDetector.onTouchEvent(ev)
zostaną wywołane. W tej chwili nie zostanie wywołany, jeślisuper.onInterceptTouchEvent(ev)
jest fałszywy. Właśnie natrafiłem na przypadek, w którym klikalne dzieci w widoku przewijania mogą przechwytywać zdarzenia dotykowe, a onScroll w ogóle nie zostanie wywołany. W przeciwnym razie dzięki, świetna odpowiedź!Dziękuję Joel za udzielenie mi wskazówek, jak rozwiązać ten problem.
Uprościłem kod (bez potrzeby użycia GestureDetector ), aby osiągnąć ten sam efekt:
źródło
Myślę, że znalazłem prostsze rozwiązanie, tylko to wykorzystuje podklasę ViewPager zamiast (macierzystego) ScrollView.
AKTUALIZACJA 2013-07-16 : Dodałem również przesłonięcie dla
onTouchEvent
. Może to pomóc w kwestiach wymienionych w komentarzach, chociaż YMMV.Jest to podobne do techniki stosowanej w onScroll () w android.widget.Gallery . Jest to dodatkowo wyjaśnione w prezentacji Google I / O 2013 Pisanie niestandardowych widoków na Androida .
Aktualizacja 2013-12-10 : Podobne podejście opisano również w poście od Kirilla Grouchnikova na temat (wtedy) aplikacji Android Market .
źródło
ScrollView
zLinearLayout
w którymUninterceptableViewPager
jest umieszczony. Rzeczywiście,ret
zawsze jest fałsz ... Jakiś pomysł, jak to naprawić?TableRow
co jest w środku,TableLayout
które jest w środkuScrollView
(tak, wiem ...), i działa zgodnie z przeznaczeniem. MożeonScroll
zamiast tego możesz spróbować zastąpićonInterceptTouchEvent
, tak jak robi to Google (linia 1010)Dowiedziałem się, że czasami jeden ScrollView odzyskuje ostrość, a drugi traci ostrość. Można temu zapobiec, przyznając tylko jeden punkt przewijania scrollView:
źródło
Nie działało to dla mnie dobrze. Zmieniłem to i teraz działa płynnie. Jeśli ktoś zainteresowany.
źródło
Dzięki Neevek jego odpowiedź zadziałała dla mnie, ale nie blokuje przewijania w pionie, gdy użytkownik zaczął przewijać widok poziomy (ViewPager) w kierunku poziomym, a następnie bez podnoszenia w pionie przewijania palcem zaczyna przewijać leżący poniżej widok kontenera (ScrollView) . Naprawiłem to, wprowadzając niewielką zmianę w kodzie Neevaka:
źródło
To w końcu stało się częścią biblioteki wsparcia v4, NestedScrollView . Więc, jak sądzę, w większości przypadków nie są już potrzebne lokalne hacki.
źródło
Rozwiązanie Neevek działa lepiej niż Joel na urządzeniach z wersją 3.2 i nowszą. W systemie Android występuje błąd, który powoduje wyjątek java.lang.IllegalArgumentException: pointerIndex poza zasięgiem, jeśli wykrywacz gestów jest używany w widoku scollview. Aby zduplikować problem, zaimplementuj niestandardowy widok scollview zgodnie z sugestią Joela i umieść w nim pager widoków. Jeśli przeciągniesz (nie unoś figury) w jednym kierunku (w lewo / w prawo), a następnie w przeciwnym kierunku, zobaczysz awarię. Również w rozwiązaniu Joela, jeśli przeciągniesz pager widoku, przesuwając palec po przekątnej, gdy palec opuści obszar widoku zawartości pager widok, pager powróci do poprzedniej pozycji. Wszystkie te problemy dotyczą bardziej wewnętrznego projektu Androida lub jego braku niż implementacji Joela, która sama w sobie jest sprytnym i zwięzłym kodem.
http://code.google.com/p/android/issues/detail?id=18990
źródło