Создаем игру Hangman. Часть 2. Пользовательский интерфейс
Главная » Создаем игру Hangman. Часть 2. Пользовательский интерфейс

В предыдущем уроке мы начали создавать своими руками популярную игру Hangman. Нами были загружены в проект необходимые изображения, созданы и частично отредактированы нужные layout файлы и классы java. В этом уроке мы продолжим работу над созданием игры и будем настраивать ее пользовательский интерфейс.

Работа над игровыми layout будет включать настройку адаптера, для связи кнопок с буквами и появлением частей тела человечка, при выборе пользователем неправильной буквы. Также будет настроено хранение ответов пользователя в xml файле,  настройка доступа к этим ответам, и выбор случайного слова с помощью использования java. Игра в законченном состоянии должна выглядеть вот так:

Конечный вид приложения Hangman

В предыдущем уроке мы добавили к проекту необходимые изображения частей тела человечка и виселицы. В этом уроке мы поместим их в нужные layout файлы. Положение этих изображений должно быть определено их размерами. Одним из способов грамотно подогнать человечка под виселицу (подогнать картинки друг к другу:)) - это воспользоваться Photoshop, сложить в нем все элементы вместе в необходимую картинку и потом по их координатам x и y определить правильное положение частей тела по отношению к виселице и применить их в layout файле. Но если вы используете изображения, данные к уроку, то можно смело использовать все настройки с этого урока, где уже все подогнано нужным образом. 

Открываем файл activity_game.xml. Внутри LinearLayout, который мы создали ранее, давайте добавим RelativeLayout, в котором будут нужные изображения:

<RelativeLayout
 android:layout_width="fill_parent"
 android:layout_height="wrap_content"
 android:background="#FFFFFFFF"
 android:gravity="center"
 android:paddingTop="15dp" >
 </RelativeLayout>

Теперь внутри RelativeLayout добавляем изображение виселицы:

<ImageView
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:contentDescription="@string/gallows"
 android:paddingLeft="0dp"
 android:paddingTop="0dp"
 android:src="@drawable/android_hangman_gallows"/>

Далее мы будем позиционировать остальные изображения по отношению к этому, добавленному выше. Строчка: 

android:contentDescription="@string/gallows"

у нас подчеркнута красным, потому, что такого строкового ресурса у нас пока не существует. Не обращаем внимания, мы добавим его позже.

Далее добавляем голову человечка:

<ImageView
 android:id="@+id/head"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:contentDescription="@string/head"
 android:paddingLeft="108dp"
 android:paddingTop="23dp"
 android:src="@drawable/android_hangman_head" />

Мы используем id атрибут для изображения головы для того, чтобы иметь возможность обратиться к нему в java коде. Это необходимо, потому, что в зависимости от выбора пользователя части тела будут либо появляться, либо исчезать. 

Далее добавляем тело бедняги:

 <ImageView
 android:id="@+id/body"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:contentDescription="@string/body"
 android:paddingLeft="120dp"
 android:paddingTop="53dp"
 android:src="@drawable/android_hangman_body" />

Пока что все довольно просто. Добавим остальные части тела:

<ImageView
 android:id="@+id/arm1"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:contentDescription="@string/arm"
 android:paddingLeft="100dp"
 android:paddingTop="60dp"
 android:src="@drawable/android_hangman_arm1" />
 <ImageView
 android:id="@+id/arm2"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:contentDescription="@string/arm"
 android:paddingLeft="120dp"
 android:paddingTop="60dp"
 android:src="@drawable/android_hangman_arm2" />
 <ImageView
 android:id="@+id/leg1"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:contentDescription="@string/leg"
 android:paddingLeft="101dp"
 android:paddingTop="90dp"
 android:src="@drawable/android_hangman_leg1" />
 <ImageView
 android:id="@+id/leg2"
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:contentDescription="@string/leg"
 android:paddingLeft="121dp"
 android:paddingTop="90dp"
 android:src="@drawable/android_hangman_leg2" />

Теперь давайте решим проблему с красным подчеркиванием в строках contentDescription. Открываем файл strings.xml приложения (он находится в папке values) и добавляем туда следующее:

<string name="gallows">Виселица</string>
<string name="head">Голова</string>
<string name="body">Тело</string>
<string name="arm">Рука</string>
<string name="leg">Нога</string>

После всех этих манипуляций наш файл activity_game.xml имеет вид:

Вид окна игрового приложения

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

Игра будет содержать набор слов для отгадывания, которые мы будем хранить в xml файле. Давайте создадим этот файл. В папке values приложения создаем файл arrays.xml. Создадим в этом файле тестовый массив из слов, добавив следующий код:

<resources>
 <string-array name="words">
 <item>CHARGER</item>
 <item>COMPUTER</item>
 <item>TABLET</item>
 <item>SYSTEM</item>
 <item>APPLICATION</item>
 <item>INTERNET</item>
 <item>STYLUS</item>
 <item>ANDROID</item>
 <item>KEYBOARD</item>
 <item>SMARTPHONE</item>
 </string-array>
</resources>

Мы создали массив из слов, которые должен будет отгадывать пользователь. Как видите, слова посвящены миру технологий. Если чувствуете с в себе достаточно сил, можете сделать более сложно - создать несколько массивов слов, отсортированных по категориям, предлагая пользователю перед игрой выбрать, из какой категории он хочет отгадывать слова.

Возвращаемся к редактирования файла activity_game.xml. Сразу после RelativeLayout с виселицей и человечком добавим новый LinearLayout:

<LinearLayout
 android:id="@+id/word"
 android:layout_width="fill_parent"
 android:layout_height="wrap_content"
 android:layout_marginBottom="5dp"
 android:background="#FFFFFFFF"
 android:gravity="center"
 android:orientation="horizontal"
 android:padding="10dp" >
 </LinearLayout>

Этот layout будет использоваться, как область для ответов. Мы будем хранить каждый символ отгадываемого слова в отдельном TextView, что даст возможность показывать и скрывать разные буквы отдельно, а не слово целиком. Также для этого LinearLayout мы зададим свой id, чтобы программно, с jaca класса, добавлять к этому layout необходимые элементы TextView.

Переходим к работе в файле GameActivity. Добавим сюда такие строки импорта:

import android.content.res.Resources;
import android.graphics.Color;
import android.view.Gravity;
import android.view.ViewGroup.LayoutParams;
import android.widget.LinearLayout;
import android.widget.TextView;

Добавим в класс метод onCreate:

@Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_game);
 }

С помощью команды setContentView мы настроили классу GameActivity вид, заданный в файле activity_game.xml

Теперь нам нужно объявить переменные, которые мы будем использовать. Перед методом onCreate добавляем следующее:

private String[] words;
private Random rand;
private String currWord;
private LinearLayout wordLayout;
private TextView[] charViews;

Набор слов содержится в массиве и приложение использует объект rand для выбора случайного слова из массива в тот момент, когда пользователь запускает игру. Мы устанавливаем связь с текущим словом (currWord), с LinearLayout, который мы создали как область для ответа (wordLayout), и с массивом из элементов TextView, используемых для букв (charViews). 

В методе onCreate добавим команды для чтения нашего набора слов и помещения их в массив words:

Resources res = getResources();
words = res.getStringArray(R.array.words);

Инициализируем объект rand и строку currWord:

rand = new Random();
currWord = "";

Ссылаемся на LinearLayout, который мы создали как область для ответа:

wordLayout = (LinearLayout)findViewById(R.id.word);

Каждый раз, когда игрок запускает новую игру, будет выполняться некоторое количество процессов. Чтобы держать все организованно, создадим для этого вспомогательный класс. После метода onCreate создадим класс playGame:

private void playGame() {
//Играем новую игру
}

Внутри нового метода начнем с выбора случайного слова с массива:

String newWord = words[rand.nextInt(words.length)];

Метод playGame будет вызываться когда пользователь будет начинать играть опять, после победы или поражения, нужно удостовериться в том, что мы не выберем два раза подряд одно и то же слово. Чтобы избежать этого, добавляем следующее:

while(newWord.equals(currWord)) newWord = words[rand.nextInt(words.length)];
currWord = newWord;

Теперь нам нужно создать для каждой буквы в отгадываемом слове свой TextView. Внутри этого же вспомогательного метода playGame добавим:

charViews = new TextView[currWord.length()];

Далее, убираем всем TextView с wordLayout:

wordLayout.removeAllViews();

Используем цикл for для повторения над каждой буквой ответа, создаем свой TextView для каждой буквы и настраиваем текст в TextView для правильно выбранной буквы:

for (int c = 0; c < currWord.length(); c++) {
 charViews[c] = new TextView(this);
 charViews[c].setText(""+currWord.charAt(c));
 }

Использование метода charAt позволит нам получить доступ к символам по специальному индексу. Внутри того же цикла for настраиваем параметры отображения для TextView и добавления его в LinearLayout:

charViews[c].setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
charViews[c].setGravity(Gravity.CENTER);
charViews[c].setTextColor(Color.WHITE);
charViews[c].setBackgroundResource(R.drawable.letter_bg);
//Добавляем в layout
wordLayout.addView(charViews[c]);

Мы настроили белый цвет текста и пользователь не будет видеть текст на фоне такого же белого фона. Если игрок правильно угадает букву, то цвет текста поменяется на черный и станет, таким образом, видимым. Будем вызывать созданный выше вспомогательный метод в методе onCreate. Добавьте в метод onCreate строку:

playGame();

Немного позже мы доработаем методы onCreate и playGame.

Теперь нам нужно создать область с буквами, на которые будет нажимать игрок, угадывая нужное слово. Откройте файл activity_game.xml и добавьте туда сетку, которая будет содержать буквы:

 <GridView
 android:id="@+id/letters"
 android:layout_width="fill_parent"
 android:layout_height="wrap_content"
 android:layout_gravity="center"
 android:layout_marginBottom="5dp"
 android:background="#FF000000"
 android:horizontalSpacing="5dp"
 android:numColumns="7"
 android:padding="5dp"
 android:stretchMode="columnWidth"
 android:verticalSpacing="5dp" />

Для связи букв алфавита и кнопок в созданной выше сетке, мы будем использовать адаптер. В сетке мы зададим ряды по 7 кнопок в каждом. Каждая буква должна соответствовать кнопке, добавленной в layout с помощью адаптера.

Создаем в папке layout новый файл по имени letter.xml и заполните его следующим кодом:

<Button xmlns:android="http://schemas.android.com/apk/res/android"
 android:layout_width="wrap_content"
 android:layout_height="35dp"
 android:background="@drawable/letter_up"
 android:onClick="letterPressed" />

Для фона кнопки мы создаем форму, которую сделали в прошлом уроке и устанавливаем метод onClick, который обработаем в следующий раз. 

Создадим в нашем приложении еще один java класс (напомню, у нас их два: MainActivity и GameActivity) по имени LetterAdapter, который будет наследовать класс BaseAdapter. То есть, после создания класса LetterAdapter добавьте наследование: public class LetterAdapter extends BaseAdapter. При наведении на эту строку, в Android Studio появится красный значок (не пугаемся), жмем на него и выбираем первую строку с командой Implement Methods:

Редактируем класс

Программа сама создала 4 метода, позже мы будем их использовать. Теперь все okey, идем дальше.

Добавим к импорту следующее:

import android.content.Context;
import android.view.LayoutInflater;
import android.widget.Button;

Внутри класса объявим следующие переменные:

private String[] letters;
private LayoutInflater letterInf;

Массив letters будет хранить буквы алфавита, а letterInf будет применять letter.xml к каждому элементу в адаптере. После объявленных переменных добавим метод для адаптера:

public LetterAdapter(Context c) {
 //Настройка адаптера
 }

Внутри конструктора создаем массив из всех букв алфавита от A до Z. 

letters=new String[26];
for (int a = 0; a < letters.length; a++) {
 letters[a] = "" + (char)(a+'A');
}

Каждый символ представлен как число, поэтому мы можем настроить буквы от A до Z с помощью циклического запуска с нуля и добавления значения символа A к каждому индексу массива. Внутри конструктора также добавьте строку, относящуюся к работе заполнителя layoutInflater:

letterInf = LayoutInflater.from(c);

Теперь, обновим код метода getCount (он один из 4, созданных самой программой, помните?):

@Override
 public int getCount() {
 return letters.length;
 }

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

Обновим другой стандартный метод getView:

@Override
 public View getView(int position, View convertView, ViewGroup parent) {
 //create a button for the letter at this position in the alphabet
 Button letterBtn;
 if (convertView == null) {
 //inflate the button layout
 letterBtn = (Button)letterInf.inflate(R.layout.letter, parent, false);
 } else {
 letterBtn = (Button) convertView;
 }
 //set the text to this letter
 letterBtn.setText(letters[position]);
 return letterBtn;
 }

Именно этот метод будет выстраивать пользовательский интерфейс, используя адаптер. Здесь мы наполняем letter layout и настраиваем буквы в соответствии с положением в алфавите. Остальные два метода в классе мы оставим без изменений:

@Override
 public Object getItem(int position) {
 return null;
 }

 @Override
 public long getItemId(int position) {
 return 0;
 }

Давайте еще раз навестим GameActivity.java и добавим переменные для GridView и адаптера. 

private GridView letters;
private LetterAdapter ltrAdapt;

Также добавим еще одну строку в импорт:

import android.widget.GridView;

В методе onCreate до вызова метода playGame добавим объявление о GridView:

letters = (GridView)findViewById(R.id.letters);

Добавьте в метод playGame строки о адаптере и настройке с его помощью GridView:

ltrAdapt=new LetterAdapter(this);
letters.setAdapter(ltrAdapt);

На этот урок все. Запускаем приложение в эмуляторе либо на устройстве и мы должны увидеть пользовательский интерфейс нашей будущей игры:

Вид запущенной игры

Но сейчас кнопочки с буквами еще не работают и мы все еще не может поиграть в игру Hangman. В следующем и последнем уроке мы завершим работу над этой игрой и сможем насладиться игрой в нее:).

Оригинал урока.

Категория: Уроки программирования | Просмотров: 769 | Добавил: Oleg | Теги: Hangman, пользовательский интерфейс, создание игры, Android программирование | Рейтинг: 0.0/0
Всего комментариев: 0
avatar