Intereting Posts
Как проверить доступность пространства на внешнем хранилище? Как я могу интегрировать Facebook LoginButton в PreferenceScreen Распознавание активности в Android не работает Баттерфнайн Посмотреть инъекцию Клавиатура bluetooth приведет к разрушению и восстановлению активности Простая регистрация пользователя с помощью Amazon Cognito Как я могу получить доступ к исходным ресурсам в проектах тестирования Android? Длительное время сборки с sbt android-plugin Как легко генерировать звуки синтезаторов в Android? Как реализовать ViewPager с различными фрагментами / макетами Чистая архитектура: как отразить изменения уровня данных в пользовательском интерфейсе Как настроить представление контента NativeActivity для компонента, созданного на Java Количество ссылок метода в файле .dex не может превышать 64k API 17 Как сжать JSONObject отправить его через Http в Android? Избавьтесь от строки под TabWidget

ГоризонтальныйScrollView в ScrollView Touch Handling

У меня есть ScrollView, который окружает весь мой макет, чтобы весь экран прокручивался. Первый элемент, который у меня есть в этом ScrollView, представляет собой блок HorizontalScrollView, который имеет функции, которые можно прокручивать по горизонтали. Я добавил ontouchlistener в horizontalscrollview для обработки событий касания и заставил представление «привязать» к ближайшему изображению в событии ACTION_UP.

Таким образом, эффект, который я собираюсь сделать, похож на рабочий стол Android android, на котором вы можете переходить от одного к другому, и он защелкивается на один экран, когда вы поднимаете палец.

Все это отлично работает, за исключением одной проблемы: мне нужно прорисовывать слева направо почти идеально горизонтально, чтобы ACTION_UP когда-либо регистрировался. Если я по крайней мере прокручусь по вертикали (что, как я думаю, многие люди склонны делать на своих телефонах при размахивании бок о бок), я получаю ACTION_CANCEL вместо ACTION_UP. Моя теория заключается в том, что это связано с тем, что horizontalscrollview находится в пределах scrollview, а scrollview захватывает вертикальное касание, чтобы разрешить вертикальную прокрутку.

Как отключить сенсорные события для scrollview только в пределах моего горизонтального прокрутки, но все же разрешить обычную вертикальную прокрутку в другом месте прокрутки?

Вот пример моего кода:

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

}

Solutions Collecting From Web of "ГоризонтальныйScrollView в ScrollView Touch Handling"

Обновление: я понял это. В моем ScrollView мне нужно было переопределить метод onInterceptTouchEvent, чтобы только перехватить событие касания, если движение Y – это движение X. По-видимому, поведение ScrollView по умолчанию – это перехват события касания всякий раз, когда происходит ЛЮБОЕ Y-движение. Таким образом, с помощью исправления ScrollView будет только перехватывать событие, если пользователь намеренно прокручивается в направлении Y, и в этом случае передайте ACTION_CANCEL детям.

Вот код для моего класса Scroll View, который содержит 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); } } } 

Спасибо, Джоэл за то, что дал мне понять, как решить эту проблему.

Я упростил код (без необходимости в GestureDetector ) для достижения такого же эффекта:

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

Я думаю, что я нашел более простое решение, только в этом случае используется подкласс ViewPager вместо (его родительского) ScrollView.

UPDATE 2013-07-16 : Я добавил переопределение для onTouchEvent . Возможно, это поможет в вопросах, упомянутых в комментариях, хотя 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; } } 

Это похоже на технику, используемую в android.widget.Gallery's onScroll () . Это также объясняется презентацией Google I / O 2013 « Написание пользовательских представлений для Android» .

Обновление 2013-12-10 : аналогичный подход также описан в сообщении Кирилла Гроучникова о приложении Android Market .

Я узнал, что когда-то один ScrollView восстанавливает фокус, а другой теряет фокус. Вы можете предотвратить это, только предоставив один из scrollView focus:

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

Благодаря Neevek его ответ сработал для меня, но он не блокирует вертикальную прокрутку, когда пользователь начал прокручивать горизонтальный вид (ViewPager) в горизонтальном направлении, а затем не поднимая прокрутку пальца по вертикали, он начинает прокручивать вид контейнера (ScrollView) , Я исправил это, сделав небольшое изменение в коде 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); } 

Это было плохо для меня. Я изменил его, и теперь он работает плавно. Если кто-то заинтересован.

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

}

Это, наконец, стало частью библиотеки поддержки v4, NestedScrollView . Таким образом, больше не нужны местные хаки для большинства случаев, я бы предположил.

Решение Neevek работает лучше, чем Joel's на устройствах, работающих на 3,2 и выше. В Android есть ошибка, которая вызовет java.lang.IllegalArgumentException: pointerIndex выходит за пределы диапазона, если детектор жестов используется внутри scollview. Чтобы продублировать проблему, создайте пользовательский scollview, как предложил Джоэл, и поместите в него пейджер представления. Если вы перетащите (не поднимайте фигуру) в одно направление (влево / вправо), а затем в противоположное, вы увидите крушение. Также в решении Джоэла, если вы перетащите зрительный пейджер, перемещая палец по диагонали, как только ваш палец покинет область просмотра содержимого пейджера, пейджер вернется в свою предыдущую позицию. Все эти проблемы в большей степени связаны с внутренним дизайном Android или его отсутствием, чем с реализацией Joel, которая сама по себе является кусочком умного и сжатого кода.

http://code.google.com/p/android/issues/detail?id=18990