Знакомство с 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 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)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 так, чтобы она выглядела следующим образом:
Теперь нам надо отобразить в этом 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;}}
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 следующим образом:
Теперь, запустив Life, можно увидеть, например, следующее
Заключение
Итак, мы написали первое приложение для Android, которое уже и не совсем "Hello, World". Лично мне писать для Android понравилось куда больше, чем классические мидлеты. Остался, правда, ряд претензий к Eclipse, но, возможно, это от недостатка опыта.
Спасибо, если кто осилил. Замечания приветствуются.
Дорогие пользователи и гости сайта!!! Пожалуйста, если вам не сложно,
оставте комментарий или ваше мнение о портале в Гостевой книге.
Ваше мнение очень важно для нас и очень поможет в поиске дальнейшего пути развития сайта.
С уважением, Администрация сайта AndroBum!
Посетители, находящиеся в группе Гости, не могут оставлять комментарии к данной публикации.