Работаем с жестами в Android приложении
Работаем с жестами в Android приложении
Главная » Работаем с жестами в Android приложении

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

Начинаем с создания проекта. Названия оставим по умолчанию, но, конечно, можете написать что то свое. 

Сразу начнем с работы в основном layout файле activity_main.xml. Добавим туда следующие элементы:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
 xmlns:android="schemas.android.com/apk/res/android"
 android:orientation="vertical"
 android:layout_width="fill_parent"
 android:layout_height="fill_parent">
 <TextView
 android:layout_width="fill_parent"
 android:layout_height="wrap_content"
 android:text="Drag the droid around" />
 <FrameLayout
 android:id="@+id/graphics_holder"
 android:layout_height="match_parent"
 android:layout_width="match_parent"></FrameLayout>
</LinearLayout>

Важным моментом здесь является определение элемента FrameLayout. Именно в нем будет происходить основная работа приложения. 

Теперь перейдем к файлу MainActivity.java и немного изменим метод onCreate:

@Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);

 FrameLayout frame = (FrameLayout) findViewById(R.id.graphics_holder);
 PlayAreaView image = new PlayAreaView(this);
 frame.addView(image);
}

Мы объявили здесь класс под названием PlayAreaView и Android Studio на него ругается, но не пугайтесь, мы это исправим.

А сейчас мы выполним довольно интересную часть нашей работы - создадим холст для рисования жестов. Легкий способ это сделать заключается в том, чтобы создать объект для рисования Canvas (холст) с помощью метода onDraw. Это довольно просто и удобно, так как этот метод имеет всего один параметр: объект Canvas. Рисование в Canvas происходит с помощью вызова метода drawBitmap. Создаем в приложении новый класс по имени PlayAreaView.java и добавляем туда следующий код:

private class PlayAreaView extends View {
 private Matrix translate;
 private Bitmap droid;
 protected void onDraw(Canvas canvas) {
 canvas.drawBitmap(droid, translate, null);
 Matrix m = canvas.getMatrix();
 Log.d(DEBUG_TAG, "Matrix: "+translate.toShortString());
 Log.d(DEBUG_TAG, "Canvas: "+m.toShortString());
 }
}

В нашем случае выполнение метода onDraw довольно простое. Как это обычно делается, определите еще и переменную DEBUG_TAG. Основная работа в этой части кода происходит при вызове команды drawBitmap, где первым параметром является изображение для рисования, второй параметр задает матричный объект под названием translate, который, как подсказывает его имя, будет показывать, где именно на экране будет происходить рисование. Этот матричный объект будет связывать все действия пользователя, все его прикосновения к экрану, с холстом. 

Классу PlayAreaView нужно создать конструктор, выполняющий его самые базовые, начальные настройки. Так, как приложения будет реагировать на жесты, нам необходимо задать GestureDetector (определитель жестов). GestureDetector это класс, способный опознавать события типа жестов (прикасания пользователя к дисплею), проделывать над полученной от датчиков информацией о жестах математические операции, а потом, с помощью объекта GestureListener, создавать определенную обратную реакцию. Объект GestureListener обрабатывает входящую в него информацию и выдает ответ, который мы можем видеть и реагировать не него (в нашем случае это будет перемещение изображения по экрану). В GestureDetector существует множество возможностей, но мы ограничимся только самыми необходимыми. Добавим этот метод в код файла PlayAreaView.java:

public PlayAreaView(Context context) {
 super(context);
 translate = new Matrix();
 gestures = new GestureDetector(GestureFunActivity.this,
 new GestureListener(this));
 droid = BitmapFactory.decodeResource(getResources(),
 R.drawable.ic_launcher);
}

Взглянем на код немного пристальнее. Сначала мы инициализируем необходимый объект матрицы, который будет помогать нам в рисовании, и оно будет происходить в местах, определенных по умолчанию. Далее мы создаем объект GestureDetector и задаем GestureListener. Ну и напоследок, загружаем стандартную картинку, с которой будем работать в приложении. Вы можете использовать там любое изображение по собственному желанию. Это будет изображение, которое мы с помощью жестов будем заставлять перемещаться по рабочему холсту. 

Теперь давайте создадим объект GestureDector, который будет принимать данные о жестах и обрабатывать их. Для этого в том же классе PlayAreaView.java добавим следующее:

@Override
public boolean onTouchEvent(MotionEvent event) {
 return gestures.onTouchEvent(event);
}

В процессе работы с определением жестов с помощью GestureDector также нужно реализовать класс GestureListener. Основные жесты, в которых мы заинтересованы, это двойное нажатие по экрану и направленные движения (влево, вправо и т.д.). Для реализации определения этих движений, класс GestureListener должен выполнять интерфейсы OnGestureListener и OnDoubleTapListener:

private class GestureListener implements GestureDetector.OnGestureListener,
 GestureDetector.OnDoubleTapListener {
 PlayAreaView view;
 public GestureListener(PlayAreaView view) {
 this.view = view;
 }
}

После добавления этого класса, как подкласса Activity, добавьте все необходимые для них методы, которых жаждет Android Studio. Например, вот один из них:
 

@Override
public boolean onDown(MotionEvent e) {
 Log.v(DEBUG_TAG, "onDown");
 return true;
}

Выполнение всех этих методов позволит вам изучить самые различные события, которые улавливает объект GestureDetector. Объект MotionEvent связан с самыми разными событиями, которые могут происходить при нажатии пользователем на экран. 

Стоит отметить, что существует еще такой удобный метод, как SimpleOnGestureListener, который объединяет в себе функционал сразу двух интерфейсов: OnGestureListener и OnDoubleTapListener. В нем по умолчанию возвращается значение false

Первое движение, с которым мы хотим работать в нашем приложении, это прокрутка. Оно происходит тогда, когда пользователь прикасается к экрану и, не отрывая пальца, ведет им в какую либо сторону. Это движение мы можем определить в интерфейсе OnGestureListener с помощью метода onScroll:

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2,
 float distanceX, float distanceY) {
 Log.v(DEBUG_TAG, "onScroll");

 view.onMove(-distanceX, -distanceY);
 return true;
}

Выполнение этого метода является важным в связи с событием, когда пользователь ведет по экрану пальцем и вместе с этим начинает двигаться изображение на дисплее.

Изображение будет двигаться вдоль всего экрана, а иногда даже выходить за его границы. По определению, изображение будет видимым только тогда, когда его координаты находятся в рамках объекта для рисования (а его рамки у нас совпадают с размерами рабочего окна приложения), если же затащить изображение за эти границы, то оно будет не видимым. Также мы реализуем метод, который по двойному клику будет возвращать на экран заблудшее за рамками экрана изображение:

@Override
public boolean onDoubleTap(MotionEvent e) {
 Log.v(DEBUG_TAG, "onDoubleTap");
 view.onResetLocation();
 return true;
}

В принципе, в этом методе мы просто сбрасываем положение картинки на ее положение по умолчанию.

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

В нашем случае, мы будем изменять время разности между событием, спровоцировавшим движение, и остановкой инерциального движения, а потом просто запускать анимацию движения картинки к нужному месту с нужной скоростью. Для этого будет использоваться метод onFling. Задание кажется довольно сложным, но я не дам вам шанса над ним подумать и покажу готовый код:) :

@Override
public boolean onFling(MotionEvent e1, MotionEvent e2,
 final float velocityX, final float velocityY) {
 Log.v(DEBUG_TAG, "onFling");
 final float distanceTimeFactor = 0.4f;
 final float totalDx = (distanceTimeFactor * velocityX/2);
 final float totalDy = (distanceTimeFactor * velocityY/2);

 view.onAnimateMove(totalDx, totalDy,
 (long) (1000 * distanceTimeFactor));
 return true;
}

Нам даже не нужно выполнять никаких дополнительных проверок параметров, данные о скорости полностью решают нашу задачу. Скорость будет определяться в пискелях за секунду. Мы также можем использовать данные скорости для решения вопроса о том, где остановить движущийся объект, которым у нас является картинка. В нашем случае, мы используем в своих подсчетах 40% от секунды (то есть 400 мс). Данные о скорости будут передаваться в метод onAnimateMove, где и будет задаваться скорость движения изображения. Почему используется именно 400 мс? Потому, что это значение параметра наиболее качественно подходит к большинству устройств. Используя такое значение, мы будем видеть нормальное, относительно плавное движение, а не скачки или некрасивые замедления. Стоит отметить, что эти настройки скорости и  анимации не имеют ничего общего с реальной физикой, просто в данном примере так удобно.

Теперь все жесты, которые нам были интересны, учтены. В методе PlayAreaView.java добавим такой метод:

public void onMove(float dx, float dy) {
 translate.postTranslate(dx, dy);
 invalidate();
}

Он делает две вещи. Во первых, метод переводит (здесь "переводит" означает движение от точки A в B) изображение на расстояние в соответствии с движением пальца, во вторых, аннулирует предыдущее положение изображения. Каждый раз, когда происходит движение, мы будем просто обновлять нашу матрицу, заданную ранее. 

Теперь добавим onResetLocation метод:

public void onResetLocation() {
 translate.reset();
 invalidate();
} 

Этот метод будет просто сбрасывать все настройки на настройки по умолчанию. 

При движении картинки в заданном пальцем пользователя направлении нужно настроить анимацию так, чтобы она была плавной, проходила гладко, так сказать. В Android есть встроенные классы для анимации (вспоминаем предыдущие уроки по анимации, вот, вот и вот) но в нашем случае их сложно применить. Создадим для приложения свою собственную анимацию. В Android существуют множество интерполяторов, с помощью которых можно задать различную анимацию. Вот их мы и применим для создания анимации. Давайте создадим метод под названием onAnimateMove:

private Matrix animateStart;
private Interpolator animateInterpolator;
private long startTime;
private long endTime;
private float totalAnimDx;
private float totalAnimDy;

public void onAnimateMove(float dx, float dy, long duration) {
 animateStart = new Matrix(translate);
 animateInterpolator = new OvershootInterpolator();
 startTime = System.currentTimeMillis();
 endTime = startTime + duration;
 totalAnimDx = dx;
 totalAnimDy = dy;
 post(new Runnable() {
 @Override
 public void run() {
 onAnimateStep();
 }
 });
}

Здесь мы задали начальное положение, начальное и конечное время. Мы инициализируем анимацию, используя класс OvershootInterpolator, и настраиваем ее основные характеристики. Напоследок для завершения работы над анимацией нужно вызвать еще один метод под названием onAnimateStep:

private void onAnimateStep() {
 long curTime = System.currentTimeMillis();
 float percentTime = (float) (curTime - startTime)
 / (float) (endTime - startTime);
 float percentDistance = animateInterpolator
 .getInterpolation(percentTime);
 float curDx = percentDistance * totalAnimDx;
 float curDy = percentDistance * totalAnimDy;
 translate.set(animateStart);
 onMove(curDx, curDy);

 //Log.v(DEBUG_TAG, "We're " + percentDistance + " of the way there!");
 if (percentTime < 1.0f) {
 post(new Runnable() {
 @Override
 public void run() {
 onAnimateStep();
 }
 });
 }
 }

Вот, в принципе, и все основные шаги, которые нужно выполнить для создания программы, способной распознавать жесты пользователя и реагировать на них. Конечно, руководство далеко от идеала, но здесь можно почерпнуть много интересного и познавательного материала, что поможет немного разобраться в использовании подобных инструментов. Удачи в обучении!

Категория: Уроки программирования | Просмотров: 1702 | Добавил: Oleg | Теги: Android, жесты, программирование на Android, приложение | Рейтинг: 5.0/2
Всего комментариев: 0
avatar