Класс, реализующий логику Life
Добавим в проект новый класс, назовем егоLifeModel
. Тут у нас будет реализована вся логика LifeLifeModel.java
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
так, чтобы она выглядела следующим образом: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>
empty.png
и он будет обозначать пустую клетку, второй - cell.png
, и он будет изображать живую клетку. Оба файлика положим в папку /res/drawable
Нам нужно знать, что именно отображать в гриде. Для этого нужно создать для грида поставщик данных (
Adapter
). Есть стандартные классы для адаптеров (ArrayAdapter
и др.), но нам будет удобнее написать свой, унаследованный от BaseAdapter
. Дабы не плодить файлов (да и не нужен он больше никому), поместим его внутрь класса RunActivity
. А напишем там следующее:RunActivity.java
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
поля: RunActivity.java
private GridView mLifeGrid; private LifeAdapter mAdapter;
onCreate
:RunActivity.java
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
следующим образом:RunActivity.java
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); } }; ...
Заключение
Итак, мы написали первое приложение для Android, которое уже и не совсем "Hello, World". Лично мне писать для Android понравилось куда больше, чем классические мидлеты. Остался, правда, ряд претензий к Eclipse, но, возможно, это от недостатка опыта.Спасибо, если кто осилил. Замечания приветствуются.
Исходники примера
Приятно, что девушки тоже интересуются Android :)
ОтветитьУдалитьРазместил на тебя ссылку, надеюсь ты не против :)
ОтветитьУдалитьhttp://www.maximyudin.com/2008/10/25/android/zhenskij-blog-pro-android/
в мемориз. очень доходчиво.
ОтветитьУдалитькак раз недавно поставил на коммуникатор "портированный" андроид. попользовал - оказалось удобнее WM.
Одна из лучших статей по Android
ОтветитьУдалитьмне тоже понравилось. позволяет быстро ознакомиться с многими аспектами как java, так и android человеку, который программировал много, но не в этом направлении :) спасибо.
ОтветитьУдалитьАнонимный, вы меня извините, конечно, но научиться писать на java под android и говорить, что умеешь писать на java -- это как наступить в лужу и говорить, что можешь переплыть океан.
ОтветитьУдалитьА статьи очень хорошие, спасибо.
ОтветитьУдалитьЭтот комментарий был удален автором.
ОтветитьУдалитьПривет. Зачитался :) Спасибо за разжевывание андроида для начинающих.
ОтветитьУдалитьНельзя ли заказать урок по параметрам разметки?
Никак не могу уловить закономерность всех этих длинн, ширин и прочего - вроде ставишь по логике, не работает.
Вероятно там какая то хитрая система контейнеров и относительных величин. Или андроид не подразумевает форматирование как в HTML CSS и кладет все так на экране как считает нужным?
Ну и хотелось бы про grid и tablelayout..
2 Antabis:
ОтветитьУдалитьЧестно говоря, не совсем поняла, в чем Ваша проблема. У контролов есть атрибуты width и height, с помощью которых задаются абсолютные размеры, и атрибуты layout_width и layout_height, задающие размеры относительно контейнера. Относительно таблиц: в TableRow можно задать width у какого-нибудь контрола, и весь столбец, в котором он содержится, станет такой ширины.
А вообще мысль интересная, может и действительно стоит написать подробную статью про Layout-ы. Правда, вряд ли получится это сделать в ближайшее время.
Дарья
ОтветитьУдалитьспасибо за комментарий.
Понимаете в чем проблема. Все сэмплы которые я видел в сети (особенно для нового СДК) - какие-то укушенные. То есть построены по принципу "делай так и будет тебе счастье!". И шаг влево, шаг вправо для новичка приводит к невероятным последствиям.
Вот, к примеру, пока нашел что же за зверь такой - колонки в таблице и как с ними обращаться, весь поседел.
Я сам - веб-программист и по моей логике колонки должны обявляться как контейнер ХМЛ, ну то есть парой ' col -- /col' как то так.
А в андроиде такого нет и в доке тоже не особо разбежались писать, КАК ЭТО РАБОТАЕТ. Аналогично фиг знает сколько времени искал принцип действия автоматического расширения колонок. Пока не понял, что надо просто перечислить номера в параметре - и будет счастье.
Проблемы возникают именно из-за предыдущего опыта, конепция разметки андроида как-то не вяжется ни с чем, что я знаю.
Опять же только через неделю сообразил что архитектура скорее напоминает ASP.NET чем Жаву. Своими адаптерами и прочим.
В данный момент очередной раз озадачен толкованием понятия spiner. По жизни спинер был числовым полем с кнопочками. Значение поля увеличивалось при нажатии на одну и уменьшалось при нажатии на другую. А в андроиде спинер - это же комбобокс, ну никак не спинер :) Какое-то зазеркалье сплошное.
Кстати, вы не подскажете, в каком классе искать настоящий спинер? Я видел что его используют, но не могу найти :(
Вот такие неприятности к примеру у меня, как у человека начавшего осваивать ЭТО :)
Спасибо за помощь!
Мда, понятие spinner-а действительно своеобразное. Я как-то искала нормальный spinner и не нашла, пришлось использовать TextEdit. А вы точно его отдельно видели? Это не DatePicker был?
ОтветитьУдалитьЗря Вы так уж ругаетесь. После j2me, где разметок никаких не предусмотрено, и контролы надо добавлять прямо в коде, здесь все очень даже человечно. А разметка действительно немного похожа на aspx.
Ну, я не уверен, что это несамодельный спинер был, то что я видел. Очень может быть и EditBox с двумя кнопками :)
ОтветитьУдалитьМне сложно судить о том насколько хорош Андроид для разработчика - это мой первый мобильный опыт...
Я то смотрю с точки зрения обычных приложений.
А еще было бы здорово сделать поле игры замкнутым т.е. имитирующим развертку сферы. Например соседом слева у ячеек нулевого столбца считаются ячейки последнего столбца, аналогично -- со строками.
ОтветитьУдалитьДля этого нужно всего то подправить функцию itemAt()
Понятно, что к программированию под Androind это мало относится, но глайдеры пусть стало бы сразу веселее.
отличная статья. спасибо!
ОтветитьУдалитьP.S.: а ряд претензий к Eclipse и в самом деле остался, причём очень длинный такой ряд...
It was rather interesting for me to read the article. Thanks for it. I like such topics and anything connected to this matter. I would like to read a bit more on that blog soon.
ОтветитьУдалитьAlex
Mobile phone blocker
Прошу прощения, Дарья, но я так и не понял, каким образом мы задаем кол-во строк нашему GridView(по аналогии с numColumns, через какой атрибут)?
ОтветитьУдалитьДарья, доброго времени суток!
ОтветитьУдалитьСпасибо за Ваши тьюториалы, для Android в Рунете их можно пересчитать по пальцам.
Не могли бы Вы уделить внимание, почему не работает мой код - не обновляется GridView при вызове функции adapter.NotifyDataSetChanged(). Буду премного благодарен за экспертный анализ =)
Девушка вы меня покорили своей статьей, просто шикарно!!!
ОтветитьУдалитьОтличная серия! По Android так не хватает русскоязычной документации. Спасибо Вам огромное.
ОтветитьУдалитьСпасибо, Дарья!
ОтветитьУдалитьУ меня, правда, есть ряд вопросов, так как писать под Андроид начал недавно и не знаю, как реализовать некоторые элементы программы.
Как с вами можно связаться для небольшого разговора на эту тему?
Контакты в профиле. Но рекомендую с вопросами сначала ходить в гугл. Я уже год как почти не занималась андроидом.
ОтветитьУдалитьСпасибо за столь оперативный ответ, не ожидал, если честно.
ОтветитьУдалитьДа дело в том, что мне даже тяжело определиться, какие лэйоты использовать в моём случае. Так что вопросы будут простые!)
Спасибо за этот пример. Всё же, когда читаешь объяснение по-русски понимаешь намного больше.
ОтветитьУдалитьА у тебя приложение при размерах 25х25 тоже немного медленно в симуляторе выполняется / выполнялось?
Агромадное спасибо Дарье За уроки.
ОтветитьУдалитьНе знаю, чей это глюк - может, движок сайта, Но кнопки "Следующее" и "Предыдущее" внизу страницы перепутаны...
Мегаспасибо автору!
ОтветитьУдалитьВопрос такой - в ресурсах есть иконка, и в манифесте она прописана.
А Life на экране изображает стандартную гугловскую картинку.
И еще - нигде вроде бы не прописаны запрашиваемые привилегии. А при установке просит разрешить телефонные вызовы и память(SD-карту)
Огромное СПАСИБО!
ОтветитьУдалитьВсё очень наглядно и доступно!
Респект Автору!
присоединяюсь к благодарным возгласам) Очень помогает при первых шагах на андроиде. )
ОтветитьУдалитьОчень!!! хорошая статья, все понятно и не вызывает вопросов, Огромное спасибо автору.
ОтветитьУдалитьОгромное спасибо, наконец то разобрался с GridView. Все понятно, все по полочкам.
ОтветитьУдалить