Класс, реализующий логику 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, но, возможно, это от недостатка опыта.Спасибо, если кто осилил. Замечания приветствуются.
Исходники примера
30 комментариев:
Приятно, что девушки тоже интересуются 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. Все понятно, все по полочкам.
Отправить комментарий