Из особенностей: PyGTK (hildonize for Nokia N900), само-модифицируемый код, что бы не возиться с конфиг-файлами.
(
Читать дальше
)
Я предсказывал, что 2010 год принесет на рынок смартфонов большие перемены. Я предсказывал, что ситуация будет весьма изменчивой. Я предсказывал, что в уже начавшуюся гонку вступят новые игроки. И еще я предсказывал, что битва будет жестокой. Ну что ж, жертвы уже есть: первой стал Palm. Я предполагал, что они не переживут этот год, и то, что в апреле эта марка была продана, демонстрирует, как накаляется ситуация. Итак, я дал свой прогноз на текущий год. Теперь мы получили данные от ведущих производителей и можем составить отчет о ситуации на рынке смартфонов в 1 квартале. Я использовал средние цифры суммарных продаж смартфонов за квартал по отраслевым аналитическим отчетам, получив общий объем продаж в 54,5 млн. штук.
(
Читать дальше
)
Самый простой способ это сделать - использовать Eclipse с плагином ADT.
1. Запускаем Eclipse.
2. Переходим в режим DDMS, для этого открываем в меню Window->Open Perspective->Other… и там выбираем DDMS

3. Запускаем эмулятор:
- либо из папки где у вас установлен сдк, например, путь_к_сдк-tools-emulator
- или запустив свой проект в эмуляторе
4. В Eclipse, в режиме DDMS выбираем эмулятор из списка (нужно кликнуть по нему):

5. Чуть ниже выбора эмулятора, находится окно отправки смс.

здесь указываем номер звонящего (от кого придет смс), выбираем режим SMS и указываем текст сообщения. Что бы отправить нажимаем Send.
6. Открываем окно с эмулятором и видим наше отправленное смс.

1. Для этого нам понадобится еще один эмулятор. Запускаем его: путь_к_сдк-tools-emulator.exe. Таким образом у нас должно быть открыто два эмулятора и Eclipse.
2. В Eclipse должны быть видны оба эмулятора:

Цифры рядом с emulator - это порт на котором работает эмулятор, он же и будет номером телефона.
3. Например мы хотим отправить смс из emulator-5554 на emulator-5556. Для этого открываем emulator-5554 и запускаем программу для отправки смс - Messaging.

4. Выбираем New message и создаем новую смс. В качестве Incoming number указываем номер эмулятора которому хотим отправит, в нашем случае 5556. И указываем текст сообщения.

Далее нажимаем кнопку отправить.
6. Открываем эмулятор 5556 и видим наше сообщение:

Таким образом, мы видим, что пришло сообщение от эмулятора 5554 с текстом: Hello from emulator 5554 to 5556
Описание: В этой статье мы научимся взаимодействовать через Android с различными датчиками в целях мониторинга окружающей среды.
Уровень сложности: средний
Содержание:
Платформа Android идеально подходит для создания инновационных приложений с использованием аппаратных датчиков, особенно Java-программистами. Мы рассмотрим некоторые варианты интерфейсов для приложений Android, включая использование подсистемы датчиков и записи аудиофрагментов.
Какое приложение лучше построить, чтобы задействовать аппаратные возможности устройства на платформе Android? Подойдет любое, в котором требуются электронные глаза и уши. На ум приходит электронная няня, система безопасности или даже сейсмограф. Хотя с метафизической точки зрения одновременно присутствовать в двух местах невозможно, Android может предложить некоторые практические способы преодоления этого ограничения. В рамках этой статьи под Android-устройством понимается не просто "мобильный телефон", а установленное в определенном месте устройство с беспроводным соединением с сетью, таким как EDGE или Wi-Fi. Загрузите исходный код примера приложения для этой статьи.
Один из приятных аспектов работы с платформой Android заключается в возможности получить доступ к некоторым полезным компонентам самого устройства. До сих пор разработчиков мобильных устройств разочаровывала невозможность доступа к их внутреннему оборудованию. Хотя между вами и металлом все же остается прослойка Java-среды Android, команда разработчиков Android вывела многие возможности аппаратуры на поверхность. А так как это платформа с открытым исходным кодом, можно засучить рукава и написать собственный код для решения своих задач.
Загрузите SDK Android, если вы этого еще не сделали. Рекомендуем также изучить содержимое пакета android.hardware и следить за примерами этой статьи. Пакет android.media package содержит классы, которые предоставляют разработчикам новые полезные функции.
Ниже описаны некоторые аппаратно-ориентированные функции, содержащиеся в SDK Android.
Таблица 1. Аппаратно-ориентированные функции SDK Android
| Функция | Описание |
|---|---|
|
Класс, позволяющий приложениям взаимодействовать с видеокамерой в целях фотосъемки, записи изображений с экрана предварительного просмотра или для изменения параметров настройки. |
|
Класс, обеспечивающий доступ к внутренним датчикам платформы Android. Не каждое устройство на платформе Android поддерживает все датчики из , однако интересно обдумать такие возможности. (Краткое описание имеющихся датчиков приведено ниже.) |
|
Интерфейс реализован с помощью класса, который используется для ввода значений датчиков по мере их изменения в режиме реального времени. Приложение реализует этот интерфейс для мониторинга одного или нескольких имеющихся аппаратных датчиков. Например, код из этой статьи содержит класс, который использует этот интерфейс для контроля ориентации устройства и показаний встроенного акселерометра. |
|
Класс, используемый для записи медиафрагментов, который можно применять для записи звуков в определенном месте (например, в детской комнате). Можно также анализировать аудиофрагменты для контроля доступа в помещение и в целях безопасности. Например, можно открывать дверь собственным голосом в обычное время своего прихода, вместо того, чтобы обращаться к консьержу за ключом. |
|
Класс, который позволяет распознавать лицо человека по хранящейся в памяти фотографии. Ничто не удостоверяет личность лучше, чем лицо. Если использовать его для блокировки устройства, вам больше не придется запоминать пароли – достаточно биометрических возможностей мобильного телефона. |
| android.os.* | Пакет, содержащий несколько полезных классов для взаимодействия с операционной средой, включая управление питанием, поиск файлов, обработчик и классы для обмена сообщениями. Как и многие другие портативные устройства, телефоны на базе Android могут потреблять достаточно много электроэнергии. Обеспечение "бодрствования" устройства в нужный момент, чтобы проконтролировать нужное событие, - важный аспект проектирования, заслуживающий особого внимания. |
|
При измерении событий реального мира часто имеют значение дата и время. Например, класс позволяет получить метку времени, когда происходит какое-либо событие или возникает определенное состояние. и можно использовать соответственно для выполнения периодических действий по расписанию или разового действия в определенный момент времени. |
Android.hardware.SensorManager содержит несколько констант, которые характеризуют различные аспекты системы датчиков Android, в том числе:
Центром сенсорных приложений служит интерфейс
SensorListener. Он включает в себя два необходимых метода:
onSensorChanged(int sensor,float values[]) вызывается всякий раз, когда изменяется значение датчика. Этот метод вызывается только для датчиков, контролируемых данным приложением (подробнее об этом ниже). В число аргументов метода входит целое, которое указывает, что значение датчика изменилось, и массив значений с плавающей запятой, отражающих собственно значение датчика. Некоторые датчики выдают только одно значение данных, тогда как другие предоставляют три значения с плавающей запятой. Датчики ориентации и акселерометр дают по три значения данных каждый.onAccuracyChanged(int sensor,int accuracy) вызывается при изменении точности показаний датчика. Аргументами служат два целых числа: одно указывает датчик, а другое соответствует новому значению точности этого датчика.Для взаимодействия с датчиком приложение должно зарегистрироваться на прием действий, связанных с одним или несколькими датчиками. Регистрация осуществляется с помощью метода
registerListener класса SensorManager. Пример кода для этой статьи демонстрирует, как приложение регистрируется и отменяет регистрацию с помощью SensorListener.
Помните, что не каждое устройство Android поддерживает тот или иной датчик, указанный в SDK. Если на конкретном устройстве тот или иной датчик отсутствует, ваше приложение должно обрабатывать эту ситуацию аккуратно.
Этот пример приложения просто следит за изменениями значений датчиков ориентации и акселерометра (исходный код приведен в разделе Загрузки). При изменении значений датчиков они отображаются на экране в виджете
TextView. Рисунок 1 демонстрирует приложение в действии.
Приложение создано в среде Eclipse с плагином Android Developer Tools. (Более подробную информацию о разработке Android-приложений с помощью Eclipse см. в разделе Ресурсы.) В листинге 1 приведен код этого приложения.
package com.msi.ibm.eyes;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.hardware.SensorManager;
import android.hardware.SensorListener;
public class IBMEyes extends Activity implements SensorListener {
final String tag = "IBMEyes";
SensorManager sm = null;
TextView xViewA = null;
TextView yViewA = null;
TextView zViewA = null;
TextView xViewO = null;
TextView yViewO = null;
TextView zViewO = null;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// get reference to SensorManager
sm = (SensorManager) getSystemService(SENSOR_SERVICE);
setContentView(R.layout.main);
xViewA = (TextView) findViewById(R.id.xbox);
yViewA = (TextView) findViewById(R.id.ybox);
zViewA = (TextView) findViewById(R.id.zbox);
xViewO = (TextView) findViewById(R.id.xboxo);
yViewO = (TextView) findViewById(R.id.yboxo);
zViewO = (TextView) findViewById(R.id.zboxo);
}
public void onSensorChanged(int sensor, float[] values) {
synchronized (this) {
Log.d(tag, "onSensorChanged: " + sensor + ", x: " +
values[0] + ", y: " + values[1] + ", z: " + values[2]);
if (sensor == SensorManager.SENSOR_ORIENTATION) {
xViewO.setText("Orientation X: " + values[0]);
yViewO.setText("Orientation Y: " + values[1]);
zViewO.setText("Orientation Z: " + values[2]);
}
if (sensor == SensorManager.SENSOR_ACCELEROMETER) {
xViewA.setText("Accel X: " + values[0]);
yViewA.setText("Accel Y: " + values[1]);
zViewA.setText("Accel Z: " + values[2]);
}
}
}
public void onAccuracyChanged(int sensor, int accuracy) {
Log.d(tag,"onAccuracyChanged: " + sensor + ", accuracy: " + accuracy);
}
@Override
protected void onResume() {
super.onResume();
// register this class as a listener for the orientation and accelerometer sensors
sm.registerListener(this,
SensorManager.SENSOR_ORIENTATION |SensorManager.SENSOR_ACCELEROMETER,
SensorManager.SENSOR_DELAY_NORMAL);
}
@Override
protected void onStop() {
// unregister listener
sm.unregisterListener(this);
super.onStop();
}
}
|
Это обычное приложение на основе действий, поскольку оно просто выводит на экран данные, получаемые от датчиков. Для ситуаций, когда устройство должно одновременно выполнять другие высокоприоритетные действия, приложение лучше было бы реализовать в форме сервиса.
Метод действия
onCreate получает ссылку на SensorManager, где расположены все связанные с датчиками функции. Кроме того, метод onCreate создает ссылки на шесть виджетов TextView, в которые будут выводиться результаты измерений.
Метод
onResume(), используя ссылку на SensorManager, регистрируется на прием обновлений данных датчика посредством метода registerListener:
SensorListener.SENSOR_ORIENTATION и SENSOR_ACCELEROMETER.Когда работа приложения приостанавливается, нужно отменить регистрацию приемника, чтобы больше не получать обновления значений датчика. Это делается с помощью метода
unregisterListener класса SensorManager. Единственным параметром является экземпляр SensorListener.
При вызове обоих методов
registerListener и unregisterListener приложение использует ключевое слово this. Обратите внимание на ключевое слово implements в определении класса, которое декларирует, что этот класс реализует интерфейс SensorListener. Вот почему в registerListener и unregisterListener передается this.
SensorListener должен реализовать два метода: onSensorChange и onAccuracyChanged. Наш пример приложения не заботится о точности датчиков, а лишь вводит текущие значения X, Y и Z этих датчиков. Метод onAccuracyChanged, по существу, ничего не делает; он просто добавляет запись в журнал при каждом вызове.
Кажется, что метод
onSensorChanged вызывается постоянно, так как акселерометр и датчик ориентации передают данные быстро. Чтобы определить, какой датчик передает данные, мы смотрим на первый параметр. Когда передающий датчик выявлен, соответствующие элементы пользовательского интерфейса обновляются данными, содержащимися в массиве значений с плавающей запятой, принятом в качестве второго аргумента метода. Хотя пример просто отображает эти значения, в более сложных приложениях они могут анализироваться, сравниваться с предыдущими значениями или использоваться в некоем алгоритме распознавания образов для определения того, что делает пользователь (или что происходит во внешней среде).
Теперь, когда мы рассмотрели подсистему датчиков, в следующем разделе приведем пример кода, который записывает звук на телефон Android. Этот пример работает на устройстве для разработчиков Dev1.
Пакет Android.media содержит классы для взаимодействия с мультимедийной подсистемой. Класс
android.media.MediaRecorder используется для записи медиафрагментов, включая аудио и видео. MediaRecorder действует как конечный автомат. Вы задаете различные параметры, такие как устройство-источник и формат. После установки запись может выполняться как угодно долго, пока не будет остановлена.
В листинге 2 приведен код для записи звука на устройство Android. Этот код не включает элементы пользовательского интерфейса приложения (полный исходный код см. в разделе загрузок).
Листинг 2. Запись аудиофрагмента
MediaRecorder mrec ;
File audiofile = null;
private static final String TAG="SoundRecordingDemo";
protected void startRecording() throws IOException
{
mrec.setAudioSource(MediaRecorder.AudioSource.MIC);
mrec.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mrec.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
if (mSampleFile == null)
{
File sampleDir = Environment.getExternalStorageDirectory();
try
{
audiofile = File.createTempFile("ibm", ".3gp", sampleDir);
}
catch (IOException e)
{
Log.e(TAG,"sdcard access error");
return;
}
}
mrec.setOutputFile(audiofile.getAbsolutePath());
mrec.prepare();
mrec.start();
}
protected void stopRecording()
{
mrec.stop();
mrec.release();
processaudiofile(audiofile.getAbsolutePath());
}
protected void processaudiofile()
{
ContentValues values = new ContentValues(3);
long current = System.currentTimeMillis();
values.put(MediaStore.Audio.Media.TITLE, "audio" + audiofile.getName());
values.put(MediaStore.Audio.Media.DATE_ADDED, (int) (current / 1000));
values.put(MediaStore.Audio.Media.MIME_TYPE, "audio/3gpp");
values.put(MediaStore.Audio.Media.DATA, audiofile.getAbsolutePath());
ContentResolver contentResolver = getContentResolver();
Uri base = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
Uri newUri = contentResolver.insert(base, values);
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, newUri));
}
|
В методе
startRecording создается и инициализируется экземпляр MediaRecorder.
MIC).AMR_NB - аудиоформат с частотой дискретизации 8 кГц. NB означает узкую полосу частот. Различные форматы данных и имеющиеся кодеры рассматриваются в документации SDK.Аудиофайл хранится не во внутренней памяти, а на отдельной карте.
External.getExternalStorageDirectory() возвращает имена карты памяти и временного файла, созданного в этом каталоге. Затем этот файл связывается с экземпляром MediaRecorder, обращаясь к методу setOutputFile. Аудиоданные будут храниться в этом файле.
Вызов метода
prepare завершает инициализацию MediaRecorder. Когда нужно начать процесс записи, вызывается метод start. Запись в файл на карте памяти ведется до тех пор, пока не будет вызван метод stop. Этот метод освобождает ресурсы, выделенные экземпляру MediaRecorder.
Когда аудиофрагмент записан, можно выполнить несколько действий:
В примере кода метод
processaudiofile добавляет аудиозапись в медиатеку. Для уведомления встроенного приложения о том, что доступна новая информация, используется Intent.
И последнее замечание об этом фрагменте кода: сразу после создания он не будет записывать аудио. Вы увидите созданный файл, но не услышите звука. Нужно добавить разрешение в файл AndroidManifest.xml:
<uses-permission android:name="android.permission.RECORD_AUDIO"></uses-permission> |
Теперь вы кое-что знаете о взаимодействии с датчиками Android и записи аудио. В следующем разделе приводится более широкий обзор архитектуры приложений, связанных со сбором данных и системами передачи сообщений.
Платформа Android располагает широкими возможностями по измерению параметров окружающей среды. При таком многообразии входных параметров и датчиков в сочетании с мощными вычислительными и сетевыми функциями Android становится привлекательной платформой для создания систем взаимодействия с реальным миром. На рисунке 2 упрощенно показано соотношение между входами, логикой приложений и методами уведомления или выходами.
Это очень гибкая архитектура; логику приложения можно распределять между локальным Android-устройством и серверными ресурсами, которые могут подключаться к более крупным базам данных и вычислительным мощностям. Например, аудиотрек, записанный на локальном Android-устройстве, можно отправить методом
POST на Web-сервер, где данные сопоставляются с базой данных образцов голоса. Ясно, что это только самое поверхностное знакомство с возможностями платформы. Надеюсь, что вы заинтересовались и начнете копать глубже, чтобы использовать платформу Android не только для целей мобильной телефонии.
Из этой статьи вы получили первое представление о работе с датчиками Android. Приведенные примеры приложений определяют ориентацию и ускорение, а также взаимодействуют со средствами звукозаписи с использованием класса
MediaRecorder. Android - гибкая, удобная платформа для создания систем взаимодействия с реальным миром. Сфера влияния Android очень быстро расширяется, и эта платформа осваивает все новые области применения. Не упускайте ее из вида.
| Описание | Имя | Размер |
|---|---|---|
| Исходный код примера Eyes | os-android-sensorEyes.zip | 28 КБ |
| Исходный код примера IBMAudio | os-android-sensorIBMAudio.zip | 33 КБ |
Научиться
Получить продукты и технологии
Обсудить
Когда Фрэнк Эйблсон (Frank Ableson) закончил карьеру баскетболиста в команде своего колледжа, не заключив многолетнего контракта с «Лос-Анджедес Лейкерс», он занялся разработкой компьютерных программ. Он любит решать сложные задачи, особенно из области связи и интерфейсов с аппаратурой. Свободное время Фрэнк проводит со своей женой Никки и детьми. С ним можно связаться по адресу: frank@cfgsolutions.com.
Описание: Android — это полноценная операционная среда, основанная на ядре Linux® V2.6. На начальном этапе областью распространения Android был сегмент мобильных телефонов, включая смартфоны и более дешевые раскладные устройства. Однако полный спектр вычислительных сервисов и богатые функциональные возможности Android позволяют выйти за рамки рынка мобильных телефонов. Android может быть полезен для других платформ и приложений. Настоящая статья дает введение в платформу Android и учит программированию простых приложений для этой платформы.
Уровень сложности: простой
Содержание:
BlackBerry и iPhone, две удобные и массовые мобильные платформы, олицетворяют собой противоположные концы спектра мобильных устройств. BlackBerry незаменим для корпоративного пользователя. В классе же потребительских устройств у iPhone мало конкурентов в части удобства обращения и «крутизны». Android, молодая и еще не утвердившаяся платформа, способна проявить себя на обоих концах спектра мобильных телефонов и, возможно, даже стать мостом через пропасть между работой и игрой.
Сегодня многие сетевые или поддерживающие сеть устройства работают на том или ином варианте ядра Linux. Это солидная платформа: недорогая в развертывании и поддержке и хорошо принимаемая благодаря удобному подходу к развертыванию. Пользовательский интерфейс таких устройств зачастую основан на HTML и может просматриваться с помощью браузера для РС или Mac. Но не всяким устройством нужно управлять посредством компьютера. Рассмотрим обычные бытовые приборы, такие, как плита, микроволновая печь или хлебопечка. Что если у ваших бытовых приборов была бы ОС Android и цветной сенсорный экран? Будь у плиты интерфейс Android, автор и сам, возможно, смог бы что-нибудь приготовить.
Из этой стати вы узнаете о платформе Android и о том, как ее можно использовать для мобильных и стационарных приложений. Мы установим SDK Android и создадим простую программу. Загрузите исходный код примера приложения для этой статьи.
Платформа Android является продуктом группы Open Handset Alliance, ставящей себе целью создание более совершенного мобильного телефона. Эта группа во главе с Google включает операторов мобильных сетей, производителей телефонов и компонентов, разработчиков программных решений и поставщиков услуг, а также маркетинговые компании. С точки зрения разработки программного обеспечения Android находится в самом центре мира открытого ПО.
Первым выпущенным на рынок телефоном на платформе Android стало устройство G1 производства HTC, которое распространяла T-Mobile. Это устройство появилось почти через год после того, как о нем пошли слухи, и единственным инструментом разработки программ для него были постепенно совершенствуемые последовательные выпуски SDK. Накануне выпуска G1 команда Android представила SDK v1.0, и начали появляться приложения для новой платформы.
Чтобы стимулировать инновации, Google спонсировала два «Конкурса разработчиков для Android», победители которых получили миллионы долларов. Через несколько месяцев после выхода G1 открылся сайт Android Market, откуда пользователи могли загружать приложения прямо в свой телефон. Всего за полтора года новая мобильная платформа вышла на арену.
По широте возможностей платформа Android не уступает операционным системам настольных ПК. Это многоуровневая среда на основе ядра Linux с богатыми функциональными возможностями. В подсистему пользовательского интерфейса входят:
Android содержит встраиваемый браузер на базе WebKit - того же механизма с открытым исходным кодом, который лежит в основе браузера Safari мобильного телефона iPhone.
Android обладает широким спектром возможностей подключения, охватывающим Wi-Fi, Bluetooth и протоколы передачи данных через сотовую сеть (GPRS, EDGE, 3G и др.). Популярным приемом в приложениях для Android является ссылка на Google Maps для отображения адреса непосредственно в приложении.В стек программного обеспечения Android входит и поддержка сервисов, основанных на определении местоположения (например, GPS), и акселерометров, хотя не все устройства на этой платформе оснащены необходимым оборудованием. Есть также поддержка видеокамеры.
Исторически двумя областями, где мобильные приложения отставали от своих настольных собратьев, были графика/мультимедиа и способы хранения данных. Android решает проблему графики благодаря встроенной поддержке 2-D и 3-D графики, включая библиотеку OpenGL. Задача хранения данных упрощается благодаря наличию в платформе Android популярной базы данных с открытым исходным кодом SQLite. На рисунке 1 показана упрощенная схема уровней программного обеспечения Android.
Как уже говорилось, Android работает поверх ядра Linux. Android-приложения пишутся на языке программирования Java и выполняются в виртуальной машине (VM). Важно отметить, что виртуальная машина – это не JVM, как можно было бы ожидать, а открытая технология Dalvik Virtual Machine. Каждое приложение Android запускается внутри экземпляра Dalvik VM, который, в свою очередь заключен в пределах управляемого ядром Linux процесса, как показано на рисунке 2.
Android-приложение содержит элементы одного или нескольких перечисленных ниже типов:
Приложение для Android развертывается на устройстве вместе с файлом AndroidManifest.xml. Этот файл содержит необходимую информацию о конфигурации, которая позволяет правильно установить приложение на устройстве. Он включает также необходимые имена классов и типы событий, которые может обрабатывать приложение, и разрешения, требуемые для его работы. Так, если приложению нужен доступ к сети – например, чтобы загрузить файл, - соответствующее разрешение должно быть явно указано в файле манифеста. Это конкретное разрешение могут иметь многие приложения. Такая защита путем декларирования помогает уменьшить вероятность повреждения устройства по вине некорректно написанного приложения.
В следующем разделе рассматривается среда разработки, необходимая для создания Android-приложений.
Самый простой способ приступить к разработке приложений для Android - это загрузить SDK Android и Eclipse IDE (см. Ресурсы). Разработку Android-приложений можно вести на платформах Microsoft® Windows®, Mac OS X или Linux.
В этой статье предполагается, что вы используете Eclipse IDE и плагин Android Developer Tools для Eclipse. Android-приложения пишутся на языке Java, но компилируются и выполняются в Dalvik VM (не в виртуальной машине Java). Кодирование на языке Java в рамках Eclipse – интуитивно понятный процесс; Eclipse предоставляет богатую среду Java, включая контекстно-зависимую справку и подсказки к коду. Когда ваш Java-код будет безошибочно скомпилирован, Android Developer Tools сам позаботится о том, чтобы приложение был надлежащим образом упаковано, в том числе снабдит его файлом AndroidManifest.xml.
Android-приложение можно написать и без Eclipse и плагина Android Developer Tools, но для этого нужно хорошо разбираться в Android SDK.
Android SDK распространяется в виде файла ZIP, который распаковывается в папку на жестком диске. Так как вышло несколько обновлений SDK, мы рекомендуем вам поддерживать среду разработки в порядке, чтобы можно было легко переключаться между разными установками SDK. В SDK входят:
adb (Android Debug Bridge).Android-приложения могут работать как на реальном устройстве, так и на эмуляторе Android, который прилагается к SDK Android. На рисунке 3 показан главный экран эмулятора Android.
Утилита
adb поддерживает несколько дополнительных аргументов командной строки, которые обеспечивают мощные функции, такие как копирование файлов в устройство и из него. Аргумент оболочки командной строки позволяет подключаться к самому телефону и подавать простые команды оболочки. Рисунок 4 иллюстрирует команду оболочки adb, подаваемую реальному устройству, подключенному к ноутбуку под Windows с помощью кабеля USB.
В рамках этой консоли можно:
lo - это локальное (петлевое) соединение.tiwlan0 - это соединение WiFi с адресом, предоставленным локальным сервером DHCP.PATH.su, чтобы стать суперпользователем.В этой же среде командной строки можно взаимодействовать с базами данных SQLite, запускать программы и решать многие другие задачи системного уровня. Это довольно примечательная функция с учетом того, что речь идет о подключении к телефону.
В следующем разделе мы создадим простое приложение для Android.
Этот раздел содержит краткий обзор процесса создания Android-приложения. Наш пример приложения предельно прост: это несколько видоизмененное приложение «Hello Android». Мы добавим незначительные изменения, чтобы сделать цвет фона экрана белым – тогда телефон можно будет использовать в качестве фонарика. Не очень оригинально, но как пример полезно. Загрузите весь исходный код.
Чтобы создать приложение в Eclipse, выберите File > New > Android project, что приведет к запуску мастера нового проекта Android (рисунок 5).
Теперь создадим простое приложение с одним действием, а также макет пользовательского интерфейса, который будет храниться в папке main.xml. Макет содержит текстовый элемент, который мы заменим на Android FlashLight (фонарик Android). Этот простой макет приведен в листинге 1.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@color/all_white">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello" android:textColor="@color/all_black"
android:gravity="center_horizontal"/>
</LinearLayout>
|
Создайте пару ресурсов цвета в strings.xml (листинг 2).
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="hello">Android FlashLight</string>
<string name="app_name">FlashLight</string>
<color name="all_white">#FFFFFF</color>
<color name="all_black">#000000</color>
</resources>
|
Цвет фона основного экрана определяется как
all_white. В файле strings.xml видно, что all_white соответствует значению триплета RGB #FFFFFF, что означает чисто белый цвет.
Макет содержит одно поле
TextView, которое на самом деле - просто нередактируемый фрагмент статического текста. Текст будет черным и отцентрованным по горизонтали с помощью атрибута gravity.
Приложение содержит исходный файл Java с именем FlashLight.java, как показано в листинге 3.
package com.msi.flashlight;
import android.app.Activity;
import android.os.Bundle;
public class FlashLight extends Activity {
/** Called when the activity is first created. */
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
|
Код создается непосредственно в мастере новых проектов:
onCreate, который передается в savedInstanceState. Не сомневайтесь по поводу использования этого пакета для наших целей; он применяется, когда действие приостановлено, а затем возобновляется.onCreate переопределяет метод класса действия с таким же именем. Он обращается к методу суперкласса onCreate.setContentView() относится к макету UI, определенному в файле main.xml. Содержимое main.xml и strings.xml, автоматически отображается на константы, определенные в исходном файле R.java. Никогда не редактируйте этот файл напрямую, так как он изменяется после каждой сборки.Запуск приложения приводит к отображению белого экрана с черным текстом (рисунок 6).
Настройка файла AndroidManifest.xml для приложения FlashLight показана в листинге 4.
Листинг 4. AndroidManifest.xml для приложения FlashLight
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.msi.flashlight"
android:versionCode="1"
android:versionName="1.0.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".FlashLight"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
|
Этот файл создается автоматически плагином Android Developer Tools для Eclipse. Вам не нужно ничего делать.
Разумеется, это не очень мощное приложение. Но оно может пригодиться, если вы хотите что-нибудь почитать, не нарушая сна супруги, или если погас свет и надо найти путь к предохранителям в подвале.
При чтении этой статьи вы получили самые общие сведения о платформе Android и создали небольшое приложение. Надеюсь, этот пример произвел на вас достаточное впечатление, чтобы захотелось узнать о платформе побольше. Android обещает стать ведущей платформой с открытым исходным кодом, выходящей далеко за пределы рынка сотовых телефонов.
Исходный код FlashLight (zip, 24 Кб)
Научиться
Получить продукты и технологии
Обсудить
Когда Фрэнк Эйблсон (Frank Ableson) закончил карьеру баскетболиста в команде своего колледжа, не заключив многолетнего контракта с «Лос-Анджедес Лейкерс», он занялся разработкой компьютерных программ. Он любит решать сложные задачи, особенно из области связи и интерфейсов с аппаратурой. Свободное время Фрэнк проводит со своей женой Никки и детьми. С ним можно связаться по адресу: frank@cfgsolutions.com.
Источник: megadarja.blogspot.com
Вот мы и добрались до конца. Осталось сделать только главное меню приложения, а также сделать игре настройки (собственно, меню только для того и нужно, чтобы было откуда настройки вызывать). Ну первое мы еще с прошлой статьи умеем, так что особых сложностей быть не должно. А вот второе следует рассмотреть подробнее. Итак, приступим.
В одной из прошлых статей подробно рассматривался вопрос, как создавать формы в приложении для Android и делать переходы между ними. Так что особо останавливаться я не буду, и так все ясно.
На нашей форме приветствия будет какая-нибудь картинка и три кнопки: "Начать игру", "Настройки" и "Выход". Картинку в формате png, которую мы назовем
start.png нужно положить в папку /res/drawable. Названия кнопок нужно вынести в strings.xml, добавив следующие строки:
<resources> <string name="app_name">PingPong</string>
<string name="start_title">Start Game</string>
<string name="settings_title">Settings</string>
<string name="exit_title">Exit</string>
Тогда разметка для новой формы будет выглядеть так:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:gravity="bottom" android:background="@drawable/start" android:padding="8dip"> <Button android:id="@+id/StartButton" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textStyle="bold" android:text="@string/start_title" /> <Button android:id="@+id/SettingsButton" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textStyle="bold" android:text="@string/settings_title" /> <Button android:id="@+id/ExitButton" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textStyle="bold" android:text="@string/exit_title" /> </LinearLayout>
Фоновое изображение можно задать экрану с помощью полезного свойства
android:background. Собственно, так можно задавать фон и кнопкам, и вообще чему угодно. Получили вот такую разметку:
Добавим соответствующий этой разметке класс
StartScreen.java. Сразу обработаем нажатия всех кнопок:
public class StartScreen extends Activity implements OnClickListener { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.start); // Кнопка "Start" Button startButton = (Button)findViewById(R.id.StartButton); startButton.setOnClickListener(this); // Кнопка "Exit" Button exitButton = (Button)findViewById(R.id.ExitButton); exitButton.setOnClickListener(this); // Кнопка "Settings" Button settingsButton = (Button)findViewById(R.id.SettingsButton); settingsButton.setOnClickListener(this); } /** Обработка нажатия кнопок */ public void onClick(View v) { switch (v.getId()) { case R.id.StartButton: { Intent intent = new Intent(); intent.setClass(this, GameScreen.class); startActivity(intent); break; } case R.id.SettingsButton: { break; } case R.id.ExitButton: finish(); break; default: break; } } }
По нажатию на кнопку Start происходит переход на экран с игрой. Обратите внимание, что
StartScreen при этом не закрывается. Это значит, что, когда закроется StartScreen, мы попадем обратно на экран приветствия. По нажатию на Settings пока что ничего не происходит, по Exit — приложение закрывается.
Осталось только зарегистрировать эту форму в приложении и сделать ее главной. Для этого идем в
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.pingpong" android:versionCode="1" android:versionName="1.0.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".GameScreen" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.SAMPLE_CODE" /> </intent-filter> </activity>
<activity android:name=".StartScreen">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Теперь приложение начинается с
StartScreen, все кнопки работают.
Я сделаю в настройках два параметра — максимальное количество очков и сложность. Сложность игры будем менять, варьируя скорость мячика и ракеток.
Сама форма с настройками делается достаточно просто. Есть специальный наследник класса
Activity — PreferenceActivity, который именно для этого и предназначен. Когда мы хотим сделать форму с настройками, нужно унаследоваться именно от него, и он возьмет на себя большую часть рутины.
Разметка для формы с настройками выглядит несколько необычно:
<?xml version="1.0" encoding="UTF-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" > <PreferenceCategory android:title="@string/prefs_title"> <ListPreference android:key="@string/pref_difficulty" android:title="@string/difficulty_title" android:entries="@array/difficulty" android:entryValues="@array/difficulty" android:defaultValue="1" /> <EditTextPreference android:key="@string/pref_max_score" android:title="@string/score_title" android:defaultValue="10" /> </PreferenceCategory> </PreferenceScreen>
Настолько необычно, что, если поместить этот XML в папку layout, eclipse начнет ругаться, что не может разрезолвить такие классы. Собственно, это не просто разметка: там также содержатся ключи настроек и значения по умолчанию. Поэтому мы создадим отдельную папку xml, и поместим этот файл туда. А теперь обо всем по порядку.
Ну,
PreferenceScreen все понятно, а что такое PreferenceCategory? Как ни удивительно, это категория настроек. Например, у какой-нибудь игры могут быть настройки графики, настройки звука, настройки сети и т.д.. Удобно отобразить их сгруппированными, вот так:
А можно обойтись без групп:
PreferenceScreen уже сам по себе является контейнером для настроек. В нашем случае, например, настроек мало и группировать нечего. Но это я так, для полноты картины.
Как видно даже не прошлой картинке, настройки могут быть разными. И флажки, и текстовые поля, и списки. Все они происходят от одного класса
Preference, и наследуют от него всякие полезные атрибуты, которые можно задавать в разметке, как то:
Ну и еще кое-что. Рассмотрим некоторые конкретные виды настроек.
Вот такой флажок:
Редактор текста.

Честно говоря, у редактора текста очень хотелось бы валидатор, а еще лучше маску. Например, IP-адрес имеет специфический формат, и хотелось бы запрещать пользователю вводить там что попало. Но мне такую функциональность так и не удалось обнаружить.
Своеобразная реализация Dropdown-а. Хотя, на телефоне наверно и вправду так удобнее.

На этом контроле хотелось бы остановиться подробнее. А конкретнее, рассказать, откуда он, собственно, берет элементы списка. А берет он их из ресурсов с помощью таких атрибутов.
Здесь хранится ссылка на ресурс, в котором хранятся отображаемые элементы списка. Все значения, которые хочется вынести в ресурсы, хранятся в папке values. До сих пор там была только одна XML-ка —
strings.xml. Но теперь надо добавить еще одну — arrays.xml. И добавить в узел resources следующее:
<string-array name="performance"> <item>Best performance</item> <item>Normal performance and appearance</item> <item>Best appearance</item> </string-array>
После этого можно смело указывать в
android:entries этот ресурс, список будет загружен.
Кстати говоря, в
item-ах может быть не непосредственно строка, а ссылка на строку в strings.xml. Например, в нашем случае будет так (разумеется, стоит добавить соответствующие значения в strings.xml):
<?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="difficulty"> <item>@string/difficulty_easy</item> <item>@string/difficulty_normal</item> <item>@string/difficulty_hard</item> </string-array> </resources>
А еще мне никак не удалось победить у
ListPreference атрибут adnroid:defaultValue. Не работает и все.
Настройка звукового сигнала. В нашем приложении, например, можно было бы добавить звук, с которым мячик ударяется о стенку, и выбирать этот звук с помощью такого контрола. Выглядит это так:

Класс для формы с настройками будет выглядеть так:
public class SettingsScreen extends PreferenceActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Настройки и их разметка загружаются из XML-файла addPreferencesFromResource(R.xml.preferences); } }
Кстати, настройки необязательно загружать из XML-ки, можно добавлять все эти настройки прямо в коде конструктора. В сэмплах, которые идут с Android SDK, такие примеры есть.
Добавляем в
StartScreen код для открытия формы настроек, прописываем SettingsScreen в AndroidManifest.xml. (Все выглядит точно так же, как и для GameScreen, так что листинги не привожу). И увидим мы следующее:

Не знаю, кому как, а мне нравится, когда рядом с названием опции написано ее значение. Но как это сделать автоматически, я так и не нашла, пришлось все делать руками, используя для этого поле
summary. Итак, summary настройки должно обновляться при изменении значения. По счастью, есть такое событие OnPreferenceChange. Итак, пишем:
public class SettingsScreen extends PreferenceActivity implements Preference.OnPreferenceChangeListener { /* Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Load the preferences from an XML resource addPreferencesFromResource(R.xml.preferences);
ListPreference difficulty =
(ListPreference)this.findPreference("pref_difficulty");
difficulty.setSummary(difficulty.getEntry());
difficulty.setOnPreferenceChangeListener(this);
EditTextPreference maxScore =
(EditTextPreference)this.findPreference("pref_max_score");
maxScore.setSummary(maxScore.getText());
maxScore.setOnPreferenceChangeListener(this);
Думаю, все понятно без слов. Теперь мы видим такую картину:

И, если мы будем менять настройки, изменения сразу же будут отображаться в
summary.
Ну все, настройки мы сделали, они как-то сами где-то сохранились. Теперь возникла необходимость их прочитать и что-нибудь с ними сделать. Читать и делать мы будем в классе
GameManager, а конкретно, в конструкторе. Для работы с сохраненными настройками приложения используется класс SharedPreferences. Вся работа по чтению и применению настроек выглядит так:
public GameManager(SurfaceHolder surfaceHolder, Context context) { ... // стили для рисования игрового поля ... // игровые объекты ...
// применение настроек
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
String difficulty = settings.getString(res.getString(R.string.pref_difficulty),
res.getString(R.string.difficulty_normal));
setDifficulty(difficulty);
int maxScore = Integer.parseInt(settings.getString(
res.getString(R.string.pref_max_score), "10"));
setMaxScore(maxScore);
Метод
setDifficulty приводить не буду, там ничего особо умного не написано.
SharedPreferences можно читать с помощью методов getString, getInt, getBoolean и т.п. Все они принимают два параметра &mdahs; ключ к настройке (то, что мы задавали с помощью атрибута android:key) и значение по умолчанию. Однако, воспользоваться чем-то кроме getStringмне так и не удалось.
Итак, мануал закончен. Получился он огромным, но при этом собственно про андроид оказалось не так уж и много, что самое-то обидное :( Спасибо, если кто дочитал до конца. Буду рада услышать любые мнения.
Отдельное спасибо xeye и std.denis
Источник: megadarja.blogspot.com
В этой части мы напишем обработку выигрышей-проигрышей, реализуем подсчет очков, а также сделаем, чтобы игру можно было ставить на паузу. Собственно, пауза тут несколько не в тему, но девать ее некуда, так что сделаем ее в этой части.
Помнится, мы заводили в классе
Racquet поле mScore, в котором собирались хранить количество очков у игрока. Теперь самое время начать использовать это поле.
Итак, в начале игры количество очков у обоих игроков пусто. Когда игрок не успевает отбить мяч, его противнику засчитывается очко, мячик и ракетки возвращаются на исходные позиции. Игра продолжается, пока какой-нибудь из игроков не наберет N очков. N мы пока что объявим константой, а в следующей части вынесем в настройки.
Проверка проигрыша должна осуществляться также в методе
updateObjects() GameManager-а. Описанная нами логика запишется так:
private void updateObjects() { ...
// проверка проигрыша
if (mBall.getBottom() < mThem.getBottom())
{
mUs.incScore();
reset();
}
if (mBall.getTop() > mUs.getTop())
{
mThem.incScore();
reset();
}
Racquet.incScore() увеличивает на 1 количество очков у игрока:
/** Увеличить количество очков игрока */ public void incScore() { mScore++; }
GameManager.reset() расставляет ракетки и мячик на исходные позиции, задает мячику новый случайный угол, а также делает паузу (чтобы игрок успел понять, что произошло).
private void reset() { // ставим мячик в центр mBall.setCenterX(mField.centerX()); mBall.setCenterY(mField.centerY()); // задаем ему новый случайный угол mBall.resetAngle(); // ставим ракетки в центр mUs.setCenterX(mField.centerX()); mThem.setCenterX(mField.centerX()); // делаем паузу try { sleep(LOSE_PAUSE); } catch (InterruptedException iex) { } }
LOSE_PAUSE — это константа класса GameManager, в которой задается длина паузы в миллисекундах (у меня она равна 2000). Метод же resetAngle() класса Ball выглядит следующим образом:
/** Задает новое случайное значение угла */ public void resetAngle() { mAngle = getRandomAngle(); }
Если теперь запустить приложение, то увидим, что, если упустить мячик, то он никуда не улетит, а через некоторое время восстановится в центре. А про очки пока ничего сказать нельзя, потому что они нигде не выводятся. Что ж, будем выводить.
Вывод текста на экран производится с помощью метода
drawText(String text, float x, float y, Paint paint) класса Canvas. Как можно заметить, стили текста задаются с помощью экземпляра класса Paint. Где-то в первой части мы создавали в GameManager такое поле mPaint, где хранились стили для рисования игрового поля. Для вывода текста можно использовать это же поле, и при каждой перерисовке экрана задавать ему стили сначала для игрового поля, а потом для текста. А можно завести отдельный экземпляр Paint для хранения стилей текста:
private Paint mScorePaint;
Инициализировать его в конструкторе:
public GameManager(SurfaceHolder surfaceHolder, Context context) { mSurfaceHolder = surfaceHolder; Resources res = context.getResources(); mRunning = false; // стили для рисования игрового поля mPaint = new Paint(); mPaint.setColor(Color.BLUE); mPaint.setStrokeWidth(2); mPaint.setStyle(Style.STROKE);
// стили для вывода счета
mScorePaint = new Paint();
mScorePaint.setTextSize(20);
mScorePaint.setStrokeWidth(1);
mScorePaint.setStyle(Style.FILL);
mScorePaint.setTextAlign(Paint.Align.CENTER);
А непосредственно вывод счета игры производится в методе, где происходит вся отрисовка текущей игровой ситуации —
refreshCanvas
private void refreshCanvas(Canvas canvas) { // вывод фонового изображения canvas.drawBitmap(mBackground, 0, 0, null); // рисуем игровое поле canvas.drawRect(mField, mPaint); // рисуем игровые объекты mBall.draw(canvas); mUs.draw(canvas); mThem.draw(canvas);
// вывод счета
mScorePaint.setColor(Color.RED);
canvas.drawText(String.valueOf(mThem.getScore()),
mField.centerX(), mField.top - 10, mScorePaint);
mScorePaint.setColor(Color.GREEN);
canvas.drawText(String.valueOf(mUs.getScore()),
mField.centerX(), mField.bottom + 25, mScorePaint);
Правда, совсем уж без изменения стиля не обошлось. Наши очки мы рисуем зелёным, а очки противника — красным. Запустив, увидим примерно такую картину:

А теперь нам захотелось использовать для вывода счета какой-нибудь наш красивый шрифт. Рассмотрим, как это можно сделать.
В нашем проекте есть такая папка assets, там хранятся такие ресурсы, как TrueType-шрифты, возможно, какие-то большие тексты и т.д.. Основное отличие их от ресурсов, которые хранятся в папке
res — это то, что используются они гораздо реже, и доставать их оттуда сложнее. Ресурсы из res можно запросто достать с помощью класса R, а assets вытаскиваются с помощью специального класса AssetManager.
Итак, создадим в папке
assets папку fonts и кинем туда шрифт под названием Mini.ttf. Теперь, чтобы достать этот шрифт и использовать его для вывода количества очков, достаточно добавить в инициализацию mScorePaint в конструкторе одну строчку:
mScorePaint.setTypeface(Typeface.createFromAsset(context.getAssets(), "fonts/Mini.ttf"));
context.getAssets() получит менеджер ресурсов (AssetManager) для данного приложения, откуда потом будет можно загрузить шрифт по указанному пути. Стоит обратить внимание, что путь является case-sensitive, т.е. "fonts/mini.ttf" уже ничего не загрузит.

И всё бы хорошо, но теперь время от времени стали возникать ситуации, когда в начале игры у одного из игроков выводится не 0 очков, а 1. Я так понимаю, что проблемы возникают в самом начале программы, перед
initPositions, когда у игровых объектов координаты еще не заданы, а updateObjects уже вызывается. Чтобы исправить положение, заведем в классе GameManager еще одно булево поле mInitialized, в конструкторе зададим как false, а в initPositions присвоим ему true. Тогда в run можно написать так:
public void run() { while (mRunning) { Canvas canvas = null; try { // подготовка Canvas-а canvas = mSurfaceHolder.lockCanvas(); synchronized (mSurfaceHolder) {
if (mInitialized)
{
Теперь гарантированно не будет происходить никаких проверок, пока не будут инициализированы координаты игровых объектов. Проблема решена.
Прежде всего, нам следует завести в
GameManager переменную, где бы хранилось количество очков, до которого идет игра. Заведем такую переменную и сразу сеттер к ней. Итак:
/** Максимальное число очков, до которого идет игра */ private static int mMaxScore = 5; public static void setMaxScore(int value) { mMaxScore = value; }
Саму проверка на окончание игры можно поместить как в метод
updateObjects(), так и прямо в run(). Но, думаю, правильнее именно в updateObjects():
/** Обновление состояния игровых объектов */ private void updateObjects() { // Обновление состояния игровых объектов ... // обработка столкновений ... // проверка проигрыша ...
// проверка окончания игры
if (mUs.getScore() == mMaxScore mThem.getScore() == mMaxScore)
{
this.mRunning = false;
}
Напомню, что метод
run выглядит так:
public void run() { while (mRunning) { // обновление и отрисовка объектов } }
То есть, когда
mRunning станет равным false, поток завершится. Раз он завершился — игра закончена, и надо вывести на экран ее результаты. Так что логично видеть в методе run() что-то вроде:
public void run() { while (mRunning) { // обновление и отрисовка объектов } // рисование GameOver ... }
А теперь разберемся, как это рисование может выглядеть. Как известно, при рисовании мы лочим Canvas, рисуем, и затем разлочиваем. При этом еще нужно отловить возможные исключения. Получается куча кода, которая появляется при каждом рисовании и сильно загромождает текст программы. Естественно, хочется вынести все это в метод-обертку и передавать туда ссылку на функцию, осуществляющую собственно рисование. На C# это выглядело бы так:
delegate void DrawFunction(Canvas canvas); private void draw(DrawFunction something) { Canvas canvas = null; try { canvas = mSurfaceHolder.lockCanvas(); synchronized (mSurfaceHolder) { something(canvas); } } } catch (Exception e) { } finally { if (canvas != null) { mSurfaceHolder.unlockCanvasAndPost(canvas); } } }
Но здесь нам не C#, здесь климат иной, и делегатов нет. Однако, как мне подсказал товарищ xeye, подобный код можно написать. Итак, добавим в
GameManager такой интерфейс:
private interface DrawHelper { void draw(Canvas canvas); }
И такой метод, куда мы вынесем всю работу по подготовке canvas-а:
private void draw(DrawHelper helper) { Canvas canvas = null; try { // подготовка Canvas-а canvas = mSurfaceHolder.lockCanvas(); synchronized (mSurfaceHolder) { if (mInitialized) { helper.draw(canvas); } } } catch (Exception e) { } finally { if (canvas != null) { mSurfaceHolder.unlockCanvasAndPost(canvas); } } }
Теперь можно завести конкретные реализации
DrawHelper на все случаи жизни. Я добавляю их две:
/** Хелпер для перерисовки экрана */ private DrawHelper mDrawScreen; /** Хелпер для рисования результата игры*/ private DrawHelper mDrawGameover;
Инициализирую в конструкторе таким образом:
public GameManager(SurfaceHolder surfaceHolder, Context context) { ... // функция для рисования экрана mDrawScreen = new DrawHelper() { public void draw(Canvas canvas) { refreshCanvas(canvas); } }; // функция для рисования результатов игры mDrawGameover = new DrawHelper() { public void draw(Canvas canvas) { // Вывели последнее состояние игры refreshCanvas(canvas); // смотрим, кто выиграл и выводим соответствующее сообщение String message = ""; if (mUs.getScore() > mThem.getScore()) { mScorePaint.setColor(Color.GREEN); message = "You won"; } else { mScorePaint.setColor(Color.RED); message = "You lost"; } mScorePaint.setTextSize(30); canvas.drawText(message, mField.centerX(), mField.centerY(), mScorePaint); } }; }
После этого метод
run() преображается до неузнаваемости:
/** Действия, выполняемые в потоке */ public void run() { while (mRunning) { if (mInitialized) { updateObjects(); // обновляем объекты draw(mDrawScreen); } } draw(mDrawGameover); }
И сразу результат:

Тут совсем кратко. Объявим в классе
GameManager поле:
/** Стоит ли приложение на паузе */ private boolean mPaused;
Если приложение на паузе, поток работает "вхолостую", т.е. состояния объектов не меняются и вообще ничего не происходит. Это значит, в методе
run() будет следущее:
public void run() { while (mRunning) {
if (mPaused) continue;
Будем ставить приложение на паузу, если нажата средняя клавиша джойстика:
public boolean doKeyDown(int keyCode) { switch (keyCode) { case KeyEvent.KEYCODE_DPAD_LEFT: case KeyEvent.KEYCODE_A: mUs.setDirection(GameObject.DIR_LEFT); return true; case KeyEvent.KEYCODE_DPAD_RIGHT: case KeyEvent.KEYCODE_D: mUs.setDirection(GameObject.DIR_RIGHT); return true;
case KeyEvent.KEYCODE_DPAD_CENTER:
mPaused = !mPaused;
draw(mDrawPause);
return true;
mDrawPause — хелпер для рисования паузы. Я уже не буду приводить к нему листинг, там все просто.
У нас уже совсем готовая игра. Можно играть, выигрывать, проигрывать, ставить на паузу.
Источник: megadarja.blogspot.com
В этой статье мы рассмотрим две темы: управление игровыми объектами и их взаимодействие. Мячик у нас уже летает, осталось сделать, чтобы он отражался от стен и ракеток; также стоит реализовать управление нижней ракетки игроком, а верхней — неким алгоритмом. Итак, приступим.
Для начала добавим в
GameObject следующие полезные функции:
/** Верхняя граница объекта */ public int getTop() { return mPoint.y; } /** Нижняя граница объекта */ public int getBottom() { return mPoint.y + mHeight; } /** Левая граница объекта */ public int getLeft() { return mPoint.x; } /** Правая граница объекта */ public int getRight() { return mPoint.x + mWidth; } /** Центральная точка объекта */ public Point getCenter() { return new Point(mPoint.x + mWidth / 2, mPoint.y + mHeight / 2); } /** Высота объекта */ public int getHeight() { return mHeight; } /** Ширина объекта */ public int getWidth() { return mWidth; } /** @return Прямоугольник, ограничивающий объект */ public Rect getRect() { return mImage.getBounds(); } /** Проверяет, пересекаются ли два игровых объекта */ public static boolean intersects(GameObject obj1, GameObject obj2) { return Rect.intersects(obj1.getRect(), obj2.getRect()); }
Игровые объекты ничего не знают ни о друг друге, ни об игровом поле, поэтому все столкновения будут обрабатываться GameManager-ом. Итак, рассмотрим сначала такую ситуацию:

Итак, наш мячик был в некотором состоянии A, потом, пройдя расстояние
mSpeed в заданном направлении, перешел в состояние B, и оказалось, что он вышел за пределы поля. Тут надо сделать следующее: поместить шарик в правильное состояние C, получившееся при отражении соответствующей координаты от стены, и изменить направление движения так, чтобы угол падения был равен углу отражения.
На рисунке выше показано, какие координаты будет иметь мячик при столкновении со стеной слева. Аналогично можно рассчитать и остальные случаи.
Рассмотрим варианты столкновения. Пусть ? — угол, под которым движется мячик, а ? — угол, получившийся после столкновения. Посмотрим, как ? зависит от ? в различных случаях:
| Значения ? | Столкновение с вертикалью | Столкновение с горизонталью | |||
|---|---|---|---|---|---|
| 1 | 0 < ? < ? / 2 |
![]() |
? = ? ? ? |
![]() |
? = 2? ? ? |
| 2 | ? / 2 < ? < ? |
![]() |
? = ? ? ? |
![]() |
? = 2? ? ? |
| 3 | ? < ? < 3? / 2 |
![]() |
? = 3? ? ? |
![]() |
? = 2? ? ? |
| 4 | 3? / 2 < ? < 2? |
![]() |
? = 3? ? ? |
![]() |
? = 2? ? ? |
Выяснив всй это, можно добавить в класс
Ball следующие функции:
/** Отражение мячика от вертикали */ public void reflectVertical() { if (mAngle > 0 && mAngle < PI) mAngle = PI - mAngle; else mAngle = 3 * PI - mAngle; } /** Отражение мячика от горизонтали */ public void reflectHorizontal() { mAngle = 2 * PI - mAngle; }
Обновление же в
GameManager изменится таким образом:
private void updateObjects() { mBall.update(); mUs.update(); mThem.update(); // проверка столкновения мячика с вертикальными стенами if (mBall.getLeft() <= mField.left) { mBall.setLeft(mField.left + Math.abs(mField.left - mBall.getLeft())); mBall.reflectVertical(); } else if (mBall.getRight() >= mField.right) { mBall.setRight(mField.right - Math.abs(mField.right - mBall.getRight())); mBall.reflectVertical(); } // проверка столкновения мячика с горизонтальными стенами if (mBall.getTop() <= mField.top) { mBall.setTop(mField.top + Math.abs(mField.top - mBall.getTop())); mBall.reflectHorizontal(); } else if (mBall.getBottom() >= mField.bottom) { mBall.setBottom(mField.bottom - Math.abs(mField.bottom - mBall.getBottom())); mBall.reflectHorizontal(); } }
Запускаем и видим, что мячик летает по полю и отражается от всех стен. Вообще-то ему надо отражаться только от вертикальных стен и от ракеток, но управление ракетками у нас пока не реализовано, так что для наглядности пока так, а потом мы этот кусок кода уберем.
Перемещать ракетку можно двумя способами. Первый — отлавливать нажатие кнопок вправо и влево, и при нажатии смещать ракетку в нужную сторону. Однако, как показала практика, такой способ не очень хорош, так как ракетка двигается резко и подтормаживает при первом нажатии. Более прогрессивным способом оказался другой: при нажатии клавиши назначать ракетке соответствующий Direction, а при отпускании обнулять его. А в методе
updateObjects ракетка сама перемещается в том направлении, которое у нее указано.
Итак, теперь надо бы написать код для обработки нажатия клавиш. Вообще, нажатие клавиш ловит View, и для обработки нужно перегрузить функции
onKeyDown и onKeyUp. Однако, игровыми объектами у нас ведает GameManager, так что фактическая обработка будет происходить именно там. Так что добавляем в GameView следующее:
@Override public boolean onKeyDown(int keyCode, KeyEvent event) { return mGameManager.doKeyDown(keyCode); } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { return mGameManager.doKeyUp(keyCode); }
А в
GameManager нужно добавить методы doKeyDown и doKeyUp, который будут выполнять всю работу:
/** * Обработка нажатия кнопки * @param keyCode Код нажатой кнопки * @return Было ли обработано нажатие */ public boolean doKeyDown(int keyCode) { switch (keyCode) { case KeyEvent.KEYCODE_DPAD_LEFT: mUs.setDirection(GameObject.DIR_LEFT); return true; case KeyEvent.KEYCODE_DPAD_RIGHT: mUs.setDirection(GameObject.DIR_RIGHT); return true; default: return false; } } /** * Обработка отпускания кнопки * @param keyCode Код кнопки * @return Было ли обработано действие */ public boolean doKeyUp(int keyCode) { if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) { mUs.setDirection(GameObject.DIR_NONE); return true; } return false; }
GameViewдобавить одну строчку:
public GameView(Context context, AttributeSet attrs) { super(context, attrs); // подписываемся на события Surface mSurfaceHolder = getHolder(); mSurfaceHolder.addCallback(this); mGameManager = new GameManager(mSurfaceHolder, context);
setFocusable(true);
Теперь все работает, ракетка управляется.
У компьютера логика будет простая: по возможности не допускать, чтобы мячик выходил за пределы ракетки. Если мячик окажется правее ракетки, ракетка едет направо, если левее — налево. Реализуем всю эту логику в методе
moveAI() у GameManager-а:
private void moveAI() { if (mThem.getLeft() > mBall.getRight()) mThem.setDirection(GameObject.DIR_LEFT); else if (mThem.getRight() < mBall.getLeft()) mThem.setDirection(GameObject.DIR_RIGHT); mThem.update(); }
А
GameManager.updateObjects() будет выглядеть так:
private void updateObjects() { mBall.update(); mUs.update();
moveAI();
Собственно, никто им этого делать не запрещает. Напишем в
GameManager такой метод:
private void placeInBounds(Racquet r) { if (r.getLeft() < mField.left) r.setLeft(mField.left); else if (r.getRight() > mField.right) r.setRight(mField.right); }
А в
GameManager.updateObjects() добавим:
private void updateObjects() { mBall.update(); mUs.update(); moveAI();
// чтобы ракетки не выходили за пределы поля placeInBounds(mUs); placeInBounds(mThem);
Теперь все стало хорошо, ракетки не выходят за пределы поля.
Сейчас у нас мячик летает, отражаясь от стен, а хотелось бы, чтобы он летал, отражаясь только от вертикальных стен и от ракеток. Убираем из
GameManager.updateObjects() весь код, осуществляющий отражение от горизонтальных стен, и пишем код для отражения от ракеток. Код достаточно прост:
private void updateObjects() { // Обновление положений объектов ... // Обработка столкновений с вертикальными стенами ...
// проверка столкновений мячика с ракетками if (GameObject.intersects(mBall, mUs)) { mBall.setBottom(mUs.getBottom() - Math.abs(mUs.getBottom() - mBall.getBottom())); mBall.reflectHorizontal(); } else if (GameObject.intersects(mBall, mThem)) { mBall.setTop(mThem.getTop() + Math.abs(mThem.getTop() - mBall.getTop())); mBall.reflectHorizontal(); }
Физика тут простейшая: везде угол падения равен углу отражения. Надо сказать, смотрится это местами довольно несуразно, да и искусственный интеллект в таких условиях практически непобедим. Но более реалистичное движение шарика делать не хочется. Во-первых, я в физике не очень сильна, а во-вторых, статья получится совсем уж большая и совсем не про андроид. Так что оставлю эту часть читателям в качестве легкого домашнего упражнения :)
Итак, мы написали код для управления ракетками, причем управления как человеком, так и компьютером, реализовали обработку столкновений. В принципе, уже можно играть.
Источник: megadarja.blogspot.com
Добавим в нашу игру экшена. Как известно, в пинг-понге три действующих лица: мячик и две ракетки. Имеет смысл реализовать соответствующие классы —
Ball и Racquet. А, поскольку имеются некоторые свойства, присущие обоим этим сущностям (как то: расположение, изображение, размеры и т.д.), то можно сделать базовый класс под названием GameObject. Диаграмма классов будет такая:

У всех игровых объектов есть:
mPoint — левый верхний угол прямоугольника, ограничивающего объект. С её помощью однозначно определяется положение объекта на плоскостиmImage — изображение объекта. Изображения обычно берутся из ресурсов приложения — /res/drawablemHeight, mWidth — размеры изображения объекта. Вынесены как поля класса только для того, чтобы не загромождать код всякими mImage.getIntrinsicHeight() и mImage.getIntrinsicWidth()mSpeed — скорость перемещения объекта (фактически, на сколько перемещается объект за один шаг)Кроме того, все игровые классы умеют рисовать себя на указанном Canvas-е (метод
draw) и вычислять своё следующее состояние (update). Причем, перемещение происходит так: вычисляется следующее состояние опорной точки (updatePoint), а потом туда переносится изображение. А, поскольку мячик и ракетка перемещаются по-разному, метод updatePoint сделан абстрактным.
В класс
Ball добавлено поле mAngle — угол (в градусах) к оси Ox, под которым летит мячик. В класс Racquet — поля mDirection (направление, куда движется ракетка — вправо или влево, или вообще не движется) и mScore (количество очков у игрока).
public abstract class GameObject { // Константы для направлений public final int DIR_LEFT = -1; public final int DIR_RIGHT = 1; public final int DIR_NONE = 0; /** Координаты опорной точки */ protected Point mPoint; /** Высота изображения */ protected int mHeight; /** Ширина изображения */ protected int mWidth; /** Изображение */ private Drawable mImage; /** Скорость */ protected int mSpeed; /** * Конструктор * @param image Изображение, которое будет обозначать данный объект */ public GameObject(Drawable image) { mImage = image; mPoint = new Point(0, 0); mWidth = image.getIntrinsicWidth(); mHeight = image.getIntrinsicHeight(); } /** Перемещение опорной точки */ protected abstract void updatePoint(); /** Перемещение объекта */ public void update() { updatePoint(); mImage.setBounds(mPoint.x, mPoint.y, mPoint.x + mWidth, mPoint.y + mHeight); } /** Отрисовка объекта */ public void draw(Canvas canvas) { mImage.draw(canvas); } /** Задает левую границу объекта */ public void setLeft(int value) { mPoint.x = value; } /** Задает правую границу объекта */ public void setRight(int value) { mPoint.x = value - mWidth; } /** Задает верхнюю границу объекта */ public void setTop(int value) { mPoint.y = value; } /** Задает нижнюю границу объекта */ public void setBottom(int value) { mPoint.y = value - mHeight; } /** Задает абсциссу центра объекта */ public void setCenterX(int value) { mPoint.x = value - mHeight / 2; } /** Задает левую ординату центра объекта */ public void setCenterY(int value) { mPoint.y = value - mWidth / 2; } }
public class Ball extends GameObject { private static final int DEFAULT_SPEED = 3; private static final int PI = 180; /** Угол, который составляет направление полета шарика с осью Ox */ private int mAngle; /** * @see com.android.pingpong.objects.GameObject#GameObject(Drawable) */ public Ball(Drawable image) { super(image); mSpeed = DEFAULT_SPEED; // задали скорость по умолчанию mAngle = getRandomAngle(); // задали случайный начальный угол } /** * @see com.android.pingpong.objects.GameObject#updatePoint() */ @Override protected void updatePoint() { double angle = Math.toRadians(mAngle); mPoint.x += (int)Math.round(mSpeed * Math.cos(angle)); mPoint.y -= (int)Math.round(mSpeed * Math.sin(angle)); } /** Генерация случайного угла в промежутке [95, 110]U[275,290] */ private int getRandomAngle() { Random rnd = new Random(System.currentTimeMillis()); return rnd.nextInt(1) * PI + PI / 2 + rnd.nextInt(15) + 5; } }
public class Racquet extends GameObject { private static final int DEFAULT_SPEED = 3; /** Количество заработанных очков */ private int mScore; /** Направление движения */ private int mDirection; /** Задание направления движения*/ private void setDirection(int direction) { mDirection = direction; } /** * @see com.android.pingpong.objects.GameObject#GameObject(Drawable) */ public Racquet(Drawable image) { super(image); mDirection = DIR_NONE; // Направление по умолчанию - нет mScore = 0; // Очков пока не заработали mSpeed = DEFAULT_SPEED; // Задали скорость по умолчанию } /** * @see com.android.pingpong.objects.GameObject#updatePoint() */ @Override protected void updatePoint() { // двигаем ракетку по оси Ox в соответствующую сторону mPoint.x += mDirection * mSpeed; } }
Ну вроде классы наши готовы, можно теперь их использовать в программе. Но сначала надо нарисовать картинки, для наших объектов. Картинки должны быть в png. У меня получилось так:
![]() |
Мячик |
|
|
Наша ракетка |
![]() |
Pакетка противника |
Берем все это счастье, и кидаем в /res/drawable, где у нас хранятся всякие такие ресурсы.
Теперь нам надо где-то создать экземпляры наших классов, чтобы они там жили, обновлялись и отображались на экране. Очевидно, что тут нам поможет
GameManager. Итак, добавим в него такие поля:
/** Ресурсы приложения */ private Resources mRes; /** Мячик */ private Ball mBall; /** Ракетка, управляемая игроком */ private Racquet mUs; /** Ракетка, управляемая компьютером*/ private Racquet mThem;
В конструкторе инициализируем их:
public GameManager(SurfaceHolder surfaceHolder, Context context) { mSurfaceHolder = surfaceHolder; mRunning = false; // инициализация стилей рисования mPaint = new Paint(); mPaint.setColor(Color.BLUE); mPaint.setStrokeWidth(2); mPaint.setStyle(Style.STROKE);
Resources res = context.getResources();
mField = new Rect();
mBall = new Ball(res.getDrawable(R.drawable.ball));
mUs = new Racquet(res.getDrawable(R.drawable.us));
mThem = new Racquet(res.getDrawable(R.drawable.them));
Однако, этого мало. У всех игровых объектов опорная точка задана в начале координат, т.е. они сейчас все в куче, и надо бы их растащить по местам. И самый лучший метод, где можно это сделать —
initPositions():
public void initPositions(int screenHeight, int screenWidth) { int left = (screenWidth - FIELD_WIDTH) / 2; int top = (screenHeight - FIELD_HEIGHT) / 2; mField.set(left, top, left + FIELD_WIDTH, top + FIELD_HEIGHT);
// мячик ставится в центр поля
mBall.setCenterX(mField.centerX());
mBall.setCenterY(mField.centerY());
// ракетка игрока - снизу по центру
mUs.setCenterX(mField.centerX());
mUs.setBottom(mField.bottom);
// ракетка компьютера - сверху по центру
mThem.setCenterX(mField.centerX());
mThem.setTop(mField.top);
Итак, объекты мы создали, теперь надо заставить их что-то делать. Понятно, что все изменения должны происходить в цикле, который работает в методе
run(). На каждом шаге цикла мы должны обновить состояния объектов и отобразить их. Добавим две функции:
/** Обновление объектов на экране */ private void refreshCanvas(Canvas canvas) { // рисуем игровое поле canvas.drawRect(mField, mPaint); // рисуем игровые объекты mBall.draw(canvas); mUs.draw(canvas); mThem.draw(canvas); } /** Обновление состояния игровых объектов */ private void updateObjects() { mBall.update(); mUs.update(); mThem.update(); }
А в методе
run() будут вызовы этих методов:
public void run() { while (mRunning) { Canvas canvas = null; try { // подготовка Canvas-а canvas = mSurfaceHolder.lockCanvas(); synchronized (mSurfaceHolder) {
updateObjects(); // обновляем объекты
refreshCanvas(canvas); // обновляем экран
sleep(20);
Ну все, вроде сделали, можно запускать. Ракетки не двигаются, потому что ими пока никто не управляет, а вот шарик вполне себе летает. Правда, оставляет при этом за собой шлейф:
Это всё из-за того, что мы не очищаем экран перед очередной отрисовкой. А очищать экран это тоже не так-то просто. Ничего вроде
canvas.clear() я не нашла, так что пришлось извращаться. Суть в том, что мы заводим специальный Bitmap, при инициализации Surface задать ему размеры на весь экран, а потом в refreshCanvas выводить его. При желании можно загрузить в этот Bitmap какое-нибудь изображение из ресурсов.
Итак, заводим поле:
/** Фон */ private Bitmap mBackground;
Инициализируем его в
initPositions
public void initPositions(int screenHeight, int screenWidth) { int left = (screenWidth - FIELD_WIDTH) / 2; int top = (screenHeight - FIELD_HEIGHT) / 2; mField.set(left, top, left + FIELD_WIDTH, top + FIELD_HEIGHT);
mBackground = Bitmap.createBitmap(screenWidth, screenHeight, Bitmap.Config.RGB_565);
И отрисовываем в
refreshCanvas:
private void refreshCanvas(Canvas canvas) {
// вывод фонового изображения
canvas.drawBitmap(mBackground, 0, 0, null);
И получаем примерно вот такую картину:

Мы реализовали игровые объекты, написали код для их перемещения. Мячик у нас успешно летает, но не отражается от стенок поля, а ракетки теоретически тоже могут перемещаться, но код для их управления еще не написан. Этими вещи мы рассмотрим в следующей статье.
Android является программным стеком для мобильных устройств, в том числе операционной системы, промежуточного программного обеспечения и основных приложений. Он разрабатывается группой Open Handset Alliance, включающей в себя более 30 мобильных компаний и технологий. Android был создан “с нуля”, чтобы позволить разработчикам создавать конкурентоспособные мобильные приложения, использующие все возможности мобильного телефона. Разработчики могут создавать приложения для платформы с помощью SDK Android.
С чего же начать?
Ссылки:
Замечание: даже если у вас 64 битная ОС, устанавливать нужно только 32х битный JDK
Установка
Должно выглядеть как на картинке ниже:

Настройка Eclipse
1. Запускаем Eclipse и выбираем в меню: Help > Software Updates->Find and Install…

2. Выбираем Search for new features to install и нажимаем Next >

3. В появившемся окне нажимаем кнопку New Remote Site…
Добавляем источник для Android Development Tools: https://dl-ssl.google.com/android/eclipse/

Нажимаем OK. Проверяем что наш источник выбран
, нажимаем Finish. Начнется поиск доступных плагинов, еще раз выделяем плагин для установки
, далее, соглашаемся с лицензией, Finish-> Install All.
4. Указываем Eclipse путь к установленному SDK:
В меню Window->Preferences… В разделе Android указываем путь к SDK:

После выбора должно быть что то вида:

Нажимаем OK
На этом установка и настройка готова. Можно приступать к созданию программ для Android.
Основная информация по Android может быть найдена на официальном сайте: code.google.com/android