четверг, 16 октября 2008 г.

Знакомство с Android. Часть 2: Переходы между формами

Итак, продолжим. В этой части мы добавим в проект ещё одну форму и будем открывать её по нажатию кнопки Run. Также сделаем так, чтобы параметры, которые были введены в первой форме, передавались во вторую (они там ещё пригодятся). Однако, ничего страшного мы пока с ними делать не будем, а просто напишем "Введены такие-то числа"

Обработка нажатия кнопки

Напишем в StartActivity.java следующее:

StartActivity.java

package demo.android.life;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

public class StartActivity extends Activity implements OnClickListener
{
    Button mButton;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        mButton = (Button) findViewById(R.id.run);
        mButton.setOnClickListener(this);
    }
    public void onClick(View v)
    {
        Toast.makeText(this, "It works", Toast.LENGTH_SHORT).show();
    }
}
Здесь выделены добавленные строки. Итак, мы сделали форму слушателем события нажатия на кнопку (имплементировав OnClickListener), и подписались на это событие для кнопки mButton (с помощью setOnClickListener). Для того, чтобы проверить, что всё правильно, в методе, который обрабатывает событие, показываем оповещение (Toast). Запускаем - работает.

Добавление активности

Для того, чтобы создать в приложении форму, необходимо создать два файла — XML-разметку формы(run.xml) и Java-класс формы (RunActivity.java).
Разметку сделаем простейшую:

run.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <TextView
        android:id="@+id/message"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
    />
</FrameLayout>
В классе перегрузим метод onCreate, в котором свяжем форму с этой разметкой:

RunActivity.java

package demo.android.life;

import android.app.Activity;
import android.os.Bundle;

public class RunActivity extends Activity
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.run);
    }
}
Кроме того, нужно упомянуть о новой форме в AndroidManifest.xml. Для этого в раздел application добавляем следующее:
<activity android:name="RunActivity"></activity>                 
Кстати, в новых версиях ADT можно добавлять активности не руками в XML, а с помощью какого-никакого графического интерфейса во вкладке Application —> Application Nodes.

Переход на другую активность

Нам хочется, чтобы это активность RunActivity появлялась после нажатия кнопки Run окна StartActivity. Пишем в обработчике этой кнопки следующее:

StartActivity.java

public void onClick(View v)
{
    Intent intent = new Intent();
    intent.setClass(this, RunActivity.class);

    startActivity(intent);
    finish();
}
Запускаем - работает. Теперь объясним, что сделали.

Intent

Intent - это описание некоторого действия, которое должно совершить приложение. Этим действием может быть переход к другому окну, совершение исходящего вызова, открытие списка контактов и т.д.. У Intent-а есть два основных атрибута:
  • action - функция, которую надо выполнить. Задается в виде константы: ACTION_VIEW, ACTION_DIAL и т.д.
  • data - аргумент этой функции, записанный в виде URI
Например, сочетание VIEW_ACTION content://contacts/people/1 соответствует выводу информации о контакте с идентификатором 1, а ACTION_DIAL tel:123 - выводу окна вызова с набранными цифрами 123.
Однако, нам всего этого не нужно, а нужно, чтобы обработчиком Intent-а была сама форма RunActivity. Для этого мы используем функцию setClass (или есть ещё такая setClassName).
Вызов функции startActivity(intent) запускает операцию intent (т.е. открывает окно), finish() завершает текущую задачу (т.е. закрывает окно StartActivity).

Передача данных между активностями

Итак, RunActivity появляется, но там ничего нет. Рассмотрим, каким образом можно передать параметры, введенные в StartActivity, в активность RunActivity.
У класса Intent, помимо основных атрибутов, есть ещё и второстепенные. В частности, есть такой атрибут extras, где в виде хеша (Bundle) хранятся любые дополнительные параметры. Им-то мы и воспользуемся. Итак, добавим в класс RunActivity.java следующие константы (ключи хеша):

RunActivity.java

public static final String EXT_COLS = "cols";
public static final String EXT_ROWS = "rows";
public static final String EXT_CELLS = "cells";
Теперь модифицируем обработчик onClick в классе StartActivity.java, чтобы он выглядел так

StartActivity.java

public void onClick(View v)
{
    EditText rowsEditor = (EditText)findViewById(R.id.rows_count);
    EditText colsEditor = (EditText)findViewById(R.id.columns_count);
    EditText cellsEditor = (EditText)findViewById(R.id.cells_count);
    
    int cols = Integer.parseInt(colsEditor.getText().toString());
    int rows = Integer.parseInt(rowsEditor.getText().toString());
    int cells = Integer.parseInt(cellsEditor.getText().toString());

    Intent intent = new Intent();
    intent.setClass(this, RunActivity.class);

    intent.putExtra(RunActivity.EXT_COLS, cols);
    intent.putExtra(RunActivity.EXT_ROWS, rows);
    intent.putExtra(RunActivity.EXT_CELLS, cells);

    startActivity(intent);
    finish();
}
Итак, мы передали в наш Intent дополнительные параметры. Теперь мы получим их в RunActivity в методе onCreate:

RunActivity.java

public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.run);
    Bundle extras = getIntent().getExtras();
    int cols = extras.getInt(EXT_COLS);
    int rows = extras.getInt(EXT_ROWS);
    int cells = extras.getInt(EXT_CELLS);

    TextView message = (TextView)findViewById(R.id.message);
    message.setText("Rows: " + rows + "\nColumns: " + cols + "\nCells: " + cells);
}
Теперь, после нажатия на кнопку Run мы видим следующее:

Заключение

Итак, мы добавили в наше приложение ещё одну форму, создали переход на неё и передали параметры из первой формы.
Исходники примера

19 комментариев:

  1. Добрый день. У меня проблема. Объясните пожалуйста, Intent intent = new Intent() нужно прописывать в StartScreent.java? Если да, то Intent подчеркивается как ошибка, при наведении пишет следующее - Intent cannot be resolved to a type. И вопрос второй, в AndroidManifest.xml нужно прописывать сразу после предыдущей ?

    ОтветитьУдалить
  2. 2 mik:
    А import android.content.Intent; в StartScreen есть? Если нет, то стоит добавить. Кстати, смотрите внимательнее, что советует eclipse, иногда он бывает прав :)
    Второго вопроса не поняла.

    ОтветитьУдалить
  3. Дарья, спасибо огромное, Ваш совет помог. Я как раз не прописал android.content.Intent, поэтому и выдавало ошибку. А второй вопрос я бы и сам не понял)) почему-то пол предложения куда-то потерялось, но это не важно, все заработало. Еще раз спасибо

    ОтветитьУдалить
  4. Спасибо большое за материал.

    Так как только изучаю android программирование, пишу программку по книге. Возникла проблема с переходом на другую форму или с открытием меню - выскакивает ошибка и force close. По идее что-то не так в манифесте, но вроде проверял несколько раз и все правильно... Обработчики согласно книжке тоже выполнены верно. Что может быть не так?

    ОтветитьУдалить
  5. Тоже проблема выскакивает Force close при нажатии на кнопку, методом проверок определил что срабатывает при setText в RunScreen

    ОтветитьУдалить
  6. EditText rowsEditor = (EditText)findViewById(R.id.RowsEditor);
    А не здесь ли? Лишь при добавлении этих трех строк процесс завершается. Без них все ок (последующий код, естественно, закомментирован). В чем проблема не могу понять.

    ОтветитьУдалить
  7. Всё работает! надо только думать.
    спасибо Даша

    ОтветитьУдалить
  8. intent.setClass(this, RunScreen.class);

    Eclipse выводит следующую ошибку - RunScreen cannot be resolved to a type

    ОтветитьУдалить
  9. Этот комментарий был удален автором.

    ОтветитьУдалить
  10. Действительно, при нажатии на кнопку Run приложение вылетает, хотя все с точностью скопировано отсюда. В чем может быть проблема?

    ОтветитьУдалить
  11. Дебагер вылетает на строчке startActivity(intent);

    в чем проблема?

    ОтветитьУдалить
  12. Господа, я, к сожалению, не могу определить источник ваших проблем телепатически. Погуглите про LogCat и почитайте логи.

    ОтветитьУдалить
  13. Нашел в чем загвоздка была )

    в AndroidManifest.xml не там вставил строчку runactivity :-[

    На iPhone попроще прогать будет, хотя может я просто еще не привык к xml-разметке и java ))

    ОтветитьУдалить
  14. Дарья, подскажите пожалуйста, EXT_COLS = "cols"; это строка, как тогда при выводе переменной cols, которая задана int cols = extras.getInt(EXT_COLS); должно выводиться количество колонок, в "\nColumns: " + cols? Там же ведь должно получиться "Columns: cols"

    То есть, вопрос в том, в какой момент EXT_COLS переопределяется и связывается с количеством колонок?

    ОтветитьУдалить
  15. Дарья спасиб за статьи.

    Только на мой вкус и цвет, лучше биндить события таким образом как у вас. (Через implements OnClickListener) так как часто возникают ситуации когда несколько кнопок надо прибиндить.

    Поэтому используется немного другой подход
    А именно реализация интерфейса налету.

    Например вот так:

    final Activity self = this;

    mButton.setOnClickListener(new OnClickListener(){
    @Override
    public void onClick(View v) {
    Toast.makeText(self, "It works", Toast.LENGTH_SHORT).show();
    }});

    Да и код на мой взгляд чище и понятнее становится.

    ОтветитьУдалить
  16. Отличная статья.
    От себя хотелось бы добавить: как вариант передачи сложных объектов, серилизация ProtoBuf и преобразование в byte[].

    ОтветитьУдалить
  17. Этот комментарий был удален автором.

    ОтветитьУдалить
  18. Большущее спасибо за ZIP. Два дня мучился с Layout, а оказалось, что нужно было писать заклинания в Манифесте.

    Спасибо!

    ОтветитьУдалить
  19. Здравствуйте, сделал все по инструкциям и возникла следующая проблема - на устройстве создается 2 иконки приложения вместо одной.Что же делать?

    ОтветитьУдалить

Пожалуйста, пишите содержательные комментарии и соблюдайте вежливость.