Знакомство с Android. Часть 4: Использование GridView

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

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


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

package demo.android.life;
 
import java.util.Random;
 
publicclass 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 voidnext()
{
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)
return0;
 
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 LifeAdapterextends 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 voidnext()
{
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 следующим образом:

publicclass RunActivityextends 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 с согласия автора.




Наши соцсети

Подписаться Facebook Подписаться Вконтакте Подписаться Twitter Подписаться Google Подписаться Telegram

Популярное

Ссылки

Новости [1] [2] [3]... Android/ iOS/ J2ME[1] [2] [3]) Android / Архив

Рейтинг@Mail.ru Яндекс.Метрика
MobiLab.ru © 2005-2018
При использовании материалов сайта ссылка на www.mobilab.ru обязательна