
В этом посте я расскажу, как создать метод ввода жестами для 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 со встроенным английским, русским, и цифровым (для моего почерка).