1. С количеством ходов все понятно, будем считать количество открытых картинок и выводить это число в TextView. Для таймера есть класс Chronometer, который делает как раз то что нам нужно — отчитывает время. Класс унаследован от TextView, поэтому работать с ним можно как с обычным текстовым полем. Дополнительными являются методы:
void start () - запускает отсчет времени
void stop() - останавливает отсчет
void setFormat (String format) — устанавливает формат в котором будет выводится дата. По умолчанию форматом является MM:SS (или H:MM:SS). Можно задать свой формат, при этом в строке format первое встреченное «%s», будет заменено на «HH:MM». Например: «Time: %s» будет выводить время «Time: 01:30»
2. Добавим в main.xml TextView и Chronometr
- <?xml version="1.0" encoding="utf-8"?>
 - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 - android:layout_width="fill_parent"
 - android:layout_height="fill_parent"
 - android:orientation="vertical" >
 - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 - android:layout_width="fill_parent"
 - android:layout_height="wrap_content"
 - android:orientation="horizontal" >
 - <TextView xmlns:android="http://schemas.android.com/apk/res/android"
 - android:id="@+id/stepview"
 - android:textSize="30dp"
 - android:layout_weight="1"
 - android:layout_marginLeft="10dip"
 - android:layout_width="0dip"
 - android:layout_height="wrap_content"
 - />
 - <Chronometer xmlns:android="http://schemas.android.com/apk/res/android"
 - android:id="@+id/timeview"
 - android:layout_width="100dip"
 - android:layout_height="wrap_content"
 - android:textSize="30dp"
 - />
 - </LinearLayout>
 - <GridView xmlns:android="http://schemas.android.com/apk/res/android"
 - android:id="@+id/field"
 - android:layout_width="fill_parent"
 - android:layout_height="wrap_content"
 - android:gravity="center"
 - />
 - </LinearLayout>
 
- public class MemoriaActivity extends Activity {
 - ...
 - private TextView mStepScreen;
 - private Chronometer mTimeScreen;
 - private Integer StepCount; // кол-во ходов
 - @Override
 - public void onCreate(Bundle savedInstanceState) {
 - ...
 - mTimeScreen = (Chronometer) findViewById(R.id.timeview);
 - mStepScreen = (TextView)findViewById(R.id.stepview);
 - // шрифт
 - Typeface type = Typeface.createFromAsset(getAssets(),"my-font.ttf");
 - mTimeScreen.setTypeface(type);
 - mStepScreen.setTypeface(type);
 - StepCount = 0;
 - mStepScreen.setText (StepCount.toString());
 - mTimeScreen.start();
 - ...
 - mGrid.setOnItemClickListener(new OnItemClickListener() {
 - @Override
 - public void onItemClick(AdapterView<?> parent, View v,int position, long id) {
 - mAdapter.checkOpenCells ();
 - mAdapter.openCell (position);
 - StepCount ++;
 - mStepScreen.setText (StepCount.toString());
 - if (mAdapter.checkGameOver())
 - {
 - mTimeScreen.stop();
 - String time = mTimeScreen.getText().toString();
 - String TextToast = "Игра закончена nХодов: " + StepCount.toString() + "nВремя: " + time;
 - Toast.makeText (getApplicationContext(), TextToast, Toast.LENGTH_SHORT).show();
 - }
 - }
 - });
 - }
 - }
 
5. Тут, кстати, обнаружилась небольшая ошибка в игре: нажатие на уже удаленную картинку считается ходом, что не правильно. Переделаем метод openCell() в классе GridAdapter, он должен проверять является ли карточка закрытой и только тогда ее открывать.
- public boolean openCell(int position) {
 - if (arrStatus.get(position) == Status.CELL_DELETE || arrStatus.get(position) == Status.CELL_OPEN)
 - return false;
 - if (arrStatus.get(position) != Status.CELL_DELETE)
 - arrStatus.set(position, Status.CELL_OPEN);
 - notifyDataSetChanged();
 - return true;
 - }
 
- mGrid.setOnItemClickListener(new OnItemClickListener() {
 - @Override
 - public void onItemClick(AdapterView<?> parent, View v,int position, long id) {
 - mAdapter.checkOpenCells ();
 - if (mAdapter.openCell (position)) {
 - StepCount ++;
 - mStepScreen.setText (StepCount.toString());
 - }
 - ...
 - }
 - });
 
Typeface type = Typeface.createFromAsset(getAssets(),"my-font.ttf");
mTimeScreen.setTypeface(type);
Таким способом можно установить свой шрифт для TextView. Берем файл шрифта (у меня это 'my-font.ttf'), кладем его в директорию /assets.
Typeface — класс стилей шрифта.
createFromAsset(AssetManager mgr, String path) — создает экземпляр класса Typeface, используя файл из директории /assets.
setTypeface(Typeface) — устанавливает шрифт в TextView.
7. В xml и у TextView и у Chronometer также описаны параметры шрифта
android:textSize="30dp"
android:textColor="#FFFFFF"
Две строчки, конечно, можно и скопировать в два места, но если или строчек или мест будет много, то это неудобно, да и места много занимает. Создадим стиль шрифта. Добавим файл style.xml в директорию /res/values/. Напишем туда:
- <?xml version="1.0" encoding="utf-8"?>
 - <resources>
 - <style name="MyText">
 - <item name="android:textColor">#FFFFFF</item>
 - <item name="android:textSize">30dp</item>
 - </style>
 - </resources>
 
style="@style/MyText"
Теперь чтобы изменить стиль шрифта и в TextView и в Chronometer, надо изменить только файл style.xml, например добавим тень к тексту:
- <style name="MyText">
 - <item name="android:textColor">#FFFFFF</item>
 - <item name="android:textSize">30dp</item>
 - <item name="android:shadowColor">#FF0000</item>
 - <item name="android:shadowDx">2</item>
 - <item name="android:shadowDy">2</item>
 - <item name="android:shadowRadius">3</item>
 - </style>
 
- Первое запретить поворот зкрана, сам-то телефон конечно крутить можно, но изображение от этого меняться не будет. Для этого добавим строку android:screenOrientation="portrait" в файл AndroidManifest.xml:
- <activity
 - android:name=".MemoriaActivity"
 - android:label="@string/app_name"
 - android:screenOrientation="portrait">
 
- И наконец третий способ, отловить поворот экрана программно, для этого добавим в AndroidManifest.xml за каким изменением конфигурации мы хотим следить
- <activity
 - android:name=".MemoriaActivity"
 - android:label="@string/app_name"
 - android:configChanges="orientation">
 
- public class MemoriaActivity extends Activity {
 - ...
 - @Override
 - public void onConfigurationChanged(Configuration newConfig) {
 - super.onConfigurationChanged(newConfig);
 - if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE)
 - mGrid.setNumColumns(9);
 - if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT)
 - mGrid.setNumColumns(6);
 - }
 - }
 
9. Теперь сделаем начальный экран, у меня это будет четыре кнопки (Старт, Настройки, Рекорды, Выход) на каком-нибудь фоне. Создаем файл MemoriaStart.java с классом MemoriaStart (унаследованным от Activity) и разметку start.xml. Я сделаю две разметки для вертикальной и горизонтальной ориентации.
10. Если после этого запустить программу, то откроется окно MemoriaActivity, для того чтобы первым открывалось MemoriaStart изменим AndroidManifest.xml:
- <application
 - android:icon="@drawable/ic_launcher"
 - android:label="@string/app_name" >
 - <activity
 - android:name=".MemoriaStart"
 - android:label="@string/app_name">
 - <intent-filter>
 - <action android:name="android.intent.action.MAIN" />
 - <category android:name="android.intent.category.LAUNCHER" />
 - </intent-filter>
 - </activity>
 - <activity android:name=".MemoriaActivity"
 - android:label="@string/app_name"
 - android:configChanges="orientation">
 - </activity>
 - </application>
 
- public class MemoriaStart extends Activity {
 - Button mStart;
 - Button mExit;
 - @Override
 - public void onCreate(Bundle savedInstanceState) {
 - super.onCreate(savedInstanceState);
 - setContentView(R.layout.start);
 - mStart = (Button)findViewById(R.id.butStart);
 - mExit = (Button)findViewById(R.id.butExit);
 - mStart.setOnClickListener (new OnClickListener() {
 - @Override
 - public void onClick(View v) {
 - startGame();
 - }
 - });
 - mExit.setOnClickListener (new OnClickListener() {
 - @Override
 - public void onClick(View v) {
 - finish();
 - }
 - });
 - }
 - private void startGame () {
 - Intent i = new Intent(this, MemoriaActivity.class);
 - startActivity (i);
 - }
 - }
 
Intent (намерение) — структура данных, содержащая информацию о том, какое действие необходимо выполнить. В данном случае, мы вызываем окно MemoriaActivity.
12. Запускаем, теперь при запуске программы, вначале показывается окно с кнопками, при нажатии на «Старт» запускается игра, а кнопка «Выход» закрывает приложение. Чтобы из игры вернуться на начальный экран можно нажать кнопку «Назад» на телефоне, но для наглядности после завершения игры будем показывать не просто всплывающее сообщение, а диалоговое окно с кнопкой, по нажатию на которое будем возвращаться назад.
В классе MemoriaActivity при завершении игры вызовем функцию ShowGameOver()
- public class MemoriaActivity extends Activity {
 - ...
 - mGrid.setOnItemClickListener(new OnItemClickListener() {
 - @Override
 - public void onItemClick(AdapterView<?> parent, View v,int position, long id) {
 - ...
 - if (mAdapter.checkGameOver())
 - {
 - mTimeScreen.stop();
 - ShowGameOver();
 - }
 - }
 - });
 - }
 
- public class MemoriaActivity extends Activity {
 - ...
 - private void ShowGameOver () {
 - // Диалоговое окно
 - AlertDialog.Builder alertbox = new AlertDialog.Builder(this);
 - // Заголовок и текст
 - alertbox.setTitle("Поздравляем!");
 - String time = mTimeScreen.getText().toString();
 - String TextToast = "Игра закончена nХодов: " + StepCount.toString() + "nВремя: " + time;
 - alertbox.setMessage(TextToast);
 - // Добавляем кнопку
 - alertbox.setNeutralButton("Ok", new DialogInterface.OnClickListener() {
 - public void onClick(DialogInterface arg0, int arg1) {
 - // закрываем текущюю Activity
 - finish();
 - }
 - });
 - // показываем окно
 - alertbox.show();
 - }
 - }
 
AlertDialog.Builder — вспомогательный класс, который создает диалоговое окошко.
Методы setTitle(CharSequence title) и setMessage(CharSequence message) задают соответственно заголовок и текст сообщения.
setNeutralButton(CharSequence text, DialogInterface.OnClickListener listener) — создает в окне кнопку и устанавливает обработчик нажатия на кнопку.
show() - создает и показывает созданное окно.
13. Запускаем, все работает. Мы сделали счетчики, и начальный экран. Попутно посмотрели как установить свой шрифт, как сделать общий стиль для нескольких элементов приложения, как реагировать на изменения ориентации экрана и научились выводить диалоговое окошко.
Исходники:
Memoria-2.zip
Все части урока:
1. Игровое поле 6х6 с картинками (часть 1)
2. Учет количество ходов (или времени) (часть 2)
3. Просмотр таблицы рекордов (часть 4)
4. Настройки: выбор цвета фона и набора картинок (часть 3)
5. Начальный экран (Часть 2)
Счетчик ходов работает некорректно, одно нажатие выдает за 2 и еще при нажатии на пустое поле тоже засчитываеь как ход, или нажатие на открытую картинку...
ОтветитьУдалить