Учимся программировать для Android на примере Tipster

Обучение программированию для Android на примереПроблема

Итак, Вы хотите начать изучать азы программирования под 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. Для этого экрана мы будем использовать следующие компоновки и графические элементы.

Готовое приложение Tipster

Рис. 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

 

Вас заинтересует / Intresting for you:

Версии платформы Android
Версии платформы Android 2508 просмотров Илья Дергунов Sun, 04 Nov 2018, 14:44:59
О платформе Android
О платформе Android 4885 просмотров Илья Дергунов Mon, 29 Oct 2018, 17:07:02
Источники примеров кода для со...
Источники примеров кода для со... 3575 просмотров Валерий Павлюков Sat, 24 Nov 2018, 12:19:04
Запросы разрешений от системы ...
Запросы разрешений от системы ... 4487 просмотров dbstalker Sun, 24 Feb 2019, 06:24:29
Войдите чтобы комментировать

apv аватар
apv ответил в теме #9371 5 года 1 мес. назад
Крутая статья получилась. Спасибо товарищуВалерию!)