У меня есть ScrollView, который окружает весь мой макет, так что весь экран можно прокручивать. Первый элемент, который у меня есть в этом ScrollView - это блок HorizontalScrollView, в котором есть функции, которые можно прокручивать по горизонтали. Я добавил ontouchlistener в представление horizontalscroll, чтобы обрабатывать сенсорные события и заставлять представление «привязываться» к ближайшему изображению в событии ACTION_UP.
Таким образом, эффект, на который я рассчитываю, похож на стандартный рабочий стол андроида, где вы можете перемещаться от одного к другому, и он поднимается на один экран, когда вы поднимаете палец.
Все это прекрасно работает, за исключением одной проблемы: мне нужно провести пальцем слева направо почти идеально по горизонтали, чтобы ACTION_UP когда-либо регистрировался. Если я проведу по крайней мере вертикально (как мне кажется, многие люди склонны делать это на своих телефонах при перелистывании из стороны в сторону), я получу ACTION_CANCEL вместо ACTION_UP. Моя теория заключается в том, что это происходит потому, что представление горизонтального прокрутки находится внутри просмотра прокрутки, а представление прокрутки захватывает вертикальное касание, чтобы обеспечить вертикальную прокрутку.
Как я могу отключить сенсорные события для просмотра с прокруткой прямо из моего горизонтального просмотра с прокруткой, но при этом разрешить нормальную вертикальную прокрутку в других местах просмотра с прокруткой?
Вот пример моего кода:
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/… Есть несколько дополнительных комментариев о том, что происходит при создании пользовательского класса прокрутки.Ответы:
Обновление: я понял это. В моем ScrollView мне нужно было переопределить метод onInterceptTouchEvent, чтобы перехватывать событие касания только в том случае, если движение Y - это> движение X. Кажется, что поведение ScrollView по умолчанию состоит в том, чтобы перехватывать событие касания всякий раз, когда есть ЛЮБОЕ движение Y. Таким образом, с исправлением ScrollView будет перехватывать событие, только если пользователь намеренно прокручивает в направлении Y и в этом случае передает ACTION_CANCEL детям.
Вот код для моего класса Scroll View, который содержит HorizontalScrollView:
источник
mGestureDetector.onTouchEvent(ev)
вызов будет вызван. Как и сейчас, он не будет вызван, еслиsuper.onInterceptTouchEvent(ev)
ложь. Я только что натолкнулся на случай, когда кликабельные дети в просмотре прокрутки могут получить сенсорные события, и onScroll вообще не будет вызываться. В противном случае, спасибо, отличный ответ!Спасибо, Джоэл, за подсказку, как решить эту проблему.
Я упростил код (без необходимости в GestureDetector ) для достижения того же эффекта:
источник
Я думаю, что нашел более простое решение, только при этом используется подкласс ViewPager вместо (его родительского) ScrollView.
ОБНОВЛЕНИЕ 2013-07-16 : Я также добавил переопределение для
onTouchEvent
. Это могло бы помочь с вопросами, упомянутыми в комментариях, хотя YMMV.Это похоже на технику, используемую в android.widget.Gallery onScroll () . Это объясняется в презентации Google I / O 2013 « Написание пользовательских представлений для Android» .
Обновление 2013-12-10 : аналогичный подход также описан в посте Кирилла Гручникова о (тогда) приложении Android Market .
источник
ScrollView
с,LinearLayout
в которомUninterceptableViewPager
находится. В самом деле,ret
всегда ложно ... Любая подсказка, как это исправить?TableRow
который находится внутри,TableLayout
который находится внутриScrollView
(да, я знаю ...), и он работает, как задумано. Возможно, вы могли бы попробовать переопределитьonScroll
вместо тогоonInterceptTouchEvent
, как это делает Google (строка 1010)Я обнаружил, что иногда ScrollView восстанавливает фокус, а другой теряет фокус. Вы можете предотвратить это, предоставив только один из фокусов scrollView:
источник
Это не сработало для меня. Я изменил это, и теперь это работает гладко. Если кому интересно.
источник
Благодаря Neevek его ответ сработал для меня, но он не блокирует вертикальную прокрутку, когда пользователь начинает прокручивать горизонтальный вид (ViewPager) в горизонтальном направлении, а затем, не поднимая прокрутку пальцем вертикально, начинает прокручивать вид базового контейнера (ScrollView) , Я исправил это, внеся небольшое изменение в код Neevak:
источник
Это наконец стало частью библиотеки поддержки v4, NestedScrollView . Таким образом, в большинстве случаев, я думаю, больше не нужны локальные хаки.
источник
Решение Neevek работает лучше, чем решение Joel на устройствах с версией 3.2 и выше. В Android есть ошибка, которая приводит к тому, что java.lang.IllegalArgumentException: pointerIndex выходит за пределы диапазона, если детектор жестов используется внутри scollview. Чтобы продублировать проблему, создайте пользовательский скроллвью, как предложил Джоэл, и поместите пейджер внутри. Если вы перетащите (не поднимайте фигуру) в одном направлении (влево / вправо), а затем в противоположном направлении, вы увидите сбой. Также в решении Джоэла, если вы перетаскиваете пейджер просмотра, перемещая палец по диагонали, как только ваш палец покинет область просмотра содержимого пейджера просмотра, пейджер вернется в прежнее положение. Все эти проблемы больше связаны с внутренним дизайном Android или его отсутствием, чем с реализацией Джоэла, которая сама по себе является частью умного и лаконичного кода.
http://code.google.com/p/android/issues/detail?id=18990
источник