Статьи

Как добавить контроль жестов мышью в ваши Flash-проекты: однократные жесты

Недавно я купил свой первый Bamboo, планшет Wacom, который распознает буквы из фигур, нарисованных стилусом. Это вызвало у меня воспоминания о моем первом опыте работы с приложением с управлением жестами: использование жестов мыши, веб-браузеры, такие как Maxthon (и более поздние версии Opera ), позволяли пользователям быстро перемещаться по страницам истории в истории, переключаться между различными вкладками и так далее. Мне понравился его аккуратный пользовательский интерфейс, так как он убирает традиционные щелчки мышью. Конечно, теперь доступны сложные устройства с управлением жестами, такие как Kinect, iPad и iPhone — но все это началось со старого доброго ПК. В этом уроке вы узнаете, как создать фотогалерею, которая распознает особые жесты мыши.


Давайте посмотрим на конечный результат, к которому мы будем стремиться. Для панорамирования галере в четырех основных направлениях щелкните и перетащите мышь в соответствующем направлении. Чтобы масштабировать фотографию, перетащите мышь «Юго-восток», чтобы увеличить масштаб, и перетащите мышь «Северо-Запад», чтобы уменьшить масштаб по умолчанию.

(Примечание: при панорамировании не происходит центрирования фотографии; она чувствительна к длине линии, которую вы рисуете.)


Вот что вы выучите в этом уроке, и порядок, в котором вы его выучите:

  • Векторная интерпретация жестов мыши
  • Жестко запрограммированная реализация мышиных жестов
  • Класс для обнаружения особых жестов мыши
  • Пример приложения (фотогалерея) с использованием указанного класса

Изображение, показывающее 8 основных направлений для обнаружения мышью.

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

В представлении Flash ниже показаны этапы обнаружения единственного жеста мыши справа. Для прокрутки кадров в представленной ниже презентации Flash (мышь вниз — движение мыши — вверх) в любом из следующих направлений:

  • На восток, чтобы прокрутить кадр вперед
  • На запад, чтобы прокрутить кадр назад
  • На север, чтобы перейти к последнему кадру
  • На юг, чтобы перейти к первому кадру

Реализация шага 2 будет легкой. Однако вероятность того, что жесты пользователей потерпят неудачу, составляет 90%. Диаграмма ниже показывает жесты, которые обычно совершаются (в середине); они редко соответствуют жесткому вектору, направленному вправо (влево). Таким образом, лучше дать смягчение за неточности жестов (справа).

Концепция углового смягчения для обнаружения жестов.

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

Реализация углового смягчения для обнаружения жестов.

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


Давайте рассмотрим переменные в нашей жестко запрограммированной реализации на шаге 4. Я выделил важные переменные Vector2D. Обратите внимание на комментарии, которые я поместил в конце каждой переменной.

1
2
3
4
private var t:TextField;
private var earlier:Vector2D //store mouse location upon first click
private var latter:Vector2D //store mouse location upon release
private var RIGHT:Vector2D = new Vector2D(1, 0);

Я предполагаю, что вы уже знаете основы размещения TextField в своем проекте, поэтому я сосредоточусь на реализации ActionScript жеста мыши. Реализация, как указано ниже, тщательно прокомментирована. Важные векторные вычисления также выделены. Я призываю читателей изучить эти комментарии, особенно выделенные, чтобы понять операции при различных событиях во время выполнения.

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

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
public function HardCoded()
{
    //Creating a textbox
    t = new TextField();
    t.selectable = false;
    t.width = 300;
    tx = stage.stageWidth/2;
    ty = stage.stageHeight — 30;
    addChild(t);
     
    //Start of gesture detection
    stage.addEventListener(MouseEvent.MOUSE_DOWN, start);
}
 
//Register mouse location upon mouse down
private function start(e:MouseEvent):void
{
    //Register mouse suppress location
    earlier = new Vector2D(e.localX, e.localY);
     
    //Start to draw line
    graphics.lineStyle(3);
    graphics.moveTo(earlier.x, earlier.y);
     
    //add mouse move and mouse release listeners
    stage.addEventListener(MouseEvent.MOUSE_MOVE, move);
    stage.addEventListener(MouseEvent.MOUSE_UP, up);
}
 
//Draw gesture upon mouse move
private function move(e:MouseEvent):void
{
    graphics.lineTo(e.localX, e.localY);
}
 
//Evaluate gesture upon mouse release
private function up(e:MouseEvent):void
{
    //Register mouse release location
    latter = new Vector2D(e.localX, e.localY);
     
    //Calculating vector of mouse gesture
    var result:Vector2D = latter.minus(earlier);
     
    //Calculating angle from absolute RIGHT to gesture vector.
    var deviation:Number = RIGHT.angleBetween(result);
    deviation = Math2.degreeOf(deviation);
     
    //Interpreting gesture with alleviation
    if (Math.abs(deviation) < 30) t.text = «RIGHT gesture detected»;
    else t.text = «»;
     
    //Clear screen of previous drawing.
    graphics.clear();
     
    //remove mouse move and mouse up listeners
    stage.removeEventListener(MouseEvent.MOUSE_MOVE, move);
    stage.removeEventListener(MouseEvent.MOUSE_UP, up);
}

Для ясности, вот краткое изложение жестко закодированной реализации:

  1. При наведении мыши, начать обнаружение жестов.
  2. При перемещении мыши обновите последнюю позицию указателя мыши.
  3. При наведении мыши оцените весь жест, начиная с (1).

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

Погрешности для ошибок на диагоналях, больших, чем прямые.

Я хотел бы указать на другую проблему — чувствительные жесты. Чувствительные жесты определяют направления жестов, когда указатель мыши делает малейшие сдвиги в местоположении, даже к смежным пикселям. Диаграмма ниже иллюстрирует сценарии чувствительных жестов.

Чувствительные мышиные жесты.

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

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

Для обнаружения особых жестов мыши я реализовал MGesture . Загрузите и изучите этот файл ActionScript . Сначала я рассмотрю его переменные класса, а затем методы класса.

переменная Тип данных Цель
mainBox DisplayObjectContainer Контейнер, из которого обнаруживаются жесты
directions Vector. Векторы стандартных направлений
_deviationFromMains Number Угловое смягчение допускается жестом Вектор из 4 основных направлений (0 ~ 3 в directions )
_deviationFromDiagonals Number Разрешение углов допускается жестом Вектор из 4 диагональных направлений (4 ~ 7 в directions )
_minDist Number Минимальная величина для текущего жеста Вектор должен быть действительным
_earlier Vector2D Расположение первого клика
_latter Vector2D Расположение непрерывного указателя после первого клика

Ниже приведена реализация кода переменных класса. Я допустил отклонение 10 ° от основных направлений. Например, -10 ° -10 ° считается на востоке, -80 ° -100 ° считается на юге и т. Д. Я также допустил отклонение на 30 ° от диагональных направлений. Таким образом, вектор с ориентацией между 15 ° -75 ° будет считаться юго-восточным и т. Д. Кроме того, минимальная величина для превышения составляет 10 пикселей.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
private var mainBox:DisplayObjectContainer;
private var directions:Vector.<Vector2D> = new <Vector2D>[
    new Vector2D(1, 0), //East
    new Vector2D(0, 1), //South
    new Vector2D(-1, 0), //West
    new Vector2D(0, -1), //North
    new Vector2D(1, 1), //South — east
    new Vector2D(-1, 1), //South — west
    new Vector2D( -1, -1), //North — west
    new Vector2D(1, -1) //North — east
];
private var diagonals:Boolean = false;
 
private var _deviationFromMains:Number = Math2.radianOf(10);
private var _deviationFromDiagonals:Number = Math2.radianOf(30);
private var _minDist:Number = 10;
private var _earlier:Vector2D;
private var _latter:Vector2D;

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

Направление нумерации.

Ниже приведены методы класса для MGesture .

методы вход Выход Описание
MGesture Контейнер, в котором обнаружены жесты недействительным Инициирование класса, установка контейнера, из которого обнаруживаются жесты
Начало недействительным недействительным Переменные для обнаружения жестов ( _earlier , _latter ) инициирует
Обновить недействительным Вектор текущего жеста (без учета _minDist ), Vector2D _latter и возвращает текущий вектор жеста ( _earlier to _latter )
validMagnitude недействительным Вектор текущего жеста (соответствует минимальной величине _minDist ), Vector2D Проверяет, превышает _minDist величина текущего жеста _minDist
evalDirections недействительным Целое число, указывающее направление, int Оценивает текущий жест, сравнивая его вектор с указанными в directions

Все основные методы, представленные на шаге 12, описаны здесь. Прочитайте комментарии.

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
74
75
76
77
78
79
80
81
82
83
/**
 * Initiate variables
 * @param container where mouse is detected from
 */
public function MGesture(container:DisplayObjectContainer) {
    //setting container from which mouse is moving
    mainBox = container;
}
 
/**
 * Method to register initial mouse location
 */
public function start ():void {
    var startMX:Number = mainBox.mouseX;
    var startMY:Number = mainBox.mouseY;
    _earlier = new Vector2D(startMX, startMY);
    _latter = new Vector2D(startMX, startMY);
}
 
/**
 * Method to update mouse location
 * @return a Vector2D of current mouse location relative to that when start() is called;
 */
public function update ():Vector2D {
    _latter = new Vector2D(mainBox.mouseX, mainBox.mouseY);
    var vecUpdate:Vector2D = _latter.minus(_earlier);
    return vecUpdate;
}
 
/**
 * Method to validate a gesture.
 * @param newLoc Vector2D to new mouse location
 * @return null if invalid gesture, a Vector2D if valid
 */
private function validMagnitude ():Vector2D {
    var gestureVector:Vector2D = update();
    var newMag:Number = gestureVector.getMagnitude();
     
    //if magnitude condition is not fulfilled, reset gestureVector to null
    if (newMag < _minDist) gestureVector = null;
    return gestureVector;
}
 
/**
 * Method to evaluate gesture direction
 * @return Integer indicative of direction.
 */
public function evalDirections():int {
    //Pessimistic search (initialise with unsuccessful search)
    var detectedDirection:int = -1;
     
    //validate magnitude condition
    var newDirection:Vector2D = validMagnitude();
     
    //if gesture exceed minimum magnitude
    if (newDirection != null) {
         
        //evaluation against all directions
        for (var i:int = 0; i < directions.length; i++)
        {
            var angle:Number = directions[i].angleBetween(newDirection);
            angle = Math.abs(angle);
             
            //check against main directions
            if ( i < 4 && angle < _deviationFromMains) {
                detectedDirection = i;
                break;
            }
             
            //check against diagonal directions
            else if (i > 3 && angle < _deviationFromDiagonals) {
                detectedDirection = i;
                break;
            }
        }
         
        //update mouse location for next evaluation
        _earlier = _latter;
    }
     
    //return detected direction
    return detectedDirection
}

Теперь, MGesture класс MGesture установлен, мы приступим к его демонстрационному приложению (фотогалерее). Я включил исходный файл здесь. Загрузите и следуйте инструкциям. Прежде всего, поместите все изображения в папку «lib» в вашем существующем проекте. Изображения, которые я использовал здесь, любезно предоставлены моей женой и дочерью.

Размещение изображений в папке lib в проекте.

Создайте новый класс Actionscript и назовите его PhotoView . Мы будем использовать эти изображения для построения нашей галереи.

  1. Создать код для вставки изображений. Они будут распознаны как универсальный объект Class .
  2. Преобразуйте Class в Bitmap объекты, чтобы мы могли манипулировать им дальше.
  3. Поместите все эти Bitmap объекты в массив Vector для удобства выбора позже.
Создать код для вставки в PhotoView.
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
[Embed(source = ‘../lib/Week3.jpg’)]
private var Week3:Class
 
[Embed(source = ‘../lib/Family.jpg’)]
private var Family:Class
 
[Embed(source = ‘../lib/FatherDaughter.jpg’)]
private var Daughter:Class
 
[Embed(source = ‘../lib/Jovial.jpg’)]
private var Jovial:Class
 
[Embed(source = ‘../lib/NewBorn.jpg’)]
private var NewBorn:Class;
 
[Embed(source = ‘../lib/Posing.jpg’)]
private var Posing:Class
 
[Embed(source = ‘../lib/Smile.jpg’)]
private var Smile:Class
 
[Embed(source = ‘../lib/Surrender.jpg’)]
private var Surrender:Class
 
private var list:Vector.<Bitmap> = new <Bitmap> [
    new Week3 as Bitmap,
    new Family as Bitmap,
    new Daughter as Bitmap,
    new Jovial as Bitmap,
    new NewBorn as Bitmap,
    new Posing as Bitmap,
    new Smile as Bitmap,
    new Week3 as Bitmap,
    new Surrender as Bitmap
]

Здесь важно уточнить управление списком отображения PhotoView . Я включил презентацию Flash здесь. Чтобы использовать это, сделайте жест направо или налево. Для получения дополнительной информации обратитесь к шагу 2.


В конструкторе PhotoView мы инициируем все необходимые экранные объекты и размещаем их на месте. Кроме того, мы запускаем MGesture и подключаем прослушиватели событий, чтобы начать обнаружение жестов. Я выделил слушателей событий. Их детали объясняются в течение следующих двух шагов.

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
public function PhotoView()
{
    panel = new Sprite();
    panel.x = stage.stageWidth / 2;
    panel.y = stage.stageHeight / 2;
    addChild(panel);
     
    var currentBmp:int = 0;
    var bmpGaps:Number = 60;
    var bmpOnX:int = 3;
    var bmpOnY:int = 3;
     
    var bmp:Bitmap;
    var container:Sprite;
     
    //scrolling through Y
    for (var j:int = -1*Math.floor(bmpOnY/2); j < Math.ceil(bmpOnY/2); j++)
    {
        //scrolling through X
        for (var i:int = -1*Math.floor(bmpOnX/2); i < Math.ceil(bmpOnX/2); i++)
        {
            bmp = list[currentBmp];
            bmp.x = -1 * bmp.width / 2;
            bmp.y = -1 * bmp.height / 2;
            container = new Sprite();
            container.x = (bmp.width + bmpGaps )* i;
            container.y = (bmp.height + bmpGaps )* j;
            container.addChild(bmp);
            container.addEventListener(MouseEvent.MOUSE_DOWN, select);
            panel.addChild(container);
            currentBmp++ //Scroll to next bitmap
        }
    }
     
    gesture = new MGesture(stage);
    stage.addEventListener(MouseEvent.MOUSE_DOWN, start);
}

Выделенная строка 99 относится не к обнаружению жеста, а просто к выбору изображения для масштабирования и размещения его поверх всех других изображений.

1
2
3
4
5
6
7
private function select(e:MouseEvent):void
{
    //Setting current image to scale &amp;
    //Placing it on top of all other images
    ImgSelected = e.currentTarget as Sprite;
    panel.swapChildrenAt(panel.numChildren — 1, panel.getChildIndex(ImgSelected));
}

Первая функция ниже выполняется при нажатии мыши. Вторая выполняется при наведении мыши. Я выделил start() и evalGesture() а также эван-слушателей.

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
private function start(e:MouseEvent):void
{
    //Start gesture detection &amp;
    //Listen for mouse up event
    gesture.start();
    stage.addEventListener(MouseEvent.MOUSE_UP, end);
}
 
private function end(e:MouseEvent):void
{
    //Prepare current gesture’s magnitude for animation purpose
    //implement a maximum cap on gesture’s magnitude
    gestureMag = gesture.update().getMagnitude() / 2;
    gestureMag = Math.min(gestureMag, maxMag);
     
    //Evaluate current gesture
    direction = gesture.evalGesture();
     
    //Once a valid gesture is detected, perform animation
    //No further gestures will be detected until animation ends
    if (direction > -1) {
        stage.addEventListener(Event.ENTER_FRAME, move);
        stage.removeEventListener(MouseEvent.MOUSE_DOWN, start);
    }
}

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

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
private function move(e:Event):void
{
    var currentMag:Number
     
    //Motion of panel translation
    if (direction < 4) {
         
        //Function of easing motion
        currentMag = gestureMag * Math.cos(currentAngle += 0.1);
        if (direction == 0) panel.x += currentMag;
        else if (direction == 1) panel.y += currentMag;
        else if (direction == 2) panel.x -= currentMag;
        else if (direction == 3) panel.y -= currentMag;
    }
     
    //Motion of image scaling
    else {
         
        //Setting a maximum cap on motion
        gestureMag = Math.min(0.30, gestureMag);
         
        //Function of easing motion
        currentMag = gestureMag * Math.cos(currentAngle += 0.1);
         
        //Conditions to scale up:
        //Gesture is to South-East &amp;
        //Image is not scaled up already
        if (direction == 4 && ImgSelected.scaleX < 1.30){
            ImgSelected.scaleX = ImgSelected.scaleY = -1 * currentMag + 1.30
        }
         
        //Conditions to scale down:
        //Gesture is to North-West &amp;
        //Image is scaled up
        else if (direction == 6 && ImgSelected.scaleX > 1){
            ImgSelected.scaleX = ImgSelected.scaleY = currentMag + 1;
        }
    }
     
    //If angle on easing function exceeds 90 degrees/ 0.5 Pi radian,
    //stop animation &amp;
    if (currentAngle > Math.PI/2) {
        stage.removeEventListener(Event.ENTER_FRAME, move);
        stage.addEventListener(MouseEvent.MOUSE_DOWN, start);
        direction = -1;
        currentAngle = 0;
    }
}

Теперь все готово. Наконец, вы можете опубликовать свою работу, нажав Ctrl + Enter на FlashDevelop. Опять таки. вот кусок конечного продукта.


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