Сайт разработчика Александр Климова

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Memoria - игра для тренировки памяти. Часть 2

В первой части мы начали делать игру для тренировки памяти. Собственно игра уже готова, осталось добавить к ней красоты. В этом уроке добавим таймер, который будет отсчитывать время с начала до окончания игры, учет ходов, то есть количество открытых карточек и добавим начальный экран.

Учёт ходов

С количеством ходов всё понятно - будем считать количество открытых картинок и выводить это число в TextView. Для таймера есть класс Chronometer, который делает как раз то что нам нужно — отсчитывает время.

Добавим в main.xml элементы TextView и Chronometer


<?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:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dip"
            android:layout_weight="1"
            android:textSize="30dp" />

        <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>

В классе MemoriaActivity напишем их обработку:


...
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();
        }
      }
    });
  }
}

Запускаем:

Memoria

Тут, кстати, обнаружилась небольшая ошибка в игре: нажатие на уже удаленную картинку считается ходом, что неправильно. Переделаем метод 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;
}

А в классе MemoriaActivity будем правильно считать ходы


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());
    }
    ...
  }
});

Использование собственных шрифтов

Обратите внимание, что в этом коде мы используем свой шрифт (у меня это 'my-font.ttf'), предварительно добавленный в папку assets.

В 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>
        <item name="android:shadowColor">#782144</item>
        <item name="android:shadowDx">2</item>
        <item name="android:shadowDy">2</item>
        <item name="android:shadowRadius">3</item>
    </style>

    <style name="ButtonText">
        <item name="android:layout_width">fill_parent</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:textColor">#ffffff</item>
        <item name="android:gravity">center</item>
        <item name="android:layout_margin">1dp</item>
        <item name="android:textSize">16dp</item>
        <item name="android:textStyle">bold</item>
        <item name="android:shadowColor">#000000</item>
        <item name="android:shadowDx">2</item>
        <item name="android:shadowDy">2</item>
        <item name="android:shadowRadius">3</item>
    </style>

</resources>

А в main.xml заменим эти строчки у TextView и Chronometer на:

style="@style/MyText"

Теперь, чтобы изменить стиль шрифта и в TextView и в Chronometer, надо изменить только файл style.xml, например можно добавить тень к тексту:

Поворот экрана

Попробуем повернуть экран горизонтально (на эмуляторе это можно сделать нажав Ctrl + F11) - выглядит теперь не очень хорошо. Здесь есть три решения.

Первое - запретить поворот зкрана. Сам телефон крутить, конечно, можно, но изображение от этого меняться не будет. Для этого добавим строку android:screenOrientation="portrait" в файл AndroidManifest.xml.


<activity
  android:name=".MemoriaActivity"
  android:label="@string/app_name" 
  android:screenOrientation="portrait">

Второй вариант - написать другую разметку xml для горизонтальной ориентации экрана. Создадим директорию /res/layout-land, а в ней файл main.xml с новой разметкой.

Третий способ - отловить поворот экрана программно, для этого добавим в AndroidManifest.xml за каким изменением конфигурации мы хотим следить:


<activity
  android:name=".MemoriaActivity"
  android:label="@string/app_name" 
  android:configChanges="orientation">

А в класс MemoriaActivity добавим метод onConfigurationChanged(Configuration newConfig), который вызывается каждый раз, когда конфигурация аппарата меняется.


@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);
}

В вертикальном расположении экрана у нас поле 6х6, а в горизонтальном - 9х4.

Начальный экран

Теперь сделаем начальный экран, на котором будут четыре кнопки (Старт, Настройки, Рекорды, Выход) на каком-нибудь фоне. Создаем файл MemoriaStart.java с классом MemoriaStart (унаследованным от Activity) и разметку start.xml. Сделаем две разметки для вертикальной и горизонтальной ориентации.


Если после этого запустить программу, то откроется окно 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);
  }
}

Для того чтобы открыть другое окно программы используется метод void startActivity (Intent intent), которому передается экземпляр класса Intent.

Запускаем проект - теперь при запуске программы, вначале показывается окно с кнопками, при нажатии на «Старт» запускается игра, а кнопка «Выход» закрывает приложение. Чтобы из игры вернуться на начальный экран можно нажать кнопку «Назад» на телефоне, но для наглядности после завершения игры будем показывать не просто всплывающее сообщение, а диалоговое окно с кнопкой, по нажатию на которое будем возвращаться назад.

В классе 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();
      }
    }
  });
}

И напишем саму функцию 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();
  }
}
Game over

Заключение

Запускаем и проверяем. Всё работает. Мы сделали счётчики и начальный экран. Попутно посмотрели, как установить свой шрифт, как сделать общий стиль для нескольких элементов приложения, как реагировать на изменения ориентации экрана и научились выводить диалоговое окошко.

Источник: Программирование для android - Игра для тренировки памяти (часть два)

Реклама