Pull to refresh

Слайдинг в двух — нет, в четырех направлениях

Reading time 4 min
Views 4.7K
На Хабре не раз встречались статьи про слайдинг экранов под Android ViewPager, ViewPagerIndicator. Я хочу предложить вариант слайдинга в четырех направлениях.



Не так давно у меня возникла необходимость сделать слайдинг экрана в четрех направлениях: влево, вправо, вверх и вниз. Поиски по интернету не дали положительного результата, или может я плохо искал. Так что в итоге я решил сделать то, что мне нужно сам.

Надо сказать, что начал я не с нуля, за основу была положена реализация слайдинга по горизонтали отсюда RealViewSwitcher.
Итак, вся реализация размещается в одном классе TwoDirectionsViewSwitcher, который наследуется от ViewGroup. Для реализации необходимого функционала, достаточно переопределить следующие методы базового класса:

В методе onMeasure задаются размеры для будущих экранов:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)


В onLayout производится компоновка экранов и собственно разделение на строки (количество строк передается в конструкторе):

protected void onLayout(boolean changed, int l, int t, int r, int b)


Сам конструктор в который передается контекст приложения и количество строк, на которые необходимо разбить общий список экранов:

public TwoDirectionsViewSwitcher(Context context, int rows)


Следующий, ключевой, метод обработки прикосновений к экрану onTouchEvent:

public boolean onTouchEvent(MotionEvent ev)


И последний, метод расчета скролла дочерних экранов computeScroll:
public void computeScroll()


Теперь несколько подробнее остановимся на некоторых ключевых методах класса. Метод onLayout.

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

Более сложный метод — это обработчик прикосновений к экрану onTouchEvent.

Здесь он мало отличается от типовой реализации, обрабатываются различные типы действий:
  • MotionEvent.ACTION_DOWN — прикосновение к экрану;
  • MotionEvent.ACTION_MOVE — движение по экрану после прикосновения;
  • MotionEvent.ACTION_UP — окончание движение (поднятие пальца с экрана)

В обработчике соытия MotionEvent.ACTION_DOW определяется факт начала движения по экрану, задаются текущие положения по X и Y нажатия экрана и задается признак начала скроллинга.

В обработчике MotionEvent.ACTION_MOVE обрабатывается смещение по эрану (случай, когда смещение превысило возвращаемое методом ViewConfiguration.get(getContext()).getScaledTouchSlop() значение), вычисляется значение скролла по X и Y и определяется направление движения по абсолютному значению смещения по осям:

  // приращение по X
  final int deltaX = (int) (mLastMotionX - x);
  // приращение по Y
  final int deltaY = (int) (mLastMotionY - y);
  mLastMotionX = x;
  mLastMotionY = y;
  // смещение View относительно экрана по осям
  final int scrollX = getScrollX();
  final int scrollY = getScrollY();
  // определение направления перемещения, по горизонтали или вертикали
  if (Math.abs(deltaX) > Math.abs(deltaY) && isMoveBegin) {
     isMoveBegin = false;
     isXMove = true;
     isYMove = false;
  } else if (Math.abs(deltaX) < Math.abs(deltaY) && isMoveBegin) {
     isMoveBegin = false;
     isXMove = false;
     isYMove = true;
  }

   // обработка движения по горизонтали
   if (isXMove) {
       if (deltaX < 0) {
         if (scrollX > 0) {
            scrollBy(Math.max(-scrollX, deltaX), 0);
         }
      } else if (deltaX > 0) {
         final int availableToScroll = getChildAt(getChildCount() / mRows -1).getRight() - scrollX - getWidth();
           if (availableToScroll > 0) {
               scrollBy(Math.min(availableToScroll, deltaX), 0);
           }
      }
  } 
  // обработка движения по вертикали 
     else if (isYMove) {
              if (deltaY < 0) {
                if (scrollY > 0) {
                       scrollBy(0, Math.max(-scrollY, deltaY));
               }
             } else if (deltaY > 0) {
                  final int availableToScroll = getChildAt(getChildCount() - 1).getBottom() - scrollY - getHeight();
                     if (availableToScroll > 0) {
                             scrollBy(0, Math.min(availableToScroll, deltaY));
                     }
             }
      }
 }
 break;


Кроме того для ортогонального перемещения по осям введены три переменные isXMove, isYMove, isMoveBegin. Последняя из которых принимает значение true при обработке MotionEvent.ACTION_DOWN.

Дальше собственно идет скроллинг экрана с использованием нескольких утилитных методов.

Обработчик MotionEvent.ACTION_UP отвечает за завершение прокрутки экрана и обнуление рабочих переменных. Если перемещение экрана произошло больше чем на половину, происходит пролистывание на следующий экран, в противном случае старый экран возвращается в свое начальное положение.

Еще хотелось бы остановиться на моменте, когда нужно переместиться не на первый экран, а на произвольный, при создании экземпляра класса. Для этого создан конструктор:
    public TwoDirectionsViewSwitcher(Context context, int rows, int currentScreen) {
        super(context);
        mCurrentScreen = currentScreen;
        mRows = rows;
        init();
    }

В методе onMeasure при первом открытии обрабатывается заданная изначально позиция:
        if (mFirstLayout) {
            int row = mCurrentScreen / (getChildCount() / mRows);
            int cell = mCurrentScreen % (getChildCount() / mRows);
            scrollTo(cell * width, row * height);
            mFirstLayout = false;
        }

Конечно, данный класс не претендует на завершенность, в нем, например, стоило бы реализовать обработку изменения расположения экрана (сейчас он корректно работает в режиме Landscape), добавить эффектов, учитывать кратность общего числа экранов и количества строк, но даже в таком виде он выполняет те функции, для которых я его собственно и разрабатывал — перемещение в четырех направлениях.

Полный код проекта с примером использования опубликован на github TwoDirectionsViewSwitcher.
Tags:
Hubs:
+19
Comments 4
Comments Comments 4

Articles