Урок 10. Оптимизируем реализацию обработчиков.


В этом уроке мы:

- научимся использовать один обработчик для нескольких View-элементов
- научим Activity выступать в качестве обработчика

Создадим проект:
Project name: P0101_Listener
Build Target: Android 2.3.3
Application name: Listener
Package name: ru.startandroid.develop.listener
Create Activity: MainActivity


Будем работать с теми же View, что и в предыдущем уроке. Код для main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/linearLayout1"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_margin="30dp">
<TextView
android:layout_width="wrap_content"
android:text="TextView"
android:layout_height="wrap_content"
android:id="@+id/tvOut"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="50dp">
</TextView>
<Button
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:id="@+id/btnOk"
android:text="OK"
android:layout_width="100dp">
</Button>
<Button
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:id="@+id/btnCancel"
android:text="Cancel"
android:layout_width="100dp">
</Button>
</LinearLayout>
</LinearLayout>


Один обработчик для двух кнопок

Итак, у нас есть TextView с текстом и две кнопки. Как и на прошлом уроке, мы сделаем так, чтобы по нажатию кнопки менялось содержимое TextView. По нажатию кнопки OK – будем выводить текст: «Нажата кнопка ОК», по нажатию Cancel – «Нажата кнопка Cancel». Но сейчас мы сделаем это с помощью одного обработчика, который будет обрабатывать нажатия для обеих кнопок.

Напомню механизм обработки событий на примере нажатия кнопки. Сама кнопка обрабатывать нажатия не умеет, ей нужен обработчик (listener), который присваивается с помощью метода setOnClickListener. Когда на кнопку нажимают, обработчик реагирует и выполняет код из метода onClick.

Соответственно для реализации необходимо выполнить следующие шаги:
- создаем обработчик
- заполняем метод onClick
- присваиваем обработчик кнопке

В нашем случае мы будем присваивать один обработчик обеим кнопкам, а внутри обработчика надо будет определять, какая именно кнопка была нажата.

Подготовим объекты и создадим обработчик:

public class MainActivity extends Activity {

 
TextView tvOut;
  Button btnOk;
  Button btnCancel;

 
/** Called when the activity is first created. */
 
@Override
 
public void onCreate(Bundle savedInstanceState) {
   
super.onCreate(savedInstanceState);
    setContentView
(R.layout.main);

   
// найдем View-элементы
   
tvOut = (TextView) findViewById(R.id.tvOut);
    btnOk =
(Button) findViewById(R.id.btnOk);
    btnCancel =
(Button) findViewById(R.id.btnCancel);

   
// создание обработчика
   
OnClickListener oclBtn = new OnClickListener() {
     
@Override
     
public void onClick(View v) {
       
// TODO Auto-generated method stub

     
}
    }
;

 
}
}


Давайте заполнять метод onClick. На вход ему подается объект класса View, это как раз то, что нам нужно. Это View на которой произошло нажатие и которая вызвала обработчик. Т.е. в нашем случае это будет либо кнопка OK либо Cancel. Нам осталось узнать ID этой View и сравнить его с нашими R.id.btnOk и R.id.btnCancel, чтобы определить какая именно это кнопка. Чтобы получить ID какой-либо View, используется метод getId. Для перебора результатов используем java-оператор switch.

Реализация метода onClick:

      public void onClick(View v) {
       
// по id определеяем кнопку, вызвавшую этот обработчик
       
switch (v.getId()) {
       
case R.id.btnOk:
         
// кнопка ОК
         
tvOut.setText("Нажата кнопка ОК");
         
break;
       
case R.id.btnCancel:
         
// кнопка Cancel
         
tvOut.setText("Нажата кнопка Cancel");
         
break;
       
}
      }


Если сейчас запустить приложение и проверить, то ничего не произойдет. Обработчик то мы создали, но не присвоили его кнопкам. Обеим кнопкам присваиваем один и тот же обработчик:

    btnOk.setOnClickListener(oclBtn);
   btnCancel.setOnClickListener
(oclBtn);

Вот теперь можем запускать и проверять, все должно работать.

Как вы понимаете, один обработчик может быть присвоен не двум, а любому количеству кнопок. И не только кнопкам. У остальных View-элементов тоже есть различные события, которые нуждаются в обработчиках. В дальнейшем мы еще будем с ними работать. А сейчас важно понять схему, как происходит обработка событий.

Отличие способа реализации на этом уроке от прошлого урока в том, что сейчас мы создали один объект-обработчик для обеих кнопок, а на прошлом уроке - два объекта, по одному каждой кнопке. Есть правило – чем меньше объектов вы создаете, тем лучше, т.к. под каждый объект выделяется память, а это достаточно ограниченный ресурс, особенно для телефонов. Поэтому создавать один обработчик для нескольких View это правильнее с точки зрения оптимизации. К тому же кода становится меньше и читать его удобнее.

Есть еще один способ создания обработчика, который вовсе не потребует создания объектов. Будет использоваться уже созданный объект – Activity


Activity, как обработчик

Кнопка присваивает себе обработчика с помощью метода setOnClickListener (View.OnClickListener l). Т.е. подойдет любой объект с интерфейсом View.OnClickListener. Почему бы классу Activity не быть таким объектом? Мы просто укажем, что Activity-класс реализует интерфейс View.OnClickListener и заполним метод onCreate.

Создадим для этого новый проект:
Project name: P0102_ActivityListener
Build Target: Android 2.3.3
Application name: ActivityListener
Package name: ru.startandroid.develop.activitylistener
Create Activity: MainActivity


Экран снова возьмем тот же самый:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/linearLayout1"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_margin="30dp">
<TextView
android:layout_width="wrap_content"
android:text="TextView"
android:layout_height="wrap_content"
android:id="@+id/tvOut"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="50dp">
</TextView>
<Button
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:id="@+id/btnOk"
android:text="OK"
android:layout_width="100dp">
</Button>
<Button
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:id="@+id/btnCancel"
android:text="Cancel"
android:layout_width="100dp">
</Button>
</LinearLayout>
</LinearLayout>


Подготовим объекты и добавим реализацию интерфейса (implements onClickListener)

public class MainActivity extends Activity implements OnClickListener {

 
TextView tvOut;
  Button btnOk;
  Button btnCancel;

 
/** Called when the activity is first created. */
 
@Override
 
public void onCreate(Bundle savedInstanceState) {
   
super.onCreate(savedInstanceState);
    setContentView
(R.layout.main);

   
// найдем View-элементы
   
tvOut = (TextView) findViewById(R.id.tvOut);
    btnOk =
(Button) findViewById(R.id.btnOk);
    btnCancel =
(Button) findViewById(R.id.btnCancel);
 
}
}


OnClickListener подчеркнут красным, т.к. его нет в импорте. Поэтому CTRL+SHIFT+O и выбираем View.OnClickListener.

Теперь Eclipse ругается на класс MainActivity. Это происходит потому, что для класса прописан интерфейс, но нет реализации методов этого интерфейса. Исправим это с помощью Eclipse. Наведите курсор на MainAcivity и выберите Add unimplemented methods


Eclipse добавит знакомый нам метод onClick. Только теперь этот метод будет реализован в Activity, а не в отдельном объекте-обработчике. Соответственно Activity и будет выступать обработчиком.

Заполним метод точно так же как и раньше. Ничего не изменилось. Ему на вход так же подается View (на которой произошло событие), по Id мы определим, какая именно эта View и выполним соответствующие действия:

  public void onClick(View v) {
   
// по id определеяем кнопку, вызвавшую этот обработчик
   
switch (v.getId()) {
   
case R.id.btnOk:
     
// кнопка ОК
     
tvOut.setText("Нажата кнопка ОК");
     
break;
   
case R.id.btnCancel:
     
// кнопка Cancel
     
tvOut.setText("Нажата кнопка Cancel");
     
break;
   
}
  }


Осталось в методе onCreate присвоить обработчик кнопкам. Это будет объект this, т.е. текущий объект MainActivity.

    btnOk.setOnClickListener(this);
   btnCancel.setOnClickListener
(this);


При такой реализации мы не создали ни одного лишнего объекта (Activity создается в любом случае) и затраты памяти минимальны, это рекомендуемый метод. Но, возможно, такой способ покажется сложным и непонятным, особенно если мало опыта в объектно-ориентированном программировании. В таком случае используйте ту реализацию, которая вам понятна и удобна. А со временем и опытом понимание обязательно придет.

Полный код:

public class MainActivity extends Activity implements OnClickListener {

 
TextView tvOut;
  Button btnOk;
  Button btnCancel;

 
/** Called when the activity is first created. */
 
@Override
 
public void onCreate(Bundle savedInstanceState) {
   
super.onCreate(savedInstanceState);
    setContentView
(R.layout.main);

   
// найдем View-элементы
   
tvOut = (TextView) findViewById(R.id.tvOut);
    btnOk =
(Button) findViewById(R.id.btnOk);
    btnCancel =
(Button) findViewById(R.id.btnCancel);

   
// присваиваем обработчик кнопкам
   
btnOk.setOnClickListener(this);
    btnCancel.setOnClickListener
(this);
 
}

 
@Override
 
public void onClick(View v) {
   
// по id определеяем кнопку, вызвавшую этот обработчик
   
switch (v.getId()) {
   
case R.id.btnOk:
     
// кнопка ОК
     
tvOut.setText("Нажата кнопка ОК");
     
break;
   
case R.id.btnCancel:
     
// кнопка Cancel
     
tvOut.setText("Нажата кнопка Cancel");
     
break;
   
}
  }

}


Самая простая реализация обработчика

Есть еще один способ реализации. В layout-файле (main.xml) при описании кнопки пишем:

<?xml version="1.0" encoding="utf-8"?>
<Button
android:id="@+id/btnStart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onClickStart"
android:text="start">
</Button>

Т.е. используем атрибут onClick. В нем указываем имя метода из Activity. Этот метод и сработает при нажатии на кнопку.

Далее, добавляем этот метод в Activity (MainActivity.java). Требования к методу: public, void и на вход принимает View:

  public void onClickStart(View v) {
   
// действия при нажати на кнопку
 
}

В методе прописываете необходимые вам действия, и они будут выполнены при нажатии кнопки.