Проблема
Итак, Вы хотите начать изучать азы программирования под Android на конкретном практическом примере. Давайте начнем. Когда вы идете с друзьями в ресторан и хотите разделить чек и чаевые, вы можете столкнуться с множеством расчетов и разногласий. Вместо этого желательно использовать приложение, которое позволяет просто добавить процент чаевых к сумме и разделить его на количество посетителей. Tipster — это реализация этой идеи на платформе Android, представляющее собой законченное приложение.
Решение
Это простое упражнение обучит Вас использовать базовые элементы графического пользовательского интерфейса на платформе Android, с некоторыми простыми вычислениями и событийно-ориентированным пользовательским интерфейсом, связывающим все это вместе.
Обсуждение
Для компоновки графических элементов платформа Android использует XML- файлы. В нашем примере проекта подключаемый модуль Android для среды Eclipse генерирует файл компоновки main.xml
. Этот файл содержит XML-определения различных графических элементов и их контейнеров.
Кроме того, существует файл strings. xml
, который содержит все строковые ресурсы, используемые в приложении. Для пиктограммы приложения по умолчанию предоставляется файл icon.png
.
В проект входит также файл R.java
, который автоматически генерируется (и обновляется при внесении любых изменений в любой файл ресурсов XML). Этот файл имеет константы, определенные для каждой компоновки и графического элемента. Не редактируйте этот файл вручную; инструмент SDK делает это за вас, когда вы вносите какие-либо изменения в свои XML-файлы. В нашем примере Tipster.java является основным файлом Java для класса Activity.
Создание компоновки и размещение графических элементов
Конечной целью является создание компоновки, аналогичной показанной на рис. 1. Для этого экрана мы будем использовать следующие компоновки и графические элементы.
Рис. 1. Готовое приложение Tipster в действии
TableLayout
Отображает компоненты в табличной форме. Аналог дескриптора Table в парадигме HTML.
TableRow
Определяет строку в элементе TableLayout. Аналог дескрипторов TR и TD в языке HTML.
TextView
В этом представлении отображается метка для отображения статического текста на экране.
EditText
Это представление предоставляет текстовое поле для ввода значений.
Radi oG roup
Группа переключателей, только один из которых может быть включен в отдельный момент времени (по аналогии с кнопкой выбора станции на автомобильном радио).
RadioButton
Переключатель для использования в группе.
Button
Обычная кнопка.
View
Мы будем использовать представление для создания визуального разделителя с определенными атрибутами высоты и цвета.
Ознакомьтесь с этими графическими элементами, поскольку вы будете довольно часто использовать их в своих приложениях. Когда будете переходить к генератору документации Javadocs для данной компоновки и компонентов графического интерфейса, найдите атрибуты XML. Это поможет вам скорректировать использование файла компоновки main.xml
и Java-кода (Tipster.java и R.java).
Также доступен редактор визуальных компоновок в среде IDE, который позволяет создавать компоновку, перетаскивая графические элементы из палитры, как любой другой инструмент для проектирования форм. Тем не менее это, вероятно, хорошее упражнение для создания компоновки вручную с помощью языка XML, по крайней мере, на начальных этапах обучения на платформе Android. Позже, когда вы узнаете все нюансы API компоновки XML, вы можете поручить эту задачу таким инструментам.
Файл компоновки main.xml содержит информацию о компоновке (см. пример 1). Графический элемент TableRow создает одну строку в компоновке TableLayout
, поэтому используются столько объектов TableRows, сколько требуется строк. В этом разделе мы будем использовать восемь объектов TableRows — пять для графических элементов, включая визуальный разделитель под кнопками, и три — для области результатов, находящейся под кнопками и разделителями.
Пример 1. Файл /res/layout/main.xml
<?xml version="1.0" encoding="utf-8"?>
<!— Используем TableLayout - аналог таблицы HTML —>
<TableLayout
android:id="@+id/TableLayout01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:stretchColumns="1"
xmlns:android="http://schemas.android.com/apk/res/android">
<!— Строка 1: текстовая метка в нулевом столбце, интервал в два столбца,
и текстовая метка во втором столбце.
Итого в этой строке четыре столбца —>
<TableRow>
<TextView
android:id="@+id/txtLbl1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="0"
android:text="@string/textLbl1"/>
<EditText <!— БЛОК №1 —>
android:id="@+id/txtAmount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:numeric="decimal"
android:layout_column="2"
android:layout_span="2"
/>
</TableRow>
<!— Строка 2: текстовая метка в нулевом столбце, интервал в два столбца, и
текстовая метка во втором столбце,
Итого в этой строке четыре столбца —>
<TableRow>
<TextView
android:id="@+id/txtLbl2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="0"
android:text="@string/textLbl2"/>
<EditText <!— БЛОК №2 —>
android:id="@+id/txtPeople"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:numeric="integer"
android:layout_column="2"
android:layout_span="2"/>
</TableRow>
<!— Строка 3: одна текстовая метка в нулевом столбце —>
<TextView
android:id="@+id/txtLbl3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/textLbl3"/>
</TableRow>
<!— Строка 4: RadioGroup, состоящая из RadioButtons в нулевом столбце, интервал
в три столбца. В каждой ячейке строки таблицы расположен отдельный
переключатель. В последней ячейке с номером 4 расположено текстовое поле для
ввода процентов чаевых —>
<TableRow>
<RadioGroup
android:id="@+id/RadioGroupTips"
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="0"
android:layout_span="3"
android:checkedButton="@+id/radioFifteen">
<RadioButton android:id="@+id/radioFifteen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/rdoTxt15"
android:textSize="15sp" />
<RadioButton android:id="@+id/radioTwenty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/rdoTxt20"
android:textSize="15sp" />
<RadioButton android:id="@+id/radioOther"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/rdoTxtOther"
android:textSize="15sp" />
</RadioGroup>
<EditText
android:id="@+id/txtTipOther"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:numeric="decimal"/>
</TableRow>
<!— Строка для кнопок Calculate и Rest. Кнопка Calculate находится во втором
столбце, a Rest - в третьем —>
<TableRow>
<Button
android:id="@+id/btnReset"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:text="@string/btnReset"/>
<Button
android:id="@+id/btnCalculate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="3"
android:text="@string/btnCalculate"/>
</TableRow>
<!— Класс TableLayout позволяет вставлять между элементами TableRow любые
другие представления. В качестве разделяющей линии мы используем пустое
представление. Оно отделяет область, расположенную под кнопками, в которой
будут отображаться результаты вычислений —>
<View
android:layout_height="2px"
android:background="#DDFFDD"
android:layout_marginTop="5dip"
android:layout_marginBottom="5dip"/>
<!— Еще один экземпляр TableRow, который используется для размещения
текстового представления результатов в нулевом столбце, а самих результатов -
во втором —>
<TableRow android:paddingBottom="10dip" android:paddingTop="5dip">
<TextView
android:id="@+id/txtLbl4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="0"
android:text="@string/textLbl4"/>
<TextView
android:id="@+id/txtTipAmount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:layout_span="2"/>
</TableRow>
<TableRow android:paddingBottom="10dip" android:paddingTop="5dip">
<TextView
android:id="@+id/txtLbl5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="0"
android:text="@string/textLbl5"/>
<TextView
android:id="@+id/txtTotalToPay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:layout_span="2"/>
</TableRow>
<TableRow android:paddingBottom="10dip" android:paddingTop="5dip">
<TextView
android:id="@+id/txtLbl6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="0"
android:text="@string/textLbl6"/>
<TextView
android:id="@+id/txtTipPerPerson"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_column="2"
android:layout_span="2"/>
</TableRow>
<!— Конец всех строк и графических элементов —>
</TableLayout>
TableLayout и TableRow
Изучив файл main.xml
, легко выяснить, что классы TableLayout
и TableRow
просты в использовании. Сначала создается экземпляр TableLayout
, а затем в него вставляется экземпляр TableRow
. После этого в этот экземпляр TableRow
можно вставлять любые другие графические элементы, такие как Textview
, EditView
и т.д.
Обратите внимание на атрибуты, особенно android:stretchColumns
, android:layout_column
и android:layout_span
, которые позволяют размещать графические элементы так же, как и обычную таблицу HTML. Я рекомендую вам следить за ссылками на эти атрибуты и выяснить, как они работают с классом TableLayout
.
Управление входными значениями
Обратите внимание на графический элемент EditText
в файле main.xml
в БЛОКЕ №1. Это первое текстовое поле для ввода общей суммы чека. Здесь мы хотим вводить только цифры. Мы можем вводить десятичные числа, потому что реальные счета ресторана могут содержать и центы, а не только доллары, поэтому мы используем атрибут android:numeric
со значением decimal
. Это позволит использовать целые значения, такие как 10 и десятичные значения, такие как 10.12, но предотвратит любой другой тип записи.
Точно так же в БЛОКЕ №2 используется атрибут android:integer
, потому что не бывает половины посетителя ресторана!
Это простой и лаконичный способ управления входными значениями, избавляющий нас от необходимости писать код проверки в файле Tipster.java и гарантирующий, что пользователь не будет вводить бессмысленные значения. Эта функция ограничений на основе XML для платформы Android довольно эффективна и полезна. Вы должны изучить все возможные атрибуты, которые сопровождают определенные графические элементы, чтобы извлечь максимальную выгоду из этого сокращенного способа задания ограничений. Я надеюсь, что в будущей версии платформа Android позволит вводить диапазоны для атрибута android:numeric, чтобы мы могли определить, какой диапазон чисел мы хотим допустить.
Поскольку диапазоны пока недоступны (насколько мне известно), вы позже увидите, что нам следует проверять определенные значения, такие как нулевые или пустые, чтобы гарантировать, что наш калькулятор не даст сбой.
Изучение файла Tipster.java
Теперь рассмотрим файл Tipster.java, который контролирует наше приложение. Это основной класс, который выполняет компоновку и обработку событий и обеспечивает логику приложения. Подключаемый модуль Android Eclipse создает в нашем проекте файл Tipster.java, по умолчанию содержащий код, показанный в примере 2.
Пример 2. Фрагмент кода из файла TipsterActivity.java
package com.examples.tipcalc;
import android.app.Activity;
public class Tipster extends Activity {
/** Called when the Activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
Класс Tipster
расширяет класс android.app.Activity
. Экземпляр класса Activity
— это отдельная целенаправленная активность, которую может выполнить пользователь. Класс Activity
создает окно, а затем компоновку пользовательского интерфейса. Чтобы разместить свой интерфейс в объекте класса Activity
, необходимо вызвать метод setContentView (View view)
, поэтому его следует рассматривать как пустой кадр, который вы заполняете своим пользовательским интерфейсом.
Теперь просмотрим фрагмент класса Tipster.java
, показанный в примере 3. Сначала мы определяем графические элементы как члены класса. (Посмотрите, в частности, на часть кода между метками №1 и №2.) Затем используем метод findViewByld (int id)
для поиска графических элементов. Идентификатор каждого графического элемента, определенный в файле main.xml
, автоматически определяется в файле R.java
при очистке и создании проекта в среде Eclipse. (По умолчанию в среде Eclipse устанавливается автоматическая сборка, поэтому при обновлении файла main.xml
файл R.java
обновляется мгновенно.)
Каждый графический элемент является производным от класса view и содержит специальные функции графического интерфейса, поэтому класс Textview
предоставляет способ поместить метки в пользовательский интерфейс, а класс EditText
предоставляет текстовое поле. Обратите внимание на код в примере 3, расположенный между метками №3 и №6. Вы можете увидеть, как findViewByld ()
используется для поиска графических элементов.
Пример 3. Второй фрагмент кода из файла TipsterActivity.java
public class Tipster extends Activity {
// Графические элементы приложения (ВИДЖЕТЫ)
private EditText txtAmount; // МЕТКА №1
private EditText txtPeople;
private EditText txtTipOther;
private RadioGroup rdoGroupTips;
private Button btnCalculate;
private Button btnReset;
private TextView txtTipAmount;
private TextView txtTotalToPay;
private TextView txtTipPerPerson; // МЕТКА №2
// Идентификатор выбранного переключателя
private int radioCheckedId = -1;
/** Вызывается при создании первого экземпляра класса Activity. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Доступ к графическим элементам в файле R.java по их идентификатору
txtAmount = (EditText) findViewById(R.id.txtAmount); // МЕТКА №3
// On app load, the cursor should be in the Amount field
txtAmount.requestFocus(); // МЕТКА №4
txtPeople = (EditText) findViewById(R.id.txtPeople);
txtTipOther = (EditText) findViewById(R.id.txtTipOther);
rdoGroupTips = (RadioGroup) findViewById(R.id.RadioGroupTips);
btnCalculate = (Button) findViewById(R.id.btnCalculate);
// При загрузке приложения кнопка Calculate заблокирована
btnCalculate.setEnabled(false); // МЕТКА №5
btnReset = (Button) findViewById(R.id.btnReset);
txtTipAmount = (TextView) findViewById(R.id.txtTipAmount);
txtTotalToPay = (TextView) findViewById(R.id.txtTotalToPay);
txtTipPerPerson = (TextView) findViewById(R.id.txtTipPerPerson); // МЕТКА №6
// При загрузке приложения поле редактирования
// Other Tip Percentage заблокировано
txtTipOther.setEnabled(false); // МЕТКА №7
Обеспечение простоты использования
Наше приложение должно быть не менее удобным, чем любая веб-страница. Обеспечение удобства использования улучшает опыт взаимодействия с пользователем. Чтобы устранить связанные с этим проблемы, просмотрите пример 3 еще раз.
Сначала рассмотрим МЕТКУ №4, в котором мы используем метод requestFocus()
класса View
. Поскольку графический элемент EditText
получен из класса View
, этот метод применим и к нему. Когда наше приложение загружается, фокус и курсор будут находиться в текстовом поле Total Amount
(Итого). Это напоминает популярные экраны входа в веб-приложения, где курсор находится в текстовом поле UserName
(Имя пользователя).
Теперь посмотрите на МЕТКУ №5, где кнопка Calculate
(Вычислить) отключается с помощью вызова метода setEnabled (boolean enabled)
в графическом элементе Button
. Это делается для того, чтобы пользователь не мог щелкнуть на нем, прежде чем введет значения в обязательные поля. Если разрешить пользователю нажимать кнопки Calculate, не вводя значения в поля Total Amount и No. of People (Количество людей), нам пришлось бы писать код проверки этих условий. Это приведет к появлению всплывающего предупреждения о пустых значениях, добавит ненужный код и лишнее взаимодействие с пользователем. Когда пользователь видит, что кнопка Calculate отключена, совершенно очевидно, что если все значения не введены, сумма чаевых не может быть вычислено.
Теперь рассмотрим МЕТКУ №7 в примере 3. Здесь текстовое поле Other Tip Percentage (Другие проценты чаевых) отключено. Это сделано потому, что при загрузке приложения по умолчанию выбран переключатель 15% tip (“15% чаевых). Этот выбор по умолчанию выполняется в файле main.xml
в следующем выражении:
android:checkedButton="@+id/radioFifteen"
Атрибут android:checkedButton
класса RadioGroup
позволяет выбрать по умолчанию один из графических элементов RadioButton
в группе.
Большинство пользователей, которые использовали популярные приложения на рабочем столе, а также в Интернете, знакомы с парадигмой "отключить графические элементы при определенных условиях”. Добавление таких небольших удобств всегда делает приложение более пригодным для использования, и опыт взаимодействия с пользователем становится богаче.
Обработка событий пользовательского интерфейса
Подобно популярным каркасам пользовательского интерфейса Windows, Java Swing, Flex и другим, платформа Android предоставляет модель, позволяющую прослушивать определенные события пользовательского интерфейса, вызванные взаимодействием с пользователем. Рассмотрим, как использовать модель событий Android в нашем приложении.
Сначала сосредоточимся на переключателях в пользовательском интерфейсе. Мы хотим знать, какой переключатель выбран пользователем, так как это позволит нам определить процент чаевых в наших расчетах. Для прослушивания переключателей используется статический интерфейс OnCheckedChangeListener()
. Он сообщает нам, когда изменится выбор переключателя.
Текстовое поле Other Tip Percentage должно активироваться только при выборе переключателя Other (Другие). Если установлен переключатель 15% tip или 20% tip, то это текстовое поле должно быть заблокировано. Помимо этого, мы хотим добавить еще немного логики ради удобства использования. Как мы обсуждали ранее, мы не должны активировать кнопку Calculate, пока все обязательные поля не будут иметь допустимые значения. С помощью переключателей мы хотим убедиться, что кнопка Calculate будет активирована для следующих двух условий.
- Установлен переключатель Other, а в текстовом поле Other Tip Percentage указано допустимое значение.
- Установлен переключатель 15% tip или 20% tip, а текстовые поля Total Amount и No. of People имеют допустимые значения.
Посмотрите пример 4, в котором описаны переключатели. Компоненты исходного кода довольно понятны.
Пример 4. Третий фрагмент кода из файла TipsterActivity.java
/*
* Присоединяем объект OnCheckedChangeListener к переключателю,
* чтобы следить за переключателями, установленными пользователем
*/
rdoGroupTips.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
// Активируем/блокируем поле Other Tip Percentage
if (checkedId == R.id.radioFifteen
|| checkedId == R.id.radioTwenty) {
txtTipOther.setEnabled(false);
/*
* Активируем кнопку Calculate если поля Total Amount и
* No. of People содержат корректные значения.
*/
btnCalculate.setEnabled(txtAmount.getText().length() > 0
&& txtPeople.getText().length() > 0);
}
if (checkedId == R.id.radioOther) {
// Активируем поле Other Tip Percentage
txtTipOther.setEnabled(true);
// Передаем фокус этому полю
txtTipOther.requestFocus();
/*
* Активируем кнопку Calculate, если поля Total Amount и
* No. of People содержат корректные значения. Кроме того,
* гарантируем, что пользователь введет значение Other Tip
* Percentage до активации кнопки Calculate.
*/
btnCalculate.setEnabled(txtAmount.getText().length() > 0
&& txtPeople.getText().length() > 0
&& txtTipOther.getText().length() > 0);
}
// Выясняем выбор процентов, сделанный пользователем
radioCheckedId = checkedId;
}
});
Контроль основных активностей в текстовых полях
Как я упоминал ранее, кнопка Calculate не должна быть активирована, если текстовые поля не содержат допустимых значений. Поэтому мы должны гарантировать, что кнопка Calculate будет активирована только в том случае, если текстовые поля Total Amount, No. of People и Other Tip Percentage имеют допустимые значения. Текстовое поле Other Tip Percentage активируется, только если установлен переключатель Other.
Нам не нужно беспокоиться о типе значений, т.е. вводит ли пользователь отрицательные значения или буквы, потому что для текстовых полей был определен атрибут android:numeric
, ограничивающий типы значений, которые пользователь может ввести. Мы просто должны убедиться, что эти значения введены.
Итак, мы используем статический интерфейс OnKeyListener()
, который будет уведомлять нас, когда нажата кнопка. Уведомление поступает до того, как информация о фактически нажатой кнопке будет отправлена в графический элемент EditText
.
Посмотрите фрагменты кода в примерах 5 и 6, которые касаются ключевых событий в текстовых полях. Как и в примере 4, комментарии к исходному коду совершенно не требуют пояснений.
Пример 5. Четвертый фрагмент кода из файла TipsterActivity.java
/*
* Связываем класс KeyListener с полями редактирования Tip Amount, No. of People и
* Other Tip Percentage
*/
txtAmount.setOnKeyListener(mKeyListener);
txtPeople.setOnKeyListener(mKeyListener);
txtTipOther.setOnKeyListener(mKeyListener);
Обратите внимание, что мы создаем только один слушатель вместо создания анонимных/внутренних слушателей для каждого текстового поля. Я не уверен, что мой стиль лучше или эффективнее, но я всегда пишу в этом стиле, если слушатели собираются выполнять некоторые общие действия. Здесь общая проблема для всех полей редактирования заключается в том, что они не должны быть пустыми, и только когда у них есть значения, должна быть актирована кнопка Calculate.
Пример 6. Пятый фрагмент кода из файла TipsterActivity.java
/*
* Класс KeyListener для полей Total Amount, No. of People и Other Tip Percentage.
* Это слушатель кнопок проверяет следующие условия:
*
* 1) Если пользователь установил переключатель Other Tip Percentage,
* то поле редактирования Other Tip Percentage должно содержать корректное
* значение, введенное пользователем. Кнопка Calculate активируется, только если
* пользователь ввел корректное значение.
*
* 2) Если пользователь не ввел значения в полях Total Amount и No. of People,
* вычисления выполнить невозможно. Следовательно, кнопка Calculate button
* активируется, только если пользователь ввел корректные значения.
*/
private OnKeyListener mKeyListener = new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
switch (v.getId()) { // МЕТКА №1
case R.id.txtAmount: // МЕТКА №2
case R.id.txtPeople: // МЕТКА №3
btnCalculate.setEnabled(txtAmount.getText().length() > 0
&& txtPeople.getText().length() > 0);
break;
case R.id.txtTipOther: // МЕТКА №4
btnCalculate.setEnabled(txtAmount.getText().length() > 0
&& txtPeople.getText().length() > 0
&& txtTipOther.getText().length() > 0);
break;
}
return false;
}
};
В МЕТКЕ №1 из примера 6 мы проверяем идентификатор объекта класса view
. Помните, что каждый графический элемент имеет уникальный идентификатор, определенный в файле main.xml
. Эти значения затем определяются в генерируемом классе R.java
.
В МЕТКЕ №2 и №3 мы проверяем введенное значение, если в полях Total Amount или No. of People произошло событие, связанное с кнопкой. Мы гарантируем, что пользователь не оставил оба поля пустыми.
В МЕТКЕ №4 мы проверяем, установил ли пользователь переключатель Other, а затем гарантируем, что поле Other не пустое. Мы также проверяем еще раз, не пусты ли поля Total Amount и No. of People.
Итак, цель нашего класса KeyListener
теперь ясна: убедиться, что все поля редактирования не пусты, и только затем активировать кнопку Calculate.
Прослушивание щелчков на кнопках
Теперь рассмотрим кнопки Calculate и Reset (Сброс). Когда пользователь щелкает на этих кнопках, мы используем статический интерфейс OnClickListener()
, который сообщает нам, когда произошел щелчок на кнопке.
Как и в случае полей редактирования, мы создаем только один слушатель, и внутри него выясняем, на какой кнопке произошел щелчок. В зависимости от кнопки, на который был щелчок, вызывается метод calculate()
или reset()
. В примере 7 показано, как слушатель щелчков добавляется к кнопкам.
Пример 7. Шестой фрагмент кода из файла TipsterActivity.java
/* Связываем слушатель с кнопками Calculate и Reset */
btnCalculate.setOnClickListener(mClickListener);
btnReset.setOnClickListener(mClickListener);
В примере 8 показано, как определить, какая кнопка была нажата, проверяя идентификатор представления, который получает событие щелчка.
Пример 8. Седьмой фрагмент кода из файла TipsterActivity.java
/**
* Класс ClickListener для кнопок Calculate and Reset buttons.
* В зависимости от нажатой кнопки вызывается соответствующий метод
*/
private OnClickListener mClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
if (v.getId() == R.id.btnCalculate) {
calculate();
} else {
reset();
}
}
};
Сброс приложения
Когда пользователь нажимает кнопку Сброс, поля редактирования должны быть очищены, переключатель 15% tip установлен по умолчанию и любые рассчитанные результаты удалены. Метод reset()
приведен в примере 9.
Пример 9. Восьмой фрагмент кода из файла TipsterActivity.java
/**
* Сбрасывает текстовые представления результатов внизу экрана,
* очищает поля редактирования и устанавливает переключатели по умолчанию.
*/
private void reset() {
txtTipAmount.setText("");
txtTotalToPay.setText("");
txtTipPerPerson.setText("");
txtAmount.setText("");
txtPeople.setText("");
txtTipOther.setText("");
rdoGroupTips.clearCheck();
rdoGroupTips.check(R.id.radioFifteen);
// Set focus on the first field
txtAmount.requestFocus();
}
Проверка входных данных для расчета чаевых
Как я уже говорил, мы ограничиваем типы значений, которые пользователь может ввести в текстовые поля. Тем не менее пользователь все равно может ввести нулевое значение в поля Total Amount, No. of People или Other Tip Percentage, что приводит к возникновению ошибки, такой как деление на нуль.
Если пользователь вводит нуль, мы должны показать всплывающее окно с запросом на ввод ненулевого значения. Мы обрабатываем эту ситуацию с помощью метода showErrorAlert(String errorMessage, final int fieldld)
, который обсудим более подробно позже.
Сначала рассмотрим пример 10, который демонстрирует метод calculated. Обратите внимание на то, что значения, введенные пользователем, анализируются как значения типа double. Теперь обратите внимание на МЕТКИ №1 и №2, в которых проверяются нулевые значения. Если пользователь вводит нуль, мы показываем всплывающее окно с запросом действительного значения. Затем обратите внимание на МЕТКУ №3, где активируется поле Other Tip Percentage, если пользователь установил переключатель Other. Здесь мы также должны проверить, равен ли процент нулю.
Пример 10. Девятый фрагмент кода из файла TipsterActivity.java
/**
* Вычисляем сумму чаевых по данным, введенным пользователем.
*/
private void calculate() {
Double billAmount = Double.parseDouble(
txtAmount.getText().toString());
Double totalPeople = Double.parseDouble(
txtPeople.getText().toString());
Double percentage = null;
boolean isError = false;
if (billAmount < 1.0) { // МЕТКА №1
showErrorAlert("Enter a valid Total Amount.",
txtAmount.getId());
isError = true;
}
if (totalPeople < 1.0) { // МЕТКА №2
showErrorAlert("Enter a valid value for No. of People.",
txtPeople.getId());
isError = true;
}
/*
* Если пользователь не установил никакой переключатель, принимается
* значение по умолчанию, равное 15%. Однако для безопасности это надо
* проверить
*/
if (radioCheckedId == -1) {
radioCheckedId = rdoGroupTips.getCheckedRadioButtonId();
}
if (radioCheckedId == R.id.radioFifteen) {
percentage = 15.00;
} else if (radioCheckedId == R.id.radioTwenty) {
percentage = 20.00;
} else if (radioCheckedId == R.id.radioOther) {
percentage = Double.parseDouble(
txtTipOther.getText().toString());
if (percentage < 1.0) { // МЕТКА №3
showErrorAlert("Enter a valid Tip percentage",
txtTipOther.getId());
isError = true;
}
}
/*
* Если все поля заполнены правильными значениями,
* приступаем к вычислению суммы чаевых
*/
if (!isError) {
Double tipAmount = ((billAmount * percentage) / 100); // МЕТКА №4
Double totalToPay = billAmount + tipAmount;
Double perPersonPays = totalToPay / totalPeople; // МЕТКА №5
txtTipAmount.setText(tipAmount.toString()); // МЕТКА №6
txtTotalToPay.setText(totalToPay.toString());
txtTipPerPerson.setText(perPersonPays.toString()); // МЕТКА №7
}
}
При загрузке приложения по умолчанию устанавливается переключатель 15% tip. Если пользователь изменяет выбор, мы назначаем идентификатор выбранного переключателя переменной radioCheckedld
(см. пример 4) в методе OnCheckedChangeListener
.
Но если пользователь принимает выбор по умолчанию, переменная radioCheckedld
будет иметь значение по умолчанию, равное -1. Короче говоря, мы никогда не узнаем, какой переключатель был выбран. Конечно, мы знаем, какой из них выбран по умолчанию, и можно было бы закодировать логику немного иначе, предположив, что чаевые составляют 15%, если переменная radioCheckedld
имеет значение -1. Но если вы обратитесь к интерфейсу прикладного программирования, то увидите, что метод getCheckedRadioButtonld()
можно вызвать только в классе RadioGroup
, а не в отдельных переключателях. Это связано с тем, что метод OnCheckedChangeListener
предоставляет нам идентификатор установленного переключателя.
Отображение результатов
Вычисление чаевых не представляет сложностей. Если все проверки завершились успешно, то булев флаг isError будет равен false. Сумма чаевых вычисляется во фрагменте кода между МЕТКОЙ №4 и №5 в примере 10. Вычисленные значения представляются в виде графических элементов TextView
, закодированных между МЕТКОЙ №6 и №7.
Отображение предупреждений
Для отображения всплывающих окон платформа Android предоставляет класс AlertDialog. Это позволяет нам отображать диалоговое окно с тремя кнопками и сообщением.
В примере 11 показан метод showErrorAlert()
, который использует класс AlertDialog
для отображения сообщений об ошибках. Обратите внимание, что мы передаем этому методу два аргумента: string errorMessage
и int fieldld
. Первый аргумент — это сообщение об ошибке, которое мы хотим показать пользователю. Переменная fieldID
— это идентификатор поля, вызвавшего ошибку. После того как пользователь закроет диалоговое окно предупреждения, идентификатор fieldID
позволит нам установить фокус в это поле, чтобы пользователь знал, какое поле содержит ошибку.
Пример 11. Десятый фрагмент кода из файла TipsterActivity.java
/**
* Демонстрирует сообщение об ошибке в диалоговом окне предупреждения *
* Oparam errorMessage
* Строка для вывода сообщения об ошибке
* @param fieldld
* Идентификатор поля, вызвавшего ошибку,
* который нужен для того, чтобы установить фокус
* на это поле после закрытия диалогового окна.
*/
private void showErrorAlert(String errorMessage,
final int fieldId) {
new AlertDialog.Builder(this).setTitle("Error")
.setMessage(errorMessage).setNeutralButton("Close",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
findViewById(fieldId).requestFocus();
}
}).show();
}
Когда все это будет собрано вместе, приложение должно выглядеть так, как показано на рис. 1.
Внимание! Исходный код данного проекта можно скачать по этой ссылке.
Резюме
Разработка приложений для операционной системы Android не очень отличается от любого другого инструментария пользовательского интерфейса, включая Microsoft Windows, X Windows или Java Swing. Конечно, платформа Android имеет свою специфику и, в целом, очень хороший дизайн. Парадигма компоновки XML является довольно эффективной и полезной для создания сложных пользовательских интерфейсов с использованием простого XML. Кроме того, модель обработки событий проста, функциональна и интуитивно понятна для использования в коде.
См. также
Справочная документация на страницах:
https://developer.android.com/guide/index.html
https://developer.android.com/reference/index.html