В этом руководстве будут рассмотрены альтернативные методы мобильного взаимодействия, такие как трекболы, мыши и стилусы. Чтобы изучить эту концепцию, мы сосредоточимся на улучшении графического приложения для добавления поддержки этих устройств ввода.
Android-устройств сейчас много и они разные, с различными аппаратными средствами управления и версиями программного обеспечения. В недавней серии мы создали приложение для рисования для Android, используя взаимодействие с сенсорным экраном, но некоторые пользователи могут получать доступ к вашим приложениям разными способами. Например, платформа Android работает на устройствах с трекболом, стилусами и даже традиционной мышью и клавиатурой. В этом учебном пособии мы рассмотрим варианты, которые вы можете изучить при создании приложений, доступных для ряда пользовательских устройств, а также выделите дополнительные возможности, которые предлагают эти модели взаимодействия.
В этом учебном пособии мы будем ссылаться на параметры, которые вы можете увидеть в действии, если посмотрите приложение Android SDK API Demos. Вы можете найти это в папке установки вашего SDK по адресу samples / android- <версия> / ApiDemos . Соответствующие классы для начала — TouchPaint и FingerPaint , хотя оба они ссылаются на другие классы в приложении. Приложение FingerPaint использует взаимодействие с сенсорным экраном, а приложение TouchPaint поддерживает взаимодействие с трекболом, мышью и стилусом. Вы можете открыть, изучить и поэкспериментировать с приложением API Demos в Eclipse, запустив его на эмуляторе или устройстве, чтобы ознакомиться с функциональностью.
Мы не будем создавать приложение в этом руководстве, но мы укажем, как можно улучшить любые функции рисования, которые вы используете, в соответствии с целями, которые вы ставите перед собой. Мы укажем, как использовать код, который мы охватываем, если вы создадите приложение из серии рисунков, и вы сможете использовать это руководство в качестве вдохновения для возможных расширений функциональности в нем.
Загружаемый исходный код содержит классы Java для приложения для рисования, которое мы создали в серии (плюс рисунки и непрозрачность) с добавленной ниже функцией трекбола. Мы сосредоточимся в первую очередь на взаимодействии с трекболом, поскольку оно является наиболее сложным и принципиально отличается от взаимодействия с сенсорным экраном. Мы также будем использовать это для изучения деталей сенсорного взаимодействия на более глубоком уровне, чем в серии.
1. Подготовка
Шаг 1
Хотя на самом деле мы не будем создавать новое приложение в этом учебном пособии, давайте сначала рассмотрим примерный отрывок функций рисования, которые вы можете использовать, чтобы опробовать варианты, которые мы рассмотрим ниже. Если вы создали приложение для рисования серии, вы можете использовать его, чтобы опробовать новые опции.
Типичное приложение для рисования на Android будет иметь собственный вид, представляющий область холста, на которой пользователи могут рисовать. Ниже приведен основной пример такого класса View:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
|
public class DrawingView extends View {
//drawing path
private Path drawPath;
//drawing and canvas paint
private Paint drawPaint, canvasPaint;
//initial color
private int paintColor = 0xFFFF0000;
//canvas
private Canvas drawCanvas;
//canvas bitmap
private Bitmap canvasBitmap;
//constructor
public DrawingView(Context context, AttributeSet attrs){
super(context, attrs);
setupDrawing();
}
//prepare drawing
private void setupDrawing(){
drawPath = new Path();
drawPaint = new Paint();
drawPaint.setColor(paintColor);
drawPaint.setAntiAlias(true);
drawPaint.setStrokeWidth(50);
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.ROUND);
canvasPaint = new Paint(Paint.DITHER_FLAG);
}
//view assigned size
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
canvasBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
drawCanvas = new Canvas(canvasBitmap);
}
//draw view
@Override
protected void onDraw(Canvas canvas) {
canvas.drawBitmap(canvasBitmap, 0, 0, canvasPaint);
canvas.drawPath(drawPath, drawPaint);
}
//respond to touch interaction
@Override
public boolean onTouchEvent(MotionEvent event) {
float touchX = event.getX();
float touchY = event.getY();
//respond to down, move and up events
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
drawPath.moveTo(touchX, touchY);
break;
case MotionEvent.ACTION_MOVE:
drawPath.lineTo(touchX, touchY);
break;
case MotionEvent.ACTION_UP:
drawPath.lineTo(touchX, touchY);
drawCanvas.drawPath(drawPath, drawPaint);
drawPath.reset();
break;
default:
return false;
}
//redraw
invalidate();
return true;
}
}
|
В этом случае View облегчает рисование с помощью прослушивателя onTouchEvent для сенсорных экранов, поэтому он идеально подходит для взаимодействия в стиле рисования пальцем. Когда пользователь касается экрана, путь перемещается к начальной точке его пальца. Когда они двигают пальцем, все еще касаясь экрана, путь переходит от предыдущей позиции к новой. Когда пользователь убирает палец с экрана, путь передается и рисуется.
Обратите внимание, что в вышеприведенном классе рисунок реализован путем определения координат X и Y того, где палец пользователя коснулся экрана, независимо от того, прикоснулись ли они к нему в данный момент, перетаскивают его или просто подняли. Эта модель взаимодействия работает точно так же, если пользователь рисует с помощью мыши или стилуса, хотя они предлагают дополнительные параметры, которые мы рассмотрим ниже. Если пользователь выбирает рисование с помощью трекбола, нам нужна другая реализация, через которую мы будем работать дальше. Слушатель событий трекбола также получает параметр MotionEvent , но информация, которую мы можем извлечь из него, отличается.
2. Трекбол Взаимодействие
Шаг 1
Если пользователь взаимодействует с помощью трекбола, вы можете справиться с этим несколькими способами. В примере кода TouchPaint перемещение трекбола в области рисования обрабатывается так же, как и касание и перемещение по нему пальца, мыши или стилуса, с серией овальных фигур, нарисованных вдоль пути, по которому следует пользователь. Поскольку в серии приложений для рисования мы приняли несколько иной подход (указанный в пользовательском классе View выше), мы переведем его на взаимодействие с трекболом. Результат будет упрощенным, но проиллюстрирует различия между трекболом и сенсорным взаимодействием.
Если касание и перемещение по холсту рассматривается как действие рисования, то мы можем рассматривать нажатие и вращение трекбола таким же образом. Это означает, что так же, как мы обнаружили нажатие, перемещение и поднятие пальца, стилуса или мыши, мы захотим обнаружить нажатие, вращение и отпускание трекбола. Как и в случае с сенсорным взаимодействием, мы можем начать путь, на котором нажат трекбол, переместить его, используя линию, когда трекбол катится, и завершить операцию рисования пути, когда он отпущен.
Предположим, что мы будем обрабатывать только трекбол для рисования на холсте, поэтому пользователь не сможет использовать трекбол для взаимодействия с другими элементами пользовательского интерфейса. Трекболы на устройствах Android обычно связаны с сенсорными экранами.
Чтобы обнаружить взаимодействие с трекболом, добавьте следующий метод в ваш класс View:
1
2
3
4
|
@Override
public boolean onTrackballEvent(MotionEvent event) {
//respond to trackball interaction
}
|
Этот метод аналогичен прослушивателю событий касания, получающему параметр MotionEvent и возвращающему логическое значение, которое определяет, как будут обрабатываться будущие события того же рода.
Шаг 2
При обработке действий трекбола сначала нам нужно выяснить, какое действие пользователя вызвало метод. Нас интересуют три действия: нажатие на трекбол, перемещение после нажатия и отпускание после рисования. Нас интересует только событие перемещения трекбола, когда оно нажато. По этой причине мы хотим обнаружить возникающие нажатия, указывающие на то, что пользователь в данный момент рисует. Чтобы добиться этого, добавьте в ваш класс логическую переменную, чтобы отслеживать, нажата ли трекбол в данный момент:
1
|
private boolean trackOn=false;
|
Мы можем предположить, что трекбол будет изначально. Мы также хотим отслеживать положение пользователя, зарегистрированное с помощью трекбола, поэтому добавим еще пару переменных экземпляра:
1
|
private float trackX, trackY;
|
Мы увеличим значения движений трекбола, поскольку трекболы на устройствах Android, как правило, невелики, и мы не хотим, чтобы пользователю приходилось слишком сильно их катить, чтобы рисовать по холсту. Добавьте другую переменную экземпляра для значения масштабирования, которое мы будем использовать:
1
|
private int scale=5;
|
Вы можете настроить это, если хотите. Поскольку координаты трекбола являются относительными, мы будем использовать ширину и высоту области холста как часть нашего вычисления. Добавьте переменные экземпляра для них:
1
|
private int canvasWidth, canvasHeight;
|
Вы можете инициализировать их после того, как размер холста был установлен, поэтому для приложения серии чертежей сделайте это в методе onSizeChanged :
1
2
|
canvasWidth = canvasBitmap.getWidth();
canvasHeight = canvasBitmap.getHeight();
|
Шаг 3
Вернувшись в метод onTrackballEvent , теперь вы можете получить информацию о том, где расположен трекбол. В приемнике событий касания мы просто извлекали координаты X и Y из объекта MotionEvent, используя getX и getY . Однако, когда MotionEvent был запущен трекболом, это фактически указывает на различную информацию. При использовании трекбола значения X и Y являются относительными, а не абсолютными, поэтому мы должны выполнить некоторую обработку, чтобы превратить это значение в значение, которое мы можем передать объектам Path и Canvas для рисования в нужном месте.
Чтобы использовать взаимодействие трекбола для рисования линии, как мы это делали с помощью касания, мы можем использовать следующую технику, которая использует упрощенную и слегка измененную версию алгоритма, который вы увидите в приложении API Demos. В методе onTrackballEvent сначала получите тип произошедшего события:
1
|
int action = event.getActionMasked();
|
Давайте теперь вычислим координаты X и Y, которые мы хотим передать объектам рисования:
1
2
|
trackX += event.getX() * event.getXPrecision() * scale;
trackY += event.getY() * event.getYPrecision() * scale;
|
Умножение указателя X или Y на точность должно дать нам более точное аппаратное значение. Мы также умножаем на число масштабирования, которое мы определили как переменную. Так как координаты трекбола являются относительными, нам нужно проверить, движемся ли мы за пределы области холста:
1
2
|
if(trackX >= canvasWidth) trackX = canvasWidth-1;
if(trackY >= canvasHeight) trackY = canvasHeight-1;
|
Шаг 4
Теперь давайте обработаем событие, которое происходит, когда пользователь нажимает трекбол, после вычисления значений X и Y:
1
2
3
4
|
if (action==MotionEvent.ACTION_DOWN)
{
//trackball pressed
}
|
Внутри условного блока сначала установите логическую переменную, которую мы создали, в true:
1
|
trackOn=true;
|
Начните операцию рисования, используя ту же технику, которую мы использовали для модели с сенсорным экраном:
1
|
drawPath.moveTo(trackX, trackY);
|
Когда пользователь впервые нажимает трекбол для рисования, он по умолчанию начинается в верхнем левом углу. Для последующих операций рисования начальная точка будет относительно предыдущих операций рисования. Теперь мы готовы рисовать, когда пользователь перемещает трекбол после нажатия на него — после проверки условного блока на действие «вниз»:
1
2
3
|
if (action==MotionEvent.ACTION_MOVE && trackOn) {
//trackball pressed and moving
}
|
Мы проверяем событие перемещения, но хотим реализовать рисование только при нажатии трекбола, потому что событие перемещения также срабатывает, когда пользователь перемещает трекбол, не нажимая его. Внутри этого блока мы можем рисовать снова, используя ту же технику.
1
|
drawPath.lineTo(trackX, trackY);
|
Наконец, мы можем завершить рисование после отпускания трекбола после условного события move:
1
2
3
4
|
if(action==MotionEvent.ACTION_UP)
{
//trackball released
}
|
Теперь мы можем снова использовать тот же подход, что и когда пользователь перестает касаться экрана. Если трекбол в данный момент нажат, мы будем знать, что рисунок произошел:
1
2
3
4
5
|
if(trackOn){
drawCanvas.drawPath(drawPath, drawPaint);
drawPath.reset();
trackOn=false;
}
|
После условного блока события «вверх» завершите метод, аннулировав представление и вернув истинное значение, чтобы будущие события трекбола были обработаны:
1
2
|
invalidate();
return true;
|
Шаг 5
Это завершает основной алгоритм рисования трекбола. Однако ваш пользовательский класс View для рисования сможет обрабатывать события трекбола, только если он имеет фокус. В действии, в котором размещен пользовательский вид (основное действие в приложении серии рисунков), вы можете достичь этого, отправив событие следующим образом:
1
2
3
|
public boolean dispatchTrackballEvent(MotionEvent ev) {
//trackball event
}
|
В этом методе вам понадобится ссылка на пользовательский чертежный вид в вашей деятельности. Для этого сфокусируйтесь на нем и укажите метод обработки событий, который мы добавили:
1
2
|
drawView.requestFocus();
return drawView.onTrackballEvent(ev);
|
Шаг 6
Трекболы на устройствах Android сейчас встречаются относительно редко, но вы можете проверить свою функциональность на эмуляторе. Обратите внимание, однако, что результаты эмулятора будут немного грубыми. При создании виртуального устройства включите поддержку трекбола на нем. Затем вы можете включать и выключать трекбол, взаимодействуя с вашим приложением на виртуальном устройстве, нажав клавишу F6. Вы можете столкнуться с проблемами при работе трекбола на эмуляторе для определенных уровней API.
Когда вы попробуете функцию рисования с помощью трекбола, эмулируя нажатия трекбола нажатием кнопки мыши, вы сразу увидите, что это не самая чувствительная модель взаимодействия при рисовании. Вот почему некоторые приложения используют подход, продемонстрированный в демоверсиях API, который включает немного более сложный алгоритм. Вы также увидите внутреннюю сложность в управлении взаимодействием при рисовании с помощью трекбола, а не касания, стилуса или мыши. Проблема в том, что пользователь не может знать, где находится его начальная точка. Одним из возможных способов решения этой проблемы будет эмуляция курсора во время использования трекбола, перемещение его так, чтобы оно отражало положение рисования, прежде чем пользователь нажмет его, чтобы они могли с большей точностью начать свои операции рисования.
Совет: Более подробный подход к отображению координат трекбола в операциях рисования см. В классе TouchPaint в демонстрационных программах API. Он использует историческую информацию, предоставленную MotionEvent, чтобы нарисовать серию точек вместо линии, перемещая каждую из них относительно последней, ссылаясь на методы getHistoricalX и getHistoricalY в цикле.
3. Взаимодействие мыши и стилуса
Шаг 1
Как упоминалось выше, ваши сенсорные функции рисования, управляемые событиями, будут работать для взаимодействия мыши и стилуса. Однако есть и другие варианты, которые можно использовать для улучшения функциональности рисования для этих элементов оборудования. Например, как для мыши, так и для стилуса вы можете обнаружить данные о давлении, наклоне и ориентации, что позволит вам реализовать такие улучшения рисования, как увеличение непрозрачности или, возможно, размера кисти, в зависимости от ваших существующих алгоритмов. Данные наклона и ориентации можно использовать в приложениях для рисования, где используется не круглая форма кисти. В некоторых случаях устройство с сенсорным экраном также может предоставлять информацию о давлении и ориентации. Все соответствующие методы находятся в классе MotionEvent .
Проверьте следующие методы для этих дополнительных данных:
1
2
3
4
5
6
7
|
//used in event handlers
motionEvent.getPressure();
motionEvent.getTouchMajor();
motionEvent.getTouchMinor();
motionEvent.getOrientation();
MotionEvent.AXIS_DISTANCE;
MotionEvent.AXIS_TILT;
|
Шаг 2
Ваши приложения могут обнаруживать взаимодействие с помощью кнопок мыши или стилуса, а также уточнения самой функции рисования. Например, в классе TouchPaint из демоверсий API кнопки используются для изменения цвета и переключения инструментов рисования. Вы также можете обрабатывать события наведения для этих моделей взаимодействия, потенциально позволяя вам указать положение рисования пользователя или реализовать определенные функции рисования до того, как на самом деле коснется экран.
См. Следующие выдержки кода для получения дополнительной информации об этом:
1
2
3
4
5
|
//used in event handlers tailored to specific input tools
motionEvent.getToolType();
MotionEvent.BUTTON_PRIMARY;
MotionEvent.BUTTON_SECONDARY;
MotionEvent.BUTTON_TERTIARY;
|
Посмотрите классы API Demos для более подробного обзора того, как их использовать.
Вывод
Теперь мы рассмотрели и указали ряд вариантов взаимодействия с пользователем для рисования приложений в Android. Если вы работали над приложением, которое мы изначально создали в серии, посмотрите, можете ли вы подумать о способах использования этой новой информации для дальнейшего улучшения приложения. Существует также множество других вариантов, таких как различные типы кистей, возможность устанавливать более широкий диапазон цветов или рисунков, загрузка изображений в приложение и обмен изображениями из него. Если вы думаете о большем и имеете удачу в их реализации, сообщите нам об этом в комментариях!