Pull to refresh

Gesturin — ввод жестами для Android

Reading time5 min
Views5.7K

В этом посте я расскажу, как создать метод ввода жестами для Android. Данный метод ввода можно сделать похожим на рукописный ввод, но есть одно ограничение — нужно рисовать букву (цифру, слово) не отрывая пальца. В реализацию входит метод ввода и утилита для настройки жестов, которая является переделанным «Gesture Builder» из примеров к Android SDK.


Создание «клавиатуры»


Начнём с создания класса клавиатуры. Все методы ввода делаются на основе InputMethodService, а для распознавания жестов мы включили в него OnGesturePerformedListener. Чтобы не было проблем с распознаванием, каждый язык будет иметь отдельный файл. Для этого добавим в класс два свойства: карту языков и название текущего языка.
// HashMap<Название языка, библиотека жестов>
private HashMap<String, GestureLibrary> mGestureLibMap;
// Текущий язык
private String mCurrentGestureLib = null;

Загружать языки будет метод loadLanguages, который будет сканировать директорию файлов приложения и загружать всё содержимое как языки.
private void loadLanguages() {
    // Собираем список "языков"
    File[] files = getFilesDir().listFiles();
    mGestureLibMap = new HashMap<String, GestureLibrary>();

    // Загружаем "языки"
    for(int i = 0; i < files.length; i++) {
        GestureLibrary gestureLib = GestureLibraries.fromFile(files[i]);

        if (!gestureLib.load()) {
            Toast.makeText(this, "Can't load gestures " + files[i].getName(),
                    Toast.LENGTH_LONG).show();
            continue;
        }

        // Добавляем язык в список
        mGestureLibMap.put(files[i].getName(), gestureLib);
    }

    if(mCurrentGestureLib == null || !mGestureLibMap.containsKey(mCurrentGestureLib)) {
        // Находим первый язык и устанавлием его
        mCurrentGestureLib = mGestureLibMap.keySet().toArray(new String[0])[0];
        if(mGestureLibMap.containsKey(mCurrentGestureLib.toLowerCase())) 
            mCurrentGestureLib = mCurrentGestureLib.toLowerCase();
    }
}

loadLanguages будет вызываться в методе onStartInputView, который вызывается каждый раз при вызове клавиатуры. Данное решение возможно вызовет тормоза при большом количестве языков, но таким образом всегда будут «свежие» жесты. Можно было бы исхитриться с файлом последнего обновления жестов или static свойством, но я не стал хитрить так как не заметил никаких тормозов при стандартном наборе (en, ru, 09).

Жесты распознаются в методе onGesturePerformed (подробнее о жестах можно узнать тут). Если название жеста начинается с "@@", то будем считать, что за ним идёт keyCode, который будем посылать приложению. Если начинается с чего-то другого, то будем отсылать это как текст. Сам метод выглядит так:
public void onGesturePerformed(GestureOverlayView overlay, Gesture gesture) {
    // Распознаём жест
    ArrayList<Prediction> predictions = mGestureLibMap.get(mCurrentGestureLib).recognize(gesture);

    if (predictions.size() > 0) {
        // Выбираем самый подходящий вариант
        Prediction prediction = predictions.get(0);

        if(prediction.name.startsWith("@@")) {
            // Если название начинаеться с @@, то посылаем нажатие кнопки с данным кодом
            try {
                keyDownUp(Integer.valueOf(prediction.name.substring(2)));
            } catch(NumberFormatException e) {
                Log.w("GestureKeyboard", "NumberFormatException: " + e.getLocalizedMessage());
            }
        } else {
            // Посылаем название жеста как текст
            getCurrentInputConnection().commitText(editString(prediction.name),
                    prediction.name.length());
        }
    }
}

В этом методе можно заметить не известный метод editString. Данный метод изменяет регистр в зависимости от состояния кнопок «Upper» и «Lower».
private String editString(String in) {
    // Если включен lower, то преобразуем в LowerCase
    if(mLowerLock.isChecked()) return in.toLowerCase();
    // Если upper, то в UpperCase
    if(mUpperLock.isChecked()) return in.toUpperCase();
    // Или возвращаем оригинал
    return in;
}

А метод keyDownUp посылает два события приложению – keyDown и keyUp. Он был взят из примера SoftKeyboard и выглядит так:
private void keyDownUp(int keyEventCode) {
    // Посылаем события keyDown и keyUp
    getCurrentInputConnection().sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyEventCode));
    getCurrentInputConnection().sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyEventCode));
}

Мы загружали различные языки, значит, их можно менять. Меняются они связкой двух методов — onLangClick и setCurrentLanguage. При клике по кнопке onLangClick мы ищем в списке языков следующий за текущим, или (если такого нет) меняем язык на самый первый. При смене языка так же учитываться состояние кнопки «Caps Lock».
public void onLangClick(View v) {
    // Клик по кнопке смены языка
    // Получаем список языков
    String gestureNames[] = mGestureLibMap.keySet().toArray(new String[0]);

    boolean next = false;

    for(int i = 0; i < gestureNames.length; i++) {
        if(next) {
            // Это следующий язык за текущим
            if(!mCurrentGestureLib.toLowerCase().equals(gestureNames[i].toLowerCase())) {
                // Это не текущий язык в другом регистре
                setCurrentLanguage(gestureNames, i);				
                next = false;
                break;
            }
            continue;
        }
        if(mCurrentGestureLib.toLowerCase().equals(gestureNames[i].toLowerCase())) {
            // Нашли текущий язык в списке
            next = true;
        }
    }

    if(next) setCurrentLanguage(gestureNames, 0);

    // Устанавливаем язык
    mLangSwither.setText(mCurrentGestureLib);
}

private void setCurrentLanguage(String gestureNames[], int index) {
    mCurrentGestureLib = gestureNames[index];

    // Если есть этот язык в LowerCase, то включаем его
    if(mGestureLibMap.containsKey(gestureNames[index].toLowerCase())) {
        mCurrentGestureLib = gestureNames[index].toLowerCase();
    }

    // Если включен капс, то ищем этот язык в UpperCase и включаем если есть
    if(mCapsLock.isChecked() && mGestureLibMap.containsKey(gestureNames[index].toUpperCase())) {
        mCurrentGestureLib = gestureNames[index].toUpperCase();
    }
}

Для смены регистра используются три кнопки: Upper, Lower, Caps. Что-то интересное представляет только капс. Язык может быть в двух регистрах. Регистр можно записать в названия языка (пример: en и EN). А кнопка капс переключает эти регистры или при их отсутствии изменяет состояние кнопок «Upper» и «Lower».
if(isChecked) {
    mLowerLock.setChecked(false);

    if(mGestureLibMap.containsKey(mCurrentGestureLib.toUpperCase())) {
        // Устанавливаем язык в UpperCase
        mUpperLock.setChecked(false);
        mCurrentGestureLib = mCurrentGestureLib.toUpperCase();
    } else {
        // Если нет языка в UpperCase, то включаем UpperLock
        mUpperLock.setChecked(true);
    }
} else {
    mUpperLock.setChecked(false);

    if(mGestureLibMap.containsKey(mCurrentGestureLib.toLowerCase())) {
        // Устанавливаем язык в LowerCase
        mLowerLock.setChecked(false);
        mCurrentGestureLib = mCurrentGestureLib.toLowerCase();
    } else {
        // Или включаем LowerLock
        mLowerLock.setChecked(true);
    }
}

// Устанавливем язык
mLangSwither.setText(mCurrentGestureLib);

Немного приукрасив UI получилось то что показано на скриншоте.
Класс метода ввода уже готов, теперь нужно в AndroidManifest.xml что-то такое:

<service 
    android:name="GestureKeyboard"
    android:permission="android.permission.BIND_INPUT_METHOD">
    <intent-filter>
        <action android:name="android.view.InputMethod"/>
    </intent-filter>
    <meta-data android:name="android.view.im" android:resource="@xml/method"/>
</service>

Немного приукрасив UI, получилось то, что показано на скриншоте.

Про настройки расскажу на словах. С начала я взял исходники Gesture Builder из Android SDK, и скопировал в мой проект нужные исходники и ресурсы. Ресурсы и исходники были подчищены от «лишнего». Далее изменил класс GestureBuilderActivity так, чтобы он открывал указанные жесты (а не /sdcard/gestures). И сделал список языков с функциями добавление, удаления и переименования.

Результат


Получившиеся «клавиатура» не плохо распознаёт мой почерк, но пользоваться на постоянной основе её я не стал — клавиши мне удобней. Все исходники можно посмотреть на googlecode.com. Там же скачать apk со встроенным английским, русским, и цифровым (для моего почерка).
Tags:
Hubs:
Total votes 32: ↑28 and ↓4+24
Comments2

Articles