Программирование Android и iPhone
Вконтакте Twitter Google+ Facebook RSS
Главная Android
Знакомство с Android. Часть 4: Использование GridView

Итак, в нашем приложении осталось всего ничего: реализовать собственно алгоритм игры Life и отобразить его в GridView. Этим-то мы сейчас и займёмся.

Класс, реализующий логику Life


Добавим в проект новый класс, назовем его LifeModel. Тут у нас будет реализована вся логика Life

package demo.android.life;
 
import java.util.Random;
 
public class LifeModel
{
    // состояния клетки
    private static final Byte CELL_ALIVE = 1; // клетка жива
    private static final Byte CELL_DEAD = 0; // клетки нет
 
    // константы для количества соседей
    private static final Byte NEIGHBOURS_MIN = 2; // минимальное число соседей для живой клетки
    private static final Byte NEIGHBOURS_MAX = 3; // максимальное число соседей для живой клетки
    private static final Byte NEIGHBOURS_BORN = 3; // необходимое число соседей для рождения клетки
 
    private static int mCols; // количество столбцов на карте
    private static int mRows; // количество строк на карте
    private Byte[][] mCells; // расположение очередного поколения на карте. 
                            //Каждая ячейка может содержать либо CELL_ACTIVE, либо CELL_DEAD
 
    /**
     * Конструктор
     */
    public LifeModel(int rows, int cols, int cellsNumber)
    {
        mCols = cols;
        mRows = rows;
        mCells = new Byte[mRows][mCols];
 
        initValues(cellsNumber);
    }
 
    /**
     * Инициализация первого поколения случайным образом
     * @param cellsNumber количество клеток в первом поколении
     */
    private void initValues(int cellsNumber)
    {
        for (int i = 0; i < mRows; ++i)
            for (int j = 0; j < mCols; ++j)
                mCells[i][j] = CELL_DEAD;
 
        Random rnd = new Random(System.currentTimeMillis());
        for (int i = 0; i < cellsNumber; ++i)
        {
            int cc;
            int cr;
            do
            {
                cc = rnd.nextInt(mCols);
                cr = rnd.nextInt(mRows);
            }
            while (isCellAlive(cr, cc));
            mCells[cr][cc] = CELL_ALIVE;       
        }
    }
 
    /**
     * Переход к следующему поколению
     */
    public void next()
    {
        Byte[][] tmp = new Byte[mRows][mCols];
 
        // цикл по всем клеткам
        for (int i = 0; i < mRows; ++i)
            for (int j = 0; j < mCols; ++j)
            {
                // вычисляем количество соседей для каждой клетки
                int n = 
                    itemAt(i-1, j-1) + itemAt(i-1, j) + itemAt(i-1, j+1) +
                    itemAt(i, j-1) + itemAt(i, j+1) +
                    itemAt(i+1, j-1) + itemAt(i+1, j) + itemAt(i+1, j+1);
 
                tmp[i][j] = mCells[i][j];
                if (isCellAlive(i, j))
                {
                    // если клетка жива, а соседей у нее недостаточно или слишком много, клетка умирает
                    if (n < NEIGHBOURS_MIN || n > NEIGHBOURS_MAX)
                        tmp[i][j] = CELL_DEAD;
                }
                else
                {
                    // если у пустой клетки ровно столько соседей, сколько нужно, она оживает 
                    if (n == NEIGHBOURS_BORN)
                        tmp[i][j] = CELL_ALIVE;        
                }
            }
        mCells = tmp;
    }
 
    /**
     * @return Размер поля
     */
    public int getCount()
    {
        return mCols * mRows;
    }
 
    /**
     * @param row Номер строки
     * @param col Номер столбца
     * @return Значение ячейки, находящейся в указанной строке и указанном столбце
     */
    private Byte itemAt(int row, int col)
    {
        if (row < 0 || row >= mRows || col < 0 || col >= mCols)
            return 0;
 
        return mCells[row][col];
    }
 
    /**
     * @param row Номер строки
     * @param col Номер столбца
     * @return Жива ли клетка, находящаяся в указанной строке и указанном столбце
     */
    public Boolean isCellAlive(int row, int col)
    {
        return itemAt(row, col) == CELL_ALIVE;
    }
 
    /**
     * @param position Позиция (для клетки [row, col], вычисляется как row * mCols + col)
     * @return Жива ли клетка, находящаяся в указанной позиции
     */
    public Boolean isCellAlive(int position)
    {
        int r = position / mCols;
        int c = position % mCols;
 
        return isCellAlive(r,c);
    }
}

GridView. Отображение первого поколения клеток

Модифицируем разметку run.xml так, чтобы она выглядела следующим образом:

<?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">
    <GridView
        android:id="@+id/life_grid" 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        
        android:padding="1dp"
        android:verticalSpacing="1dp"
        android:horizontalSpacing="1dp"
        android:columnWidth="10dp"

        android:gravity="center"
    />
    <Button
        android:id="@+id/close"
        android:text="@string/close"
        android:textStyle="bold"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
    />
</LinearLayout>

Теперь нам надо отобразить в этом GridView данные. Думаю, вполне логичным для данной задачи было бы отображение клеток в виде графических файлов. Создаем два графических файлика, на одном изображаем черный квадратик, на другом - зелёный. Первый назовём empty.png и он будет обозначать пустую клетку, второй - cell.png, и он будет изображать живую клетку. Оба файлика положим в папку /res/drawable

Нам нужно знать, что именно отображать в гриде. Для этого нужно создать для грида поставщик данных (Adapter). Есть стандартные классы для адаптеров (ArrayAdapter и др.), но нам будет удобнее написать свой, унаследованный от BaseAdapter. Дабы не плодить файлов (да и не нужен он больше никому), поместим его внутрь класса RunActivity. А напишем там следующее:

class LifeAdapter extends BaseAdapter
{
    private Context mContext;
 
    private LifeModel mLifeModel;
 
    public LifeAdapter(Context context, int cols, int rows, int cells)
    {
        mContext = context;
        mLifeModel = new LifeModel(rows, cols, cells);
    }
 
    public void next()
    {
        mLifeModel.next();
    }
 
    /**
     * Возвращает количество элементов в GridView 
     */
    public int getCount()
    {
        return mLifeModel.getCount();
    }
 
    /**
     * Возвращает объект, хранящийся под номером position
     */
    public Object getItem(int position)
    {
        return mLifeModel.isCellAlive(position);
    }
 
    /**
     * Возвращает идентификатор элемента, хранящегося в под номером position
     */
    public long getItemId(int position)
    {
        return position;
    }
 
    /**
     * Возвращает элемент управления, который будет выведен под номером position
     */
    public View getView(int position, View convertView, ViewGroup parent)
    {
        ImageView view; // выводиться у нас будет картинка
 
        if (convertView == null)
        {
            view = new ImageView(mContext);
 
            // задаем атрибуты
            view.setLayoutParams(new GridView.LayoutParams(10, 10));
            view.setAdjustViewBounds(false);
            view.setScaleType(ImageView.ScaleType.CENTER_CROP);
            view.setPadding(1, 1, 1, 1);
        }
        else
        {
            view = (ImageView)convertView;
        }
 
        // выводим черный квадратик, если клетка пустая, и зеленый, если она жива
        view.setImageResource(mLifeModel.isCellAlive(position) ? R.drawable.cell : R.drawable.empty);
 
        return view;
    }
}
 

Теперь добавим в RunActivity поля:

private GridView mLifeGrid;
private LifeAdapter mAdapter;

и модифицируем onCreate:

public void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.run);
 
    mCloseButton = (Button) findViewById(R.id.close);
    mCloseButton.setOnClickListener(this);
 
    Bundle extras = getIntent().getExtras();
    int cols = extras.getInt(EXT_COLS);
    int rows = extras.getInt(EXT_ROWS);
    int cells = extras.getInt(EXT_CELLS);
    mAdapter = new LifeAdapter(this, cols, rows, cells);
 
    mLifeGrid = (GridView)findViewById(R.id.life_grid);
    mLifeGrid.setAdapter(mAdapter);
    mLifeGrid.setNumColumns(cols);
    mLifeGrid.setEnabled(false);
    mLifeGrid.setStretchMode(0);
}
 

Запускаем и видим:



Отображение последующих поколений

Вот мы и добрались почти до самого конца. Осталось отобразить ход игры.

Каждую секунду нам нужно отправлять кому-то команду о том, что нужно обновить модель и UI. Для этого лучше всего подходит класс Handler. Назначение и поведение этого класса достойны отдельной статьи, но вкратце можно сказать, что он, ассоциировавшись с неким потоком и очередью сообщений, может отправлять туда на выполнение всякие Runnables и Messages. Одно из главных применений класса Handler — запуск Runnable по расписанию. Для этого в нем имеются методы вроде post, postDelayed и postAtTime

Итак, для отображения последующих поколений клеток модифицируем класс RunActivity следующим образом:

public class RunActivity extends Activity implements OnClickListener
{
    ...
 
    private Handler mHandler;
 
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.run);
 
        mCloseButton = (Button) findViewById(R.id.close);
        mCloseButton.setOnClickListener(this);
 
        Bundle extras = getIntent().getExtras();
        int cols = extras.getInt(EXT_COLS);
        int rows = extras.getInt(EXT_ROWS);
        int cells = extras.getInt(EXT_CELLS);
        mAdapter = new LifeAdapter(this, cols, rows, cells);
 
        mLifeGrid = (GridView)findViewById(R.id.life_grid);
        mLifeGrid.setAdapter(mAdapter);
        mLifeGrid.setNumColumns(cols);
        mLifeGrid.setEnabled(false);
        mLifeGrid.setStretchMode(0);
 
        mHandler = new Handler();
        mHandler.postDelayed(mUpdateGeneration, 1000);
    }
 
    private Runnable mUpdateGeneration = new Runnable()
    {
        public void run()
        {
            mAdapter.next();
            mLifeGrid.setAdapter(mAdapter);
 
            mHandler.postDelayed(mUpdateGeneration, 1000);
        }
    }; 
    ...

Теперь, запустив Life, можно увидеть, например, следующее


Заключение

Итак, мы написали первое приложение для Android, которое уже и не совсем "Hello, World". Лично мне писать для Android понравилось куда больше, чем классические мидлеты. Остался, правда, ряд претензий к Eclipse, но, возможно, это от недостатка опыта.

Спасибо, если кто осилил. Замечания приветствуются.

Исходники примера


Об авторе: darja живет Владивостоке, работает программистом, интересуется разными технологиями. Цикл статей перекликается на сайте www.mobilab.ru с согласия автора.


 

Добавить комментарий

Войти через социальную сеть: ВконтактеTwitterYandexGooglefacebookMail.ruLoginzaMyOpenIDOpenIDWebMoney


Защитный код
Обновить



ИНТЕРЕСНОЕ



Новости [1] [2] [3]... / Программинг ( Android/ iOS/ J2ME[1] [2] [3]) / Безопасность / Статьи / Софт ( Android / iOS) / Форум / Архив ( Symbian/ Статьи)
Рейтинг@Mail.ru

MobiLab.ru © 2005-2012
При использовании материалов сайта ссылка на www.mobilab.ru обязательна