Программирование для мобильных устройств в массы: Использование слов и жестов в приложении Overheard Word

Программная интеграция стороннего кода в пользовательский интерфейс собственного Android-приложения

В репозитарии GitHub, как и во многих других репозитариях, имеется множество программного кода от сторонних разработчиков, однако для успешной интеграции этого с кода с пользовательским интерфейсом вашего Android-приложения необходимо знание определенных тонкостей. В этом месяце Эндрю Гловер показывает, как повысить уровень демонстрационного приложения Overheard Word с помощью основанного на JSON механизма для работы со словами и некоторых заранее подготовленных функций, использующих жесты смахивания (swipe). В статье будет показано, что платформа Android с легкостью воспринимает сторонний код, однако вам все-таки придется реализовать определенную логику, чтобы интерфейс вашего приложения работал надлежащим образом.

Эндрю Гловер, президент компании, Stelligent Incorporated

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



26.11.2013

Если вы прочли предшествующие статьи в данном цикле и проработали включенные в них демонстрационные примеры, то вы овладели определенными базовыми навыками разработки Android-приложений. Помимо настройки среды Android-разработки и написания своего первого приложения Hello World вы узнали, как заменить нажимные кнопки жестами смахивания, а также как реализовать меню (или панели действий) и значки в специальном мобильном приложении. В этой статье вы продолжите свое движение по этой траектории и научитесь пользоваться сторонними библиотеками для расширения или для совершенствования функциональности своего приложения. Сначала мы установим несколько библиотек с открытым исходным кодом и прочитаем файлы, а затем мы программным образом интегрируем эту новую функциональность в пользовательский интерфейс нашего демонстрационного приложения.

Когда и в предыдущих статьях, я буду использовать в демонстрационных целях свое приложение Overheard Word. Если у вас еще нет этого приложения, то прежде чем идти дальше, вам следует клонировать GitHub-репозиторий с моим демоприложением Overheard Word.

Об этом цикле статей

Мобильные приложения растут как грибы после дождя, и специалистам по их разработке есть, где применить свои знания. Предлагаемый цикл статей developerWorks знакомит с мобильными технологиями тех разработчиков, которые уже обладают опытом в области программирования, но только начинают осваивать сферу мобильности. Можно начать с написания "родных" приложений на языке Java, а затем добавлять в свой инструментарий JVM-языки, платформы скриптов, технологии HTML5/CSS/JavaScript, сторонние инструменты и многое другое. Так, шаг за шагом, вы овладеете навыками, необходимыми для выполнения практически любого сценария разработки мобильных приложений.

Thingamajig: Подключаемый механизм для работы со словами

Overheard Word — это англоязычное приложение, которое помогает пользователям изучать новые слова и формировать словарь "на лету". В предыдущих статьях мы начали с разработки базового приложения, а затем добавили в него жесты смахивания для упрощения навигации и значки для повышения привлекательности пользовательского интерфейса. Все это позволило усовершенствовать данное приложение, однако ему по-прежнему не хватает определенного ингредиента: приложению Overheard Word необходима возможность для работы со словами!

Чтобы приложение Overheard Word действительно могло работать со словами, я создал небольшой механизм под названием Thingamajig, который инкапсулирует представление слова и соответствующие ему определения. Thingamajig управляет созданием слов и их определений посредством JSON-документов и совершенно не зависит от платформы Android. Хостинг Thingamajig (как и приложения Overheard Word) осуществляется на сайте GitHub. Вы можете клонировать соответствующий репозитарий или загрузить исходный код, а затем запустить ant.

Вы можете скопировать этот небольшой JAR-файл (8 КБ) в свой каталог libs. Если ваш проект Android настроен надлежащим образом, то ваша IDE-среда автоматически распознает любой файл в каталоге libs как зависимость.

В настоящее время код в моем механизме Thingamajig инициализируется с помощью экземпляра JSON-документа. JSON-документ может представлять собой файл, размещенный в файловой системе какого-либо устройства, ответ на HTTP-запрос или даже ответ на запрос к базе данных — в настоящее время это не имеет для нас никакого значения. Гораздо важнее то обстоятельство, что у вас есть отличная библиотека, которая позволяет вам работать с объектами Word. Каждый объект Word содержит коллекцию объектов Definition и соответствующую часть речи (part of speech). Хотя слова и их определения не исчерпываются этим простым отношением, на данный момент нам этого вполне достаточно. Позднее мы сможем добавить примеры предложений, синонимы и антонимы.


Сторонние библиотеки в Android-проекте

Интеграция сторонней библиотеки в Android-проект не составляет никакого труда — каждый новый Android-проект имеет специальный каталог libs, куда можно помещать сторонние JAR-файлы. В процессе сборки Android-проекта "нормальные" JVM-файлы и сторонние JAR-файлы преобразуются с целью обеспечения совместимости с Dalvik, специализированной виртуальной машиной для платформы Android.

Ограничения мобильной среды

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

Помимо подключения к моему приложению библиотеки для механизма слов я собираюсь добавить другое стороннее приложение под названием Gesticulate. Как и библиотеку для механизма Thingamajig, вы сможете получить приложение Gesticulate посредством его клонирования или загрузки его исходного кода с сайта GitHub. Затем запустите ant и получите JAR-файл, который можно будет поместить в каталог libs нашего приложения.


Обновление пользовательского интерфейса

Теперь мы переходим к основной части этой статьи, посвященной обновлению вашего пользовательского интерфейса с целью интеграции всего стороннего кода, который вы только что получили бесплатно. К счастью, я заранее предусмотрел этот момент в жизненном цикле моего приложения Overheard Word. Когда я в свое время писал первую итерацию этого приложения, я определил простой макет, состоящий из слова, из названия соответствующей этому слову части речи и из его определения (см. рис.1).

Рисунок 1. Представление по умолчанию для приложения Overheard Word
The default view in the Overheard Word app screen

Этот макет определен с помощью т. н. "заполняющего текста" (placeholder text), который я собираюсь заменить реальными словами, полученными из моего подключаемого механизма определения слов. Таким образом, я инициализирую серию слов, беру одно слово и использую его значения для соответствующего обновления пользовательского интерфейса.

Чтобы обновить пользовательский интерфейс, я должен получить дескрипторы для отдельных элементов представления и предоставить им значения. Например, отображенное слово Pedestrian определяется как виджет TextView, у которого идентификатор ID имеет значение word_study_word (см. Листинг 1).

Листинг 1. Виджет TextView, определенный в макете
 <TextView
        android:id="@+id/word_study_word"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginBottom="10dp"
        android:layout_marginTop="60dp"
        android:textColor="@color/black"
        android:textSize="30sp" 
        android:text="Word"/>

Обратите внимание, что текста для = "Word" я задал в формате XML; однако я могу программно настраивать это значение посредством получения ссылки на экземпляр TextView и вызова setText.

Теперь мне необходимо создать список слов. Напоминаю, что мой механизм для работы со словами способен создавать экземпляры Word с помощью JSON-документов, поэтому мне достаточно настроить мое Android-приложение таким образом, чтобы оно включало и читало эти специальные файлы.

Работа с файлами: каталоги res и raw

По умолчанию Android-приложение имеет доступ по чтению к обеспечивающей файловой системе устройства, однако у вас есть доступ только к локальной файловой системе, с которой работает ваше приложение. Соответственно, вы сможете включать файлы в свое приложение и ссылаться на них соответствующим образом (это означает, что вы сможете читать и записывать файлы своего приложения локально; запись в файловую систему за пределами вашего приложения, напр., на SD-карту, требует специальных разрешений). Мой механизм для работы со словами способен взять JSON-документ, чтобы инициализировать список слов, поэтому ничего не мешает мне включить JSON-документ, который мое приложение будет читать в процессе исполнения.

Как и активы в виде значков, которые я представил в своей предыдущей статье, вы сможете хранить файлы в каталоге res. Если мне требуются специальные файлы, я предпочитаю добавлять их в каталог raw.К любому файлу, который я помещают в этот каталог, можно обратиться посредством сгенерированного файла R, который я уже использовал несколько раз для приложения Overheard Word. По существу платформа Android берет активы из каталога res и создает класс с именем R, который затем предоставляет дескриптор для этих активов. Если актив является файлом, то файл R предоставит ссылку для открытия этого файла и для получения его содержимого.

Я создаю каталог raw внутри структуры каталога res и помещаю в него JSON-документ (см. рис. 2).

Рисунок 2. Каталог raw с новыми словами
A view of the raw directory showing some words.

Затем Eclipse заново компонует проект, в результате чего мой файл R получает удобную ссылку на новый файл (см. рис. 3).

Рисунок 3. Обновленный файл R
A view of the updated R file.

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

Построение списка слов

После запуска приложения я выполняю серию шагов, которая загружает исходный JSON-документ и формирует список слов. С этой целью я создал метод buildWordList (см. Листинг 2).

Листинг 2. Чтение содержимого файла в Android
private List<Word> buildWordList() {
  InputStream resource = 
    getApplicationContext().getResources().openRawResource(R.raw.words);
  List<Word> words = new ArrayList<Word>();
  try {
    StringBuilder sb = new StringBuilder();
    BufferedReader br = new BufferedReader(new InputStreamReader(resource));
    String read = br.readLine();
    while (read != null) {
        sb.append(read);
        read = br.readLine();
    }
    JSONObject document = new JSONObject(sb.toString());
    JSONArray allWords = document.getJSONArray("words");
    for (int i = 0; i < allWords.length(); i++) {
        words.add(Word.manufacture(allWords.getJSONObject(i)));
    }
  } catch (Exception e) {
    Log.e(APP, "Exception in buildWordList: " + e.getLocalizedMessage());
  }
  return words;
}

Обратите внимание на несколько моментов в методе buildWordList. Во-первых, InputStream создается с использованием вызовов платформы Android, которые в конечном итоге ссылаются на файл words.json в каталоге raw. Для представления маршрута я нигде не использую String, что позволяет портировать мой код на обширный ассортимент устройств. Во-вторых, для преобразования содержимого (представленного посредством String) в серию JSON-документов я пользуюсь простой JSON-библиотекой, включенной в платформу Android. Статический метод manufacture в приложении Word берет одиночный JSON-документ, представляющий слово.

Формат JSON для слова показан в Листинге 3:

Листинг 3. JSON-представление слова
{
  "spelling":"sagacious",
  "definitions": [
     {
      "part_of_speech":"adjective",
      "definition":"having or showing acute mental discernment
        and keen practical sense; shrewd"
     }
    ]
}

Мой класс WordStudyEngine является главным фасадом для механизма Thingamajig. Он производит случайные слова и функции на основе списка экземпляров Word Таким образом, мой следующий шаг состоит в инициализации моего механизма с использованием недавно созданного списка WordList (см. Листинг 4).

Листинг 4. Инициализация класса WordStudyEngine
List<Word> words = buildWordList();
WordStudyEngine engine = WordStudyEngine.getInstance(words);

После того, как у меня будет инициализированный экземпляр механизма, я смогу запросить у него слово (которое автоматически подвергается рандомизации), а затем обновить три элемента пользовательского интерфейса соответствующим образом. Например, я могу обновить фрагмент определения word (см. Листинг 5).

Листинг 5. Программное обновление элемента пользовательского интерфейса
Word aWord = engine.getWord();
TextView wordView = (TextView) findViewById(R.id.word_study_word);
wordView.setText(aWord.getSpelling());

В Листинге 5findViewById— это вызов платформы Android, берущий целочисленный идентификатор (ID), который вы можете получить от класса R своего приложения. Вы сможете программным образом задать текст TextView. Кроме того, вы можете установить тип шрифта, цвет шрифта или размер отображаемого текста: wordView.setTextColor(Color.RED).

В Листинге 6 этот же процесс используется для обновления таких элементов пользовательского интерфейса моего приложения, как part-of-speech (часть речи) и definition (определение).

Листинг 6. Другие программные обновления
Definition firstDef = aWord.getDefinitions().get(0);
TextView wordPartOfSpeechView = (TextView) findViewById(R.id.word_study_part_of_speech);
wordPartOfSpeechView.setText(firstDef.getPartOfSpeech());

TextView defView = (TextView) findViewById(R.id.word_study_definition);
defView.setText(formatDefinition(aWord));

В Листинге 5 и в Листинге 6 обратите внимание, насколько полезным является файл R для того, чтобы ссылаться на элементы макета по именам. Метод formatDefinition, находящийся в моем классе Activity берет строку определения и использует ее первый символ. Кроме того, этот метод форматирует строку посредством постановки точки в конце предложения, если ее там нет (обратите внимание на то, что Thingamajig не имеет никакого отношения к форматированию — это лишь механизм для работы со словами).

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

Рисунок 4. Приложение Overheard Word имеет слова!
An updated view of the Overheard Word display screen displaying the word 'sagacious'.

Добавление жестов: Связывание жестов смахивания со словами

Теперь, когда я беспрепятственно могу отобразить слово, я хочу предоставить пользователям возможность для быстрого "смахивания" (swipe) всех слов в моем механизме для работы со словами. Чтобы упростить себе задачу, я собираюсь использовать стороннюю библиотеку Gesticulate (которую я сам создал). Эта библиотека вычисляет скорость и направление смахивания. Кроме того, я поместил логику смахивания в метод с именем initializeGestures.

После того, как я инициализировал жесты смахивания, мой первый шаг состоит в перемещении логики, обеспечивающей отображение отдельного слова, в новый метод, который я затем смогу вызывать. Это позволит отображать новое слово, когда пользователь осуществит очередное смахивание. В Листинге 7 показан мой обновленный метод onCreate (вызов которого первоначально осуществлялся в том случае, когда платформа Android создавала экземпляр приложения):

Листинг 7. Теперь метод onCreate отображает слово при инициализации посредством жестов
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  Log.d(APP, "onCreated Invoked");
  setContentView(R.layout.activity_overheard_word);
  
  initializeGestures();
  
  List<Word> words = buildWordList();
  
  if (engine == null) {
    engine = WordStudyEngine.getInstance(words);
  }
  Word firstWord = engine.getWord();
  displayWord(firstWord);
}

Обратите внимание на переменную engine, которую я определил как переменную private static экземпляра класса Activity приложения OverheardWord. Позднее я коротко объясню, почему я поступил таким образом.

Затем я перехожу к методу initGestureDetector ссылка на который имеется в методе initializeGestures (просмотрите клонированный вами код). Напоминаю, что метод initGestureDetector имеет логику для выполнения соответствующего действия в том случае, когда пользователь на экране устройства осуществляет смахивание вверх, вниз, налево или направо. В приложении Overheard Word я хочу отображать новое слово в том случае, когда пользователь осуществляет смахивание справа налево (left swipe). Сначала я удаляю сообщение Toast (которое я использовал в качестве заполнителя для этого кода) и заменяю его вызовом displayWord (см. Листинг 8).

Листинг 8. Теперь метод initGestureDetector отображает слово в случае смахивания влево
private GestureDetector initGestureDetector() {
  return new GestureDetector(new SimpleOnGestureListener() {
    public boolean onFling(MotionEvent e1, MotionEvent e2, 
      float velocityX, float velocityY) {
      try {
       final SwipeDetector detector = new SwipeDetector(e1, e2, velocityX, velocityY);
       if (detector.isDownSwipe()) {
         return false;
       } else if (detector.isUpSwipe()) {
         return false;
       } else if (detector.isLeftSwipe()) {
         displayWord(engine.getWord());
       } else if (detector.isRightSwipe()) {
         Toast.makeText(getApplicationContext(), 
           "Right Swipe", Toast.LENGTH_SHORT).show();
       }
      } catch (Exception e) {}
     return false;
    }
  });
}

Мой механизм слов, представленный переменной engine должен быть доступен в масштабе всего класса Activity. Именно поэтому я определил его как переменную экземпляра — это гарантирует, что при каждом смахивании влево будет отображаться новое слово.


Смахивание в прямом и в обратном направлениях

Теперь, когда я открываю свое приложение и начинаю смахивание, при каждом смахивании влево я вижу отображение нового слова и его определения. Однако при смахивании в другом направлении я получаю лишь крошечное сообщение, информирующее меня о том, что я осуществил смахивание. Не правда ли, было бы лучше, если бы я смог посредством смахивания направо возвращаться к предыдущему слову?

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

Для начала я создам связанный список LinkedList для всех слов, которые уже были отображены (см. Листинг 9). Затем я сохраню указатель на индекс элемента в этом списке, который я смогу использовать для получения уже просмотренных слов. Естественно, я определю эти переменные экземпляра как static. Кроме того, при инициализации своего указателя я присвою ему значение -1 и буду инкрементно наращивать его при каждом добавлении нового слова к экземпляру LinkedList. Как и в большинстве массивоподобных коллекций типа backed collection в любом языке, индекс списка LinkedList начинается с нулевого значения.

Листинг 9. Новые переменные экземпляра
private static LinkedList<Word> wordsViewed;
private static int viewPosition = -1;

В Листинге 10 я инициализирую LinkedList в методе onCreate моего приложения.

Листинг 10. Инициализация списка LinkedList
if (wordsViewed == null) {
  wordsViewed = new LinkedList<Word>();
}

Теперь при отображении первого слова я должен добавить его к экземпляру LinkedList (wordsViewed) и прибавить инкремент к переменной указателя viewPosition (см. Листинг 11).

Листинг 11. Инкрементное наращивание позиции представления
Word firstWord = engine.getWord();
wordsViewed.add(firstWord);
viewPosition++;
displayWord(firstWord);

Логика смахивания

Теперь я перехожу к реализации логики, что требует определенного обдумывания. Когда пользователь осуществляет смахивание влево, я хочу показать следующее слово, а когда он осуществляет смахивание вправо, я хочу показать предыдущее слово. Если пользователь снова осуществляет смахивание вправо, необходимо показывать ему второе предыдущее слово и т.д. Этот процесс должен продолжаться до тех пор, пока пользователь не дойдет до первого отображенного слова. Если после этого пользователь снова станет осуществлять смахивание влево, список слов должен отображаться в том же порядке, в каком это происходило ранее. На этот момент необходимо обратить внимание, поскольку в реализации по умолчанию предполагается, что WordStudyEngine возвращает слова в произвольном порядке.

В Листинге 12 я зафиксировал логику того, что я хотел сделать. Первая булева оценка инкапсулируется в методе с именем listSizeAndPositionEql и осуществляется достаточно просто: wordsViewed.size() == (viewPosition + 1).

Если значение listSizeAndPositionEql оценивается как true (истина), то приложение отображает новое слово с помощью метода displayWord. Однако если значение listSizeAndPositionEqlоценивается как false (ложь), то это означает, что пользователь осуществил смахивание назад; поэтому в этом случае осуществляется извлечение соответствующего слова из списка с помощью указателя viewPosition.

Листинг 12. Список LinkedList для прокручивания в прямом и в обратном направлениях
public boolean onFling(MotionEvent e1, MotionEvent e2, float velX, float velY) {
 try {
  final SwipeDetector detector = new SwipeDetector(e1, e2, velX, velY);
  if (detector.isDownSwipe()) {
   return false;
  } else if (detector.isUpSwipe()) {
   return false;
  } else if (detector.isLeftSwipe()) {
    if (listSizeAndPositionEql()) {
     viewPosition++;
     Word wrd = engine.getWord();
     wordsViewed.add(wrd);
     displayWord(wrd);
    } else if (wordsViewed.size() > (viewPosition + 1)) {
       if (viewPosition == -1) {
        viewPosition++;
       } 
      displayWord(wordsViewed.get(++viewPosition));
    } else {
      return false;
   }
  } else if (detector.isRightSwipe()) {
    if (wordsViewed.size() > 0 && (listSizeAndPositionEql() || (viewPosition >= 0))) {
      displayWord(wordsViewed.get(--viewPosition));
    } else {
      return false;
    }
  }
 } catch (Exception e) {}
 return false;
}

Обратите внимание: значение указателя viewPosition, равное -1, означает, что посредством смахиваний в обратном направлении пользователь вернулся к самому началу — в этом случае указатель списка уменьшился до 0. Это вызвано тем, что в основанной на Java реализации LinkedList отсутствует элемент -1 (в некоторых других языках отрицательные значения позиционирования начинаются с задней части списка; в этом случае значение -1 соответствовало бы хвостовому элементу).

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

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


Заключение

Теперь приложение Overheard Word прекрасно работает — после того, когда мы добавили несколько более интересных функций поверх базовой Android-оболочки. Конечно, можно сделать еще очень многое, однако теперь у нас есть функционирующее приложение, которое обрабатывает жесты смахивания, имеет значки и меню, и даже осуществляет чтение из специального файла слов, полученного из стороннего источника. В следующем месяце я покажу, как расширить возможности приложения Overheard Word в области использования стилей, а затем реализую интеллектуальный вопросник.

Ресурсы

Научиться

Получить продукты и технологии

  • Overheard Word: Демонстрационное Android-приложение, хостинг которого осуществляется на GitHub.
  • Thingamajig: Простой механизм для работы со словами слов, используемый в приложении Overheard Word.
  • Gesticulate: Алгоритм детектирования swipe-жестов для платформы Android.
  • Загрузить Android: Комплект Android SDK предоставляет API-библиотеки и инструменты разработчика, необходимые для построения, тестирования и отладки приложений для платформы Android.

Обсудить

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

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



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

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java, Мобильные приложения
ArticleID=954533
ArticleTitle=Программирование для мобильных устройств в массы: Использование слов и жестов в приложении Overheard Word
publish-date=11262013