Статьи

Android жесты и сенсорная механика

Я думаю, что мы все согласны с тем, что мобильные технологии стали важной частью эпохи технологий и сейчас являются одним из основных направлений деятельности разработчиков. Одной из причин успеха мобильных устройств является возможность взаимодействия с пользователями с помощью жестов и специальных возможностей. Мобильные технологии установили новые ограничения для термина « взаимодействие с пользователем» .

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

Мобильные Жесты

В этой статье я представлю некоторые из наиболее важных взаимодействий и жестов пользователей Android, а также реализую собственные жесты Android и сенсорные события.

Чтобы создать полностью интерактивное приложение, разработчику нужно больше, чем просто onClick поэтому Android SDK поддерживает различные жесты для обнаружения. Это позволяет разработчикам создавать способы взаимодействия пользователей с приложениями. Android предоставляет класс GestureDetector для обнаружения основных событий движения и MotionEvent для обработки более сложных жестов.

1. GestureDetector

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

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

Обнаружение общих жестов

Следующий пример является примером общих жестов класса GestureDetector . Сначала объявите экземпляр GestureDetector внутри вашего класса Activity.

  private GestureDetector mGestureDetector; 

Создайте класс Java внутри класса Activity, который реализует интерфейсы GestureDetector.OnGestureListener и GestureDetector.OnDoubleTapListener для обнаружения основных жестов Android.

  class Android_Gesture_Detector implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener { @Override public boolean onDown(MotionEvent e) { Log.d("Gesture ", " onDown"); return true; } @Override public boolean onSingleTapConfirmed(MotionEvent e) { Log.d("Gesture ", " onSingleTapConfirmed"); return true; } @Override public boolean onSingleTapUp(MotionEvent e) { Log.d("Gesture ", " onSingleTapUp"); return true; } @Override public void onShowPress(MotionEvent e) { Log.d("Gesture ", " onShowPress"); } @Override public boolean onDoubleTap(MotionEvent e) { Log.d("Gesture ", " onDoubleTap"); return true; } @Override public boolean onDoubleTapEvent(MotionEvent e) { Log.d("Gesture ", " onDoubleTapEvent"); return true; } @Override public void onLongPress(MotionEvent e) { Log.d("Gesture ", " onLongPress"); } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { Log.d("Gesture ", " onScroll"); if (e1.getY() < e2.getY()){ Log.d("Gesture ", " Scroll Down"); } if(e1.getY() > e2.getY()){ Log.d("Gesture ", " Scroll Up"); } return true; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { if (e1.getX() < e2.getX()) { Log.d("Gesture ", "Left to Right swipe: "+ e1.getX() + " - " + e2.getX()); Log.d("Speed ", String.valueOf(velocityX) + " pixels/second"); } if (e1.getX() > e2.getX()) { Log.d("Gesture ", "Right to Left swipe: "+ e1.getX() + " - " + e2.getX()); Log.d("Speed ", String.valueOf(velocityX) + " pixels/second"); } if (e1.getY() < e2.getY()) { Log.d("Gesture ", "Up to Down swipe: " + e1.getX() + " - " + e2.getX()); Log.d("Speed ", String.valueOf(velocityY) + " pixels/second"); } if (e1.getY() > e2.getY()) { Log.d("Gesture ", "Down to Up swipe: " + e1.getX() + " - " + e2.getX()); Log.d("Speed ", String.valueOf(velocityY) + " pixels/second"); } return true; } } 

Класс Android_Gesture_Detector будет перехватывать все реализованные в нем основные жесты, но чтобы использовать эти жесты, я переопределяю метод onTouchEvent() Activity, чтобы перехватывать эти сенсорные события и перенаправлять их в класс Android_Gesture_Detector .

  @Override public boolean onTouchEvent(MotionEvent event) { mGestureDetector.onTouchEvent(event); return super.onTouchEvent(event); // Return true if you have consumed the event, false if you haven't. // The default implementation always returns false. } 

Давайте запустим детектор жестов. В onCreate() добавьте следующее, и детектор жестов Android готов обнаруживать взаимодействия с пользователем.

  // Create an object of the Android_Gesture_Detector Class Android_Gesture_Detector android_gesture_detector = new Android_Gesture_Detector(); // Create a GestureDetector mGestureDetector = new GestureDetector(this, android_gesture_detector); 

Тестирование класса жестов

Запустите приложение и начните взаимодействовать с приложением. Результат жестов отобразится в окне Android Studio LogCat.

Тестовый вывод

После обнаружения вы можете создать функциональность приложения внутри переопределенных методов.

Щепотка жест

Еще один полезный жест в любом приложении — это возможность масштабировать элементы пользовательского интерфейса. Android предоставляет класс ScaleGestureDetector для обработки жестов при масштабировании видов. Следующий код представляет собой реализацию простого приложения для Android, которое использует класс ScaleListener для выполнения жеста повышения в imageView и масштабирования его на основе движений пальцев.

 import android.app.Activity; import android.os.Bundle; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.widget.ImageView; import android.widget.Toast; public class MainActivity extends Activity { private ImageView imageView; private float scale = 1f; private ScaleGestureDetector detector; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); imageView=(ImageView)findViewById(R.id.imageView); detector = new ScaleGestureDetector(this,new ScaleListener()); } public boolean onTouchEvent(MotionEvent event) { // re-route the Touch Events to the ScaleListener class detector.onTouchEvent(event); return super.onTouchEvent(event); } private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { float onScaleBegin = 0; float onScaleEnd = 0; @Override public boolean onScale(ScaleGestureDetector detector) { scale *= detector.getScaleFactor(); imageView.setScaleX(scale); imageView.setScaleY(scale); return true; } @Override public boolean onScaleBegin(ScaleGestureDetector detector) { Toast.makeText(getApplicationContext(),"Scale Begin" ,Toast.LENGTH_SHORT).show(); onScaleBegin = scale; return true; } @Override public void onScaleEnd(ScaleGestureDetector detector) { Toast.makeText(getApplicationContext(),"Scale Ended",Toast.LENGTH_SHORT).show(); onScaleEnd = scale; if (onScaleEnd > onScaleBegin){ Toast.makeText(getApplicationContext(),"Scaled Up by a factor of " + String.valueOf( onScaleEnd / onScaleBegin ), Toast.LENGTH_SHORT ).show(); } if (onScaleEnd < onScaleBegin){ Toast.makeText(getApplicationContext(),"Scaled Down by a factor of " + String.valueOf( onScaleBegin / onScaleEnd ), Toast.LENGTH_SHORT ).show(); } super.onScaleEnd(detector); } } } 

Класс ScaleListener переопределяет три метода onScale , onScaleBegin и onScaleEnd . Во время выполнения метода onScale изменение размера imageView выполняется на основе масштабного коэффициента движения пальца на экране устройства.

Щепотка жест

2. Движение Событие

Android Simple GestureDetector полезен для базовых жестов. Но для любого жеста, требующего двух (или более) касаний, вам нужно искать в другом месте. Для реализации нестандартной и более сложной сенсорной механики мы используем Motion Event. В большинстве приложений вам не нужно зацикливаться на реализации собственного сенсорного события, но если вы когда-нибудь захотите создать полностью интерактивное приложение с классными сенсорными событиями, этот класс — решение. Ядро класса MotionEvent захватывает сенсорные события в Activity, переопределяя onTouchEvent() вызов onTouchEvent() или Views, используя метод setOnTouchListener() для прослушивания сенсорных событий в представлении. События сенсорного просмотра можно View.OnTouchListener реализовав View.OnTouchListener в классе Activity, как я сделаю в следующем примере.

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

ACTION_DOWN

Для первого указателя, который касается экрана. Новый штрих начался.

ACTION_MOVE

Произошло изменение в жесте касания. Палец движется.

ACTION_UP

Последний указатель покидает экран.

ACTION_POINTER_DOWN

Для дополнительных указателей, которые выходят на экран после первого. (мультитач)

ACTION_POINTER_UP

Отправляется, когда неосновной указатель идет вверх. Указатель вверх (мультитач)

ACTION_CANCEL

Событие касания было отменено, что-то еще взяло под контроль событие.

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

Давайте начнем с этих событий касания. Создайте активность Android, которая реализует View.OnTouchListener . В файле макета задания задайте основной относительный макет идентификатор rootLayout . Как реализовано в примере, представления этого действия смогут отвечать на сенсорные события, инициируемые действиями пользователя в этом макете.

Идея состоит в том, чтобы добавить элементы imageView в корневой макет, каждый из которых может действовать в зависимости от события касания. Чтобы добавить новые imageView в макет, мы добавляем кнопку, которая вызывает Add_Image() каждом нажатии.

Эта функция создает imageView , устанавливает для него ресурс изображения, создает набор параметров макета, прикрепляет их к imageView , добавляет этот imageView к корневому макету и устанавливает прослушиватель Touch для этого представления.

  private void Add_Image() { final ImageView iv = new ImageView(this); iv.setImageResource(R.drawable.image); RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(150, 150); iv.setLayoutParams(layoutParams); RootLayout.addView(iv, layoutParams); iv.setOnTouchListener(this); } 

Наиболее важной частью является представление TouchListener . Слушатель выполняет действия, основанные на событиях касания, описанных выше.

Во время события ACTION_DOWN я получаю ACTION_DOWN X и Y касания и ACTION_DOWN соответствующие поля вида, чтобы получить точную точку внутри вида, в котором было выполнено касание.

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

Во время ACTION_MOVE я реализовал действия для интересных функций, описанных выше. Одна классная вещь в классе MotionEvent — это возможность определять количество пальцев, которые коснулись экрана, с помощью getPointerCount() .

Один палец — двигай взгляд.

Получите LayoutParams вида и сбросьте их в зависимости от движения пальца на экране. Левое поле делается равным позиции события касания за вычетом значения X касания внутри вида, установленного во время ACTION_DOWN поэтому представление будет перемещаться в зависимости от значения X пальца в макете и относительно выравнивать себя в зависимости от положения касания в представлении.

Та же логика применяется к верхнему полю, но основана на оси Y. Для правого и нижнего полей установлено значение 500, чтобы изображение не сжималось при касании правой и нижней границ макета, а скрывалось за ним.

 RelativeLayout.LayoutParams Params = (RelativeLayout.LayoutParams) view.getLayoutParams(); Params.leftMargin = X - Position_X; Params.topMargin = Y - Position_Y; Params.rightMargin = -500; Params.bottomMargin = -500; view.setLayoutParams(Params); 

Два пальца — растянуть вид

Получите LayoutParams вида и измените ширину и высоту вида. Во время этого действия изменения размера поля обзора не изменяются, и изменение размера будет изменяться в зависимости от движения первого пальца, поэтому размеры будут установлены в соответствии со значениями положения первого пальца на макете. Константы Possition_X и Possition_Y не влияют на изменение размера, они предназначены для визуальных целей.

  RelativeLayout.LayoutParams layoutParams1 = (RelativeLayout.LayoutParams) view.getLayoutParams(); layoutParams1.width = Position_X +(int)event.getX(); layoutParams1.height = Position_Y + (int)event.getY(); view.setLayoutParams(layoutParams1); 

Три пальца — вращай вокруг

Вращение вида довольно просто. Получите текущее вращение представления, добавьте к нему константу с плавающей точкой и снова установите его для представления с setRotation() метода setRotation() во время ACTION_MOVE с тремя пальцами.

 view.setRotation(view.getRotation() + 10.0f); 

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

Полный код действия MotionEvent теперь:

 import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.os.Bundle; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.ImageView; import android.widget.RelativeLayout; public class MainActivity extends Activity implements View.OnTouchListener { int clickCount; private ViewGroup RootLayout; private int Position_X; private int Position_Y; long startTime = 0 ; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); RootLayout = (ViewGroup) findViewById(R.id.rootLayout); //new image Button NewImage = (Button)findViewById(R.id.new_image_button); NewImage.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Add_Image(); } }); clickCount = 0; } private void Add_Image() { final ImageView iv = new ImageView(this); iv.setImageResource(R.drawable.image); RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(150, 150); iv.setLayoutParams(layoutParams); RootLayout.addView(iv, layoutParams); iv.setOnTouchListener(this); } public boolean onTouch(final View view, MotionEvent event) { final int X = (int) event.getRawX(); final int Y = (int) event.getRawY(); int pointerCount = event.getPointerCount(); switch (event.getAction() & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) view.getLayoutParams(); Position_X = X - layoutParams.leftMargin; Position_Y = Y - layoutParams.topMargin; break; case MotionEvent.ACTION_UP: if (startTime == 0){ startTime = System.currentTimeMillis(); }else { if (System.currentTimeMillis() - startTime < 200) { AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); builder.setMessage("Are you sure you want to delete this?"); builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { view.setVisibility(View.GONE); } }); builder.setNegativeButton("No", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); AlertDialog alertDialog = builder.create(); alertDialog.show(); } startTime = System.currentTimeMillis(); } break; case MotionEvent.ACTION_POINTER_DOWN: break; case MotionEvent.ACTION_POINTER_UP: break; case MotionEvent.ACTION_MOVE: if (pointerCount == 1){ RelativeLayout.LayoutParams Params = (RelativeLayout.LayoutParams) view.getLayoutParams(); Params.leftMargin = X - Position_X; Params.topMargin = Y - Position_Y; Params.rightMargin = -500; Params.bottomMargin = -500; view.setLayoutParams(Params); } if (pointerCount == 2){ RelativeLayout.LayoutParams layoutParams1 = (RelativeLayout.LayoutParams) view.getLayoutParams(); layoutParams1.width = Position_X +(int)event.getX(); layoutParams1.height = Position_Y + (int)event.getY(); view.setLayoutParams(layoutParams1); } //Rotation if (pointerCount == 3){ //Rotate the ImageView view.setRotation(view.getRotation() + 10.0f); } break; } // Schedules a repaint for the root Layout. RootLayout.invalidate(); return true; } } 

Тестирование приложения MotionEvent

Переместить взгляды

Переместить виды

Изменить размер представления

Изменить размер просмотров

Spin view

Spin View

Вывод

В этой статье я представил два основных типа классов жестов Android с базовыми знаниями, которые помогут вам начать их использовать. Используйте то, что вы узнали здесь, чтобы улучшить взаимодействие с пользователем вашего приложения и дайте мне знать, если у вас есть какие-либо вопросы или комментарии.